Passing pieces of markup to components in Angular 2 and problems with dynamic content

, , angular.js, typescript, javascript

Hello, everyone! In the process of web applications developing, a programmer has to work with the HTML templates whether he or she wants it or not. Yes, that's how web apps work.

In Angular 2, as we already know, everything is a component with an HTML template attached. Components can be inserted into each other, they can pass variables to each other and may even call methods or subscribe to each other events.

The lion's share of the settings in this super structure is located in html.

In addition, it is often necessary to draw something in the child component depending on a fact in which parent component is nested a child component. And in this article, I will try to describe how to work with all of this.

Projections and Transclusion

For those who have written or still writes on Angular 1.x, there are extremely relevant dynamic techniques of loading templates into the directive: transclude, $compile and stuff. Well, we would like to announce: in Angular 2, it is not working the same way, simply as the second version of this wonderful framework interacts completely differently with DOM. However, it would be strange if there was no possibility of such manipulations. There is a way, and it is called content projection. It could be activated via themagic tag.

In order to simply insert something in our component, we need to indicate where we want to insert the content. For this purpose, we use ng-content. Here is the code:

import {Component, NgModule} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'

@Component({
 selector: 'my-app',
 template: <div>
     <child-component>
       <p>Hello there is ng-content from {{name}} !</p>
     </child-component>
   </div>,
})
export class App {
 name:string;
 constructor() {
   this.name = 'Parent'
 }
}

@Component({
 selector: 'child-component',
 template: <div>
     <ng-content></ng-content>
     <p>Hello {{name}} !</p>
   </div>,
})
export class Child {
 name:string;
 constructor() {
   this.name = 'Child'
 }
}

@NgModule({
 imports: [ BrowserModule ],
 declarations: [ App, Child ],
 bootstrap: [ App ]
})
export class AppModule {}

A working example is available at this link. Here I want to make anote about the scope of variables. Despite the fact that the <p>Hello {{name}} !</p> code is inside of <child-component>, the {{ name }} variable will be still taken from the parent component.

Thus, everything that is passed to the child component through ng-content is actually a part of the parent component and will use variables from parent component.

You might be wondering - what to do if we need to project content from the parent component to the multiple places in child component? For this purpose, ng-content has a Select attribute. This attribute must contain a selector that will be used to search for the projected content. This may be the select =" some-element " selector tag, the select =" [attr-name] " select =" [attr-name = value] " attribute or the select =." Class-name " class. Again, here is a bit of code:

@Component({
 selector: 'my-app',
 template: &lt;div&gt;
     &lt;child-component&gt;
       &lt;p content-header&gt;{{name}} header !&lt;/p&gt;
       &lt;p class=&quot;content-footer&quot;&gt;{{name}} footer !&lt;/p&gt;
     &lt;/child-component&gt;
   &lt;/div&gt;,
})
export class App {
 name:string;
 constructor() {
   this.name = 'Parent'
 }
}

@Component({
 selector: 'child-component',
 template: &lt;div&gt;
     &lt;ng-content select=&quot;[content-header]&quot;&gt;&lt;/ng-content&gt;
     &lt;p&gt;Hello {{name}} !&lt;/p&gt;
     &lt;ng-content select=&quot;.content-footer&quot;&gt;&lt;/ng-content&gt;
   &lt;/div&gt;,
})
export class Child {
 name:string;
 constructor() {
   this.name = 'Child'
 }
}

@NgModule({
 imports: [ BrowserModule ],
 declarations: [ App, Child ],
 bootstrap: [ App ]
})
export class AppModule {}

Here, we send header via an attribute selector and footer via a class selector. A working example can be seen here.

Sending a component instance

Now, as I have already described <ng-content> basic capabilities, let's leave it alone. The Angular 2 framework system has other interesting possibilities, for example, Template reference variables. This feature is quite briefly described in the documentation, but it has managed to appeal to so many Angular developers.

So what is this? Template reference variables is just a convenient way of interacting the components and the DOM elements.

How to use it? To send a request from one Component to another or to a DOM element, you must forward a link to the DOM-element or to the Angular Component by using the # element (to avoid the attacks of aesthetes, the Angular 2 authors permit the use of ref- instead of #. I am not such a geek, so I will use the #) element here. The simplest example:

import {Component, NgModule, Input} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'

