提问者:小点点

角度变化检测NgIF


所以我试图更好地理解Angulars ChangeDetection,并陷入了一个问题:https://plnkr.co/edit/M8d6FhmDhGWIvSWNVpPm?p=preview

这个Plunkr是我应用程序代码的简化版本,基本上有一个父组件和一个子组件。两者都具有ChangeDettionStrategy。启用OnPush

父母亲组成部分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();
    }
}

小孩组成部分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);
    }
}

因此,在单击按钮时,父项更新数据,该按钮触发子项中的ngochanges。每次数据更改时,child。组件更改统计信息中的值。我希望在

我注意到:如果我在父对象上删除OnPush,Angular将抛出一个异常表达式在检查后已更改。上一个值:“false”。当前值:“true”。我猜这是来自*ngIf=“stats.dataSize

这就是为什么我试着设置这个。指挥官。markForCheck()handleStatsChangehandleStatsChange将在child中调用。这并没有任何后果,不管怎样抛出异常。

我猜父级上的更改检测不会被触发,因为父级中没有@Input更改,因此ngIf不会更新??点击按钮两次将实际显示Works。我之所以这么做是因为新的摘要周期现在确实开始了(由事件触发),并且父级ChangeDetectorRef现在正在更新模板?

那么为什么Angular update{{stats.dataSize

任何帮助非常感谢:)


共3个答案

匿名用户

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

这就是为什么在检查值后会出现更改值的错误。

匿名用户

再深入研究一下生命周期钩子文档,我注意到它们提供了处理更改检测的非常好的示例。

CounterParentComponent中的反例中,他们必须通过运行新的“勾号”来更新其日志服务,这意味着使用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周期的一轮,这已经足够长了。

匿名用户

我今天在intersection observer中遇到了这个问题,将一个项目滚动到视图中本来应该做些什么,99%的时间它做了,但1%的时间它没有启动。

重要提示:异步管道实际上在内部调用markForCheck。您可以查看源代码(接近末尾)。

我过去使用的一个解决方案是创建一个管道来创建“延迟”。这有助于“表达式更改”错误,因为它有效地导致了新的更改检测器周期。

这应该是最后的选择——但我已经用过好几次了,因为我在别处找不到合适的时机。

<div *ngIf="model.showPanel | delayTime | async">...</div>

这将有一个默认的0ms和延迟()RxJS管道使用setTimeout:

@Pipe({
    name: 'delayTime'
})
export class DelayTimePipe implements PipeTransform {

    constructor() {}

    transform(value?: Observable<any> | any, ms?: number): Observable<any> {
        if (isObservable(value)) {
            return value.pipe(delay(ms || 0));
        } else {
            throw debuggerError('[DelayTimePipe] Needs to be an observable');
        }
    }
}