Load Dynamic Components Inside a Modal Dialog in Angular 2

Luis Eduardo
The Startup
Published in
3 min readSep 23, 2020

Using modal windows from the NGX bootstrap library can be very convenient and easy to use. However, in a large application in which we may use it multiple times, a Modal window can make us write a lot of duplicated code.

The problem

Let’s think about this example:

Each Modal window needs a Close Button at the top and a Submit Button at the bottom. We don’t want to write these markup for every component in which we use the Modal Window. We want to be able to reuse these elements and the methods they might trigger for all our use cases.

Getting started:

For this solution use the modal window library from:
https://valor-software.com/ngx-bootstrap

Install Modal from NGX bootstrap

ng add ngx-bootstrap  --component modals

According to the documentation, you can open a template reference inside de Modal or a Component inside like this example:

demo.component.ts

@Component({
selector: 'app-demo',
template: `<div onClick="openModalWithComponent()">Open modal </div>`,
})
export class DemoComponent {
bsModalRef: BsModalRef;
constructor(private modalService: BsModalService) {}

openModalWithComponent() {
this.bsModalRef = this.modalService.show(ModalContentComponent);
}
close() {
this.bsModalRef.hide();
}
submit() {
console.log('submit');
}
}

Where ModalContentComponent looks like this:

modal-content.component.html

<div class="modal-header">
Here header title
<button type="button" class="close pull-right" aria-label="Close" onClick="close()">
<div class="close-modal" aria-hidden="true"></div>
</button>
</div>
<div class="modal-body">
<!-- here the content -->
</div>
<div class="modal-footer">
<button class="btn btn-success"onClick="submit()">Submit</button>
</div>

Here is the issue: The header and footer markup is duplicated in every place where the modal component is used.

To solve this, instead of loading every component in build time, we need to load new components at runtime.

How? With Dynamic components using ComponentFactoryResolver.

The Solution

The first thing we need to do is to create a wrapper component for the modal, which will contain the parts that we will use every time we display the modal window, in this case, header and footer.

modal-wrapper.component.html

<div class="modal-header">
here title
<button type="button" class="close pull-right" aria-label="Close" onClick="close()">
<div class="close-modal" aria-hidden="true"></div>
</button>
</div>
<div class="modal-body">
<div #container></div>
</div>
<div class="modal-footer">
<button class="btn btn-success"onClick="submit()">Submit</button>
</div>

note that inside the modal-body element, we have an element ref.

<div #container></div>

Now let’s have a look at the Component TS file.

modal-wrapper-component.ts

@Component({
selector: 'modal-wrapper',
templateUrl: './modal-wrapper.component.html',
})
export class ModalWrapperComponent implements OnInit {

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

constructor(private componentFactoryResolver: ComponentFactoryResolver) {
}

ngOnInit(): void {

}
}

We need a view container Reference which is #container, which is the place where our component will load. Also, we need to inject ComponentFactoryResolver which is the one which will create an instance of the component.

We can also add an Input to pass the name of which component we want to load. So, in the end, our file will look like this:

export class ModalWrapperComponent implements OnInit {

@ViewChild('container', { read: ViewContainerRef, static: true }) container: ViewContainerRef;
@Input() componentName: string;

private componentRef: ComponentRef<{}>;
private componentsMapping = {
myFirstComponent: MyFirstComponent,
mySecondComponent: MySecondComponent
};

constructor(private componentFactoryResolver: ComponentFactoryResolver) {
}

ngOnInit(): void {
const component =this.componentsMapping[this.componentName];

const factory = this.componentFactoryResolver.resolveComponentFactory(component);
this.componentRef = this.container.createComponent(factory);

}
}

Great. So now we have a component which will take another component and load it inside <div #container></div>

Now let's get all the things together.

In our demo component, we can create a modal window in 2 ways, either by using a template reference or loading a component. Let’s try it with Template Reference first.

With Template

@Component({
selector: 'app-demo',
template: `
<div onClick="openModalWithComponent(template)">
Open modal
</div>
<div #template>
<modal-wrapper componentName="myFirstComponent" /> </div>
</div>
`,
})
export class DemoComponent {
bsModalRef: BsModalRef;
constructor(private modalService: BsModalService) {}

openModalWithComponent(template: TemplateRef<any>) {
this.bsModalRef = this.modalService.show(template);
}
}

With Component:

@Component({
selector: 'app-demo',
template: `
<div onClick="openModalWithComponent('mySecondComponent')">
Open modal
</div>
`,
})
export class DemoComponent {
bsModalRef: BsModalRef;
constructor(private modalService: BsModalService) {}

openModalWithComponent(componentNameToLoadInBody) {
ModalWrapperComponent.prototype.componentName = componentNameToLoadInBody;
this.bsModalRef = this.modalService.show(ModalWrapperComponent);
}
}

And that should do the trick.

Conclusion:

To avoid repeating HTML parts for our modal window components, such as the header, the close button, or the footer, we can create a wrapper component that loads dynamic components in runtime by using an element reference with the help of ComponentFactoryResolver provided by Angular 2.

--

--