2

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?

Amit Vishwakarma
  • 316
  • 2
  • 10
Alberto Siurob
  • 151
  • 1
  • 3
  • 11
  • have you tried triggering change detection manually? – eko Apr 26 '21 at 18:09
  • How could I do that? – Alberto Siurob Apr 26 '21 at 18:14
  • `constructor(private cdr: ChangeDetectorRef){}` and then use it in the line after you set the data like `this.cdr.detectChanges()` – eko Apr 26 '21 at 18:24
  • Is not happening anything, what could be the problem? – Alberto Siurob Apr 26 '21 at 18:48
  • why do you have ! in your property definitions `@Input() component!: DynamicComponentInterface;` – Kieran Apr 29 '21 at 00:04
  • Typescript says: `component has no initializer and is not definitely assigned in the constructor.` – Alberto Siurob Apr 29 '21 at 00:13
  • @Kieran You can find about `!` in this question: https://stackoverflow.com/a/42274019/5107490 – ShayD Apr 29 '21 at 13:24
  • Why `@Input` doesn't trigger change detection is because you don't use the input. You are using a direct property access. I would use setters for inputs and call `markForCheck()` when the value changes. Should do the trick – Sergey Apr 29 '21 at 15:19
  • Here is why `ngOnChanges` not working for you https://indepth.dev/posts/1054/here-is-what-you-need-to-know-about-dynamic-components-in-angular#ngonchanges – Sergey Apr 29 '21 at 15:24

3 Answers3

4

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

Sergey
  • 7,184
  • 13
  • 42
  • 85
0

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);
Nugu
  • 852
  • 7
  • 10
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)

Omar
  • 2,726
  • 2
  • 32
  • 65