【问题标题】:Angular 6 View is not updated after changing a variable within subscribe更改订阅中的变量后,Angular 6 View 未更新
【发布时间】:2018-05-24 22:45:01
【问题描述】:

为什么订阅中的变量更改时视图没有更新?

我有这个代码:

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/rxjs 5 到 6 的更新?

现在我有 Angular 6.0.2 和 rxjs 6.0.0。相同的代码在 Angular 5.2 和 rxjs 5.5.10 中运行正常,无需调用 detectChanges

【问题讨论】:

  • 嗨,丹尼,我的回答对您有帮助还是您需要更多信息?如果有帮助,请将其标记为已接受的答案。
  • 好的,谢谢。不知道区域的概念。

标签: typescript angular6 observable rxjs6 angular-changedetection


【解决方案1】:

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

编辑:似乎并非所有解决方案都可以正常工作。对于大多数用户来说,第一个解决方案“Angular Zone”可以胜任。

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 时,它将为您提供最新的价值。 BehaviorSubject 基本相同,但允许您定义默认值(在您的示例中有意义,但不一定总是正确的选择)。在这个初始发射之后,它基本上是一个正常的重播主题:

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);
      }
   );
}

在您看来,您可以使用async 管道订阅主题:

{{testEmitter$ | async}}

3 提取代码到新组件

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

所以新组件的代码如下:

@Input() testVariable = '';

并且 testVariable 像以前一样在 HTML 中分配,带有花括号。

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

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

这样你就进入了 Angular 区域。

4 个人喜好

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

【讨论】:

  • 谢谢,在我的情况下,this.ngZone.run 只能工作
  • 有趣。 Angular 似乎将行为从版本更改为版本。有时可能会很头疼。至少你找到了你的解决方案。我很高兴它有所帮助。
  • 我还尝试了您提供的 RxJS 示例,但它不像 @GeorgeC 那样工作。写道,我建议您更新您的答案,这可能仅适用于某些版本的行为,但不适用于最新版本。
  • @BornToCode 如果你想更新一个数组,你可以尝试使用 spead 操作符:更多信息请看这里developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… 否则尝试玩这个代码this.ngZone.run( () =&gt; { this.testVariable += '-bar'; });
  • 我在最新版本的 Rxjs 上做得很好!
【解决方案2】:

对我来说,以上都不起作用。然而,ChangeDetectorRef 成功了。

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

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

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

【讨论】:

  • 严重的用户必须等待至少 5 秒才能看到更改?
  • @RishabhGusain 哈哈哈。 5 秒是模拟 REST 服务之类的异步调用。随意不要将其包含在您的代码中。当然没必要。
【解决方案3】:

重要提示 - 科目中的捷径可能会造成问题!

我知道这不是 PO 所要求的,但有时可能会发生非常常见的问题,也许它会对某人有所帮助。

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

但在我的情况下,问题是我使用简短的语法将主题订阅为 observable 而不是完整的,当我更改它时,它解决了问题。

所以虽然这不起作用:

myServiceObservableCall.subscribe(this.myBehavSubj)

并且在日志上的行为相同,我可以正确地看到更改,只是在视图上没有。

这确实会更新视图:

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

虽然看起来是一样的,而且上面只是短途语法。

请你解释原因

【讨论】:

  • 什么是真正的 F*UK !??它对我有用。
  • 很长的路要走,但它工作了 2 分钟,然后没有。我有这个问题 1 周了,找不到解决方案
  • @andressh11 在社区将尝试提供帮助的其他问题中分享您的代码。完成后发给我一个链接。
【解决方案4】:

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

【讨论】:

    【解决方案5】:

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

    例如这样:

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

    【讨论】:

      【解决方案6】:

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

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

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

      }

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-01-28
        • 2017-04-07
        • 1970-01-01
        • 2019-04-27
        • 2019-03-25
        • 2022-07-01
        • 2018-01-24
        • 1970-01-01
        相关资源
        最近更新 更多