Back
Feb 28, 2017

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

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 a note about the scope of variables. Despite the fact that the

Hello {{name}} !

code is inside of , 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: `
   <div>
     <child-component>
       <p content-header>{{name}} header !</p>
       <p class="content-footer">{{name}} footer !</p>
     </child-component>
   </div>
 `,
})
export class App {
 name:string;
 constructor() {
   this.name = 'Parent'
 }
}

@Component({
 selector: 'child-component',
 template: `
   <div>
     <ng-content select="[content-header]"></ng-content>
     <p>Hello {{name}} !</p>
     <ng-content select=".content-footer"></ng-content>
   </div>
 `,
})
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 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: `
   <div>
     <counter #appCounter></counter>
     <child-component [counterComponent]="appCounter"></child-component>
     <button (click)="appCounter.decrease()">Decrease counter</button>
   </div>
 `,
})
export class App {
 constructor() {
 }
}

@Component({
 selector: 'child-component',
 template: `
   <div>
     <button (click)="counterComponent.increase()">Increase counter</button>
   </div>
 `,
})
export class Child {
 @Input() counterComponent: Counter;

 constructor() {
 }
}

@Component({
 selector: 'counter',
 template: `
   <div>
     Counter: {{count}}
   </div>
 `,
})
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 appCounterinstance 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