我有一个简单的组件,该组件使用返回Observable的服务。此类Observable用于通过*ngIf
控制html元素的创建。
代码在这里
@Component({
selector: 'hello',
template: `<h1 *ngIf="stuff$ | async as obj">Hello {{obj.name}}!</h1>`,
styles: [`h1 { font-family: Lato; }`],
})
export class HelloComponent implements AfterViewInit {
constructor(
private myService: MyService,
) {}
stuff$;
ngAfterViewInit() {
this.stuff$ = this.myService.getStuff();
}
}
@Injectable({
providedIn: 'root'
})
export class MyService {
getStuff() {
const stuff = {name: 'I am an object'};
return of(stuff).pipe(delay(100));
}
}
如您所见,getStuff()
返回并且可以通过delay
观察到,一切都按预期进行。
现在,我删除了delay
,并在控制台上收到以下错误消息
错误:ExpressionChangedAfterItHasBeenCheckedError:检查表达式后,表达式已更改。上一个值:'ngIf:null'。当前值:“ ngIf:[object Object]”。
This is a Stackbliz that reproduces the case.
这是不可避免的吗?有办法避免这种错误吗?
这个小例子复制了一个真实的例子,其中MyService
查询后端,因此延迟。
这对我来说很重要,因为我希望能够以同步方式运行测试以模拟真实的后端,并且当我尝试这种方法时,也会得到相同的结果。
答案 0 :(得分:3)
该错误基本上表示一个更改触发了另一个更改。它仅在没有delay(100)
的情况下发生,因为RxJS严格是顺序的并且订阅是同步发生的。如果您使用delay(100)
(即使仅使用delay(0)
,也会使发射异步,因此它发生在另一帧中并被另一个更改检测周期捕获。
避免这种情况的一种非常简单的方法就是使用setTimeout()
或NgZone.run()
包装分配给变量的变量。
或更多个“ Rx方式”正在使用subscribeOn(async)
运算符:
import { asyncScheduler } from 'rxjs';
import { observeOn } from 'rxjs/operators';
...
return of(stuff).pipe(
observeOn(asyncScheduler)
);
答案 1 :(得分:0)
您要在初始化视图后分配 。试试:
@Input() name: string;
stuff$ = this.myService.getStuff();
或者:
ngOnInit() { // or even in constructor()
this.stuff$ = this.myService.getStuff();
}
答案 2 :(得分:0)
如果您需要在ngAfterViewInit
生命周期挂钩中同步更改模型,则可以触发更改检测以避免ExpressionChangedAfterItHasBeenCheckedError
错误:
constructor(private changeDetectorRef: ChangeDetectorRef) { }
ngAfterViewInit() {
this.stuff$ = this.myService.getStuff();
this.changeDetectorRef.detectChanges();
}
有关演示,请参见this stackblitz。
答案 3 :(得分:-1)
为什么当您尝试从myService中获取东西时,为什么也将东西$作为@Input处理呢?渲染时是一种冲突。
也许ngAfterViewChecked会比ngAfterViewInit更好。但是解决方案仍然不是很好。