angular2 学习笔记 ( Form 表单 )

更新 : 2020-04-06

valueChanges 和 statusChanges 触发多次 
很多事情会导致上面这 2 个事件触发, 比如 updateValueAndValidity()
哪怕你只是要 update validity 没有更新 value
所以, 如果只是想关心 "value 真的 change 了" 那么需要 rxjs 过滤一下
this.formGroup.get('title')!.statusChanges
.pipe(
startWith(this.formGroup.get('title')!.status),
distinctUntilChanged(),
skip(1)
)
.subscribe(v => {
console.log('status change', v);
});

加 distinct 就可以了, 如果你不希望第一次触发, 那么就 skip 1

 

更新: 2020-01-26

formControl.valueChanges + startWith or defer

formControl 是一个 Subject 而不是 BehaviorSubject 所以当我们 subscrube 它以后, 即便它有初始值, 也不会马上触发.

const formControl = new FormControl('dada');
setTimeout(() => {
formControl.valueChanges.subscribe(v => console.log(v)); // 不触发
}, 1000);

碰到这样的情况一般上我们会用 startWith 来解决.

const formControl = new FormControl('dada');
const o = formControl.valueChanges.pipe(startWith(formControl.value));
setTimeout(() => {
o.subscribe(v => console.log(v)); // dada
}, 1000);

但是如果间中, 值改变的话,这招就不灵了

const formControl = new FormControl('dada');
const o = formControl.valueChanges.pipe(startWith(formControl.value));
formControl.setValue('tata');
setTimeout(() => {
o.subscribe(v => console.log(v)); // dada
}, 1000);

依然是 dada, 而不是 tata. 这是因为 startwith 的值是一开始执行时就存进去了的.

一个解决方式是使用 defer

const formControl = new FormControl('dada');
// const o = formControl.valueChanges.pipe(startWith(formControl.value));
const o = defer(() => {
return formControl.valueChanges.pipe(startWith(formControl.value));
});
formControl.setValue('tata');
setTimeout(() => {
o.subscribe(v => console.log(v)); // tata
}, 1000);

更新: 2020-01-01 

number 触发 valuechange 2 次 + validation 的 坑

https://github.com/angular/angular/issues/12540

input type = number 在 blur 的时候会触发一次 value changes 这个 bug 已经很久了.

本来嘛也不在乎. 直到今天做 dynamic validation, 手动监听 valuechange 然后去 set error

发现在 blur 以后 errors 就被 reset 了. 后来才明白。

value change 触发, ng 就会去 clear all error 然后重新去跑 validation.

而我自己写的 validation 却没有这个概念就被 ng 给 clear 了.

当然还有一个原因是我写了 distinct until change, 而 number fire 得时候 value 是一样得, 所以我得 validation 没跑.

以防万一最好不要写 distinct 每次都跑呗。

更新: 2019-12-12

async validation 什么时候 call

当我们创建 formGroup 的时候, validation 会跑一次.

当我们 binding [formGroup] 的时候, validation 也会跑一次. (这个有点意想不到 /.\ )

还有一般的 set, update, reset value 自然也是会跑咯

refer : https://github.com/angular/angular/issues/21500

kara 说最少 2 次, 我觉得也合理啦,只是感觉挺浪费的. 通常 init value 都是 valid 的丫

angular2 学习笔记 ( Form 表单 )

更新: 2019-08-17 

上次提到说通过 custom value accessor 来实现 uppercase 不过遇到 material autocomplete 就撞墙了.

因为 material 也用了 custom value accssor ...

那就不要那么麻烦了,绑定一个 blur 事件,在用于 blur 的时候才变 uppercase 呗.

另外说一下 number validation,

ng 的 input type=number 是无法验证的

因为原生的 input type=number 在 invalid 的时候 value 是 empty string

而 ng 遇到 empty string 会直接返回 null

所以如果我们想让用户知道 number invalid 我们得修改 value accessor

