I am using the vmware/clarity
design system and I'm trying to implement dynamic app-level alerts using the dynamic component loading outlined in angular.io. I've followed this pattern before, but I can't seem to get this to work with the alerts coming from Clarity.
import { AfterViewInit, Component, ComponentFactoryResolver, OnDestroy, ViewChild } from '@angular/core';
import { ClrAlert } from '@clr/angular';
import { AlertsHostDirective } from './directives/alerts-host.directive';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements AfterViewInit, OnDestroy {
@ViewChild(AlertsHostDirective) alerts: AlertsHostDirective;
private interval: NodeJS.Timer;
constructor(private _componentFactoryResolver: ComponentFactoryResolver) { }
ngAfterViewInit(): void {
this.alert();
this.getAlerts();
}
ngOnDestroy(): void {
clearInterval(this.interval);
}
private alert() {
const componentFactory = this._componentFactoryResolver.resolveComponentFactory(ClrAlert);
const viewContainerRef = this.alerts.viewContainerRef;
const componentRef = viewContainerRef.createComponent(componentFactory);
componentRef.instance.isAppLevel = true;
componentRef.changeDetectorRef.detectChanges();
}
private getAlerts() {
this.interval = setInterval(() => {
this.alert();
}, 5000);
}
}
<clr-main-container>
<clr-alerts>
<ng-template appAlertsHost></ng-template>
<clr-alert clrAlertType="info"
[clrAlertAppLevel]="true">
<div class="alert-item">
<span class="alert-text">This is the first app level alert. </span>
<div class="alert-actions">
<button class="btn alert-action">Fix</button>
</div>
</div>
</clr-alert>
<clr-alert clrAlertType="danger"
[clrAlertAppLevel]="true">
<div class="alert-item">
<span class="alert-text">This is a second app level alert.</span>
<div class="alert-actions">
<button class="btn alert-action">Fix</button>
</div>
</div>
</clr-alert>
</clr-alerts>
...
import { Directive, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[appAlertsHost]'
})
export class AlertsHostDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
If I put the directive on the ClrAlerts
component, it works the way it should in that app level alerts are appended after the ClrAlerts
in the DOM. But I want all my app-level alerts to appear inside that component as they come in. In turn, I'd like for the pager component that appears to be updated as well.
Is this possible?
@Eudes pointed out that I was making things too complicated. Basically, I just need to keep track of an array of notifications so that when something "alert-able" happens, I can just push an alert object onto the array and have them render through *ngFor
.
However, there seems to an issue with the ClrAlerts
parent component handling these changes correctly in certain situations. For example, if you start with alerts in the array, close them with user interaction, and then add more, the current alert is not set correctly. Therefore, this seems to be the cleanest solution I could come up with:
<clr-main-container>
<clr-alerts>
<clr-alert *ngFor="let alert of alerts"
[clrAlertType]="alert.type"
[clrAlertAppLevel]="true">
<div class="alert-item">
<span class="alert-text">{{alert.text}}</span>
<div class="alert-actions"
*ngIf="alert.action">
<button class="btn alert-action">{{alert.action}}</button>
</div>
</div>
</clr-alert>
</clr-alerts>
...
import 'rxjs/add/Observable/of';
import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
import { ClrAlerts } from '@clr/angular';
export interface AppLevelAlert {
type: 'info' | 'warning' | 'success' | 'danger';
text: string;
action: string;
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements AfterViewInit, OnDestroy {
@ViewChild(ClrAlerts) alertsContainer: ClrAlerts;
private interval: NodeJS.Timer;
alerts: AppLevelAlert[] = [];
ngAfterViewInit(): void {
this.alert();
this.getAlerts();
}
ngOnDestroy(): void {
clearInterval(this.interval);
}
private alert() {
this.alerts.push({
type: 'success',
text: 'This was added later!',
action: 'Do nothing'
});
if (!this.alertsContainer.currentAlert) {
this.alertsContainer.multiAlertService.current = 0;
}
}
private getAlerts() {
this.interval = setInterval(() => {
this.alert();
}, 5000);
}
}