How to create dynamic tab component in Angular?


Listening is fun too.

Straighten your back and cherish with coffee - PLAY !

 
 

Introduction

This article aims to clarify the concept of dynamic components and template references in angular by building a dynamic tab component. ComponentFactoryResolver, ViewContainerRef, ngTemplateOutlet, and ngTemplateOutletContext will all be covered.

What are dynamic components in Angular?

Dynamic simply defines as the component locations in the application is not defined at build-time.

The component is instantiated and placed in an application at runtime.

Great to get a quick overview, out tab component API currently looks as follows.


    Content of Tab 1
    Content of Tab 2
  

[app.component.html]
 

As you can see, it is already possible to define tabs statically, by using the tab component placed inside the .

We now want to enhance this API so that we can open new tab pages dynamically in response to user interaction.

Assume for example that we have a first statically defined tab that contains a list of people.

angular1

[output:1]
 

We want the editing to happen in separate tab pages that are dynamically added whenever we click the "Add new person" or "edit" button below an existing entry.

angular2

[output:2]
 

Desired API for our dynamic tabs

So, what does our dynamic tabs API look like?

First and foremost, we must be able to customize the content of our dynamic tabs.

This is done in the AppComponents view, which also contains our app-tabs> component.

Angular provides us with the ng-template> tag to define the so-called template of the dynamic tab we wish to display.


    This is the content of dynamic tab.
  

[app.component.html]
 

Whenever the user clicks the "edit" or "Add new person" button, some handler in our component code is definitely invoked.

We want to be able to call up the construction of a new tab from within there, something like this:

import { Component, ViewChild } from '@angular/core';
import { TabsComponent } from './tabs/tabs.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  @ViewChild('personEdit') editPersonTemplate;
  @ViewChild('about') aboutTemplate;
  @ViewChild(TabsComponent) tabsComponent;
  people = [
    {
      id: 1,
      name: 'Mr.Raj',
      surname: 'Shah',
      twitter: '@raj'
    }
  ];
  onEditPerson(person) {
    this.tabsComponent.openTab(
      `Editing ${person.name}`,
      this.editPersonTemplate,
      person,
      true
    );
  }
 onAddPerson() {
    this.tabsComponent.openTab('New Person', this.editPersonTemplate, {}, true);
  }

[app.component.ts]
 

Thats all there is to the API were interested in; now lets dig a little deeper into the code to see how the tabscomponent.openTab(....) function is implemented.

   

Reference elements from the view

As youve observed, we reference two components from of the AppComponent view template in our onEditPerson(...) function: this.tabsComponent and this.editPersonTemplate.





  

[app.component.html]

Note that we added a template variable to our from before. Within our AppComponent we can now reference our tabs component () as well as the ng-template by using Angular’s @ViewChild decorator.

import { Component, ViewChild } from '@angular/core';
import { TabsComponent } from './tabs/tabs.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  @ViewChild('personEdit') editPersonTemplate;
  @ViewChild('about') aboutTemplate;
  @ViewChild(TabsComponent) tabsComponent;
  people = [
    {
      id: 1,
      name: 'Mr.Raj',
      surname: 'Shah',
      twitter: '@raj'
    }
  ];
  onEditPerson(person) {
    this.tabsComponent.openTab(
      `Editing ${person.name}`,
      this.editPersonTemplate,
      person,
      true
    );
  }
onAddPerson() {
    this.tabsComponent.openTab('New Person', this.editPersonTemplate, {}, true);
  }
onPersonFormSubmit(dataModel) {
    if (dataModel.id > 0) {
      this.people = this.people.map(person => {
        if (person.id === dataModel.id) {
          return dataModel;
        } else {
          return person;
        }
      });
    } else {
      // create a new one
      dataModel.id = Math.round(Math.random() * 100);
      this.people.push(dataModel);
    }
 // close the tab
    this.tabsComponent.closeActiveTab();
  }
 onOpenAbout() {
    this.tabsComponent.openTab('About', this.aboutTemplate, {}, true);
  }
}


[app.component.html]
 

We access the template by its name, which we defined in the view, namely "personEdit," instead of the TabsComponent by its type, which is obviously handier.

Great, now we have all of the variables we need to provide to our openTab(...) function.

Define an Anchor point

The TabsComponent will be dynamically injected with new TabComponent objects.

To do so, we must first specify a region in our tab component where our dynamic tab pages will be placed.