registerOnChange(fn: (_: number | null) => void): void {
this.onChange = (e: Event) => {
const input = e.target as HTMLInputElement;
let value: number | null;
if (input.validity.badInput) {
value = NaN;
} else if (input.value === '') {
value = null;
} else {
value = parseFloat(input.value);
}
console.log(value);
fn(value);
};
}

让它返回 nan 才可以。

更新: 2019-07-31

今天想做一个 input uppercase,就是用户输入 lowercase 自动变成 uppercase 的 input

ng1 的做法是用 formatter 和 parser 来实现,但是 ng2 没有这个东西了。

https://github.com/angular/angular/issues/3009

2015 年就提了这个 feature issue,但是直到今天有没有好的 idea 实现。

formatter parser 对 validation 不利,而且难控制执行顺序.

目前我们唯一能做的就是自己实现一个 value accessor 来处理了.

原生的 input, Angular 替我们实现了 DefaultValueAccessor,所以 input 天生就可以配合 FormControl 使用.

@Directive({
selector:
'input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]',
// TODO: vsavkin replace the above selector with the one below it once
// https://github.com/angular/angular/issues/3011 is implemented
// selector: '[ngModel],[formControl],[formControlName]',
host: {
'(input)': '$any(this)._handleInput($event.target.value)',
'(blur)': 'onTouched()',
'(compositionstart)': '$any(this)._compositionStart()',
'(compositionend)': '$any(this)._compositionEnd($event.target.value)'
},
providers: [DEFAULT_VALUE_ACCESSOR]
})
export class DefaultValueAccessor implements ControlValueAccessor {

源码里可以看到,Angular 监听的是 oninput 还有 compositionstart 这个是为了处理中文输入

上网找了一些方法,有人说监听 keydown or keypress 然后 set event.target.value

有些人说监听 input.

但其实这 2 个都是不对的.

我们知道用户输入时, 事件触发的顺序时 keydown -> keypress -> input -> keyup. 如果用户长按不放,keydown, keypress, input 会一直触发, keyup 则只会触发一次.

而 keydown, keypress 触发时,我们只能获取到 event.key 和 event.code 当时的 event.target.value 依然时旧的值.

我们监听 oninput 也不行,因为 Angular 的指令会更快的执行.

所以唯一我们能做的是写一个 CustomValueAccessor 去替换掉 DefaultValueAccessor

angular2 学习笔记 ( Form 表单 )

每一个组件只能允许一个 value accessor. 选择的顺序是 custom 优先, builtin 第二, default 第三

下面这些是 build in 的, default 指的是 input text 和 textarea

const BUILTIN_ACCESSORS = [
CheckboxControlValueAccessor,
RangeValueAccessor,
NumberValueAccessor,
SelectControlValueAccessor,
SelectMultipleControlValueAccessor,
RadioControlValueAccessor,
];

所以只要我们写一个 input directive 实现 custom value accessor 那么我们的就会优先选中执行. 而 default value accessor 就不会被执行了.

这样我们就实现我们要的东西了。

更新 : 2019-07-17

async validator

终于有需求要用到了.

async 的问题主要就是发的频率, 用户一直输入, 一直发请求去验证伤服务器性能.

有 2 种做法, 一是用 updateOn: blur,这个很牛, 一行代码搞定, 但是未必满足体验.

另一种是用返回 obserable + delay, 可以看下面这个链接

https://*.com/questions/36919011/how-to-add-debounce-time-to-an-async-validator-in-angular-2

关键就是 ng 监听到 input 事件后会调用我们提供的方法, 然后 ng 去 subscribe 它等待结果.

而第二个 input 进来的时候 ng 会 unsubscribe 刚才那一次, 所以我们把请求写在 delay 之后, 一旦被 unsubscribe 后就不会发出去了.

没有看过源码, 但是推测 ng 内部是用 switchmap 来操作的, 所以是这样的结果. 很方便哦.

另外, 只有当普通的 validation 都 pass 的情况下, ng 才会检查 async 的 validation 哦.

当 async validator 遇到 OnPush, view 的更新会失效.

https://github.com/angular/angular/issues/12378

解决方法是加一个 tap markForCheck

asyncValidators: (formControl: FormControl): Observable<ValidationErrors | null> => {
return of([]).pipe(delay(400), switchMap(() => {
return from(this.customerService.checkCustomerNameUniqueAsync({ name: formControl.value })).pipe(map((isDuplicate) => {
return isDuplicate ? { 'unique': true } : null;
}), tap(() => this.cdr.markForCheck()));
}));

或者使用 (statusChanges | async) === 'PENDING'

更新 : 2019-07-16 

动态验证简单例子

ngOnInit() {
this.formGroup = this.formBuilder.group({
name: [''],
email: ['']
});
this.formGroup.valueChanges.subscribe((value: { name: string, email: string }) => {
if (value.name !== '' || value.email !== '') {
this.formGroup.get('name').setValidators(Validators.required);
this.formGroup.get('email').setValidators(Validators.required); // this.formGroup.updateValueAndValidity({ emitEvent: false }); // 调用 formGroup 是不足够的, 它并不会去检查 child control
this.formGroup.get('name').updateValueAndValidity({ emitEvent: false }); // 这个就有效果, 但是记得要放 emitEvent false, 不然就死循环了
// 最后.. 这里不需要调用 ChangeDetectorRef.markForCheck() view 也会更新
}
});
}

更新 : 2019-05-25

disabled 的 control 不会被纳入 form.valid 和 form.value 里, 这个和 html5 行为是一致的.

https://github.com/angular/angular/issues/11432   kara commented on Sep 9, 2016

更新 : 2018-02-13 

valueChanges and rxjs for filter first value 
需求是这样的 
let fc = new FormControl('');
fc.valueChanges.subscribe(v => console.log(v));
fc.setValue(''); // '' to '' 没有必要触发
fc.setValue('a'); // '' to a 触发

可是结果 2 个都触发了.

那这样试试看 :

fc.valueChanges.pipe(distinctUntilChanged()).subscribe(v => console.log(v));

结果还是一样.

问题在哪里呢 ?

首先 ng 的 formControl 看上去想是 BehaviorSubject 因为它有 default 值, 但是行为却像是 Subject. 因为

let fc = new FormControl('dada');
fc.valueChanges.subscribe(v => console.log(v)); //并没有触发

虽然之前的代码问题出在, 没有初始值, 所以 distinctUntilChanged 就发挥不了作用了

我们需要用 startWith 告诉它初始值

let fc = new FormControl('dada');
fc.valueChanges.pipe(startWith('dada'), distinctUntilChanged(), skip(1)).subscribe(v => console.log(v));
fc.setValue('dada'); // 不触发
fc.setValue('dada1'); //触发了

startWith 会马上输入一个值, 然后流入 distinct, distinct 会把值对比上一个(目前没有上一个), 然后记入这一个, 在把值流入 skip(1), 因为我们不要触发初始值, 所以使用了 skip, 如果没有 skip 这时 subscribe 会触发. (startWith 会触发 subscribe)

这样之后的值流入(不经过 startWith 了, startWith 只用一次罢了), distinc 就会和初始值对比就是我们要的结果了.

如果要在加上一个 debounceTime, 我们必须加在最 startWith 之前.

pipe(debounceTime(200), startWith(''), distinctUntilChanged(), skip(1))

一旦 subscribe startWith 输入值 -> distinct -> skip

然后 setValue -> debounce -> distinc -> 触发 ( startWith 只在第一次有用, skip(1) 也是因为已经 skip 掉第一次了)

更新 : 2018-02-10

form.value 坑

let ff = new FormGroup({
name : new FormControl('')
});
ff.get('name')!.valueChanges.subscribe(v => {
console.log(v); // 'dada'
console.log(ff.value); // { name : '' } 这时还没有更新到哦
console.log(ff.getRawValue()) // { name : 'dada' }
});
ff.get('name')!.setValue('dada');
console.log(ff.value); // { name : 'dada' }

更新 : 2017-10-19 

this.formControl.setValidators(null);
this.formControl.updateValueAndValidity();

reset validators 后记得调用从新验证哦,Ng 不会帮你做的.

更新 : 2017-10-18 

formControl 的监听与广播

两种监听方式

1. control.valueChanges.subscribe(v)  
2. control.registerOnChange((value, emitViewToModelChange)
通常我们是想监听来自 view 的更新, 当 accessor.publishMethod(v) 的时候, 上面第一种会被广播, 第二种则收不到. 所以想监听 view -> model 使用第一种 
那么如果我们要监听来自 control.setValue 的话, model -> view or just model change, 我们使用第 2 种, 
setvalue 允许我们广播时声明要不要 让第一种和第二种触发
emitEvent = false 第一种不触发
emitModelToViewChange = false 第 2 种不触发 
emitViewToModelChange = false 第 2 种触发, 然后第二个参数是 就是 emitViewToModelChange 
对了,虽然两种叫 changes 但是值一样也是会触发的,如果不想重复值触发的话,自己写过滤呗.
总结: 
在做 view accessor 时, 我们监听 formControl model to view 所以使用 registerOnChange
// view accessor
this.viewValue = this.formControl.value; // first time
this.formControl.registerOnChange((v, isViewToModel) => { // model to view
console.log('should be false', isViewToModel);
this.viewValue = v;
});

然后通过 formControl view to model 更新

viewToModel(value: any) {
this.formControl.setValue(value, {
emitEvent: true,
emitModelToViewChange: false,
emitViewToModelChange: true
});
}

然后呢在外部,我们使用 valueChanges 监听 view to model 的变化

this.formControl.valueChanges.subscribe(v => console.log('view to model', v)); // view to model

再然后呢, 使用 setValue model to view

modelToView(value: any) {
this.formControl.setValue(value, {
emitEvent: false,
emitModelToViewChange: true,
emitViewToModelChange: false
});
}

最关键的是在做 view accessor 时, 不要依赖 valueChanges 应该只使用 registerOnChange, 这好比你实现 angular ControlvalueAccessor 的时候,我们只依赖 writeValue 去修改 view.

对于 model to view 的时候是否允许 emitEvent 完全可以看你自己有没有对其依赖,但 view accessor 肯定是不依赖的,所以即使 emitEvent false, model to view 依然把 view 处理的很好才对。

更新 : 2017-08-06 

formControlName and [formControl] 的注入

 <form [formGroup]="form">
<div formGroupName="obj">
<input formControlName="name" type="text">
<input sURLTitle="name" formControlName="URLTitle" type="text">
</div>
</form> <form [formGroup]="form">
<div [formGroup]="form.get('obj')">
<input [formControl]="form.get('obj.name')" type="text">
<input [sURLTitle]="form.get('obj.name')" [formControl]="form.get('obj.URLTitle')" type="text">
</div>
</form>

这 2 种写法出来的结果是一样的.

如果我们的指令是 sURLTitle

那么在 sURLTitle 可以通过注入获取到 formControl & formGroup

@Directive({
selector: '[sURLTitle]'
})
export class URLTitleDirective implements OnInit, OnDestroy { constructor(
// 注意 : 不雅直接注入 FormGroupDirective | FormGroupName, 注入 ControlContainer 才对.
// @Optional() private formGroupDirective: FormGroupDirective,
// @Optional() private formGroupName: FormGroupName,
private closestControl: ControlContainer, // 通过抽象的 ControlContainer 可以获取到上一层 formGroup
@Optional() private formControlDirective: FormControlDirective,
@Optional() private FormControlName: FormControlName,
) { } @Input('sURLTitle')
URLTitle: string | FormControl private sub: ISubscription
ngOnInit() {
let watchControl = (typeof (this.URLTitle) === 'string') ? this.closestControl.control.get(this.URLTitle) as FormControl : this.URLTitle;
let sub = watchControl.valueChanges.subscribe(v => {
(this.formControlDirective || this.FormControlName).control.setValue(s.toURLTitle(v));
});
} ngOnDestroy() {
this.sub.unsubscribe();
}
}

更新 : 2017-04-21 

form 是不能嵌套的, 但是 formGroup / formGroupDirective 可以

submit button 只对 <form> 有效果. 如果是 <div [formGroup] > 的话需要自己写 trigger

angular2 学习笔记 ( Form 表单 )

child form submit 并不会让 parent form submit, 分的很开.

更新 : 2017-03-22

小提示 :

我们最好把表单里面的 button 都写上 type.

因为 ng 会依据 type 来做处理. 比如 reset

要注意的是, 如果你不写 type, 默认是 type="submit".

<form [formGroup]="form" #formComponent >
<button type="button" ></button>
<button type="submit" ></button>
<button type="reset" ></button>
</form>

另外, ng 把 formGroup 指令和 formGroup 对象区分的很明显,我们可不要搞混哦.

上面 formComponent 是有 submitted 属性的, form 则没有

formComponent.reset() 会把 submitted set to false, form.reset() 则不会.

formComponent.reset() 会间接调用 form.reset(), 所以数据会清空.

<button type="reset"> 虽然方便不过不支持 window.confirm

我们要自己实现 reset 的话,就必须使用 @Viewchild 来注入 formGroup 指令.

2016-08-30

refer :

ng2 的表单和 ng1 类似, 也是用 control 概念来做操作, 当然也有一些地方不同

最大的特点是它把表单区分为 template drive and model drive

template drive 和 ng1 很像, 就是通过指令来创建表单的 control 来加以操作.

model drive 则是直接在 component 内生成 control 然后再绑定到模板上去.

template drive 的好处是写得少,简单, 适合用于简单的表单

简单的定义是 :

-没有使用 FormArray,

-没有 async valid,

-没有 dynamic | condition validation

-总之就是要很简单很静态就对了啦.

当然如果你打算自己写各做复杂指令去让 template drive 无所不能, 也是可以办到的. 有心铁棒磨成针嘛.. 你爱磨就去啦..

model drive 的好处就是方便写测试, 不需要依赖 view.

模板驱动 (template drive):

<form novalidate #form="ngForm" (ngSubmit)="submit(form)">

</form>

没能嵌套表单了哦!

通过 #form="ngForm" 我们可以获取到 ngForm 指令, 并且操作它, 比如 form.valid, form.value 等

ngSubmit 配合 button type=submit 使用

<input type="text" placeholder="name"
[(ngModel)]="person.name" name="name" #name="ngModel" required minlength="5" maxlength="10" />
<p>name ok : {{ name.valid }}</p>

[(ngModel)] 实现双向绑定和 get set value for input

name="name" 实现 create control to form

#name 引用 ngModel 指令,可以获取 name.valid 等

required, minlength, maxlength 是原生提供的验证. ng2 给的原生验证指令很少,连 email,number 都没有哦.

<fieldset ngModelGroup="address">
<legend>Address</legend>
<div>
Street: <input type="text" [(ngModel)]="person.address.country" name="country" #country="ngModel" required />
</div>
<div>country ok : {{ country.valid }}</div>
</fieldset>

如果值是对象的话,请开多一个 ngModelGroup, 这有点像表单嵌套了.

<div>
<div *ngFor="let data of person.datas; let i=index;">
<div ngModelGroup="{{ 'datas['+ i +'].data' }}">
<input type="text" [(ngModel)]="data.key" name="key" #key="ngModel" required />
</div>
</div>
</div>

遇到数组的话,建议不适用 template drive, 改用 model drive 比较适合. 上面这个有点牵强...

在自定义组件上使用 ngModel

ng1 是通过 require ngModelControl 来实现

ng2 有点不同

@Component({
selector: "my-input",
template: `
<input type="text" [(ngModel)]="value" />
`,
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MyInputComponent),
multi: true
}]
})

