Reputation: 1836
I am going to resolve components dynamically for different statements.
So, I created one directive and one component for it.
import { Directive, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[mcmsDynamicHost]'
})
export class DynamicHostDirective {
constructor(
public viewContainerRef: ViewContainerRef
) { }
}
import { AfterViewInit, Component, ComponentFactoryResolver, Input, ViewChild } from '@angular/core';
import { DynamicHostDirective } from '@mcms/ui-kit/dynamic-host/dynamic-host.directive';
@Component({
selector: 'mcms-dynamic-host',
templateUrl: './dynamic-host.component.html',
styleUrls: ['./dynamic-host.component.scss']
})
export class DynamicHostComponent implements AfterViewInit {
@ViewChild(DynamicHostDirective) dynamicHost: DynamicHostDirective;
@Input() component;
constructor(
private componentFactoryResolver: ComponentFactoryResolver
) { }
ngAfterViewInit(): void {
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.component);
const viewContainerRef = this.dynamicHost.viewContainerRef;
viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent(componentFactory);
}
}
// component template
<ng-template mcmsDynamicHost></ng-template>
Now I am using them in this way.
<div *ngFor="let item of dashboard">
<mcms-dynamic-host [component]="item.card"></mcms-dynamic-host>
</div>
this.dashboard = [
{cols: 2, rows: 3, y: 0, x: 0, dragEnabled: true, card: WelcomeCardComponent, title: 'Welcome to Municipal CMS!'},
{cols: 1, rows: 3, y: 0, x: 2, dragEnabled: true, card: SiteResumeCardComponent, title: 'Site Resume'},
{cols: 2, rows: 5, y: 3, x: 0, dragEnabled: true, card: TrafficActivityCardComponent, title: 'Traffic activity'},
];
Static components are rendered without any issue that works. But if I try to bind some values to template inside dynamically resolved components for example *ngFor or even *ngIf, all throws error - ExpressionChangedAfterItHasBeenCheckedError
But when I did this trick,
ngOnInit(): void {
setTimeout(() => {
this.data = [
{ image: 'assets/images/green-icons/page-small.svg', title: 'Pages', count: 5 },
{ image: 'assets/images/green-icons/user-small.svg', title: 'Active Users', count: 7 },
{ image: 'assets/images/green-icons/blog-small.svg', title: 'Blog Post', count: 12, pending: 3 },
{ image: 'assets/images/green-icons/comment-small.svg', title: 'Comments', count: 54, pending: 8 },
{ image: 'assets/images/green-icons/media-small.svg', title: 'Digital Assets', count: 230 },
];
}, 100);
}
no errors. It's bit strange for me, anyway we can go with this, but when I try to use for example @angular/material component there's no way to do this kind of trick.
Anyone experieced similar issue?
Here goes stackblitz link - https://stackblitz.com/edit/angular-kf3pdw
Upvotes: 1
Views: 1522
Reputation: 1836
There was a stupid bug on my code. With Angular v9, @ViewChild decorator static flag default is false
. This means resolve query result after change detection runs. It should be true
in order to resolve the query result before change detection runs.
Stackblitz updated with the correct answer. Hope this experience helps others! Cheers!
https://angular.io/api/core/ViewChild#description
https://angular.io/guide/static-query-migration
Upvotes: 1
Reputation: 31815
Try moving the data assignation to the ngAfterViewInit
lifecycle hook instead of ngOninit
By the way, dynamic component loading should usually be done by using the CDK Portal
Upvotes: 1