angular11源碼探索十七[表單]

ngModel

ngModel不能用來把表單控件註冊到父formGroup指令中。否則會報錯
若是你想避免註冊這個表單控件,請在ngModelOptions中指出它是獨立的:html

<input [(ngModel)]="person.firstName" [ngModelOptions]="{standalone: true}">

指令頭部算法

selector: '[ngModel]:not([formControlName]):not([formControl])',
  exportAs: 'ngModel'

拿出別名,用[(ngModel)] 就別跟formControlName 或者formControl 一塊兒用api

別名的用處很大,能夠直接拿到管道內部的內容,這個咱們在以前的文章介紹過瀏覽器

拿到視圖展現的內容  
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();

別名的使用app

<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'
})

其實愈來愈感受這種用法體現了別名的強大之處ui

<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/NgModelGroupthis

@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遺留過來的功能,不建議使用,spa

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>
相關文章
相關標籤/搜索