首先写一个 provide 扩展 NG_VALUE_ACCESSOR 让 ng 认识它 .

export class MyInputComponent implements OnInit, ControlValueAccessor {}

实现 ControlValueAccessor 接口

//outside to inside
writeValue(outsideValue: any): void {
this._value = outsideValue;
}; //inside to outside
//注册一个方法, 当 inside value updated then need call it : fn(newValue)
registerOnChange(fn: (newValue : any) => void): void {
this.publichValue = fn;
} //inside to outside
registerOnTouched(fn: any): void {
this.publichTouched = fn;
}

主要 3 个方法

writeValue 是当外部数据修改时被调用来更新内部的。

registerOnChange(fn) 把这个 fn 注册到内部方法上, 当内部值更新时调用它 this.publishValue(newValue);

registerOnTouched(fn) 也是一样注册然后调用当 touched

使用时这样的 :

<my-input [(ngModel)]="person.like" name="like" email #like="ngModel" ></my-input>
value : {{ like.value }}

执行的顺序是 ngOnInit-> writeValue-> registerOnChange -> registerOnTouched -> ngAfterContentInit -> ngAfterViewInit

如果内部也是使用 formControl 来维护 value 的话, 通常在写入时我们可以关掉 emitEvent, 不然又触发 onChange 去 publish value (但即使你这样做,也不会造成死循环 error 哦, ng 好厉害呢)

