【问题标题】:Angular Changing subject in ngOnInit of a child that Parent component is subscribed to doesnt affect Parent订阅父组件的子组件的 ngOnInit 中的角度更改主题不会影响父组件
【发布时间】:2020-09-15 13:04:24
【问题描述】:

我使用 Angular 9 我有存储BehaviorSubject 的父子组件和服务。所有组件都有OnPush策略

  1. 父组件使用异步管道订阅该服务主题
  2. 父组件包含子组件
  3. 子组件在其生命周期开始时更改该服务主题 (ngOnInit)

通过此步骤,在孩子更改可观察对象后,父母不会立即做出反应。 这是可重现的 demo 。例如,在您关注子项的输入之前,父项不会更新。

我已经调试了进程,发现在异步管道执行markForCheck 之后调用了tick 函数。所以我不明白为什么视图没有更新? 我注意到,如果我将策略更改回DefaultExpressionChangedAfterItHasBeenCheckedError 将出现,因此看起来此更改发生在主更改检测过程之外。

您能帮我解决当前方法的问题吗?按照我的描述进行更改的正确方法是什么?

【问题讨论】:

    标签: angular rxjs


    【解决方案1】:

    问题出在数据流中,当 OnPush 组件处于渲染中间时,它的状态(类属性)应该是稳定的,但是您示例中的方法会破坏它,因为在渲染中间已更改了可观察值而它的指针 item$ 保持不变,对于 OnPush 来说,它没有理由重新渲染 async

    因此可能的解决方案是:

    • 手动告诉 Angular 组件已更新
    • 在组件稳定时引起变化
    • 避免 onPush 让 Angular 进行深度检查以检测更改的可观察值。

    一旦父组件的内容被渲染并且组件稳定,您可以强制签入父组件的ngAfterContentInit

    @Component({
      selector: 'my-app',
      templateUrl: './app.component.html',
      styleUrls: [ './app.component.css' ],
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    export class AppComponent implements AfterContentInit {
      constructor(private iService: ItemsService, private cdr: ChangeDetectorRef) { }
        item$ = this.iService.item$;
    
        ngAfterContentInit() { // <- add this
          this.cdr.detectChanges();
          this.cdr.markForCheck();
        }
    }
    

    这里是演示:https://stackblitz.com/edit/angular-ivy-pxxiee?file=src%2Fapp%2Fcomponents%2Finner%2Finner.component.ts


    如果您不想使用ngAfterContentInit,那么您可以推迟发出,使其在所有生命周期挂钩之后发出。

        item$ = this.itemSubject.asObservable().pipe(
          delay(0),
        );
    

    这里是演示:https://stackblitz.com/edit/angular-ivy-v7upxo?file=src/app/services/items.service.ts

    【讨论】:

    • 在下面查看我的答案。
    • 错了。问题仅在于发出的值。并推荐 setTimeout 而不是 rxjs 是如此 IMO。
    • 使用延迟或其他任何东西,这不是问题。我推荐 setTimeout 只是为了观察行为。不要在代码中做。
    • 我已经在我的回答中添加了这个注释。
    • 是的,因为组件的属性已更改。在items$ 的情况下,它保持不变。
    【解决方案2】:

    出现此问题,因为您的父组件需要您的子组件初始化属性,然后才能将其视图标记为已初始化。在父级初始化完成之前,您更改了模板中显示的值。这就是为什么当你移动到默认策略时,你会得到 expression has changed 错误。

    为了绕过这一点,请将您在 ngOnInit 中的值更改语句放入 setTimeout,以便第一个父级可以完成其初始化,然后您发出该值。

    注意:- 我说 setTimeout 只是为了观察行为。不是解决方案。

    【讨论】:

    • 将语句包装在 setTimeout 或类似的内容中,相当于按下按钮或聚焦输入,我不明白你的回答。我没有在子组件中显示与父组件相关的任何内容。
    • 看到您的子组件选择器在父组件模板内吗?因此在 Child 完成其视图初始化之前,父级无法完成其对视图的初始化。在完成父视图初始化之前,您更改了必须从子视图显示的值。只需用谷歌搜索删除 onpush 时遇到的错误。你会得到它。
    • 是的,我了解范围界定以及发生此错误的原因。但是事件认为我使用 OnPush 并且我看到调用了 tick() 函数。所以问题更多的是关于为什么调用 tick() 函数时没有更新父级?我清楚地看到在 tick() 之前调用了 markForChecking
    • 因为滴答声甚至在第一次渲染完成之前就发生了。
    猜你喜欢
    • 2018-03-15
    • 2017-10-13
    • 1970-01-01
    • 2017-05-18
    • 2020-09-02
    • 2019-01-25
    • 2020-06-28
    • 1970-01-01
    • 2017-10-22
    相关资源
    最近更新 更多