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 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 { }
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;