Angulars content projection method is currently being used to load over our statically specified ones.


[app.component.html]
 

The static tabs get “projected” into the section and referenced in the code via the @ContentChildren.

We cant utilize the same @ContentChildren mechanism because its only useful for projected material that needs to happen statically.

Similarly, our dynamic tab components must require a different anchor point.


[app.component.html]
 

To do so, I’m using an with a special directive attached to it: dynamic-tabs. Our so-called anchor directive is very simple:


import { Directive, ViewContainerRef } from 
'@angular/core';

@Directive({
  selector: '[appDynamicTabs]'
})
export class DynamicTabsDirective {
constructor(public viewContainer: ViewContainerRef) { } }


This one is simply necessary for eventually retrieving the viewContainerRef.

We can later access it through another viewContainer variable by injecting it in the function Object() { [native code] } of our directive and using the public modifier.

Use a template variable as an anchor instead.

Instead of writing a particular anchor directive, you can utilize a simple template variable and access the view container it belongs to using the @ViewChild decorator.


  • ...

In the TabsComponent class we then grab the view container of #container as follows.

import { Component, ComponentFactoryResolver, ContentChildren, OnInit, QueryList, ViewChild } from '@angular/core';
import { DynamicTabsDirective } from './dynamic-tabs.directive';
import { TabComponent } from './tab/tab.component';

@Component({
  selector: 'app-tabs',
  templateUrl: './tabs.component.html',
  styleUrls: ['./tabs.component.css']
})
export class TabsComponent implements OnInit {
  dynamicTabs: TabComponent[] = [];
  @ContentChildren(TabComponent) tabs: QueryList;
  @ViewChild(DynamicTabsDirective) dynamicTabPlaceholder: DynamicTabsDirective;
constructor(private _componentFactoryResolver: ComponentFactoryResolver) { }
  ngAfterContentInit() {
    // get all active tabs
    const activeTabs = this.tabs.filter(tab => tab.active);
 // if there is no active tab set, activate the first
    if (activeTabs.length === 0) {
      this.selectTab(this.tabs.first);
    }
  }


[app.component.ts]
 

Both methods can be employed because they have no significant advantages or disadvantages.

The ViewContainerRef object is directly referenced by dynamicTabPlaceholder, so that we can be used right away.

We will use a separate anchor directive in this post.

NgTemplateOutletContext allows us to associate data with dynamic templates.

Obviously, a template without some dynamic data is quite boring. We need to be able to pass in the person object were changing in our example.

So let’s take a closer look at our definition:

 
    
    
  


@
[add-person.component.html]
 
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';

@Component({
  selector: 'app-person-edit',
  templateUrl: './person-edit.component.html',
  styleUrls: ['./person-edit.component.css']
})
export class PersonEditComponent implements OnInit {
  personForm: FormGroup;
  @Input() person;
  @Output() savePerson = new EventEmitter();
  constructor(private fb: FormBuilder) {
    this.personForm = this.fb.group({
      id: '',
      name: '',
      surname: '',
      twitter: ''
    });
   }
 ngOnInit() {
    this.personForm.setValue({
      id: this.person.id || -1,
      name: this.person.name || '',
      surname: this.person.surname || '',
      twitter: this.person.twitter || ''
    });
  }
  onPersonFormSubmit() {
    let dataModel = this.personForm.value;
    this.savePerson.emit(dataModel);
  }
}

[add-person.component.ts]
 

One-stopoplossing voor Angular-webontwikkeling ?

Uw zoekopdracht eindigt hier.

 

Inside the template, the component takes care of editing our person record by creating a proper form with bindings, events etc.

angular3

[output:3]
 

angular4

[output:4]
 

angular5

[output:5]
 

angular6

[output:6]
 

The component has an outbound event and accepts an input binding [person] (savePerson).

Let’s first look at the simpler outbound one.

We can directly hook it to the AppComponent code where our is defined and just implement the event as we normally would.

Templates can be assigned directly to the component in which they are defined, although they may be rendered inside another component, like in our instance, the TabComponent.

Think about it, this is really powerful.

Conclusion

Many people are confused by dynamic components in Angular at first. It’s not as easy as in AngularJS (version 1.x) where you could simply render in a DOM string containing a bunch of directives/components, and then theyd load up on their own. As youve seen, Angular takes a more verbose approach. Component factories, view containers, and passing template references around them were covered. But it performs a useful function, and once you get the hang of it, it isnt all that difficult.

How to create dynamic tab component in Angular?

Introduction

This article aims to clarify the concept of dynamic components and template references in angular by building a dynamic tab component. ComponentFactoryResolver, ViewContainerRef, ngTemplateOutlet, and ngTemplateOutletContext will all be covered.

What are dynamic components in Angular?

Dynamic simply defines as the component locations in the application is not defined at build-time.

The component is instantiated and placed in an application at runtime.

Great to get a quick overview, out tab component API currently looks as follows.


