在订阅

时间:2018-05-24 22:45:02

标签: observable subscribe angular6 rxjs6 angular-changedetection

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

我有这段代码:

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

为什么不显示: 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

6 个答案:

答案 0 :(得分:11)

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

1个角度区域

  

此服务最常见的用途是在启动包含一个或多个不需要Angular处理UI更新或错误处理的异步任务的工作时优化性能。可以通过runOutsideAngular启动此类任务,如果需要,可以通过 run 重新进入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';
      });
      }
   );
}

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

2个RxJS

另一种方法是使用rxjs更新视图。首次订阅ReplaySubject时,它将为您带来最新的价值。 BehaviourSubject基本相同,但是允许您定义默认值(在您的示例中很有意义,但不一定一直都是正确的选择)。在此初始发射之后,基本上是一个正常的重放主题:

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


ngOnInit() {

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

您认为,您可以使用async管道订阅主题:

{{testEmitter$ | async}}

3将代码提取到新组件

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

因此,新组件具有如下代码:

@Input() testVariable = '';

并且像以前一样,在HTML中分配了testVariable并带有卷曲的刹车。

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

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

这样,您将进入Angular区域。

4个人偏好

我个人的喜好是使用rxjs或组件方式。对我来说,使用detectChanges或NGZone更难受。

答案 1 :(得分:3)

对我来说,以上方法均无效。但是, ChangeDetectorRef 可以达到目的。

从Angular Docs实现的地方:

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

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

答案 2 :(得分:1)

重要提示-主题太短会造成问题!

我知道这并非采购订单要求的,但是有时可能会发生非常普遍的问题,也许对某人会有帮助。

这很简单,我也没有深入研究这个问题。

但是在我的情况下,问题是我使用了较短的语法来使主题可观察而不是全部订阅,并且当我对其进行更改时,它解决了问题。

因此,尽管无法正常工作:

myServiceObservableCall.subscribe(this.myBehavSubj)

并具有与日志记录相同的行为,我可以正确地看到更改,而只能在视图上看到。

这确实会更新视图:

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

尽管看起来和上面的短途语法似乎相同。

请您解释原因

答案 3 :(得分:1)

我发现最有效的方法是将变量赋值包装在 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';
}

答案 4 :(得分:0)

我来到这里是因为 cdkVirtualFor 没有更新推送到数组中的新项目。 所以如果有人有同样的问题,问题的根源在于 cdkVirtualFor 只有当数组不可变地更新时才会更新。

例如:

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

答案 5 :(得分:-1)

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

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

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

}