在订阅中更改变量后,不会更新Angular 6 View

Dan*_*cos 20 subscribe observable angular6 angular-changedetection rxjs6

当变量在订阅中发生变化时,为什么视图没有更新?

我有这个代码:

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';
}
Run Code Online (Sandbox Code Playgroud)

example.component.html

{{testVariable}}
Run Code Online (Sandbox Code Playgroud)

但视图显示:foo-Hello.

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

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

我不应该从每个订阅中调用此方法,或者根本不应该(angular应该处理这个).有正确的方法吗?

我是否遗漏了Angular/rxjs 5到6的更新内容?

现在我有Angular版本6.0.2和rxjs 6.0.0.相同的代码在Angular 5.2和rxjs 5.5.10中正常工作,无需调用detectChanges.

bes*_*ser 28

据我所知,如果您更改"Angular区域"中的数据,Angular只会更新视图.您的示例中的异步调用不符合此条件.但是如果需要,可以将它放在Angular区域中或使用rxjs或将部分代码提取到新组件来解决此问题.我会解释所有:

1角区

此服务最常见的用途是在启动由一个或多个异步任务组成的工作时优化性能,这些异步任务不需要由Angular处理UI更新或错误处理.这些任务可以通过runOutsideAngular启动,如果需要,这些任务可以通过运行重新进入Angular区域. https://angular.io/api/core/NgZone

关键部分是"运行"功能.您可以注入NgZone并将值更新放在NgZone对象的运行回调中:

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';
      });
      }
   );
}
Run Code Online (Sandbox Code Playgroud)

根据这个答案,它会导致整个应用程序检测到更改,而您的ChangeDetectorRef.detectChanges方法只会检测组件及其后代的更改.

2 RxJS

另一种方法是使用rxjs来更新视图.当您第一次订阅ReplaySubject时,它将为您提供最新值.一个BehaviourSubject基本相同,但允许您定义的默认值(是有道理的在你的榜样,但并不一定是正确的选择,所有的时间).这个初始发射后它基本上是一个正常的主题:

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);
      }
   );
}
Run Code Online (Sandbox Code Playgroud)

在您的视图中,您可以使用异步管道订阅主题:

{{testEmitter$ | async}}
Run Code Online (Sandbox Code Playgroud)

3将代码提取到新组件

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

所以新组件的代码如下:

@Input() testVariable = '';
Run Code Online (Sandbox Code Playgroud)

并且像以前一样使用卷曲的brakets在HTML中分配testVariable.

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

<app-child [testVariable]="testVariable"></app-child>
Run Code Online (Sandbox Code Playgroud)

这样你就处于Angular区域.

4个人偏好

我个人的偏好是使用rxjs或组件方式.使用detectChanges或者NGZone对我来说感觉更加hacky.

  • 谢谢,在我的情况下,`this.ngZone.run`只能工作 (2认同)
  • 有趣。Angular似乎将行为从一个版本改为另一个版本。有时可能很头疼。至少您找到了解决方案。我很高兴它有所帮助。 (2认同)

Dal*_*rzo 7

对我来说,以上都没有奏效。然而,ChangeDetectorRef成功了。

来自角度文档;这就是你可以实现它的方法

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

Run Code Online (Sandbox Code Playgroud)

https://angular.io/api/core/ChangeDetectorRef

  • @RishabhGusain 哈哈哈。这 5 秒正在模拟 REST 服务等异步调用。请不要将其包含在您的代码中。当然,这是没有必要的。 (4认同)

Der*_* J. 5

我发现最有效的方法是将变量赋值包装在函数中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';
}
Run Code Online (Sandbox Code Playgroud)