繁体   English   中英

发射事件后防止ngOnChanges触发(Angular 2+)

[英]Prevent ngOnChanges from firing after emitting event (Angular 2+)

在Angular 2+中,可以使用@Input@Output参数完成自定义双向数据绑定。 因此,如果我想让子组件与第三方插件进行通信,我可以按如下方式进行:

export class TestComponent implements OnInit, OnChanges {
    @Input() value: number;
    @Output() valueChange = new EventEmitter<number>();

    ngOnInit() {
        // Create an event handler which updates the parent component with the new value
        // from the third party plugin.

        thirdPartyPlugin.onSomeEvent(newValue => {
            this.valueChange.emit(newValue);
        });
    }

    ngOnChanges() {
        // Update the third party plugin with the new value from the parent component

        thirdPartyPlugin.setValue(this.value);
    }
}

并像这样使用它:

<test-component [(value)]="value"></test-component>

在第三方插件触发事件以通知我们更改后,子组件通过调用this.valueChange.emit(newValue)更新父组件。 问题是ngOnChanges随后在子组件中触发,因为父组件的值已更改,这会导致调用thirdPartyPlugin.setValue(this.value) 但是插件已经处于正确状态,因此这可能是不必要的/昂贵的重新渲染。

所以我经常做的是在我的子组件中创建一个标志属性:

export class TestComponent implements OnInit, OnChanges {
    ignoreModelChange = false;

    ngOnInit() {
        // Create an event handler which updates the parent component with the new value
        // from the third party plugin.

        thirdPartyPlugin.onSomeEvent(newValue => {
            // Set ignoreModelChange to true if ngChanges will fire, so that we avoid an
            // unnecessary (and potentially expensive) re-render.

            if (this.value === newValue) {
                return;
            }

            ignoreModelChange = true;

            this.valueChange.emit(newValue);
        });
    }

    ngOnChanges() {
        if (ignoreModelChange) {
            ignoreModelChange = false;

            return;
        }

        thirdPartyPlugin.setValue(this.value);
    }
}

但这感觉就像一个黑客。

在Angular 1中,使用= binding接受参数的指令具有相同的确切问题。 因此,我会通过要求ngModelController完成自定义双向数据绑定,这在模型更新后不会导致重新渲染:

// Update the parent model with the new value from the third party plugin. After the model
// is updated, $render will not fire, so we don't have to worry about a re-render.

thirdPartyPlugin.onSomeEvent(function (newValue) {
    scope.$apply(function () {
        ngModelCtrl.$setViewValue(newValue);
    });
});

// Update the third party plugin with the new value from the parent model. This will only
// fire if the parent scope changed the model (not when we call $setViewValue).

ngModelCtrl.$render = function () {
    thirdPartyPlugin.setValue(ngModelCtrl.$viewValue);
};

这工作,但ngModelController似乎真的是为表单元素设计的(它内置了验证等)。 因此,在不是表单元素的自定义指令中使用它感觉有点奇怪。

问:是否有角最佳实践2+在一个子组件,这不会触发实现自定义双向数据绑定ngOnChanges更新使用父组件后,子组件EventEmitter 或者我应该像在Angular 1中那样与ngModel集成,即使我的子组件不是表单元素?

提前致谢!


更新 :我在评论中检查了@Maximus建议的Angular变更检测所需的所有内容 看起来ChangeDetectorRef上的detach方法将阻止更新模板中的任何绑定,如果这是你的情况,这可能有助于提高性能。 但它不会阻止ngOnChanges

thirdPartyPlugin.onSomeEvent(newValue => {
    // ngOnChanges will still fire after calling emit

    this.changeDetectorRef.detach();
    this.valueChange.emit(newValue);
});

到目前为止,我还没有找到使用Angular的变化检测来实现这一目标的方法(但我在这个过程中学到了很多东西!)。

我最终用ngModelControlValueAccessor尝试了这个。 这似乎完成了我需要的,因为它在Angular 1中表现为ngModelController

export class TestComponentUsingNgModel implements ControlValueAccessor, OnInit {
    value: number;

    // Angular will pass us this function to invoke when we change the model

    onChange = (fn: any) => { };

    ngOnInit() {
        thirdPartyPlugin.onSomeEvent(newValue => {
            this.value = newValue;

            // Tell Angular to update the parent component with the new value from the third
            // party plugin

            this.onChange(newValue);
        });
    }

    // Update the third party plugin with the new value from the parent component. This
    // will only fire if the parent component changed the model (not when we call
    // this.onChange).

    writeValue(newValue: number) {
        this.value = newValue;

        thirdPartyPlugin.setValue(this.value);
    }

    registerOnChange(fn: any) {
        this.onChange = fn;
    }
}

并像这样使用它:

<test-component-using-ng-model [(ngModel)]="value"></test-component-using-ng-model>

但同样,如果自定义组件不是表单元素,使用ngModel似乎有点奇怪。

也遇到了这个问题(或者至少是非常相似的东西)。

我最后使用了你上面讨论过的hacky方法,但稍作修改,我使用了setTimeout来重置状态以防万一。

(对我个人来说,如果使用双向绑定,ngOnChanges主要是有问题的,所以如果不使用双向绑定,setTimeout会阻止挂起disableOnChanges)。

changePage(newPage: number) {
    this.page = newPage;
    updateOtherUiVars();

    this.disableOnChanges = true;
    this.pageChange.emit(this.page);
    setTimeout(() => this.disableOnChanges = false, 0);     
}

ngOnChanges(changes: any) {
    if (this.disableOnChanges) {
        this.disableOnChanges = false;
        return;
    }

    updateOtherUiVars();
}

这正是Angular的意图以及你应该尝试使用而不是反对的东西 更改检测由检测其模板绑定中的更改并将其沿组件树向下传播的组件工作。 如果您可以依赖于组件输入的不变性来设计应用程序,则可以通过设置@Component({changeDetection:ChangeDetectionStrategy.OnPush})来手动控制它,该组件将测试引用以确定是否继续更改检测儿童成分。

所以说,我的经验是,第三方插件的包装可能无法有效地处理和利用这种类型的策略。 您应该尝试使用上述知识,以及良好的设计选择,例如分离表示与容器组件的关注点,以利用检测策略来实现良好的性能。

您还可以将changes: SimpleChanges传递给ngOnInit(changes: SimpleChanges)并检查对象以了解有关数据流的更多信息。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM