延申閱讀
ng conf 2019
The Forms Awakens | Sander Elias
https://www.youtube.com/watch?v=JCjyjdlaoaI&list=PLOETEcp3DkCpimylVKTDe968yNmNIajlR&index=23
https://github.com/SanderElias/ngObservableForm
Reactive Forms Demistified | Sani Yusuf & Katerina Skroumpelou
https://www.youtube.com/watch?v=Rq4vjSkidPk&list=PLOETEcp3DkCpimylVKTDe968yNmNIajlR&index=34
https://www.youtube.com/watch?v=-0xwlICnq4w&list=PL9LUW6O9WZqgUMHwDsKQf3prtqVvjGZ6S&index=5
Reactive Form 推薦使用
與Template Drive Form一樣的是:有Form Control、Form Group
多一個Form Array
首先,先開好片頭導讀的文章,再跟著影片一起看,以後才有能力自己查文件
起手式
import { ReactiveFormsModule } from '@angular/forms'; // <==
@NgModule({
imports: [
ReactiveFormsModule // <==
],
})
export class AppModule { }
import { Component } from '@angular/core';
import { FormGroup, FormControl, FormArray } from '@angular/forms';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
formData = new FormGroup({ // FormGroup:一個表單
name: new FormControl(), // FormControl:表單裡的control
itemsFormControls: new FormArray([
new FormControl(),
new FormControl(),
new FormControl()
]),
itemsFormGroups: new FormArray([
new FormGroup({ address: new FormControl() }),
new FormGroup({ address: new FormControl() }),
new FormGroup({ address: new FormControl() })
]),
});
// FormGroup()接 controls: { [key:string] : AbstractControl }
name = new FormControl('');
get itemsFormControls(){
return this.formData.get('itemsFormControls') as FormArray;
}
get itemsFormGroups(){
return this.formData.get('itemsFormGroups') as FormArray;
}
// 移除FormArray裡面的某一個control
remove(idx){
this.itemsFormGroups.removeAt(idx);
// ^^^^^^^^^這是FormArray ^^^要移除的index
}
add(){
this.itemsFormGroups.push(
new FormGroup({address:new FormControl('手動新增的')})
);
}
insert(idx){
this.itemsFormGroups.insert(
idx+1, // 插入的位置
new FormGroup({address:new FormControl('手動插入的')})
);
}
// 全部清空只留一筆
clear(){
while (this.itemsFormGroups.length >1){
this.itemsFormGroups.removeAt(1);
}
}
// 清空controls裡的值,或重設為某值
reset(){
this.items.reset(); // 如果都沒給,controls的值會變null
this.items.reset([{address: '111'}]); // 裡面放array
}
resetFormData(){
this.formData.reset({});
}
}
vvvvvv 在ts裡 formData = new FormGroup()
<form [formGroup]="formData">
^^^^^^^ 類似Template Form的ngForm
<label>Name</label>
<input type="text" />
與Template Form不同的是,ReactiveFormsModule不用加name="xxx" ngModel
而且設定formControlName="name"
FormArray 範例
1. formArray裡面放controls
<div formArrayName="itemsFormControls">
^^^^^ 此items非下面的items,而是還要再ts裡寫一個get items()
可以減少在HTML裡的code
<div *ngFor="let item of itemsFormControls.controls; let i=index">
^^^^^^^^^^^ FormArray裡面每一個Object
<input type="text" [formControlName]="i"/>
</div>
</div>
2. formArray裡面放formGroups
<div formArrayName="itemsFormGroups">
<div *ngFor="let item of itemsFormGroups.controls; let i=index"
[formGroupName]="i">
^^^^^^^^^^^^^^^^^^^
<input type="text" [formControlName]="address"/>
^^^^^
接在input後面
<button (click)="remove(i)">X</button>
<!-- this.itemsFormGroups.removeAt(idx); -->
^^^^^^^^^^^^^^^這是formArray
<button (click)="insert(i)">+</button>
</div>
<button (click)="add()">ADD(push在最後)</button>
<button (click)="clear()">用while清到只餘一筆</button>
<button (click)="reset()">reset controls裡的值</button>
</div>
</form>
{{ formData.value | json }}
文件:
https://angular.io/api/forms/FormArray
https://angular.io/api/forms/FormArray#adding-or-removing-controls-from-a-form-array
To change the controls in the array, use the push, insert, removeAt or clear methods in FormArray itself.
建議使用push,insert,removeAt來操作FormArray裡面的controls
移除FormArray裡面的control
Resets the FormArray and all descendants are marked pristine and untouched, and
the value of all descendants to null or null maps.
練習用資料
formData = new FormGroup({
name: new FormControl(),
items: new FormArray([
new FormGroup({ address: new FormControl() }),
new FormGroup({ address: new FormControl() }),
new FormGroup({ address: new FormControl() })
])
})
https://angular.io/api/forms/FormArray#set-the-updateon-property-for-all-controls-in-a-form-array
預設是改變資料 就會更新control的值
const arr = new FormArray([
new FormControl()
], {updateOn: 'blur'}); // 輸入的當下值不會改變,直到離開時才會更新
減少model change的頻率(不用RxJS)
https://angular.io/api/forms/FormBuilder
https://angular.io/api/forms/AbstractControl#valueChangesvalueChanges: Observable<any>
A multicasting observable that emits an event every time the value of the control changes, in the UI or programmatically.
app.component.ts
import { Component } from '@angular/core';
import { FormGroup, FormControl, FormArray, FormBuilder } from '@angular/forms';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnChanges{
constructor(private fb: FormBuilder){
// 由於Reactive Form跟model的資料是同步,所以可以寫在constructor
this.formData.get('name').valueChanges.subscribe(console.log);
^^^^^當name改變時觸發
this.formData.get('name').valueChanges.pipe().subscribe(console.log);
^^^^ 可以用RxJS整合處理多個欄位的值
}
formData = new FormGroup({
name: new FormControl('test value changes'), // <----- 若這裡給預設值不會觸發
// 在ngOnInit()或ngOnChanges()設預設值就會觸發constructor()裡的監聽
items: new FormArray([
new FormGroup({ address: new FormControl() }),
new FormGroup({ address: new FormControl() }),
new FormGroup({ address: new FormControl() })
])
})
formData2 = this.fb.group({
items: this.fb.array([
// 一樣開始放 FormGroup 或 FormControl
], { updateOn: 'blur' }) // 也是有updateOn
});
}
constructor(private fb: FormBuilder){
重新計算control的value跟validation status
https://angular.io/api/forms/AbstractControl#updateValueAndValidity
使用情境:
預設會連帶更新上層(ancestors)的value跟validity
當進驗手動驗證時(當關掉自動驗證時),要自己更新model的value
updateValueAndValidity(opts: { onlySelf?: boolean; emitEvent?: boolean; } = {}): void
// 假設FormArray裡有3個FormControl
formData = new FormGroup({
name: new FormControl(),
items: new FormArray([
new FormGroup({ address: new FormControl() }),
new FormGroup({ address: new FormControl() }),
new FormGroup({ address: new FormControl() })
])
});
reset(){
this.items.reset([
{ address: '1' },
{ address: '2' },
{ address: '3' }
]);
console.log(this.items.value); // 此FormArray的所有值
let newValue = this.items.value.slice();
newValue[1].address = '5'; // 更新第2個control的address
this.items.reset(newValue);
}
editData = {
name: 'Kevin',
items: [
{address:1}, // new FormGroup({ address: new FormControl() }),
{address:2},
{address:3}
]
};
get items(){
return this.formData.get('items') as FormArray;
}
// 要更新的model
formData = this.fb.group({ // private fb: FormBuilder
name: '',
items: this.fb.array([]) // 空的FormArray
});
ngOnInit() {
this.formData.reset(); // 都填null
// 寫法一:
1. 建表單架構
this.editData.items.forEach(item => { // 走訪this.editData.items,塞到formData.items
// 應該類似加,讓結構一樣 new FormGroup({ address: new FormControl() }),
this.items.push(this.fb.group(item));
^^^^^^^^^^ this.formData.get('items')
});
2.更新值
this.formData.patchValue(this.editData); // form的結構一樣的,更新值
// 寫法二: 用reset
1. 先清空
while(this.items.length >1){
this.items.removeAt(1);
}
2. 建表單架構
this.editData.items.forEach(item => {
this.items.push(this.fb.group( { address:null} ));
});
3. 更新值
this.formData.reset(this.editData); // form的結構一樣的,更新值
如果是該items有掛Observable監聽 的時候
this.formData.get('items') = new this.fb.array();
^^^^^^^^^^ 指到新物件,但舊的物件還活著,監聽都還在
沒有比較好的方法,把對FormArray的監聽都移除
(所以要透過push,insert,removeAt models來操作FormArray)