    Content of Tab 1
    Content of Tab 2
  

[app.component.html]
 

As you can see, it is already possible to define tabs statically, by using the tab component placed inside the .

We now want to enhance this API so that we can open new tab pages dynamically in response to user interaction.

Assume for example that we have a first statically defined tab that contains a list of people.

angular1

[output:1]
 

We want the editing to happen in separate tab pages that are dynamically added whenever we click the "Add new person" or "edit" button below an existing entry.

angular2

[output:2]
 

Desired API for our dynamic tabs

So, what does our dynamic tabs API look like?

First and foremost, we must be able to customize the content of our dynamic tabs.

This is done in the AppComponents view, which also contains our app-tabs> component.

Angular provides us with the ng-template> tag to define the so-called template of the dynamic tab we wish to display.


    This is the content of dynamic tab.
  

[app.component.html]
 

Whenever the user clicks the "edit" or "Add new person" button, some handler in our component code is definitely invoked.

We want to be able to call up the construction of a new tab from within there, something like this:

import { Component, ViewChild } from '@angular/core';
import { TabsComponent } from './tabs/tabs.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  @ViewChild('personEdit') editPersonTemplate;
  @ViewChild('about') aboutTemplate;
  @ViewChild(TabsComponent) tabsComponent;
  people = [
    {
      id: 1,
      name: 'Mr.Raj',
      surname: 'Shah',
      twitter: '@raj'
    }
  ];
  onEditPerson(person) {
    this.tabsComponent.openTab(
      `Editing ${person.name}`,
      this.editPersonTemplate,
      person,
      true
    );
  }
 onAddPerson() {
    this.tabsComponent.openTab('New Person', this.editPersonTemplate, {}, true);
  }

[app.component.ts]
 

Thats all there is to the API were interested in; now lets dig a little deeper into the code to see how the tabscomponent.openTab(....) function is implemented.

   

Reference elements from the view

As youve observed, we reference two components from of the AppComponent view template in our onEditPerson(...) function: this.tabsComponent and this.editPersonTemplate.





  

[app.component.html]

Note that we added a template variable to our from before. Within our AppComponent we can now reference our tabs component () as well as the ng-template by using Angular’s @ViewChild decorator.

import { Component, ViewChild } from '@angular/core';
import { TabsComponent } from './tabs/tabs.component';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  @ViewChild('personEdit') editPersonTemplate;
  @ViewChild('about') aboutTemplate;
  @ViewChild(TabsComponent) tabsComponent;
  people = [
    {
      id: 1,
      name: 'Mr.Raj',
      surname: 'Shah',
      twitter: '@raj'
    }
  ];
  onEditPerson(person) {
    this.tabsComponent.openTab(
      `Editing ${person.name}`,
      this.editPersonTemplate,
      person,
      true
    );
  }
onAddPerson() {
    this.tabsComponent.openTab('New Person', this.editPersonTemplate, {}, true);
  }
onPersonFormSubmit(dataModel) {
    if (dataModel.id > 0) {
      this.people = this.people.map(person => {
        if (person.id === dataModel.id) {
          return dataModel;
        } else {
          return person;
        }
      });
    } else {
      // create a new one
      dataModel.id = Math.round(Math.random() * 100);
      this.people.push(dataModel);
    }
 // close the tab
    this.tabsComponent.closeActiveTab();
  }
 onOpenAbout() {
    this.tabsComponent.openTab('About', this.aboutTemplate, {}, true);
  }
}


[app.component.html]
 

We access the template by its name, which we defined in the view, namely "personEdit," instead of the TabsComponent by its type, which is obviously handier.

Great, now we have all of the variables we need to provide to our openTab(...) function.

Define an Anchor point

The TabsComponent will be dynamically injected with new TabComponent objects.

To do so, we must first specify a region in our tab component where our dynamic tab pages will be placed.

Angulars content projection method is currently being used to load over our statically specified ones.