@Component({
 selector: 'my-app',
 template: &lt;div&gt;
     &lt;counter #appCounter&gt;&lt;/counter&gt;
     &lt;child-component [counterComponent]=&quot;appCounter&quot;&gt;&lt;/child-component&gt;
     &lt;button (click)=&quot;appCounter.decrease()&quot;&gt;Decrease counter&lt;/button&gt;
   &lt;/div&gt;,
})
export class App {
 constructor() {
 }
}

@Component({
 selector: 'child-component',
 template: &lt;div&gt;
     &lt;button (click)=&quot;counterComponent.increase()&quot;&gt;Increase counter&lt;/button&gt;
   &lt;/div&gt;,
})
export class Child {
 @Input() counterComponent: Counter;

 constructor() {
 }
}

@Component({
 selector: 'counter',
 template: &lt;div&gt;
     Counter: {{count}}
   &lt;/div&gt;,
})
export class Counter {
 private count: number = 0;

 increase() {
   this.count++;
 }

 decrease() {
   this.count--;
 }
}

@NgModule({
 imports: [ BrowserModule ],
 declarations: [ App, Child, Counter ],
 bootstrap: [ App ]
})
export class AppModule {}

Let me tell you what's going on here. First, we add the Counter component inside the App component. Further, through the # element, we announce that we want to have a link to its appCounter instance variable. In the App component template, we can refer to this variable, and that's what we actually do in order to call the decrease method.

And we pass appCounter through the Input-variable counterComponent to the Child component. As a result, in the Child component, we have a counterComponent variable that stores a link to the instance of the Counter neighbor component. Yes, yes, now we have an access to the public variables and to the Counter methods component from the Child.

Will use this benefit and call counterComponent.increase() by clicking on the button in the Child. Here is another link to a working example.

Transferring a template instance

We can pass references with #. This can be ElementRef for access to the DOM element, ComponentRef for access to the component and... attention! (drumroll)... to TemplateRef!

TemplateRef indicates on the <template> </ template> element, which is actually Angular 2 Directive and which we can use in components. How to use it? I will show you now:

import {Component, NgModule, Input, TemplateRef} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'

