In this article, we will continue learning ComponentFactoryResolver
and create a dynamic-component-loader directive (or called "component-outlet").
The component-outlet
will have the ability to load any (but one) component at run-time and hence we can have much advanced usage and fun(?) with it :)
Our goal is to show customers’ data in LIST style or CARD style, which depends on the choosing of the user. We will make use of the Component-Outlet
to show the different styles.
import { Directive, Component, ComponentFactory, OnChanges, Input, ViewContainerRef, Compiler, ComponentFactoryResolver } from '@angular/core';
@Directive({
selector: '[component-outlet]'
})
export class ComponentOutlet implements OnChanges {
@Input() selector: string;
componentRef;
constructor(
private vcRef: ViewContainerRef,
private resolver: ComponentFactoryResolver) {
}
ngOnChanges() {
if (!this.selector) return;
const factories = Array.from(this.resolver['_factories'].values());
const factory: any = factories.find((x: any) => x.selector === this.selector);
const compRef:any = this.vcRef.createComponent(factory);
if (this.componentRef) {
this.componentRef.destroy();
}
this.componentRef = compRef;
}
public ngOnDestroy() {
if (this.componentRef) {
this.componentRef.destroy();
this.componentRef = null;
}
}
}
OnChange will be fired while any data-bound property of a directive/component changes. For more information, see Lifecycle hooks.
Okay, let’s put the directive on the main component and load customerdynamic-list
component (or customerdynamic-card
component) in constructor.
import {Component, OnInit} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser'
@Component({
selector: 'customermvc-index',
template: `
<div class="form-group row" style="max-width:70%">
<div class="col-sm-3"><button (click)="showLists()">Show Lists</button></div>
<div class="col-sm-3"><button (click)="showCards()">Show Cards</button></div>
</div>
<div>
<div component-outlet selector="{{component}}"></div>
</div>
`,
})
export class CustomerDynamicIndexComponent implements OnInit {
private component: string;
constructor() {
this.component = "customerdynamic-list"; //or "customerdynamic-card"
}
private showLists() {
this.component = "customerdynamic-list";
}
private showCards() {
this.component = "customerdynamic-card";
}
}
We can use property binding on the Component-Outlet directive as well.
<div component-outlet [selector]="component"></div>
I will ignore the html, cus it’s not the key point here.
import {Component, OnInit, Input} from '@angular/core';
import {Customer} from '../../../class/Customer';
import {CustomerService} from '../../../service/customer.service';
import {RestUriService} from '../../../service/resturi.service';
@Component({
selector: 'customerdynamic-list',
providers: [CustomerService, RestUriService],
templateUrl: '/app/component/Basic/CustomerDynamic/list.component.html'
})
export class CustomerDynamicListComponent implements OnInit {
customers: Customer[];
constructor(private custService: CustomerService) {
}
ngOnInit() {
this.initCustomers();
}
private initCustomers() {
this.custService.getAll().then(
data => {
this.customers = data
});
}
}
card.component.ts
is the same as list.component.ts except for their HTML.