Your Own Angular 8 Dynamic Modal Library

Who doesn’t need a nice modal library that supports dynamic components? There are tons of third-party libraries that let you create dynamic modals with dynamic contents. Some are robust but yet you’ll find yourself standing on a dead end at some point since almost all of such libraries have limitations when it comes to customization and sharing data between modal and base component.

I started off building my own library utilizing Angular Dynamic Components and Bootstrap Modal. The library may not be the ultimate champion but it surely is a fighter and it pretty much fulfills all the modal needs in my application.

Without any further ado let’s get started.

import {AfterViewInit, Component, ComponentFactoryResolver, OnInit, ViewChild, ViewContainerRef} from '@angular/core';
import {IDynamicModalContent} from './dynamic-modal-content';

declare var $: any;

@Component({
  selector: 'app-modal',
  templateUrl: './modal.component.html',
  styleUrls: ['./modal.component.scss']
})
export class ModalComponent implements OnInit, AfterViewInit {

  header: string;

  modalId: string;

  component: any;

  modalElement: any;

  submitCallback: (arg: any) => void;

  @ViewChild('modalContent', {static: true, read: ViewContainerRef})
  viewContainerRef: ViewContainerRef;

  componentRef: any;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {
  }

  ngOnInit() {
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.component);
    this.componentRef = this.viewContainerRef.createComponent(componentFactory);
  }

  ngAfterViewInit(): void {
    this.modalElement = $('#' + this.modalId);
    this.modalElement.modal('show');
  }

  onSubmit(): void {
    (this.componentRef.instance as IDynamicModalContent).submit(this.submitCallback);
    this.modalElement.modal('hide');
  }
}

This is the modal class that creates a dynamic component and injects it into the modal’s content body. Class properties (modalId, header, component, modalElement, and submitCallback) are injected by ModalService which we will talk about later.

First, we’re getting the ViewContainerRef of the container using the reference variable #modelContent defined in our modal template. This is where the dynamic modal content is going to get injected. We then use component which is a component type passed onto this class by our ModalService to resolve the factory for this component. With the help of ViewContainerRef and ComponentFactory, we then instantiate the dynamic component for our modal’s body content.

Inside ngAfterViewInit() we’re simply grabbing the modal element by its id and then calling Bootstraps’s .modal('show') API to display the modal.

onSubmit() invokes the submit() method implemented by our dynamic component. The dynamic component needs to implement IDynamicModalContent interface. The submit() method takes in an argument which is a callback function from the caller/base component. This callback function will help us pass event or any sort of data whenever a user clicks the confirmation button on the modal.

<div id="{{modalId}}" class="modal" tabindex="-1" role="dialog" data-backdrop="static" data-keyboard="true">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div *ngIf="header" class="modal-header">
        <h5>{{header}}</h5>
      </div>
      <div class="modal-body">
        <div #modalContent></div>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
        <button type="button" (click)="onSubmit()" class="btn btn-primary">OK</button>
      </div>
    </div>
  </div>
</div>

This is the modal template. It has a header, a body with the reference variable #modalContent, and a footer that has Close and Ok buttons.

export interface IDynamicModalContent {
  submit(callback: (arg: any) => void): void;
}

IDynamicModalContent interface should be implemented by all the dynamic components that are to be injected in the modal. It mandates the implementation of submit() method which takes a callback function as an argument. The callback function is to pass data on the confirmation button click.

import {ComponentFactoryResolver, ComponentRef, Injectable, ViewContainerRef} from '@angular/core';
import {ModalComponent} from './modal.component';

@Injectable({
  providedIn: 'root'
})
export class ModalService {

  viewRefs = new Map<ViewContainerRef, ComponentRef<ModalComponent>>();

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {
  }

  createModal(id: string, header: string, viewContainerRef: ViewContainerRef, component, submitCallback: (arg: any) => void) {
    if (this.viewRefs.has(viewContainerRef)) {
      viewContainerRef.clear();
      this.viewRefs.delete(viewContainerRef);
    }

    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(ModalComponent);

    const componentRef = viewContainerRef.createComponent(componentFactory);
    (componentRef.instance as ModalComponent).modalId = id;
    (componentRef.instance as ModalComponent).header = header;
    (componentRef.instance as ModalComponent).component = component;
    (componentRef.instance as ModalComponent).submitCallback = submitCallback;

    this.viewRefs.set(viewContainerRef, componentRef);
  }
}

Just like how we’re creating dynamic component inside the modal component, we’ll use ModalService to create the modal component itself dynamically. The service also sets instance variables of ModalComponent class.

import {ModalService} from './modal.service';
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';

import {ModalComponent} from './modal.component';

@NgModule({
  imports: [
    CommonModule
  ],
  declarations: [
    ModalComponent
  ],
  exports: [
    ModalComponent
  ]
})
export class CustomModalModule {
  static forRoot() {
    return {
      ngModule: CustomModalModule,
      providers: [ModalService]
    };
  }
}

Since we want all this as a library, we’re going to put everything in a custom module. After this, we can import it in our application and start using it.

The entire source code is available in this Git Repository. It also has an example of how you can import the module and use it in your application.

Leave a Reply

%d bloggers like this: