AngularJS 2.0 From The Beginning - ngContent - Day Twelve

I am here to continue the discussion around AngularJS 2.0. So far, we discussed about data binding, input properties, output properties, pipes, viewchild, and also about directives in Angular 2.0. Now in this article, I will discuss how to use ngContent or transclusion in Angular 2.0. Also, in case you did not have a look at the previous articles of this series, go through the links mentioned below.

In my previous article, I already discussed about the injectable Service in Angular 2.0. In this article, we will discuss about the content template or ng-content in Angular 2.0.

In Angular 1.0, there is a concept of Transclusion. Actually, transclusion in an Angular 1.x is represent the content replacement such as a text node or html, and injecting it into a template at a specific entry time. Same thing in Angular 2.0 is totally forbidden. This is now done in Angular 2.0 through modern web APIs, such as shadow DOM which is known as content projection.

Content Projection

So now, we know what we are looking from an Angular 1.x perspective so that we can easily migrate the same in Angular 2.0. Actually, projection is a very important concept in Angular. It enables developer to develop or build reusable components and make the application more scalable and flexible.

In Web Components, we had the <content> element, which was recently deprecated, which acted as a Shadow DOM insertion point. Angular 2 allows Shadow DOM through the use of ViewEncapsulation. Early alpha versions of Angular 2 adopted the <content> element, however due to the nature of a bunch of Web Component helper elements being deprecated, it was changed to <ng-content>. Actually ViewEncapsulation defines whether the template and styles defined within the component can affect the whole application or vice versa. Angular provides three encapsulation strategies,

  • Emulated (default)
    styles from main HTML propagate to the component. Styles defined in this component's @Component decorator are scoped to this component only.
  • Native
    styles from main HTML do not propagate to the component. Styles defined in this component's @Component decorator are scoped to this component only.

  • None
    styles from the component propagate back to the main HTML and therefore are visible to all components on the page. Be careful with apps that have None and Nativecomponents in the application. All components with None encapsulation will have their styles duplicated in all components with Native encapsulation.

