Detect data changes on dynamic component is not working

Viewed 1528

I'm working with dynamic components, I followed this tutorial and works fine.

This is my implementation

dynamic-component.directive.ts

import { Directive, ViewContainerRef } from '@angular/core';    
@Directive({
    selector: '[DynamicComponent]'
})
export class DynamicComponentDirective {
    constructor(public viewContainerRef: ViewContainerRef) { }
}

And, this is the logic, to create dynamic components. It can detect when data is emitted with @Output This is the code

import { Component, ComponentFactoryResolver, ComponentRef, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
import { DynamicComponentDirective } from './dynamic-component.directive';
import { DynamicComponentInterface } from '@interfaces/dynamic-component.interface';

@Component({
    selector: 'dynamic-component',
    template: '<ng-template DynamicComponent></ng-template>'
})
export class DynamicComponent implements OnInit, OnDestroy, OnChanges {

    @Input() component!: DynamicComponentInterface;
    @Output() sentData: EventEmitter<any> = new EventEmitter<any>();

    @ViewChild(DynamicComponentDirective, { static: true })
    directive!: DynamicComponentDirective;
    componentRef!: ComponentRef<DynamicComponentInterface>;
    constructor(
    private factoryResolver: ComponentFactoryResolver
    ) { }

    ngOnInit(): void {
    if ( this.component ) {
        this.loadComponent();
    }
    }

    ngOnDestroy(): void {
    this.componentRef.destroy();
    }

    ngOnChanges( changes: SimpleChanges ): void {
    console.log( changes );
    }



    loadComponent(): void {


    const componentFactory = this.factoryResolver
    .resolveComponentFactory( this.component.component );


    const containerRef = this.directive.viewContainerRef;

    this.componentRef = containerRef
    .createComponent<DynamicComponentInterface>( componentFactory );


    this.componentRef.instance.data = this.component.data;
    this.componentRef.instance.emitter?.
    subscribe( (values: any) => this.sentData.emit( values ) );
    this.componentRef.changeDetectorRef.detectChanges();
    }
}

All works fine, This is my how it works...

in the app.component.html

<mat-accordion [multi]="true">
    <mat-expansion-panel *ngFor="let expansion of salaryPanelRight" [expanded]="true">
    <mat-expansion-panel-header>
        {{ expansion.title }}
    </mat-expansion-panel-header>

    <dynamic-component [component]="expansion.panel"></dynamic-component>
    </mat-expansion-panel>
</mat-accordion>

This is how the dynamic-component builds on the logic file

import { Component } from '@angular/core';
import { DynamicComponentInterface } from '@interfaces/dynamic-component.interface';
import { PanelJobDetailsComponent } from './components/panel-job-details/panel-job-details.component';
import { PanelSalaryDetailsResumeComponent } from './components/panel-salary-details-resume/panel-salary-details-resume.component';
import { PanelSalaryResumeComponent } from './components/panel-salary-resume/panel-salary-resume.component';

@Component({
    selector: 'app-salary-data',
    templateUrl: './salary-data.component.html',
    styleUrls: ['./salary-data.component.scss']
})
export class SalaryDataComponent {
    salaryPanelRight: SalaryPanel [] = [
    {
        title: $localize`:@@salary-tab-panel-resume-details: Title 1`,
        panel: {
        component: PanelJobDetailsComponent,
        data: {
            hireSchema: null
        }
        },
    },
    {
        title: $localize`:@@salary-tab-panel-resume-salary: Title 2`,
        panel: {
        component: PanelSalaryResumeComponent
        }
    },
    {
        title: $localize`:@@salary-tab-panel-resume-salary-details: Title 3`,
        panel: {
        component: PanelSalaryDetailsResumeComponent
        }
    },
    ];
    constructor() { }

    getValues( input: any ): void {
    const value = input.target.value;
    this.salaryPanelRight[0].panel.data.hireSchema = value;
    }

}

interface SalaryPanel {
    title: string;
    panel: DynamicComponentInterface;
}

For example PanelJobDetailsComponent has this @Input() hireSchema!: string; and his HTML is only {{ hireSchema }}

It is not changing when I change the value from parent component, exactly in this part

      getValues( input: any ): void {
        const value = input.target.value;
        this.salaryPanelRight[0].panel.data.hireSchema = value;
      }

I have read and implemented ngOnChanges but is not working, the data is not changing. Actually I did this.componentRef.changeDetectorRef.detectChanges(); also but it is not working. When I do console.log on ngOnChanges nothing is happening, even I fired blur event from the input to fire the new data.

How can I pass the changed data to my implementation?

3 Answers

Why ngOnChanges is not working is explained in this article.

In order to have your change detection work you could create setters in your dynamic components and call changeDetectorRef.markForCheck() from them. I would not recommend using detectChanges as it may create more problems than it solves if used not very carefully

Assign a new array to salaryPanelRight member variable whenever you make any changes to the array contents. Then ngOnChanges() will be fired when there is a change in the array reference, there wont be any change detected when content of the array is modified.

this.salaryPanelRight = this.salaryPanelRight.slice(0);

Its not possible for a dynamically created component to be aware of changes.

What worked for me was triggering change detection at the time the component was made.

this.containerFactory = this.componentFactoryResolver.resolveComponentFactory(ValuePolicyComponent);
        this.newComponent = 
        this.viewContainerRef.createComponent(this.containerFactory);
        this.newComponent.instance.template = this.templateRef;
        this.newComponent.instance.valueKey = this.nxValuePolicy;
        this.newComponent.instance.featureKey = this.featureKey;
        const cdr = this.newComponent.injector.get(ChangeDetectorRef);
        cdr.detectChanges();

(maybe this is not necessary if the parent component is using default change detection)

Related