从子组件1更新父组件中的值会导致Angular中的ExpressionChangedAfterItHasBeenCheckedError [英] Updating value in parent component from child one causes ExpressionChangedAfterItHasBeenCheckedError in Angular

查看:64
本文介绍了从子组件1更新父组件中的值会导致Angular中的ExpressionChangedAfterItHasBeenCheckedError的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有两个组成部分:ParentComponent > ChildComponent和一个服务,例如TitleService.

I have two component: ParentComponent > ChildComponent and a service, e.g. TitleService.

ParentComponent看起来像这样:

export class ParentComponent implements OnInit, OnDestroy {

  title: string;


  private titleSubscription: Subscription;


  constructor (private titleService: TitleService) {
  }


  ngOnInit (): void {

    // Watching for title change.
    this.titleSubscription = this.titleService.onTitleChange()
      .subscribe(title => this.title = title)
    ;

  }

  ngOnDestroy (): void {
    if (this.titleSubscription) {
      this.titleSubscription.unsubscribe();
    }
  }    

}

ChildComponent看起来像这样:

export class ChildComponent implements OnInit {

  constructor (
    private route: ActivatedRoute,
    private titleService: TitleService
  ) {
  }


  ngOnInit (): void {

    // Updating title.
    this.titleService.setTitle(this.route.snapshot.data.title);

  }

}

这个想法很简单:ParentController在屏幕上显示标题.为了始终呈现实际标题,它订阅了TitleService并侦听事件.更改标题后,将发生事件并更新标题.

The idea is very simple: ParentController displays the title on screen. In order to always render the actual title it subscribes to the TitleService and listens for events. When title is changed, the event happens and title is updated.

加载ChildComponent时,它会从路由器获取数据(动态解析),并告诉TitleService用新值更新标题.

When ChildComponent loads, it gets data from the router (which is resolved dynamically) and tells TitleService to update the title with the new value.

问题是此解决方案导致此错误:

The problem is this solution causes this error:

Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'Dynamic Title'.

该值似乎在变更检测回合中被更新.

It looks like the value is updated in a change detection round.

我是否需要重新安排代码以实现更好的实现,还是必须在某处启动另一轮变更检测?

  • 我可以将setTitle()onTitleChange()调用移至受人尊敬的构造函数,但是我已经读到,除了初始化local之外,在构造函数逻辑中进行任何繁重的工作"都是不明智的做法属性.

  • I can move the setTitle() and onTitleChange() calls to the respected constructors, but I've read that it's considered a bad practice to do any "heavy-lifting" in the constructor logic, besides initializing local properties.

此外,标题应由子组件确定,因此无法从中提取此逻辑.

Also, the title should be determined by the child component, so this logic couldn't be extracted from it.

我已经实现了一个最小的示例,以更好地演示该问题.您可以在 GitHub存储库中找到它.

I've implemented a minimal example to better demonstrate the issue. You can find it in the GitHub repository.

经过深入研究,仅在ParentComponent中使用*ngIf="title"时出现问题:

After thorough investigation the problem only occurred when using *ngIf="title" in ParentComponent:

<p>Parent Component</p>

<p>Title: "<span *ngIf="title">{{ title }}</span>"</p>

<hr>

<app-child></app-child>

推荐答案

文章

The article Everything you need to know about the ExpressionChangedAfterItHasBeenCheckedError error explains this behavior in great details.

有两种可能的解决方案:

There are two possible solutions to your problem:

1)将app-child放在ngIf之前:

<app-child></app-child>
<p>Title: "<span *ngIf="title">{{ title }}</span>"</p>

2)使用异步事件:

export class TitleService {
  private titleChangeEmitter = new EventEmitter<string>(true);
                                                       ^^^^^^--------------

经过深入调查,问题仅在使用时出现 * ngIf ="title"

After thorough investigation the problem only occurred when using *ngIf="title"

您要描述的问题不是ngIf特有的,并且可以通过实现依赖于父输入的自定义指令来轻松重现,该父输入在将输入传递给指令后的更改检测期间会同步更新 :

The problem you're describing is not specific to ngIf and can be easily reproduced by implementing a custom directive that depends on parent input that is synchronously updated during change detection after that input was passed down to a directive:

@Directive({
  selector: '[aDir]'
})
export class ADirective {
  @Input() aDir;

------------

<div [aDir]="title"></div>
<app-child></app-child> <----------- will throw ExpressionChangedAfterItHasBeenCheckedError

为什么发生这种情况实际上需要对特定于更改检测和组件/指令表示的Angular内部有很好的了解.您可以从以下文章开始:

Why that happens actually requires a good understanding of Angular internals specific to change detection and component/directive representation. You can start with these articles:

  • The mechanics of DOM updates in Angular
  • Everything you need to know about change detection in Angular
  • Angular’s $digest is reborn in the newer version of Angular

尽管无法在此答案中详细解释所有内容,但这是高级解释.在摘要周期期间,Angular执行对子指令的某些操作.这些操作之一是更新输入并在子指令/组件上调用ngOnInit生命周期挂钩.重要的是这些操作必须严格执行:

Although it's not possible to explain everything in details in this answer, here is the high level explanation. During digest cycle Angular performs certain operations on child directives. One of such operations is updating inputs and calling ngOnInit lifecycle hook on child directives/components. What's important is that these operations are performed in strict order:

  1. 更新输入
  2. 致电ngOnInit

现在您具有以下层次结构:

Now you have the following hierarchy:

parent-component
    ng-if
    child-component

执行上述操作时,Angular遵循此层次结构.因此,假设当前Angular检查parent-component:

And Angular follows this hierarchy when performing above operations. So, assume currently Angular checks parent-component:

  1. 更新ng-if上的title输入绑定,将其设置为初始值undefined
  2. 致电ngOnInit以获得ng-if.
  3. child-component
  4. 不需要更新
  5. child-component调用ngOnInti,将标题更改为parent-component上的Dynamic Title
  1. Update title input binding on ng-if, set it to initial value undefined
  2. Call ngOnInit for ng-if.
  3. No update is required for child-component
  4. Call ngOnInti for child-component which changes title to Dynamic Title on parent-component

因此,当在ng-if上更新属性时,我们最终遇到了Angular传递title=undefined的情况,但是在更改检测完成后,在parent-component上有了title=Dynamic Title.现在,Angular运行第二个摘要以验证没有更改.但是,当它与当前值与上一个摘要中传递给ng-if的当前值进行比较时,它会检测到更改并抛出错误.

So, we end up with a situation where Angular passed down title=undefined when updating properties on ng-if but when change detection is finished we have title=Dynamic Title on parent-component. Now, Angular runs second digest to verify there's no changes. But when it compares to what was passed down to ng-if on the previous digest with the current value it detects a change and throws an error.

parent-component模板中更改ng-ifchild-component的顺序将导致以下情况:在ng-if的角度更新属性之前,parent-component的属性将被更新.

Changing the order of ng-if and a child-component in the parent-component template will lead to the situation when property on parent-component will be updated before angular updates properties for a ng-if.

这篇关于从子组件1更新父组件中的值会导致Angular中的ExpressionChangedAfterItHasBeenCheckedError的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