fbpx Skip to content

Aquent | DEV6

Modal Templates in Angular 2 – Part 1: Inline Templates

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.

With the recent release of Angular 2 final back in September, the race is on to update some of the more popular open source libraries from Angular 1. One of the most popular such libraries is ng-bootstrap, already well on its way to Angular 2 and TypeScript compatibility.

This post is part 1 of a 3-part series in which I will cover various techniques for using bootstrap’s NgModal  to open modal dialog windows with Angular 2.

In exploring the new framework, I noticed a very interesting change from Angular 1 in the NgModal directive’s open method signature:

open(content: string | TemplateRef<any>, options: NgbModalOptions)

The content parameter can be string, which is pretty straightforward as the modal just displays the string content. Alternately, the content can be a TemplateRef. This is where things get tricky.

Angular 1’s modal method signature included a component and a controller property, using Angular’s $compiler to render the template content and optional controller dynamically. This allowed developers to keep commonly used modal templates in external HTML files and render them at runtime.

Angular 2 has moved away from $compiler, so the approach to rendering dynamic content is a little different. The main issue arises when trying to generate a TemplateRef instance to hand off to the modal window. From the Angular.io documentation:

“You can access a TemplateRef, in two ways. Via a directive placed on a <template> element (or directive prefixed with*) and have the TemplateRef for this Embedded View injected into the constructor of the directive using the TemplateRef Token. Alternatively you can query for the TemplateRef from a Component or a Directive via Query.”

Two similar approaches, but the common requirement here is that the template needs to be rendered from within a component or directive; hence it must make its way onto the DOM somehow. This is sufficient for our first use case, which I will call the “Inline Template”.

Inline Templates

Say you have a custom form that takes details on a car. You could then have an optional modal window that would allow for more customization of a specific feature. Since this modal template will likely only live within this form, you can declare the template within this form component, like so:

<form (ngSubmit)="submitForm()" #carForm="ngForm" novalidate>

…

<button type="button" class="btn" (click)="openModal()">Customize</button>

 

<button type="submit" class="btn btn-primary" [disabled]="!carForm.valid">Submit</button>
</form>

<template #engineTemplate>
    <h4 class="modal-title">Engine</h4>
    <div class="modal-body">

…

…
    </div>
    <div class="modal-footer">
        <button type="button" class="btn btn-secondary" (click)="cancelEngine()">Cancel</button>
        <button type="button" class="btn btn-secondary" (click)="saveEngine()">Done</button>
    </div>
</template>

From here, the logic that controls the modal window can be contained within the same component, or if you’d like to separate it out, you can declare a component or directive within the template itself and then only interact with the bootstrap Modal service.

export class InlineFormComponent {
   @ViewChild("engineTemplate") private engineModal: TemplateRef<any>;
   ...
   dialog: NgbModalRef | null;
  
   constructor(private modalService: NgbModal) {}
  
 

   openModal(): void {
      this.dialog = this.modalService.open(this.engineModal);
   }
  
   cancelModal (): void {
      if ( this.dialog ) {
         this.dialog.dismiss();
         this.dialog = null;
      }
   }

...
}

This code demonstrates a couple of key concepts:

  • @ViewChild(“engineTemplate”) will capture the template (tagged with #engineTemplate) as a TemplateRef variable. Note that this variable is null until the onAfterViewInit lifecycle phase, but since we are not using it until a user action (button click) we don’t need to hook into the lifecycle.
  • Bootstrap’s modal is injected through the constructor of this form to expose the open method.
  • A variable is kept (dialog) to store the modal reference. This allows the component to close the modal afterward.
  • The template references members of the component class that contains it.

One final note about using ng-bootstrap’s modal is that it requires a root template tag, usually declared at the bottom of your entry template:

<template ngbModalContainer></template>

This gives NgModal a point of reference for rendering its modal windows on top of your other content.

Inline templating is a reasonable approach when your development needs require tight coupling between the modal window and the location that calls it. However, if you ever needed to re-use your template in another location, you’d find yourself copying the template or using messy inclusion techniques to obtain your TemplateRef.

For a more generic template based approach, I will take a look at employing a template store in part 2.

Sign up for our Angular Course

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

view course details