2017-04-20 59 views
8

在我的Angular(4 + Typescript)項目中,我使用了已經準備好的組件(自從擴展ControlValueAccessor的方式是一樣的,但是如果這個問題我用ng2-select),這是我想要的,因爲它具有更好的用戶體驗和更好的外觀。將它包含到我使用的反應形式的表單組件中後,我就可以在提交時返回(用於表單控件)基於在準備組件中使用的類SelectItem的對象來管理選定的項目 - 如下所示:Angular 4 ControlValueAccessor在提交和值更改事件上的值

// console.log(this.myForm.values) 
Object { 
    txtInput: 'value', 
    // ... 
    preparedComponent: SelectItem { // <-- 
     id: '1', 
     text: 'Chosen item' 
    } 
} 

由於構成要素基本上是標準選擇控制,我期待,我會回來形成串選擇項目的ID更換 - 這樣的:

// console.log(this.myForm.values) 
Object { 
    txtInput: 'value', 
    // ... 
    preparedComponent: '1' // <-- 
} 

我後瞭解了ControlValueAccessor的實際情況以及它的工作原理,我改變了提交內容的方式(或者更好地說改變了項目 - onChange)到項目的ID。問題解決了一段時間,直到我開始設置預定義值(在編輯實例上)以形成控件而不是空值(添加實例時)。我發現這次writeValue正在根據它作爲預定義值發送給它的內容來分配值來形成控件。所以在這我給預設值的情況下...

Object { 
    id: '1', 
    text: 'Chosen item' 
} 

...這也是上提交,就像在上面的第一個代碼示例中的對象的值。我爲此做了一個解決方法,在調用writeValue之後立即分配正確的值來控制onChange。這是做它的方式真的不合適,但我不能想出比其他任何東西:

public writeValue(val: any): void { 
    this.selectedItems = val; // val is for example Object { id: '1', text: 'Chosen item' } 

    setTimeout(() => { 
     this.onChange(val.id); // just an example of emitted value, there should be some checks val is object and id exists in real code 
    }, 0); 
} 

而這也工作了,只要我沒有訂閱開始形成控制的valueChanges。問題在於,每次將預定義值分配給準備好的組件時,即使在使用{ emitEvent: false }時,也會在提交後使用該值。

所以這是我想用處理準備好的組件來處理的事情,因爲我想從控制提交中獲得正確的價值,並且我不想讓訂閱者發生變化,直到值真的沒有改變,而不僅僅是替換爲預定義的值。任何想法如何處理這將不勝感激。

+0

我有點經歷了這個確切的過程。我不完全確定我明白你的確切問題。 – Amit

+0

問題主要是'writeValue'內'onChanges'。首先,因爲'setTimeout'實際上是我作爲解決方法做的最後一件事,其次是因爲它在用預定義值初始化時發出change事件。理想情況下,輸入和輸出值將被分開 - 輸入將接受對象,輸出將返回所選對象的ID。因此,不需要在init初始值上發生變化,因爲這不是預期的默認行爲(對於控制的setValue或者復位形式的'{emitEvent:false}')Angular不會發出事件當chaning值時)。 – user1257255

+1

我相當有信心,這是更多的問題,因爲你(我也有)「戰鬥」ng2選擇(這幾乎沒有維護btw) – Amit

回答

2

我不確定我是否正確地理解了這一點,但我想給它一個鏡頭。

你想要的組件輸入是以下類型:

{ 
    id: string, 
    text: string 
} 

但你想要的組件的輸出只是所選項目的字符串ID。

我認爲這個數據流有點尷尬。在初始化期間,preparedComponentobject,但是當選擇某個項目時,您想將其變爲string。除了類型安全問題,它只是感覺不直觀。

爲了比較,想象與ngModel和文本輸入這樣做:

<input type="text" [(ngModel)]="preparedComponent" /> 

你會期望在一個對象來傳遞,並取回當用戶鍵入一個字符串?可能不會。


但讓我們假設它出於某種原因必須是這樣的。您繼續修改ng2-selectControlValueAccessor的實現,或者創建您自己的組件,實現ControlValueAccessor幷包裝ng2-select。 (我不知道你在做什麼,你在做什麼)。當選擇一個項目時,現在您撥打this.onChange(val.id),該項目使用字符串ID更新父組件。

然後您注意到一個單獨的問題,即如果您預先定義了選定的值,則ng2-select將在初始化期間觸發更改事件。這很奇怪,因爲用戶還沒有與選擇控件進行交互。所以你試着用emitEvent: false在表格控件上調用setValue來初始化preparedComponent,但我猜這是行不通的,因爲雖然它可能防止了在初始化數據時發出變化事件,但是ng2-select對數據進行了第二次更改立即。

所以相反,您通過等待第一次更新ng2-select更新父組件到一個新對象,然後(使用setTimeout)用實際需要的字符串ID覆蓋它。這會使preparedComponent保持最新狀態,但不會解決更改事件觸發的問題,即使用戶未與控件交互時也是如此。 (順便說一句,如果你用自己的組件包裝ng2-select,那麼我不確定爲什麼你需要做這個setTimeout部分,因爲你不會有另一個功能,當ng2-select通知你一個新的選擇的項目,正如我在前面的段落中提到)


因爲我不能告訴你是否直接修改ng2-select,或用自己的組件包裹它:

如果要修改ng2-select,爲什麼不只是找到它的writeValue方法並刪除發出事件的代碼?這樣,當您預先定義一個值時,它會更新DOM,但不會覆蓋您的值。

如果您正在使用自己的組件包裹ng2-select,爲什麼不修改writeValue方法來存儲一個標誌,如果ng2-select即將觸發一個事件,這樣你就可以忽略它:

private: ignoreSelectedEvent = false; 

public writeValue(val: any): void { 
    this.ignoreSelectedEvent = true; 

    // Update ng2-select, which will cause an event to fire 
} 

然後在你的函數,反應到一個新的選擇項:

if (this.ignoreSelectedEvent) { 
    this.ignoreSelectedEvent = false; 

    return; 
} 

// Update the model with the string ID 

this.onChange(val.id); 
0

基本的問題是問是否有可能以不同的值類型發送到控制組件的初始值,這是由寫入處理價值法。然後這個應該將該值轉換爲字符串ID或字符串ID數組,取決於選擇哪種類型 - 單個值或多個值。正如弗蘭克在他的回答中所說的那樣,做這件事有點尷尬,並用setTimeout解決方法來調用onChange。

所以解決方案是設置初始值,以便將某個對象轉換爲ID/ID數組後,控件返回onChange中的值。組件本身必須根據發送到其中的ID/ID從所有可用項目中選擇適當的對象。