NgModel 指令使用场景比较多,还会和 NgForm 结合使用,所以非常有必要单独写一篇学习笔记。
NgModel 根据领域对象创建一个 FormControl
实例,并把它绑定到一个表单控件元素上。
官方说明:
这个
FormControl
实例将会跟踪值、用户交互和控件的验证状态,以保持视图与模型的同步。 如果用在某个父表单中,该指令还会把自己注册为这个父表单的子控件。这个指令可以单独使用,也可以用作一个大表单的一部分。你所要做的一切就是用
ngModel
选择器来激活它。它可以接受一个领域模型作为可选的
Input
。如果使用[]
语法来单向绑定到ngModel
,那么在组件类中修改领域模型将会更新视图中的值。 如果使用[()]
语法来双向绑定到ngModel
,那么视图中值的变化会随时同步回组件类中的领域模型。
在独立控件模式下使用 ngModel
如果你希望查看与 FormControl
相关的属性(比如校验状态),你也可以使用 ngModel
作为键,把该指令导出到一个局部模板变量中(如:#myVar="ngModel"
)。 你也可以使用该指令的 control
属性来访问此控件,实际上你要用到的大多数属性(如 valid
和 dirty
)都会委托给该控件,这样你就可以直接访问这些属性了。 你可以在 AbstractControlDirective
中直接查看这些属性的完整列表。 如下所示:
abstract class AbstractControlDirective { abstract control: AbstractControl | null value: any valid: boolean | null invalid: boolean | null pending: boolean | null disabled: boolean | null enabled: boolean | null errors: ValidationErrors | null pristine: boolean | null dirty: boolean | null touched: boolean | null status: string | null untouched: boolean | null statusChanges: Observable<any> | null valueChanges: Observable<any> | null path: string[] | null reset(value: any = undefined): void hasError(errorCode: string, path?: string | (string | number)[]): boolean getError(errorCode: string, path?: string | (string | number)[]): any } 复制代码
下面是一个在简单的独立控件中使用 ngModel
的例子:
import {Component} from '@angular/core'; @Component({ selector: 'example-app', template: ` <input [(ngModel)]="name" #ctrl="ngModel" required> <p>Value: {{ name }}</p> <p>Value: {{ ctrl.value }}</p> <p>Valid: {{ ctrl.valid }}</p> <button (click)="setValue()">Set value</button> `, }) export class SimpleNgModelComp { name: string = ''; setValue() { this.name = 'Nancy'; } } 复制代码
页面测试:
在表单中使用 ngModel
当在 <form>
标签中使用 ngModel
时,你还需要提供一个 name
属性,以便该控件可以使用这个名字把自己注册到父表单中。
在父表单的上下文中,通常不用包含单向或双向绑定,因为这个父表单将会为你同步该值。 你可以使用 ngForm
把它导出给一个模板局部变量(如 #f="ngForm"
),以访问它的属性。 可以在任何需要提交表单的地方使用它。
如果你只是要为表单设置初始值,对 ngModel
使用单向绑定就够了。在提交时,你可以使用从表单导出的值,而不必使用领域模型的值。
下面的例子展示了如何在表单中使用 ngModel
:
import {Component} from '@angular/core'; import {NgForm} from '@angular/forms'; @Component({ selector: 'example-app', template: ` <h2>ngForm中使用 ngModel</h2> <div> <form #f="ngForm" (ngSubmit)="onSubmit(f)" novalidate> <input name="first" ngModel required #first="ngModel"> <br> <input name="last" ngModel> <br> <button>Submit</button> </form> <p>First name value: {{ first.value }}</p> <p>First name valid: {{ first.valid }}</p> <p>Form value: {{ f.value | json }}</p> <p>Form valid: {{ f.valid }}</p> <div [hidden]="!f.valid"> <p>{{submitMessage }}</p> </div> </div> `, }) export class SimpleFormComp { submitMessage = ''; onSubmit(f: NgForm) { console.log(f.value); // { first: '', last: '' } console.log(f.valid); // false this.submitMessage = '数据已提交'; } } 复制代码
注意:单独 ngModel 的作用是通知 ngForm.value,我要向你那里加入一个 property,其 key 值是组件的 name属性值,其 value 为空字符串。 所以如果没有为 ngModel 赋值的话,则必须存在 name 属性。
页面测试:
在表单组中使用独立 ngModel
使用带有“ngModel"的”“标签时,系统会自动为这个标签创建一个叫做”FormControl"的对象,并且会自动把它添加到”FormGroup"中。而“FormControl"在”FomGroup“中是用""标签上的”name"属性来做标识的。
<form #f="ngForm"> <input type="text" ngModel name="firstField"> <span>{{ f.controls['firstField']?.value }}</span> </form> 复制代码
如果没有使用“name”这个属性,那么将会报错:
Error: If ngModel is used within a form tag, either the name attribute must be set or the form control must be defined as 'standalone' in ngModelOptions. 复制代码
解决方法除了把“name”属性添加上外,还有第二种选择,就是给""标签设置一个 ngModelOptions。如下:
<form #f="ngForm"> <input type="text" ngModel [ngModelOptions]="{standalone: true}"> <span>{{ f.controls['firstField']?.value }}</span> </form> 复制代码
当设置了这个属性,的 FormControl 对象就不会添加到FormGroup内,也就不能通过
{{ f.controls['firstField']?.value }} 索引到该对象的值了。 复制代码
通过选项设置 ngModel 的 name 属性
在讲解该案例前,需要创建一个自定义表单控件,这里我直接将相关代码列举出来,具体讲解我会在参考文献处标注。
首先需要创建一个组件 formcontrol,修改 formcontrol.component.ts:
import { Component, Input, forwardRef } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS, AbstractControl, ValidatorFn, ValidationErrors, FormControl } from '@angular/forms'; @Component({ selector: 'form-control', templateUrl: './formcontrol.component.html', providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => FormcontrolComponent), multi: true }] }) export class FormcontrolComponent implements ControlValueAccessor { @Input() _count: number = 0; propagateOnChange: (value: any) => void = (_: any) => { }; propagateOnTouched: (value: any) => void = (_: any) => { }; ngOnInit() { } get count() { return this._count; } set count(value: number) { this._count = value; this.propagateOnChange(this._count); } writeValue(value: any) { if (value) { this.count = value; } } registerOnChange(fn: any) { this.propagateOnChange = fn; } registerOnTouched(fn: any) { this.propagateOnTouched = fn; } increment() { this.count++; } decrement() { this.count--; } } 复制代码
对应的 formcontrol.component.html :
<div> <p>当前值: {{ count }}</p> <button (click)="increment()"> + </button> <button (click)="decrement()"> - </button> </div> 复制代码
然后在 simple-form.component.html 中使用自定义控件。
<form #form="ngForm"> <form-control name="counter" ngModel></form-control> <button type="submit">Submit</button> <br> <span>counter value: {{ form.controls['counter']?.value }}</span> </form> 复制代码
页面测试:
上述代码是自定义表单控件的实现方式,基于该案例验证 ngModel 的另外一种使用场景。
下面的例子展示了设置 name 属性的另一种方式。该 name 属性要和自定义表单组件一起使用,而该自定义组件的 @Input
属性 name 已用作其它用途。
<form #form="ngForm"> <form-control name="counter" ngModel [ngModelOptions]="{name: 'counter2'}"></form-control> <button type="submit">Submit</button> <br> <span>counter value: {{ form.controls['counter']?.value }}</span> <br> <span>counter2 value: {{ form.controls['user']?.value }}</span> </form> 复制代码
页面测试:
总结
关于 NgModel 的使用场景很多,尤其会结合 NgForm 指令使用,所以搞清楚每个场景下的含义尤为重要,对于自己编写 Angular 代码有很大的帮助。以上内容为个人参考官方文档做的学习笔记,如有错误,望不吝赐教。