在订阅内更改变量后,不更新角度6视图

当订阅内的变量发生更改时,为什么视图没有被更新?

我有这个密码:

Example.component.ts

testVariable: string;


ngOnInit() {
this.testVariable = 'foo';


this.someService.someObservable.subscribe(
() => console.log('success'),
(error) => console.log('error', error),
() => {
this.testVariable += '-bar';


console.log('completed', this.testVariable);
// prints: foo-Hello-bar
}
);


this.testVariable += '-Hello';
}

Example.component.html

{{testVariable}}

但是视图显示: 你好

为什么不显示: Foo-Hello-bar

如果我在订阅内调用 ChangeDetectorRef.detectChanges(),它将显示正确的值,但为什么我必须这样做?

我不应该在每次订阅时调用这个方法,或者根本不应该调用这个方法(角度应该处理这个问题)。有正确的方法吗?

在 Angular/rxjs 5到6的更新中,我是否遗漏了什么?

现在我有 Angular 版本6.0.2和 rxjs 6.0。在 Angular 5.2和 rxjs 5.5.10中,同样的代码可以正常工作,而不需要调用 detectChanges

86177 次浏览

据我所知,角度只是更新视图,如果你改变数据在“角度区”。示例中的异步调用不符合此条件。但是如果您愿意,您可以将它放在角区域,或者使用 rxjs,或者将部分代码提取到一个新组件中来解决这个问题。我会解释一切:

编辑: 似乎并不是所有的解决方案都有效。对于大多数用户,第一个解决方案“角区”做的工作。

1角区

这个服务最常见的用途是在开始一项工作时优化性能,这项工作由一个或多个不需要用户界面更新或由 Angular 处理错误处理的异步任务组成。这些任务可以通过 runOutsideAngular 启动,如果需要,这些任务可以通过 快跑重新进入角区。 Https://angular.io/api/core/ngzone

关键部分是“ run”函数。您可以注入 NgZone 并将您的值更新放在 NgZone 对象的 run 回调中:

constructor(private ngZone: NgZone ) { }
testVariable: string;


ngOnInit() {
this.testVariable = 'foo';


this.someService.someObservable.subscribe(
() => console.log('success'),
(error) => console.log('error', error),
() => {
this.ngZone.run( () => {
this.testVariable += '-bar';
});
}
);
}

根据 这个的回答,它将导致整个应用程序检测更改,而 ChangeDetectorRef.dettChanges 方法只检测组件及其后代中的更改。

2个 RxJS

另一种方法是使用 rxjs来更新视图。当您第一次订阅 重放主题时,它会给您最新的值。行为主体基本上是相同的,但是允许您定义一个默认值(在您的示例中是有意义的,但是不一定总是正确的选择)。在这个初始发射之后,它基本上是一个正常的重播主题:

this.testVariable = 'foo';
testEmitter$ = new BehaviorSubject<string>(this.testVariable);




ngOnInit() {


this.someService.someObservable.subscribe(
() => console.log('success'),
(error) => console.log('error', error),
() => {
this.testVariable += '-bar';
this.testEmitter.next(this.testVariable);
}
);
}

在您看来,您可以使用 异步管道订阅 Subject:

\{\{testEmitter$ | async}}

3为新组件提取代码

如果您将字符串提交给另一个组件,它也将被更新。您必须在新组件中使用 @Input()选择器。

所以新组件的代码是这样的:

@Input() testVariable = '';

testVariable 像之前一样在 HTML 中使用花括号分配。

然后,在父 HTML 视图中,可以将父元素的变量传递给子元素:

<app-child [testVariable]="testVariable"></app-child>

这样你就进入角区了。

4个人偏好

我个人喜欢使用 rxjs 或组件方式。对我来说,使用 DetectChanges 或 NGZone 更加生硬。

对我来说,以上这些方法都不管用。然而,ChangeDetectorRef做到了这一点。

从角度文档; 这就是实现它的方法

    class GiantList {
constructor(private ref: ChangeDetectorRef, public dataProvider: DataListProvider) {
ref.detach();
setInterval(() => { this.ref.detectChanges(); }, 5000);
}
}


Https://angular.io/api/core/changedetectorref

重要通知-在科目短路可以造成问题!

我知道这不完全符合假释官的要求,但有时会发生一些非常普遍的问题,也许这对某些人会有帮助。

这很简单,而且我也没有深入地检查这个问题。

但在我的案例中,问题是我使用了简短的语法将主题订阅为可观察的而不是完整的,当我改变它时,它解决了这个问题。

所以当这个 没有起作用:

myServiceObservableCall.subscribe(this.myBehavSubj)

与日志上的行为相同,我正在正确地看到变化,只是在视图上没有。

这确实更新了视图:

myServiceObservableCall.subscribe((res)=>{
this.myBehavSubj.next(res);
} );

虽然它似乎是相同的,以上只有短路语法。

请你解释原因

我发现最好的方法是将变量赋值包装在 setTimeout()函数中。Angular 的 ngZone 变化检测在调用 setTimeout()函数时检查是否有变化。使用问题中的代码的示例:


ngOnInit() {
this.testVariable = 'foo';


this.someService.someObservable.subscribe(
() => console.log('success'),
(error) => console.log('error', error),
() => {
setTimeout(() => { this.testVariable += '-bar'; }, 0); // Here's the line


console.log('completed', this.testVariable);
// prints: foo-Hello-bar
}
);


this.testVariable += '-Hello';
}

以上所有的解决方案都很棒。但是,我发现了一个更简单的解决方案,我相信这个方案在大多数情况下都可以用来尝试用来自外部服务的新数据刷新 UI。

这个想法很简单,设置一个加载标志,在获取新数据时显示微调器,然后通过重置标志来清除微调器:

getData() {
// Set loading spinner
this.loading = true;
this.service.getData().subscribe((data: any) => {
this.newData = data;
//Remove loading spinner
this.loading = false;
});

}

我来到这里是因为 CdkVirtualFor没有更新推入数组中的新项目。 因此,如果有人有同样的问题,问题的根源是 CdkVirtualFor 只有当数组被不可变地更新时才会更新。

比如这个:

addNewItem(){
let  newItem= {'name':'stack'};
this.myItemArray = [...this.myList, newItem];
}