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>
posted @ 2021-01-08 00:20  猫神甜辣酱  阅读(175)  评论(0编辑  收藏  举报