首页 文章

Angular ChangeDetection NgIf

提问于
浏览
2

所以我想更好地了解Angulars ChangeDetection并偶然发现一个问题:https://plnkr.co/edit/M8d6FhmDhGWIvSWNVpPm?p=preview

这个Plunkr是我的应用程序代码的简化版本,基本上有一个 parent 和一个 child 组件 . 两者都启用 ChangeDetectionStrategy.OnPush .

parent.component.ts

@Component({
    selector: 'parent',
    template: `
            <button (click)="click()">Load data</button>
            {{stats.dataSize > 0}}
            <span *ngIf="stats.dataSize > 0">Works</span>
            <child [data]="data" [stats]="stats" (stats)="handleStatsChange()"></child>
        `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ParentComponent implements OnCheck, OnChanges {

    data = [];
    stats = {
        dataSize: 0
    };

   constructor(private cdr: ChangeDetectorRef) {
   }

    click() {
        console.log("parent: loading data");
        setTimeout(() => {
            this.data = ["Data1", "Data2"];
            this.cdr.markForCheck();
        });
    }

    handleStatsChange() {
        console.log('parent: stats change');
        this.cdr.markForCheck();
    }
}

child.component.ts

import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output, SimpleChanges } from "@angular/core";

@Component({
    selector: 'child',
    template: `
        <div *ngFor="let item of data">{{item}}</div>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit, OnChanges {

    @Input() data;
    @Input() stats;
    @Output('stats') statsEmitter = new EventEmitter();

    constructor() {
    }

    ngOnInit() {
    }


    ngOnChanges(changes: SimpleChanges): void {
        console.log("child changes: ", changes);

        this.stats.dataSize = changes['data'].currentValue.length;
        this.statsEmitter.emit(this.stats);
    }
}

因此,父按钮点击按钮时会更新 data ,这会触发子项中的 ngOnChanges . 每次数据更改时, child.component 都会更改 stats 中的值 . 我希望在父级的 <span *ngIf="stats.dataSize > 0">Works</span> 中使用此值 dataSize . 出于某种原因 *ngIf 不会更新 . 模板 {{stats.dataSize > 0}} 否则更新没有问题 .

我注意到的:如果我删除父级的 OnPush ,Angular将抛出异常 Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'. . 我想这是来自 *ngIf="stats.dataSize > 0" 首先是假的,现在在开发模式下进行第二次变化检测迭代后是真的 .

这就是为什么我在 handleStatsChange 中尝试在父设置中设置 this.cdr.markForCheck(); . handleStatsChange 将在孩子中被召唤 . 这没有任何后果,无论如何抛出异常 .

我想父级的更改检测不会被触发,因为父级没有更改@Input,因此ngIf不会更新?单击按钮 two 次实际显示 Works . 我这是因为新的摘要周期现在开始(由一个事件触发),而父母ChangeDetectorRef现在正在更新模板?

So why does Angular update {{stats.dataSize > 0}} and throw an error at ngIf?

任何帮助非常感谢:)

2 回答

  • 0

    在Angular中,您无法在更改检测周期中更改值 . 所以,

    ngOnChanges(changes: SimpleChanges): void {
            console.log("child changes: ", changes);
    
            this.stats.dataSize = changes['data'].currentValue.length;  <-- this is not allowed
            this.statsEmitter.emit(this.stats);
        }
    

    这就是为什么你在检查后得到关于值被改变的错误的原因 .

  • 0

    进一步挖掘Lifecycle Hooks Documentation我注意到他们提供了关于处理变更检测的非常好的例子 .

    CounterParentComponent 的反例中,他们必须通过运行新的'tick'来更新它们的 LoggerService ,这意味着延迟执行 setTimeout .

    计数器:

    updateCounter() {
        this.value += 1;
        this.logger.tick();
    }
    

    记录:

    tick() {  this.tick_then(() => { }); }
    tick_then(fn: () => any) { setTimeout(fn, 0); }
    

    这完全是我必须做的才能使我的代码工作 . 他们还在他们的文档中提到了这一点:

    Angular的单向数据流规则禁止在编写视图后对视图进行更新 . 组件的视图组成后,这两个钩子都会触发 . 如果钩子立即更新组件的数据绑定注释属性,Angular会抛出一个错误(试试吧!) . LoggerService.tick_then()推迟了浏览器JavaScript周期的一个回合的日志更新,这个时间足够长 .

相关问题