[app.component.html]
 

The static tabs get “projected” into the section and referenced in the code via the @ContentChildren.

We cant utilize the same @ContentChildren mechanism because its only useful for projected material that needs to happen statically.

Similarly, our dynamic tab components must require a different anchor point.


[app.component.html]
 

To do so, I’m using an with a special directive attached to it: dynamic-tabs. Our so-called anchor directive is very simple:


import { Directive, ViewContainerRef } from 
'@angular/core';

@Directive({
  selector: '[appDynamicTabs]'
})
export class DynamicTabsDirective {
constructor(public viewContainer: ViewContainerRef) { } }


This one is simply necessary for eventually retrieving the viewContainerRef.

We can later access it through another viewContainer variable by injecting it in the function Object() { [native code] } of our directive and using the public modifier.

Use a template variable as an anchor instead.

Instead of writing a particular anchor directive, you can utilize a simple template variable and access the view container it belongs to using the @ViewChild decorator.


  • ...

In the TabsComponent class we then grab the view container of #container as follows.

import { Component, ComponentFactoryResolver, ContentChildren, OnInit, QueryList, ViewChild } from '@angular/core';
import { DynamicTabsDirective } from './dynamic-tabs.directive';
import { TabComponent } from './tab/tab.component';

@Component({
  selector: 'app-tabs',
  templateUrl: './tabs.component.html',
  styleUrls: ['./tabs.component.css']
})
export class TabsComponent implements OnInit {
  dynamicTabs: TabComponent[] = [];
  @ContentChildren(TabComponent) tabs: QueryList;
  @ViewChild(DynamicTabsDirective) dynamicTabPlaceholder: DynamicTabsDirective;
constructor(private _componentFactoryResolver: ComponentFactoryResolver) { }
  ngAfterContentInit() {
    // get all active tabs
    const activeTabs = this.tabs.filter(tab => tab.active);
 // if there is no active tab set, activate the first
    if (activeTabs.length === 0) {
      this.selectTab(this.tabs.first);
    }
  }


[app.component.ts]
 

Both methods can be employed because they have no significant advantages or disadvantages.

The ViewContainerRef object is directly referenced by dynamicTabPlaceholder, so that we can be used right away.

We will use a separate anchor directive in this post.

NgTemplateOutletContext allows us to associate data with dynamic templates.

Obviously, a template without some dynamic data is quite boring. We need to be able to pass in the person object were changing in our example.

So let’s take a closer look at our definition:

 
    
    
  


@
[add-person.component.html]
 
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';

@Component({
  selector: 'app-person-edit',
  templateUrl: './person-edit.component.html',
  styleUrls: ['./person-edit.component.css']
})
export class PersonEditComponent implements OnInit {
  personForm: FormGroup;
  @Input() person;
  @Output() savePerson = new EventEmitter();
  constructor(private fb: FormBuilder) {
    this.personForm = this.fb.group({
      id: '',
      name: '',
      surname: '',
      twitter: ''
    });
   }
 ngOnInit() {
    this.personForm.setValue({
      id: this.person.id || -1,
      name: this.person.name || '',
      surname: this.person.surname || '',
      twitter: this.person.twitter || ''
    });
  }
  onPersonFormSubmit() {
    let dataModel = this.personForm.value;
    this.savePerson.emit(dataModel);
  }
}

[add-person.component.ts]
 

One-stopoplossing voor Angular-webontwikkeling ?

Uw zoekopdracht eindigt hier.

 

Inside the template, the component takes care of editing our person record by creating a proper form with bindings, events etc.

angular3

[output:3]
 

angular4

[output:4]
 

angular5

[output:5]
 

angular6

[output:6]
 

The component has an outbound event and accepts an input binding [person] (savePerson).

Let’s first look at the simpler outbound one.

We can directly hook it to the AppComponent code where our is defined and just implement the event as we normally would.

Templates can be assigned directly to the component in which they are defined, although they may be rendered inside another component, like in our instance, the TabComponent.

Think about it, this is really powerful.

Conclusion

Many people are confused by dynamic components in Angular at first. It’s not as easy as in AngularJS (version 1.x) where you could simply render in a DOM string containing a bunch of directives/components, and then theyd load up on their own. As youve seen, Angular takes a more verbose approach. Component factories, view containers, and passing template references around them were covered. But it performs a useful function, and once you get the hang of it, it isnt all that difficult.

  • Pin It
TOP