writeValue(value: any): void {
this.formControl.setValue(value,{ emitEvent : false });
};

Model drive

自定义 validator 指令

angular 只提供了 4 种 validation : required, minlength, maxlength, pattern

好吝啬 !

class MyValidators {
static email(value: string): ValidatorFn {
return function (c: AbstractControl) {
return (c.value == value) ? null : { "email": false };
};
}
} this.registerForm = this.formBuilder.group({
firstname: ['', MyValidators.email("abc")]
});

如果验证通过返回 null, 如果失败返回一个对象 { email : false };

还有 async 的,不过我们有找到比较可靠的教程,以后才讲吧.

上面这个是 model drive 的,如果你希望支持 template drive 可以参考这个 :

http://blog.thoughtram.io/angular/2016/03/14/custom-validators-in-angular-2.html

在大部分情况下, model drive 是更好的选择, 因为它把逻辑才开了, 不要依赖模板是 angular2 一个很重要的思想, 我们要尽量少依赖模板来写业务逻辑, 因为在多设备开发情况下模板是不能复用的.

而且不依赖模板也更容易测试.

我们看看整个 form 的核心是什么 ?

就是对一堆有结构的数据, 附上验证逻辑, 然后绑定到各个组件上去与用户互动.

所以 model drive 的开发流程是 : 定义出有结构的数据 -> 绑定验证 -> 绑定到组件 -> 用户操作 (我们监听并且反应)

