angular11源码探索十七[表单]
ngModel
ngModel不能用来把表单控件注册到父formGroup指令中。不然会报错
如果你想避免注册这个表单控件,请在ngModelOptions中指出它是独立的:
<input [(ngModel)]="person.firstName" [ngModelOptions]="{standalone: true}">
指令头部
selector: '[ngModel]:not([formControlName]):not([formControl])',
exportAs: 'ngModel'
拿出别名,用[(ngModel)]
就别跟formControlName
或者formControl
一起用
别名的用处很大,可以直接拿到管道内部的内容,这个我们在之前的文章介绍过
拿到视图展示的内容
viewModel: any;
*跟踪绑定到指令的名称。如果父窗体存在,则它
*使用此名称作为键来检索此控件的值。
@Input() name!: string;
是否已禁用。
@Input('disabled') isDisabled!: boolean;
@Input('ngModel') model: any;
name: 在表单控件元素上设置name属性的另一种选择
standalone: 当设置为true时,“ngModel”将不会向它的父窗体注册自己,默认值为false
uptateOne:定义表单控件值和有效性更新所依据的事件,默认change
type FormHooks = 'change'|'blur'|'submit';
@Input('ngModelOptions') options!: {name?: string, standalone?: boolean, updateOn?: FormHooks};
ngModelChange '事件'类型双向绑定,通过事件拿到值
@Output('ngModelChange') update = new EventEmitter();
别名的使用
<input type="text" [(ngModel)]="a" #dir='ngModel' [ngModelOptions]="{updateOne:'blur'}">
<span>{{dir.viewModel}}</span>
ngModel表单部分
https://angular.cn/api/forms/NgForm
@Directive({selector: '[ngModelGroup]',
exportAs: 'ngModelGroup'})
@Directive({
selector: 'form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]',
//其实我不是很建议把这个写在最外层 from上,毕竟我们的按钮位置都是不固定
host: {'(submit)': 'onSubmit($event)', '(reset)': 'onReset()'},
outputs: ['ngSubmit'],
exportAs: 'ngForm'
})
其实越来越感觉这种用法体现了别名的强大之处
<form #f="ngForm">
<!-- name是每个的属性-->
<input name="first" ngModel required #first="ngModel">
<input name="last" ngModel>
<button (click)="onSubmit(f)">Submit</button>
<button (click)="resetSub(f)">取消</button>
</form>
onSubmit(f: NgForm) {
console.log(f.value)
}
resetSub(f: NgForm) {
f.reset()
}
nGModelGroup
https://angular.cn/api/forms/NgModelGroup
@Component({
selector: 'example-app',
template: `
<form #f="ngForm" (ngSubmit)="onSubmit(f)">
<p *ngIf="nameCtrl.invalid">Name is invalid.</p>
<div ngModelGroup="name" #nameCtrl="ngModelGroup">
<input name="first" [ngModel]="name.first" minlength="2">
<input name="last" [ngModel]="name.last" required>
</div>
<input name="email" ngModel>
<button>Submit</button>
</form>
<button (click)="setValue()">Set value</button>
`,
})
export class NgModelGroupComp {
name = {first: 'Nancy', last: 'Drew'};
onSubmit(f: NgForm) {
console.log(f.value); // {name: {first: 'Nancy', last: 'Drew'}, email: ''}
console.log(f.valid); // true
}
setValue() {
this.name = {first: 'Bess', last: 'Marvin'};
}
}
上面应该是属于angular.js遗留过来的功能,不建议使用,
SelectMultipleControlValueAccessor 多选
选择器
select[multiple][formControlName]
select[multiple][formControl]
select[multiple][ngModel]
事件
host: {'(change)': 'onChange($event.target)', '(blur)': 'onTouched()'},
案例一
<label>
<select multiple [formControl]="countryControl">
<option *ngFor="let country of countries" [ngValue]="country.sex">
{{ country.name }}
</option>
</select>
</label>
按shift可以模拟选中多个
countryControl = new FormControl();
countries=[
{name:'aaa',sex:1},
{name:'bbb',sex:2},
{name:'ccc',sex:3}
]
//检测变化
this.countryControl.valueChanges.subscribe(console.log)
案例二在form表单中
<form [formGroup]="profileFormOne">
<label>
<select multiple formControlName="firstName">
<option *ngFor="let country of countries" [ngValue]="country.sex">
{{ country.name }}
</option>
</select>
</label>
</form>
this.profileFormOne.get('firstName').valueChanges.subscribe(console.log)
案例三
<select multiple [(ngModel)]="countryControl" (ngModelChange)="clickChange($event)">
countryControl = [];
clickChange(e) {
console.log(e);
}
跟踪策略[compareWith]
跟踪用于跟踪身份时的选项比较算法
我们通过通过FormControl构造函数传递对象引用来设置默认选项。现在的问题是,当我们重新填充选项列表(例如,通过HTTP调用)时,对象引用消失了,所选值的模型绑定也丢失了。
为了解决这个问题,我们可以使用compareWith指令,该指令将不再比较对象引用,而是使用布尔表达式或函数
<select [compareWith]="compareFn" [formControl]="selectedCountriesControl">
<option *ngFor="let country of countries" [ngValue]="country">
{{country.name}}
</option>
</select>
compareFn(c1: Country, c2: Country): boolean {
return c1 && c2 ? c1.id === c2.id : c1 === c2;
}
类似于这样
countries=[
{name:'aaa',id:1},
{name:'bbb',id:2},
{name:'ccc',id:3}
]
我们看看option
标签
@Directive({selector: 'option'})
...
@Input('ngValue')
set ngValue(value: any) {
if (this._select == null) return;
this._value = value;
this._setElementValue(_buildValueString(this.id, value));
this._select.writeValue(this._select.value);
}
@Input('value')
set value(value: any) {
if (this._select) {
this._value = value;
this._setElementValue(_buildValueString(this.id, value));
this._select.writeValue(this._select.value);
} else {
this._setElementValue(value);
}
}
也就是你在option
标签可以把通过value
或者ngValue
传递数据
进度条RangeValueAccessor
选择器
input[type=range][formControlName]
input[type=range][formControl]
input[type=range][ngModel]
事件
host: {
'(change)': 'onChange($event.target.value)',
'(input)': 'onChange($event.target.value)',
'(blur)': 'onTouched()'
},
跟复选框类似,这个就列举一个demo吧
const firstName=new FormControl(20),
<input type="range" [formControl]="firstName" min="10" max="100">
如果需要禁用的话 [attr.disabled]="true"
还是要这样写,以为源码中没有传进去拿不到
单选RadioControlValueAccessor
选择器
input[type=radio][formControlName]
input[type=radio][formControl]
input[type=radio][ngModel]
案例
案例一
<form [formGroup]="profileFormOne" >
<input type="radio" formControlName="firstName" value="beef" > Beef
<input type="radio" formControlName="firstName" value="lamb"> Lamb
<input type="radio" formControlName="firstName" value="fish"> Fish
</form>
案例二
<input type="radio" [formControl]="firstNames" value="beef" > Beef
firstNames=new FormControl()
案例三
其实上面的三个事件也是可以的
<input type="radio" [(ngModel)]="firstNames" value="beef333" (ngModelChange)="clickChange($event)">
事件
host: {'(change)': 'onChange()', '(blur)': 'onTouched()'},
输入的值(这个也挺重要的,帮我们如何思考怎么写demo) 提供name
属性是可选的。如果有多个单选框的话,还是建议指定下name的值
@Input() name!: string;
@Input() formControlName!: string;
@Input() value: any;
PatternValidator 正则校验
选择器
[pattern][formControlName]
[pattern][formControl]
[pattern][ngModel]
属性
pattern: string | RegExp
例如
<input name="firstName" [(ngModel)]="firstName" pattern="[a-zA-Z ]*">
数字NumberValueAccessor
选择器
input[type=number][formControlName]
input[type=number][formControl]
input[type=number][ngModel]
事件跟前面一样
当之发生改变的回调函数
registerOnChange(fn: (_: number|null) => void): void {
this.onChange = (value) => {
fn(value == '' ? null : parseFloat(value));
};
}
我们可以知道主要使用parseFloat
案例
<input type="number" [formControl]="totalCountControl">
const totalCountControl = new FormControl();
使用原生表单
默认情况下为所有表单添加novalidate
属性。
novalidate
用于禁用浏览器的原生表单验证。
如果你想在Angular表单中使用原生验证,只需添加ngNativeValidate
属性:
<form ngNativeValidate></form>
决定自己的高度的是你的态度,而不是你的才能
记得我们是终身初学者和学习者
总有一天我也能成为大佬