簡體   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