这就是有结构的数据 :

export class AppComponent {
constructor(private formBuilder: FormBuilder) { console.clear(); }
registerForm: FormGroup;
ngOnInit() {
this.registerForm = this.formBuilder.group({
firstname: ['', Validators.required],
address: this.formBuilder.group({
text : ['']
}),
array: this.formBuilder.array([this.formBuilder.group({
abc : ['']
})], Validators.required)
});
}
}

angular 提供了一个 control api, 让我们去创建数据结构, 对象, 数组, 嵌套等等.

this.formBuilder.group 创建对象

this.formBuilder.array 创建数组 ( angular 还有添加删除数组的 api 哦 )

firlstname : [ '', Validators.required, Validators.requiredAsync ] 这是一个简单的验证绑定, 如果要动态绑定的话也是通过 control api

control api 还有很多种对数据, 结构, 验证, 监听的操作, 等实际开发之后我才补上吧.

template drive 其实也是用同一个方式来实现的, 只是 template drive 是通过指令去创建了这些 control, 并且隐藏了起来, 所以其实看穿了也没什么, 我们也可以自己写指令去让 template drive 实现所有的功能.

接下来是绑定到 template 上.

<form [formGroup]="registerForm">
<input type="text" formControlName="firstname"/>
<fieldset formGroupName="address">
<input type="text" formControlName="text">
</fieldset>
<div formArrayName="array">
<div *ngFor="let obj of registerForm.controls.array.controls; let i=index">
<fieldset [formGroupName]="i">
<input type="text" formControlName="abc">
</fieldset>
</div>
</div>
</form>

