组件
@Component({
selector: 'alert-queue',
templateUrl: './alert-queue.component.html',
styleUrls: ['./alert-queue.component.scss']
})
export class AlertQueueComponent implements OnInit, OnDestroy {
public alerts: Alert[];
private alerts$: Subscription;
private alertsComplete$: Subject<boolean> = new Subject<boolean>();
constructor(private alertService: AlertService, private ref: ChangeDetectorRef) { }
ngOnInit() {
this.alerts = [];
this.alertService
.getAlertStream()
.pipe(
tap(alert => {
console.log(`alert tapped before delay: ${alert.message}`);
this.alerts.push(alert);
console.log(`${this.alerts.length}`);
this.alerts = this.alerts.slice();
this.ref.markForCheck();
}),
delay(5800),
tap(alert => {
console.log(`alert tapped after delay: ${alert.message}`);
_.remove(this.alerts, { id: alert.id });
console.log(`${this.alerts.length}`);
this.alerts = this.alerts.slice();
this.ref.markForCheck();
}),
//catchError((err, caught) => {
// console.error(`error in alert stream: ${err}`);
// return caught;
//}),
takeUntil(this.alertsComplete$)
)
.subscribe();
}
ngOnDestroy() {
this.alertsComplete$.next(true);
this.alertsComplete$.unsubscribe();
}
组件模板
<div class="alert-queue">
<div *ngFor="let alert of alerts | reverse">
<alert [alert]="alert"></alert>
</div>
</div>
警报组件(可能不相关)
<div [ngClass]="class" [@effect]="state">
<strong>{{typeDisplay()}}:</strong> {{alert.message}}
</div>
警报服务
@Injectable()
export class AlertService {
private alerts: Subject<Alert> = new Subject();
public getAlertStream(...types: AlertType[]): Observable<Alert> {
if (types.length > 0) {
return this.alerts.pipe(
filter(alert => _.some(types, t => t === alert.type))
);
}
else {
return this.alerts;
}
}
public push(message: string, type?: AlertType, options?: object): void {
if (_.isEmpty(message)) {
console.warn('Cannot push an alert with blank message, the notification was cancelled');
} else {
var alert = this.createAlert(message, (type || AlertType.Default), (options || {}));
this.alerts.next(alert);
}
}
private createAlert(message: string, type: AlertType, options: object): Alert {
return new Alert(message, type, options);
}
我尝试了许多不同的操作,但似乎无法弄清楚我在做什么错。当我从由按钮触发的常规功能推送到警报服务时,队列会更新并设置动画效果。
当它是对HTTP请求的订阅时,除非我以某种方式与DOM交互(键入输入或单击按钮,然后警报出现并刷新视图),否则屏幕根本不会更新。忽略我使用ChangeDetectorRef的地方,这是最后的尝试,试图使DOM识别数组更新。
我应该指出点击操作符中的日志消息确实出现在控制台中。严格来说,这似乎是DOM更新的问题,我无法忍受。
显然,tap方法中的回调发生在Angular上下文之外。一个有效的解决方案是将NgZone注入构造函数,并使用zone.run(...)包装回调。我也将其移到了subscription方法。
起作用的代码是:
export class AlertQueueComponent implements OnInit, OnDestroy {
public alerts: Alert[];
private alerts$: Subscription;
private alertsComplete$: Subject<boolean> = new Subject<boolean>();
constructor(private alertService: AlertService, private zone: NgZone) { }
ngOnInit() {
this.alerts = [];
this.alertService
.getAlertStream()
.pipe(
takeUntil(this.alertsComplete$)
)
.subscribe(alert => {
this.zone.run(() => {
console.log(`alert subscribe success before delay: ${alert.message}`);
this.alerts.push(alert);
setTimeout(() => {
console.log(`alert subscribe after before delay: ${alert.message}`);
_.remove(this.alerts, { id: alert.id });
}, 5800);
}, this);
});
}
ngOnDestroy() {
this.alertsComplete$.next(true);
this.alertsComplete$.unsubscribe();
}
public checkArray() {
console.log(`Array has ${this.alerts.length} elements`);
}
public getAlertClass(alert: Alert) {
return `alert alert-${alert.typeClass()}`;
}
}