@Component({
 selector: 'my-app',
 template: &lt;div&gt;
     &lt;template #customTemplate let-name=&quot;name&quot;&gt;
       There is Custom Template included to {{name}}
     &lt;/template&gt;
     &lt;template [ngTemplateOutlet]=&quot;customTemplate&quot; [ngOutletContext]=&quot;{name: &#39;App&#39;}&quot;&gt;&lt;/template&gt;
     &lt;child-component [template]=&quot;customTemplate&quot;&gt;&lt;/child-component&gt;
   &lt;/div&gt;,
})
export class App {
 constructor() {
 }
}

@Component({
 selector: 'child-component',
 template: &lt;div&gt;
     &lt;template [ngTemplateOutlet]=&quot;template&quot; [ngOutletContext]=&quot;{name: &#39;Child&#39;}&quot;&gt;&lt;/template&gt;
   &lt;/div&gt;,
})
export class Child {
 @Input() template: TemplateRef;
 constructor() {
 }
}


@NgModule({
 imports: [ BrowserModule ],
 declarations: [ App, Child ],
 bootstrap: [ App ]
})
export class AppModule {}

So here in the App component we have created a template and announced that we expect to transfer a context into this template. It will have the "name" key, the value of which should be extracted and placed into the "name" variable. This is how it will look like: let-name = "name". We have placed a link to this element in customTemplate variable by using the familiar #. In the next line, we use it with the ngTemplateOutlet directives (besides we have sent context to the directive by using ngOutletContext). Next, we passed TemplateRef to the Child component and again, in this component, we used a template but with a different context. As a result, we can reuse the same predetermined template as many as long we have the TemplateRef link to it. A working code is here.

Pseudo-dynamic creation of components

“But let me point out!”, you might say, "It's not what I need, I just want to load the html template for a component dynamically, for example from the database. How can I do that?" "There’s no way to do that" is going to be the answer to this. Either you can paste the part of the parent component or you can use a few predefined templates to upload them through ngTemplateOutlet, or you can create a component dynamically. Regarding the dynamic creation of components, it is not simple as we would like to be, but I'll tell you a little about this below. Now I will show you one way, which is useful if you have a component class with a strictly limited number of possible html templates.

The idea is to create components at a compile phase from a typescript in javascript. Yes, it's not quite the dynamic creation of components and as a result we get a js code with the number of components === number of templates. However, it will allow us to prevent repeatability of code in the original typescript.

Therefore, we need a service in which the components will be registered for this:

import {Type} from '@angular/core/src/type';

export class InnerService {

 static components: any = {};

 static getTemplate(template_name: string, unsafe?: boolean): string {
   if (template_name in this.components || unsafe) {
     try {
       return require(./templates/${template_name}.html);
     }
     catch (e) {
       return require('./templates/default.html');
     }
   }
   else {
     return require('./templates/default.html');
   }
 }

 static registerComponent(template_name: string, component: Type<any>) {
   this.components[template_name] = component;
 }

 static getComponent(template_name: string) {
   return this.components[template_name] || this.components['default'];

 }

}

This service has three static methods and one static variable-dictionary, which stores instances of components. The first method takes an html template through the “Require”. With the second method, we will add a component in our variable-dictionary. And by using the third method, we can get a component instance from the dictionary.

We also need a component-container for our dynamic insert:

@Component({
 selector: 'container-item',
 template: '<div #dynamicInner></div>',
 entryComponents: DynamicInners
})
export class ContainerItem implements OnInit {
 @Input() template_name: string;

 @ViewChild("dynamicInner", {read: ViewContainerRef}) dynamicInner: ViewContainerRef;

 protected componentRef: ComponentRef<any>;

 constructor(private resolver: ComponentFactoryResolver) { }

  ngOnInit() {
        let component = InnerService.getComponent(this.template_name);

        let factory = this.resolver.resolveComponentFactory(component);
        this.componentRef = this.dynamicInner.createComponent(factory);
        this.componentRef.instance.template_name = this.template_name;

  }
}

In this container, we send the name of the html template which we need to use. And after initializing it, we insert the appropriate component and receive its instance from our service.

Now we can add components to each template:

export class DynamicInner {
@Input() template_name: string;
}

export let DynamicInners: Type<DynamicInner>[] = [];

let templates = require.context('./templates/', false, /.html$/)

templates.keys().forEach( key => {
 let template_key: string = /([\w-]+).html$/.exec(key)[1]
 let mcp = Component({
     selector: dynamic-inner-${template_key},
     template: InnerService.getTemplate(template_key, true)
 })(class extends DynamicInner {});
 DynamicInners.push(mcp);
 InnerService.registerComponent(template_key, mcp);
});

Here, DynamicInner is a component’s class that we will use for our templates. With require.context we get a list of the html files in the directory templates. Then, we create instance component for each element in cycle and register it in our service.

Hereafter, we can do something like this in our application:

@Component({
 selector: 'child-component',
 template: &lt;div&gt;
     &lt;ng-content&gt;&lt;/ng-content&gt;
     &lt;p&gt;Hello {{name}} !&lt;/p&gt;
     &lt;p&gt;There is dynamic items:&lt;/p&gt;
     &lt;div *ngFor=&quot;let item of items&quot;&gt;
       &lt;container-item [template_name]=&quot;item&quot;&gt;&lt;/container-item&gt;
     &lt;/div&gt;
   &lt;/div&gt;,
})
export class Child {
 name:string;
 items: string[] = ['first', 'second', 'wrong'];
 constructor() {
   this.name = 'Child'
 }
}

Thus, we load the first component in the container-item with the ./templates/first.html template. Then we load ./templates/second.html, and as we do not have ./templates/wrong.html, the ./templates/default.html component will be automatically downloaded. Later, it will be enough to create the wrong.html file in the templates directory to process the “wrong” value – and it will be successfully downloaded.

Dynamic creation of components

The methods for dynamic creation of components in Angular 2 have changed several times together with the transition of framework from alpha to beta, from the beta to rc and from the rc to a release version. Thus, we may conclude, the development team had to seriously figure out how to give us a better framework. Or to make it so good, so a desire to use this framework disappeared by itself. Though, it looks like some hint at the necessity to improve the architecture of application. In the internet, you can find lots of articles and examples of how to implement dynamic creation of components in Angular 2. Unfortunately, most of them belong to the earlier versions of the product and currently, no longer applicable.

Generally, it is not a trivial task and probably, no one would want to do this, except for educational purposes. After all, to create a "fair" dynamic component, you have to:

  1. Create a Template
  2. Find ComponentFactory in the cache (if you found it then go to step 7)
  3. Create a Component
  4. Create Module for Component
  5. Compile Module
  6. Return ComponentFactory
  7. Use Target and ComponentFactory to create a dynamic component

In addition to all, it will not work at AoT compile, only with JIT. Here is a link where has been described in detail the adventures of a person who has an inquisitive mind and who have implemented this mission. But I see little sense in repeating it here.

contact us right now