值得注意的是 array 的绑定, 使用了 i

特别附上这 2 篇 :

https://scotch.io/tutorials/how-to-build-nested-model-driven-forms-in-angular-2

https://scotch.io/tutorials/how-to-deal-with-different-form-controls-in-angular-2

async validator

static async(): AsyncValidatorFn {
let timer : NodeJS.Timer;
return (control: AbstractControl) => {
return new Promise((resolve, reject) => {
clearTimeout(timer);
timer = setTimeout(() => {
console.log("value is " + control.value);
console.log("ajaxing");
let isOk = control.value == "keatkeat";
         //假设是一个 ajax 啦
setTimeout(() => {
if (isOk) {
return resolve(null);
}
else {
resolve({
sync: true
});
}
}, 5000);
}, 300);
});
};
} this.form = this.formBuilder.group({
name: ["abcde", MyValidators.sync(), MyValidators.async()]
});

angular 会一直调用 async valid 所以最好是写一个 timer, 不然一直 ajax 很浪费.

常用的手动调用 :

this.form.controls["name"].markAsPending(); //async valid 时会是 pending 状态, 然后 setErrors 会自动把 pending set to false 哦
this.form.controls["name"].setErrors({ required : true });
this.form.controls["name"].setErrors(null); // null 表示 valid 了
this.form.controls["name"].markAsTouched();
this.form.controls['name'].updateValueAndValidity(); //trigger 验证 (比如做 confirmPassword match 的时候用到)
this.form.controls['name'].root.get("age"); //获取 sibling 属性, 验证的时候经常用到, 还支持 path 哦 .get("address.text")
this.form.controls["confirmPassword"].valueChanges.subscribe(v => v); //监听任何变化

