fbpx Skip to content

Aquent | DEV6

Modal Templates in Angular 2 – Part 2: Template Stores

Written by: Tyler Padley
Note: After this writing, ng-bootstrap has modified their modal implementation to accept Component Types. The techniques discussed here can still be used to create TemplateRef objects.

Complete project files for this series are available on github.

This is part 2 of my 3-part series on Modal Templates in Angular 2. In part 1, we took a look at the changes to ng-bootstrap, in particular the Modal component’s open method that now takes an Angular 2 TemplateRef object.

In part 1, I demonstrated how an inline template can be a convenient way to handle this requirement, but also explained the many shortcomings with this approach. While it may still prove useful for some unique modal window needs, it’s not very reusable.

A template store can go a long way toward improving usability across a larger application. Conceptually the technique is pretty straightforward. Register an injectable store within the application, and populate it from a common source once the app view has been initialized. Let’s dig in:

@Injectable()
export class TemplateStore {
   private templates: [string, TemplateRef<any>][] = [];
  
   addTemplate(template: [string, TemplateRef<any>]):void {
      this.templates.push(template);
   }
   
   getTemplateByName( name: string ):TemplateRef<any> {
      return this.templates.find(template => template[0] === name)[1];
   }
}

The template store holds the templates in a Tuple, essentially providing a string as a key to locating the template. This becomes important later. You can also see that the getTemplateByName method looks up the templates by the key of the tuple and returns the reference.

So now that we have a store to hold the templates, how do we populate them? There are a number of different approaches you can take here, including:

  • Wrapping each template in a component class and including the components in the application.
  • Combining all templates into one component, and then using a directive to access the template.

You can include all the templates into a single component and then access them via @ViewChildren, but the problem with that approach becomes differentiating the components from each other. TemplateRefs only contain the ElementRef and View that they were created in, so you’d have to get into DOM manipulation to be able to properly identify them. Don’t do it.

Here is an example of using a directive to identify your templates:

@Directive({
   selector: "[dv6-template-index]",
})

export class TemplateIndexDirective implements OnInit {
   @Input("dv6-template-index") templateName: string;
  
   constructor( private templateStore: TemplateStore, private ref: TemplateRef<any> ) {
     
   }
  
   ngOnInit() {
      if ( this.ref && this.templateName ) {
         this.templateStore.addTemplate([this.templateName, this.ref]);
      }
   }
}

You can see here that the directive injects the template store, and captures the TemplateRef token. I’m also using an @Input to capture a name for the template. The usage for this directive is as follows:

<template [dv6-template-index]="'templateName'">

So we now have a template store to hold our template references, and a directive to capture the TemplateRefs once rendered. Our last steps are to include our templates in the view:

<template ngbModalContainer></template>
<dv6-template-store></dv6-template-store>

So we now have a template store to hold our template references, and a directive to capture the TemplateRefs once rendered. Our last steps are to include our templates in the view:

<template ngbModalContainer></template>
<dv6-template-store></dv6-template-store>

And then register our classes with the module:

declarations: [AppComponent, TemplateIndexDirective, TemplateStoreComponent],

providers: [TemplateStore]

We now have our template store, our templates, and the directives to capture them. We have everything we need to display our templates in the modal dialog, like so:

constructor(private modalService: NgbModal, private templateStore: TemplateStore) {
}

openModal( name: string, config: any = {} ) {
   this.config = config;
   this.modal = this.modalService.open(this.templateStore.getTemplateByName(name));
}

This system is a big improvement over inline templating, greatly increasing reusability of our templates, but it is not without its flaws. All of our templates still need to exist somewhere in the view, and we are now instantiating a directive and/or component to manage the extraction of each template.

There is another solution to this problem, one that I will demonstrate in part 3 of my tutorial series, dynamic templates.

Check out part 1 here if you missed it!

Sign up for our Angular Course

Learn the most recent version and start building your own Angular apps

view course details