javascript-防止ngOnChanges发出事件后触发(角度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).但是插件已经处于正确的状态,因此这是潜在的不必要/昂贵的重新渲染.

因此,我经常要做的是在子组件中创建一个flag属性

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中,使用=绑定接收参数的指令具有相同的确切问题.因此,相反,我将通过要求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确实似乎是为表单元素设计的(它已内置验证等).因此,在非表单元素的自定义指令中使用它有点奇怪.

问题:在Angular 2中,有没有在子组件中实现自定义双向数据绑定的最佳实践,在使用EventEmitter更新父组件后,该绑定不会在子组件中触发ngOnChanges?还是应该像在Angular 1中一样与ngModel集成,即使我的子组件不是表单元素也是如此?

提前致谢!

更新:我检查了@Maximus在评论中建议的Everything you need to know about change detection in Angular.看起来ChangeDetectorRef上的detach方法将阻止模板中的任何绑定被更新,如果这是您的情况,则可以帮助提高性能.但这不会阻止ngOnChanges的调用

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

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

到目前为止,我还没有找到使用Angular的更改检测来完成此操作的方法(但是我在此过程中学到了很多!).

我最终尝试了ngModel和ControlValueAccessor.这似乎可以满足我的需要,因为它在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.JS:NG-SELECTANDNG-OPTIONSPS:其实看英文文档比看中...
AngularJS中使用Chart.js制折线图与饼图实例  Chart.js 是...
IE浏览器兼容性后续前言 继续尝试解决IE浏览器兼容性问题,...
Angular实现下拉菜单多选写这篇文章时,引用文章地址如下:h...
在AngularJS应用中集成科大讯飞语音输入功能前言 根据项目...
Angular数据更新不及时问题探讨前言 在修复控制角标正确变...