这些方法都会按照逻辑去修改更多相关值, 比如 setErrors(null); errors = null 同时修改 valid = true, invalid = false;

特别说一下 AbstractControl.get('path'),

-当 path 有包含 "." 时 (例如 address.text), ng 会把 address 当成对象然后获取 address 的 text 属性. 但是如果你有一个属性名就叫 'address.text' : "value" 那么算是你自己挖坑跳哦.

-如果要拿 array 的话是 get('products.0') 而不是 get('products[0]') 哦.

更新 : 2016-12-23

touch and dirty 的区别

touch 表示被"动"过 ( 比如 input unblur 就算 touch 了 )

dirty 是说值曾经被修改过 ( 改了即使你改回去同一个值也时 dirty 了哦 )

更新 : 2017-02-16

概念 :

当我们自己写 accessor 的时候, 我们也应该 follow angular style

比如自定义 upload file 的时候, 当 ajax upload 时, 我们应该把 control 的 status set to "PENDING" 通过 control.markAsPending()

pending 的意思是用户正在进行操作, 可能是正在 upload or 正在做验证. 总之必须通过 control 表达出去给 form 知道, form 则可能阻止 submitt 或则是其它特定处理.

要在 accessor 里调用 formContril 我们需要注入

@Optional() @Host() @SkipSelf() parent: ControlContainer,

配合 @Input formControlName 就可以获取到 formControl

最后提一点, 当 control invalid 时, control.value 并不会被更新为 null. 我记得 angular1 会自动更新成 null. 这在 angular2 是不同的。

modelToView(value: any) {
this.formControl.setValue(value, {
emitEvent: false,
emitModelToViewChange: true,
emitViewToModelChange: false
});
}
上一篇:double函数和int函数


下一篇:ST推出新软件STM32Cube ,让STM32微控制器应用设计变得更容易、更快、更好用