Skip to content

Latest commit

 

History

History
182 lines (156 loc) · 4.22 KB

File metadata and controls

182 lines (156 loc) · 4.22 KB

Dynamic Components

Content


ngIf

The easiest way to create dynamic components, as the authentication error alert, is the ngIf:

<!-- auth.component.html -->
<!-- ... -->
<app-alert 
    [message]="error"
    *ngIf="!!error"
    (close)="onHandleError()"
    ></app-alert>
<!-- ... -->
@Component({
    // ...
})
export class AuthComponent implements OnInit {
    // ...

    onHandleError() {
        this.error = undefined;
    }
}

where the alert component is:

<!-- alert.component.html -->
<div class="backdrop" (click)="onClose()"></div>
<div class="alert-box">
    <p>{{ message }}</p>
    <div class="alert-box-actions">
        <button class="btn btn-primary" (click)="onClose()">Close</button>
    </div>
</div>
@Component({
    // ...
})
export class AlertComponent implements OnInit {
    @Input() message: string | undefined;
    @Output() close = new EventEmitter<void>();

    constructor() { }

    ngOnInit(): void { }

    onClose() {
        this.close.emit();
    }
}

Programmatically Construction

Programmatically construction documentation

To create a component programmatically, it is necessary to create and register a custom directive that allows to access the DOM using Angular:

@Directive({
    selector: '[appPlaceholder]',
})
export class PlaceholderDirective {
    constructor(public viewContainerRef: ViewContainerRef){}
}
@NgModule({
    declarations: [
        // ...
        PlaceholderDirective,
    ],
    // ...
})
export class AppModule { }

the directive can be added to a template in the html:

<!-- auth.component.html -->
<ng-template appPlaceholder></ng-template>
<!-- ... -->

it is used an ng-template in order to do no introduce overhead of loaded elements in the application.

Now it is possible to create a component programmatically in this way:

@Component({
    // ...
})
export class AuthComponent implements OnInit, OnDestroy {
    // ...
    @ViewChild(PlaceholderDirective) alertPlaceholder: PlaceholderDirective | undefined;
    private closeSubscription: Subscription | undefined;

    constructor(
        // ...
        private componentFactoryResolver: ComponentFactoryResolver,
    ) { }
        
    // ...

    onSubmit(form: NgForm) {
        // ...
        authObservable.subscribe(
            response => {
                // ...
            },
            errorMessage => {
                // ...
                this.showErrorMessage(errorMessage);
            },
        );
        // ...
    }

    // ...

    private showErrorMessage(error: string) {
        if (this.alertPlaceholder) {
            const alertComponentFactory =
                this.componentFactoryResolver.resolveComponentFactory(AlertComponent);

            const alertViewContainerRef = this.alertPlaceholder.viewContainerRef;
            // clear previous data
            alertViewContainerRef.clear();

            // component creation
            const componentRef = alertViewContainerRef.createComponent(alertComponentFactory);

            // data and event binding
            componentRef.instance.message = error;
            this.closeSubscription = componentRef.instance.close.subscribe(() => {
                if (this.closeSubscription) {
                    this.closeSubscription.unsubscribe();
                }
                alertViewContainerRef.clear();
            });
        }
    }

    ngOnDestroy(): void {
        if (this.closeSubscription) {
            this.closeSubscription.unsubscribe();
        }
    }
}

with Angular up to 8 it is also necessary to register the dynamic component in the entryComponents too:

@NgModule({
    // ...
    entryComponents: [AlertComponent],
})
export class AppModule { }

Deprecation

It is not necessary anymore to use the ComponentFactoryResolver:

const alertComponentFactory = this.componentFactoryResolver.resolveComponentFactory(AlertComponent);

But it is enough to return the component directly:

const alertComponentFactory = AlertComponent;