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.
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.
[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.
[output:2]
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.
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.
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.
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.
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]
Inside the template, the component takes care of editing our person record by creating a proper form with bindings, events etc.
[output:3]
[output:4]
[output:5]
[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.
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.
December 17, 2024
July 29, 2021
July 22, 2021
July 20, 2021
Well do everything we can to make our next best project!
Check out our most recent blogs
December 17, 2024
Excel remains one of the handy tools for professionals to handle data and perform data analysis to gain valuable insights. However, it does lack functionality to perform complex tasks...
July 29, 2021
What is Angular? Angular is a frontend development framework used for building single-page client applications using HTML and Typescript. It is written in Typescript. What...
July 22, 2021
What is a Compiler? A compiler is nothing but a part of code that converts one programming language to another. If we talk about some simple programming languages like C, C++,...
Well do everything we can to make our next best project!