4

I am trying to build a dynamic component based on a Config. The component would read the config recursively and create the component. It is found that the method ngAfterViewInit() would only be called twice.

@Component({
    selector: "dynamic-container-component",
    template: `
        <div #container
            draggable="true"
            (dragstart)="dragstart($event)"
            (drop)="drop($event)"
            (dragover)="dragover($event)"
            style="border: 1px solid; min-height: 30px"></div>
    `
})
export default class DynamicContainerComponent {

    @Input()
    dynamicConfig: DynamicConfig;

    @ViewChild("container", {read: ElementRef})
    private elementRef: ElementRef;

    private isContainer: boolean;
    private componentRef: ComponentRef<any>;
    private componentRefs: ComponentRef<any>[] = [];
    
    constructor(
        private componentFactoryResolver: ComponentFactoryResolver,
        private injector: Injector,
        private viewContainer: ViewContainerRef,
        private render: Renderer2
    ){
        console.log("running");
    }

    ngAfterViewInit(){
        
        if (this.dynamicConfig){
            console.log(this.dynamicConfig)
            if (this.dynamicConfig.getType() == ComponentType.INPUT){
                this.isContainer = false;
                let componetFactory: ComponentFactory<InputComponent> = 
                    this.componentFactoryResolver.resolveComponentFactory(InputComponent);
                this.componentRef = this.viewContainer.createComponent(componetFactory);
                this.render.appendChild(this.elementRef.nativeElement, this.componentRef.location.nativeElement);
            }else {
                this.isContainer = true;
                let items: DynamicConfig[] = this.dynamicConfig.getItems();
                if (items){
                    for (var i=0; i<items.length; i++){
                        let item: DynamicConfig = items[i];
                        let componetFactory: ComponentFactory<DynamicContainerComponent> = 
                            this.componentFactoryResolver.resolveComponentFactory(DynamicContainerComponent);
                        let componentRef: ComponentRef<DynamicContainerComponent> = 
                            this.viewContainer.createComponent(componetFactory);
                        componentRef.instance.dynamicConfig = item;
                        this.componentRefs.push(componentRef);
                        this.render.appendChild(this.elementRef.nativeElement, componentRef.location.nativeElement);
                    }
                }
            }
        }else {
            console.log("config does not exist");
        }

    }

    dragstart(event){
        debugger;
    }

    drop(event){
        debugger;
    }

    dragover(event){
        debugger;
        event.preventDefault();
    }


}

The Component would be created by other component by the following code. If The Dynamic Component would create another Dynamic Component by componentFactoryResolver.

    var configJson = {
        type: ComponentType.CONTAINER,
        items: [
            {
                type: ComponentType.CONTAINER,
                items: [{
                    type: ComponentType.CONTAINER,
                    items: [{
                        type: ComponentType.CONTAINER,
                        items: [{
                            type: ComponentType.INPUT
                        }]
                    }]
                }]
            }
        ]
    }

    this.config = new DynamicConfig();
    this.config.assign(configJson);
    console.log(this.config);

Update I found a similar issue in github: https://github.com/angular/angular/issues/10762

I have done something suggested by other people. but I think it is just a dirty fix.

ngAfterViewInit(){
    setTimeout(function(){
        if (this.dynamicConfig){
            console.log(this.dynamicConfig)
            if (this.dynamicConfig.getType() == ComponentType.INPUT){
                this.isContainer = false;
                let componetFactory: ComponentFactory<InputComponent> = 
                    this.componentFactoryResolver.resolveComponentFactory(InputComponent);
                this.componentRef = this.viewContainer.createComponent(componetFactory);
                this.render.appendChild(this.elementRef.nativeElement, this.componentRef.location.nativeElement);
            }else {
                this.isContainer = true;
                let items: DynamicConfig[] = this.dynamicConfig.getItems();
                if (items){
                    for (var i=0; i<items.length; i++){
                        let item: DynamicConfig = items[i];
                        let componetFactory: ComponentFactory<DynamicContainerComponent> = 
                            this.componentFactoryResolver.resolveComponentFactory(DynamicContainerComponent);
                        let componentRef: ComponentRef<DynamicContainerComponent> = 
                            this.viewContainer.createComponent(componetFactory);
                        componentRef.instance.dynamicConfig = item;
                        this.componentRefs.push(componentRef);
                        this.render.appendChild(this.elementRef.nativeElement, componentRef.location.nativeElement);
                    }
                }
            }
        }else {
            console.log("config does not exist");
        }
    }.bind(this))
}

1 Answer 1

5

By the time you create your dynamic component angular has almost finished change detection cycle.

This way you can either run:

componentRef.changeDetectorRef.detectChanges()

Note: setTimeout has similar effect but fires change detection cycle on the whole app

or rename lifecycle hook to ngOnInit

Also you're passing wrong input to dynamic component:

let item: DynamicConfig = items[i];
          ^^^^^^^^^^^^^
    but it is not DynamicConfig instance but rather plain object
...
componentRef.instance.dynamicConfig = item;

it should be:

let item: any = items[i];
const config = new DynamicConfig();
config.assign(item);
componentRef.instance.dynamicConfig = config;

Ng-run Example

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks @yurzui. It works if i changed to ngOnInit. I would like to ask why ngOnInit works instead of ngAfterViewInit. angular.io/guide/lifecycle-hooks
When you render dynamic component it becomes part of change detection tree so it can be checked by angular but only during change detection cycle. When you do it inside ngOnInit hook angular has time to check it between ngOnInit and ngAfterViewInit but if you render component inside ngAfterViewInit then angular can check it only during next change detection cycle

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.