To illustrate ng-content that, suppose we have a children component.

  1. import { Component } from '@angular/core';  
  2.   
  3. @Component({  
  4.   selector: 'child',  
  5.   template: `  
  6.     <div style="border: 1px solid blue; padding: 1rem;">  
  7.       <h4>Child Component</h4>  
  8.       <ng-content></ng-content>  
  9.     </div>  
  10.   `  
  11. })  
  12. export class ChildComponent {  

Then, when we use ChildComponent in the template.
  1. <child>  
  2.     <p>My <i>dynamic</i> content.</p>  
  3. </child>   

This is telling Angular that for any markup that appears between the opening and closing tag of <child>, to place inside of <ng-content></ng-content>. When doing this, we can have other components, markup, etc. projected here and the ChildComponent does not need to know about or care what is being provided.

But what if we have multiple <ng-content></ng-content> and want to specify the position of the projected content to certain ng-content? For example, for the previous ChildComponent, if we want to format the projected content into an extra area1 and area2 section. Then in the template, we can use directives, say, <area1> to specify the position of projected content to the ng-content with select="area1".

For demonstrating the concept of ngContent, we will create a modal component where modal body and footer section dynamically added by the HTML tags.
 
For example, we will create the following files with the below code.

File Name - app.component.modal.html
  1. <div class="modal" id="myModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true" [ngStyle]="{'display' : display }">  
  2.     <div class="modal-dialog">  
  3.         <div class="modal-content animated bounceInRight">  
  4.             <div class="modal-header">  
  5.                 <button type="button" class="close" (click)="fnClose()">×</button>  
  6.                 <h3 class="modal-title">{{header}}</h3>  
  7.             </div>  
  8.             <div class="modal-body">  
  9.                 <ng-content select="content-body"></ng-content>  
  10.             </div>  
  11.             <div class="modal-footer">  
  12.                 <ng-content select="content-footer"></ng-content>  
  13.             </div>  
  14.         </div>  
  15.     </div>  
  16. </div> 
File Name - app.component.modal.ts
  1. import { Component, OnInit, ViewChild, Input } from '@angular/core';  
  2.   
  3. @Component({  
  4.     moduleId: module.id,  
  5.     selector: 'modal-window',  
  6.     templateUrl: 'app.component.modal.html'  
  7. })  
  8.   
  9. export class ModalComponent implements OnInit {  
  10.     @Input() private display: string = 'none';  
  11.     @Input('header-caption') private header: string = 'Modal';  
  12.   
  13.     constructor() {  
  14.     }  
  15.   
  16.     ngOnInit(): void {  
  17.     }  
  18.   
  19.     private fnClose(): void {  
  20.         this.display = 'none';  
  21.     }  
  22.   
  23.     showModal(): void {  
  24.         this.display = 'block';  
  25.     }  
  26.   
  27.     close(): void {  
  28.         this.fnClose();  
  29.     }  
  30.   
  31.     setModalTitle(args: string): void {  
  32.         this.header = args;  
  33.     }  

File Name - app.component.parent.html
  1. <div>  
  2.     <h2>Demonstrate Modal Window using ngContent</h2>  
  3.     <input type="button" value="Show Modal" class="btn-group" (click)="fnOpenModal()" />  
  4.     <br />  
  5.     <modal-window [header-caption]="caption" #modal>  
  6.         <content-body>  
  7.             <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum tincidunt est vitae ultrices accumsan. Aliquam ornare lacus adipiscing, posuere lectus et, fringilla augue.</p>  
  8.             <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum tincidunt est vitae ultrices accumsan. Aliquam ornare lacus adipiscing, posuere lectus et, fringilla augue.</p>  
  9.             <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum tincidunt est vitae ultrices accumsan. Aliquam ornare lacus adipiscing, posuere lectus et, fringilla augue.</p>  
  10.             <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum tincidunt est vitae ultrices accumsan. Aliquam ornare lacus adipiscing, posuere lectus et, fringilla augue.</p>  
  11.             <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum tincidunt est vitae ultrices accumsan. Aliquam ornare lacus adipiscing, posuere lectus et, fringilla augue.</p>  
  12.         </content-body>  
  13.         <content-footer>  
  14.             <input type="button" class="btn-default active" class="btn btn-primary" value="Modal Close" (click)="fnHideModal();" />   
  15.         </content-footer>  
  16.     </modal-window>  
  17. </div> 
File Name - app.component.parent.ts
  1. import { Component, OnInit, ViewChild } from '@angular/core';  
  2. import { ModalComponent } from './app.component.modal';  
  3.   
  4. @Component({  
  5.     moduleId: module.id,  
  6.     selector: 'parent-content',  
  7.     templateUrl: 'app.component.parent.html'  
  8. })  
  9.   
  10. export class ParentComponent implements OnInit {  
  11.   
  12.     private caption: string = 'Custom Modal';  
  13.     @ViewChild('modal') private _ctrlModal: ModalComponent;  
  14.   
  15.     constructor() {  
  16.     }  
  17.   
  18.     ngOnInit(): void {  
  19.     }  
  20.   
  21.     private fnOpenModal(): void {  
  22.         this._ctrlModal.showModal();  
  23.     }  
  24.   
  25.     private fnHideModal(): void {  
  26.         this._ctrlModal.close();  
  27.     }  

File Name - app.module.ts
  1. import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';  
  2. import { BrowserModule } from '@angular/platform-browser';  
  3. import { FormsModule } from "@angular/forms";  
  4. import { ParentComponent } from './src/app.component.parent';  
  5. import { ModalComponent } from './src/app.component.modal';  
  6.   
  7. @NgModule({  
  8.     imports: [BrowserModule, FormsModule],  
  9.     declarations: [ParentComponent, ModalComponent],  
  10.     bootstrap: [ParentComponent],  
  11.     schemas: [NO_ERRORS_SCHEMA]  
  12. })  
  13. export class AppModule { } 
 File Name - main.ts
  1. import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';  
  2.   
  3. import { AppModule } from './app.module';  
  4.   
  5. const platform = platformBrowserDynamic();  
  6. platform.bootstrapModule(AppModule); 
File Name - index.html
  1. <!DOCTYPE html>  
  2. <html>  
  3. <head>  
  4.     <title>Angular2 - ngContent</title>  
  5.     <meta charset="UTF-8">  
  6.     <meta name="viewport" content="width=device-width, initial-scale=1">  
  7.     <link href="../resources/style/bootstrap.css" rel="stylesheet" />  
  8.     <link href="../resources/style/style1.css" rel="stylesheet" />  
  9.      
  10. </head>  
  11. <body>  
  12.     <parent-content>Loading</parent-content>  
  13.     <!-- Polyfill(s) for older browsers -->  
  14.     <script src="../resources/js/jquery-2.1.1.js"></script>  
  15.     <script src="../resources/js/bootstrap.js"></script>  
  16.   
  17.     <script src="../node_modules/core-js/client/shim.min.js"></script>  
  18.     <script src="../node_modules/zone.js/dist/zone.js"></script>  
  19.     <script src="../node_modules/reflect-metadata/Reflect.js"></script>  
  20.     <script src="../node_modules/systemjs/dist/system.src.js"></script>  
  21.     <script src="../systemjs.config.js"></script>  
  22.     <script>  
  23.         System.import('app').catch(function (err) { console.error(err); });  
  24.     </script>  
  25. </body>  
  26. </html> 
Now, execute the program and output as below.