Angular之NgModel指令学习

hresh 836 0

Angular之NgModel指令学习

NgModel 指令使用场景比较多,还会和 NgForm 结合使用,所以非常有必要单独写一篇学习笔记。

NgModel 根据领域对象创建一个 FormControl 实例,并把它绑定到一个表单控件元素上。

官方说明:

这个 FormControl 实例将会跟踪值、用户交互和控件的验证状态,以保持视图与模型的同步。 如果用在某个父表单中,该指令还会把自己注册为这个父表单的子控件。

这个指令可以单独使用,也可以用作一个大表单的一部分。你所要做的一切就是用 ngModel 选择器来激活它。

它可以接受一个领域模型作为可选的 Input。如果使用 [] 语法来单向绑定到 ngModel,那么在组件类中修改领域模型将会更新视图中的值。 如果使用 [()] 语法来双向绑定到 ngModel,那么视图中值的变化会随时同步回组件类中的领域模型。

在独立控件模式下使用 ngModel

如果你希望查看与 FormControl 相关的属性(比如校验状态),你也可以使用 ngModel 作为键,把该指令导出到一个局部模板变量中(如:#myVar="ngModel")。 你也可以使用该指令的 control 属性来访问此控件,实际上你要用到的大多数属性(如 validdirty)都会委托给该控件,这样你就可以直接访问这些属性了。 你可以在 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'; }
}

页面测试:

Angular之NgModel指令学习

在表单中使用 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 属性。

页面测试:

Angular之NgModel指令学习

在表单组中使用独立 ngModel

使用带有“ngModel"的 < input> 标签时,系统会自动为这个标签创建一个叫做”FormControl"的对象,并且会自动把它添加到”FormGroup"中。而“FormControl"在”FomGroup“中是用"< input>"标签上的”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”属性添加上外,还有第二种选择,就是给"< input>"标签设置一个 ngModelOptions。如下:

<form #f="ngForm">
  <input type="text" ngModel [ngModelOptions]="{standalone: true}">
  <span>{{ f.controls['firstField']?.value }}</span>
</form>

当设置了这个属性,< input>的 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>&nbsp;&nbsp;
  <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>

页面测试:

Angular之NgModel指令学习

上述代码是自定义表单控件的实现方式,基于该案例验证 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>

页面测试:

Angular之NgModel指令学习

总结

关于 NgModel 的使用场景很多,尤其会结合 NgForm 指令使用,所以搞清楚每个场景下的含义尤为重要,对于自己编写 Angular 代码有很大的帮助。以上内容为个人参考官方文档做的学习笔记,如有错误,望不吝赐教。

参考文献

Angular学习笔记 ——input 标签上的【name属性】和【ngModelOptions属性】

官方文档

自定义表单控件 ControlValueAccessor接口

Angular ControlValueAccessor - 自定义表单控件介绍与实战

angular自定义表单控件(转)

Angular 自定义表单控件 -- CheckboxGroupComponent

发表评论 取消回复
表情 图片 链接 代码

分享