• Angular Form 的数据流


    Angular Form 总观

    先给总结,再谈细节

    • ReactForm, Template driven Form 的差异:
      • ReactForm: 需要我们自行定义 FormControl,适用于数据结构不变,验证很方便,数据流刷新时同步的。
      • Template driven Form: 不需要我们自行定义 FormControl, 适用于数据结构易变,数据流同步,从 ts 到 Dom 是异步的,Dom 到 ts 是同步的。验证需要我们写 Directive.
    • 我们可以通过自己实现ControlValueAccessor,来实现一个自定义的 Input, 使用的时候更HTMLInputElement类似。
    • ControlValueAccessor可以级联。

    Form 的两种使用场景与区别

    React FormTemplate Form,使用的例子如下,这个是官方用例
    下面的是三个React Form的使用场景

    // React Form
    import { Component } from "@angular/core";
    import { FormControl } from "@angular/forms";
    
    @Component({
      selector: "app-reactive-favorite-color",
      template: `
        Favorite Color: <input type="text" [formControl]="favoriteColorControl" />
      `,
    })
    export class FavoriteColorComponent {
      favoriteColorControl = new FormControl("");
    }
    
    // 这个是一个相对复杂一点的ReactForm 的例子
    import { Component } from "@angular/core";
    import { FormControl, FormGroup, Validators } from "@angular/forms";
    
    @Component({
      selector: "example-app",
      template: `
        <form [formGroup]="form" (ngSubmit)="onSubmit()">
          <div *ngIf="first.invalid">Name is too short.</div>
          <input formControlName="first" placeholder="First name" />
          <input formControlName="last" placeholder="Last name" />
          <button type="submit">Submit</button>
        </form>
        <button (click)="setValue()">Set preset value</button>
      `,
    })
    export class SimpleFormGroup {
      form = new FormGroup({
        first: new FormControl("Nancy", Validators.minLength(2)),
        last: new FormControl("Drew"),
      });
    
      get first(): any {
        return this.form.get("first");
      }
    
      onSubmit(): void {
        console.log(this.form.value); // {first: 'Nancy', last: 'Drew'}
      }
    
      setValue() {
        this.form.setValue({ first: "Carson", last: "Drew" });
      }
    }
    
    // Template Driven Form
    import { Component } from "@angular/core";
    
    @Component({
      selector: "app-template-favorite-color",
      template: `
        Favorite Color: <input type="text" [(ngModel)]="favoriteColor" />
      `,
    })
    export class FavoriteColorComponent {
      favoriteColor = "";
    }
    
    // 复杂一点的Template Driven Form 的例子
    import { Component } from "@angular/core";
    import { NgForm } from "@angular/forms";
    @Component({
      selector: "example-app",
      template: `
        <form #f="ngForm" (ngSubmit)="onSubmit(f)" novalidate>
          <input name="first" ngModel required #first="ngModel" />
          <input name="last" ngModel />
          <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>
      `,
    })
    export class SimpleFormComp {
      onSubmit(f: NgForm) {
        console.log(f.value); // { first: '', last: '' }
        console.log(f.valid); // false
      }
    }
    

    这个例子里面遇到几个关键字[formControl]=xxx,[formGroup]=xxx,formControlName=xxx
    NgForm NgModel
    那么这些个关键字干了啥事,它们是怎么做到的。

    • 首先谈谈H5form功能,以及 Angular Form的功能。
      • 获取一个对象的值{obj1:val1,obj2:val2},同时能够支持对每个属性配置 validaiton.
      • Angular 在此基础上提供了一种基于 API 的操作方式,包括,值,validaiton, 以及对象的结构。所有这些操作不需要操作 DOM,只需要操作Formxxx提供的 API, API 接口,主要来自于AbstractFormControl这个接口
    • Angular 是如何做到的。这个里面两个数据流,一个是 DOM 到 我们的客户端代码,另一个是从我们的客户端代码触发到 DOM。对于 Angular 而言,它就是个搬运工,一边是 DOM,一边是客户端代码。Angular 对这两个对象做了抽象,虚化出两个类型,DOM 对应于ControlValueAccessor,客户端代码则对于与AbstractFormControl这个接口。
      • formControlDirective, 以这个为例,我们可以认为这个就是 Angular,它的内部同时拥有FormControl(客户端代码),ControlValueAccessor(DOM),那么它是怎么拿到这两个东西的呢。看源码片段.FormControl来自于Input,ControlValueAccessor来自于 DI,大部分情况下,我们用的是默认的DEFAULT_VALUE_ACCESSOR,源码如下:
    // FormControl 源码片段
    @Directive({
      selector: "[formControl]",
      providers: [formControlBinding],
      exportAs: "ngForm",
    })
    export class FormControlDirective
      extends NgControl
      implements OnChanges, OnDestroy
    {
      @Input("formControl") form!: FormControl;
      constructor(
        @Optional()
        @Self()
        @Inject(NG_VALIDATORS)
        validators: (Validator | ValidatorFn)[],
        @Optional()
        @Self()
        @Inject(NG_ASYNC_VALIDATORS)
        asyncValidators: (AsyncValidator | AsyncValidatorFn)[],
        @Optional()
        @Self()
        @Inject(NG_VALUE_ACCESSOR)
        valueAccessors: ControlValueAccessor[],
        @Optional()
        @Inject(NG_MODEL_WITH_FORM_CONTROL_WARNING)
        private _ngModelWarningConfig: string | null
      ) {
        super();
        this._setValidators(validators);
        this._setAsyncValidators(asyncValidators);
        this.valueAccessor = selectValueAccessor(this, valueAccessors);
      }
    }
    
    //DEFAULT_VALUE_ACCESSOR 源码
    export const DEFAULT_VALUE_ACCESSOR: any = {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => DefaultValueAccessor),
      multi: true,
    };
    @Directive({
      selector:
        "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]",
      // TODO: vsavkin replace the above selector with the one below it once
      // https://github.com/angular/angular/issues/3011 is implemented
      // selector: '[ngModel],[formControl],[formControlName]',
      host: {
        "(input)": "$any(this)._handleInput($event.target.value)",
        "(blur)": "onTouched()",
        "(compositionstart)": "$any(this)._compositionStart()",
        "(compositionend)": "$any(this)._compositionEnd($event.target.value)",
      },
      providers: [DEFAULT_VALUE_ACCESSOR],
    })
    export class DefaultValueAccessor
      extends BaseControlValueAccessor
      implements ControlValueAccessor
    {
      /** Whether the user is creating a composition string (IME events). */
      private _composing = false;
    
      constructor(
        renderer: Renderer2,
        elementRef: ElementRef,
        @Optional()
        @Inject(COMPOSITION_BUFFER_MODE)
        private _compositionMode: boolean
      ) {
        super(renderer, elementRef);
        if (this._compositionMode == null) {
          this._compositionMode = !_isAndroid();
        }
      }
    
      /**
       * Sets the "value" property on the input element.
       * @nodoc
       */
      writeValue(value: any): void {
        const normalizedValue = value == null ? "" : value;
        this.setProperty("value", normalizedValue);
      }
    
      /** @internal */
      _handleInput(value: any): void {
        if (!this._compositionMode || (this._compositionMode && !this._composing)) {
          this.onChange(value);
        }
      }
    
      /** @internal */
      _compositionStart(): void {
        this._composing = true;
      }
    
      /** @internal */
      _compositionEnd(value: any): void {
        this._composing = false;
        this._compositionMode && this.onChange(value);
      }
    }
    
    • NgModel,这个的情况有点复杂,相同点,它也有ControlValueAccessor,AbstractFormControl,ControlValueAccessor来自于 DI, AbstractFormControl 是它自己创建的。

    • 这两种模式下的数据流的,js 到 DOM 的数据流如下:

      • 代码调用AbstractFormControl.setValue(val),
      • 由于初始化时候,Angular 以及注册了AbstractFormControl.valueChanges事件,这个时候,注册的方法会被执行,这个方法里面会调用ControlValueAccessor.writeValue(val)这样,DOM 就刷新了。
    • DOM 到 js 的数据流如下,

      • Angular 一开始会注册ControlVAlueAccessor.registerOnChange,当 DOM 值变化时,会自动调用这个方法,这个方法里面会调用AbstractFormControl.setValue(val)
      • 然后会调用 js 到 DOM 的流程,不会出现循环触发的。
    • NgModel 的方式除了上面的流程,还有一个第三点,我们经常使用的场景时[(ngModel)]=xxx,也就是说,我们的双向数据流是我们的值也就是ngModel与 DOM 之间的。

      • 当我们的值改变了,在下一次 CD,ngModel这个组件会发现差异,它会在一个微任务里面调用AbstractFormControl.setValue(),然后走上面介绍的流程。
      • 当我们的 DOM 发生了改变,也是走上面的流程,我们的FormControl会拿到更新的值,同时会调用ngModelChange.emit(val),这样我们的代码就拿到了值。

    下面是来自于NgModel的源码片段。

    // 注册方法,当ControlValueAccess 值变化,View 该怎么变化, dir: NgModel,
    function setUpViewChangePipeline(control: FormControl, dir: NgControl): void {
      dir.valueAccessor!.registerOnChange((newValue: any) => {
        control._pendingValue = newValue;
        control._pendingChange = true;
        control._pendingDirty = true;
    
        if (control.updateOn === 'change') updateControl(control, dir);
      });
    }
    
    // 这个是FormControl值变化的时候,ControlValueAccessor 的响应。
    function setUpModelChangePipeline(control: FormControl, dir: NgControl): void {
      const onChange = (newValue: any, emitModelEvent: boolean) => {
        // control -> view
        dir.valueAccessor!.writeValue(newValue);
    
        // control -> ngModel
        if (emitModelEvent) dir.viewToModelUpdate(newValue);
      };
      control.registerOnChange(onChange);
    
      // Register a callback function to cleanup onChange handler
      // from a control instance when a directive is destroyed.
      dir._registerOnDestroy(() => {
        control._unregisterOnChange(onChange);
      });
    }
    
    function updateControl(control: FormControl, dir: NgControl): void {
      if (control._pendingDirty) control.markAsDirty();
      control.setValue(control._pendingValue, {emitModelToViewChange: false});
      dir.viewToModelUpdate(control._pendingValue);
      control._pendingChange = false;
    }
    
    //**************************NgModel************************
    // NgModel 内部的一个方法,主要就是发出(onModelChange)事件
     override viewToModelUpdate(newValue: any): void {
        this.viewModel = newValue;
        this.update.emit(newValue);
      }
    
      ngOnChanges(changes: SimpleChanges) {
        this._checkForErrors();
        if (!this._registered) this._setUpControl();
        if ('isDisabled' in changes) {
          this._updateDisabled(changes);
        }
    
        if (isPropertyUpdated(changes, this.viewModel)) {
          this._updateValue(this.model);
          this.viewModel = this.model;
        }
      }
      // 这时一个微任务,会在CD之后做。
      private _updateValue(value: any): void {
        resolvedPromise.then(() => {
          this.control.setValue(value, {emitViewToModelChange: false});
        });
      }
    
    //**************************NgModel************************
    
  • 相关阅读:
    2013第47周日整理
    2013第47周六笔记本散热及相关问题思考
    myeclipse中控制台日志比实际晚8小时解决方法及java日志处理
    2013第47周五抱怨负能量
    ORM框架
    什么是IT
    内网port映射具体解释(花生壳)
    DrawText的使用
    socket编程原理
    hibernate学习——Set集合配置
  • 原文地址:https://www.cnblogs.com/kongshu-612/p/15455650.html
Copyright © 2020-2023  润新知