Angular Form Part1
<h1 id="angular-form">Angular Form</h1><h2 id="reactive-form-vs-template-driven">Reactive Form vs Template-Driven</h2>
<ul>
<li>Reactive Form 需要我们自己创建<code>FormControl,FormGroup,FormArray</code>的对象,然后将<code>FormControl</code>绑定到 html 上的 Form 上,<code>FormControl</code>绑定到 html 上的控件上。</li>
<li>Template-Driven 则是通过<code>[(ngModel)]=xxx</code>这种方式来将控件与数据关联。它使用起来简单,但是不易扩展,功能单一。<br>
区别:</li>
<li>数据流
<ul>
<li>Reactive Form: model 与 Dom 的数据刷新是同步的,Dom 事件<code>onValueChange</code>被 Form 接管,立刻写入 FormControl 中,FormControl 的 setVaule 方法,会通过<code>dom.value=xxx</code>立刻将值写入 Dom。因为它内部保留了 Dom 的引用。</li>
<li>Template-Driven: Dom 到 model 的刷新是通过<code>onValueChange</code>,同样也是同步的。model 到 Dom 的刷新则是异步的。model 反应到<code>ngModel</code>则是需要<code>changeDetect</code>,这一步则是异步的。</li>
</ul>
</li>
<li>数据模型
<ul>
<li>Reactive: 数据结构化,数据结构不易变,直白点就是,Form 的定义来自于一个对象,每个节点的数据点就是这个对象的一个属性。再直接一点就是,它将一个对象映射到一个 Form 表单上。对象的结构原则上是不变的。</li>
<li>Template-Drive: 一个 ngModel 就是一个数据点,每个数据点之间是独立的,可以很方便的添加删除。</li>
</ul>
</li>
<li>验证
<ul>
<li>Reactive: 由于我们拥有<code>FormControl,FormGroup</code>,可以很容易的通过函数调用的方式对表单进行验证。</li>
<li>Template-Drive: 则需要我们写 directive,来进行验证。</li>
</ul>
</li>
</ul>
<hr>
<p>我们先来看一下<code>FormControl</code>的一些方法,大家可以参考<code>form.d.ts</code>其实里面说的很清楚,来了解它的一些特性。</p>
<h2 id="先了解一下三个主要的类型">先了解一下三个主要的类型。</h2>
<ul>
<li>FormControl: 一般是用来关联某个控件或者 input,它就是我们要拿到的值。可以理解成 DomElement 对于 Angular 的抽象。</li>
<li>FormGroup: 这个用来特指一个组合,一个对象,一般我们会将它跟 Form 关联,可以理解成带有{}的东西。</li>
<li>FormArray: 这个用来指数组,它里面的元素可以是一个 FormControl,或者是 FormGroup。</li>
<li>组件的定位有两种格式<code>['grandparent','parent','self','child']</code>, <code>'grandparent.parent.self.child'</code></li>
</ul>
<h2 id="这三个类型都继承了abstractcontrol所以我们先了解一下abstractcontrol这个里面定义了form最主要的功能">这三个类型都继承了<code>AbstractControl</code>,所以,我们先了解一下<code>AbstractControl</code>,这个里面定义了<strong>Form</strong>最主要的功能。</h2>
<ul>
<li>
<p>value 相关的一些属性与方法,首先 Form 中最关键的问题是 Value, 双向数据流同步组件的值。</p>
<ul>
<li>value:any,</li>
<li>setValue(value:any,options?:any),</li>
<li>patchValue(value:any,options?:any),是为 FormControl 准备的,它是只更新 Form 数据中结构与 value 结构相同的值,对其他的值保持不变。对于 FormControl 与 setValue 行为一致。对于 FormArray,它只更新重叠的属性。</li>
<li>resetValue(value:any,options?:any),它除了可以设置值,还可以设置 Control status,取决于,value 的值,与 patchValue 类似,区别是可以传递{value:xxx,disabled:true}带有状态。</li>
<li><code>valueChanges: Observable<any></code>: 每次值变化的时候,都会发事件,要么编程改变值,要么 Dom 事件促发的改变,enable()/disable() 也会触发,除非传递参数湮灭这个事件。</li>
</ul>
</li>
<li>
<p>status 相关的属性与方法。</p>
<ul>
<li>status: 一般有这几个 <strong>VALID INVALID PENDING DISABLED</strong> 这四个状态。</li>
<li>get valid():boolean, get invalid():boolean, get pending():boolean, get disabled():boolean, get enabled():boolean,</li>
<li><code>pristine, get dirty():boolean, touched, get untouched():boolean, statusChanges:Observable<any></code><br>
statusChange: 每次 status <strong>重新计算</strong>都会 emit 这个事件。pristine:原始的。</li>
<li><code>get updateOn():FormHooks</code>: 获取它是通过哪个事件来或触发去获取控件的值的,有效值为: <code>change|blur|submit</code>,默认是<code>change</code>。</li>
<li>enable() , disable(),</li>
<li>markAsTouched(),markAllAsTouched(),markAsUntouched(),markAsDirty(),markAsPristine(),markAsPending(), 这些都很直接。</li>
<li>updateValueAndValidity(): 这个很直接,看名字就知道干啥了。</li>
</ul>
</li>
<li>
<p>验证与错误</p>
<ul>
<li>error: <code>ValidationErrors:{:string}</code>,标记验证错误的结果,key 一般是 <em>ruleName</em>, value 一般是 <em>errorMessage</em>.</li>
<li><code>validator:ValidatorFn</code>,我们一般会定义很多 validations, Form 内部会被封装到这个 validator 内部,调用这个内部会按顺序来验证,结果保存到<strong>error</strong>里面。</li>
</ul>
<pre><code class="language-ts"> declare interface ValidatorFn {
(control: AbstractControl): ValidationErrors | null;
</code></pre>
<ul>
<li><code>asyncValidator: AsyncValidatorFn | null;</code> 跟上面功能类似,注意两点,详情</li>
<li>每个异步验证方法会返回一个<code>Promise<ValidationErrors>|Observables<ValidationErrors></code>,最终的结果会是<code>Fork.join(asyncValidator[])</code>,所以,我们提供的 asyncValidaor,一定要返回一个 Promise 结果,真正的 Promise, 如果是 Observable, 记住一定要让它状态变成<strong>completed</strong>.</li>
<li>异步验证只有同步验证通过了,才会触发,否则,如果同步验证挂了,异步验证压根不会执行。</li>
<li>操作 validator 的 API,要想让新的 validators 生效,必须调用<code>updateValueAndValidity()</code></li>
</ul>
<pre><code class="language-ts">setValidators(newValidator: ValidatorFn | ValidatorFn[] | null): void;
setAsyncValidators(newValidator: AsyncValidatorFn | AsyncValidatorFn[] | null): void;
clearValidators(): void;
clearAsyncValidators(): void;
</code></pre>
<ul>
<li><code>getError(errorCode: string, path?: Array<string | number> | string): any;</code>获取某个子 Control 上某个 rule 的错误内容</li>
<li><code>setErrors(errors:ValidationErrors):void</code></li>
<li><code>hasError(errorCode: string, path?: Array<string | number> | string): boolean</code></li>
</ul>
</li>
<li>
<p>父子组件操作的 API.</p>
<ul>
<li><code>get parent():FormGroup|FormArray;</code> 获取当前 Control 的父级。</li>
<li><code>setParent(parent:FormGroup|FormArray)</code>.</li>
<li><code>get(path:Array<string|number>|string):AbstractControl|null</code>获取某个路径下的 Control,路径有两种方式:数组方式,或者点分的字符串。</li>
<li><code>get root():AbstractControl</code> 获取顶级的 Contorl。</li>
</ul>
</li>
</ul>
<h2 id="接下来了解一下abstractcontroldirective-controlcontainer-ngcontrol这三个类型第一个是基类后面的两种是它的实现">接下来了解一下<strong>AbstractControlDirective ControlContainer NgControl</strong>这三个类型,第一个是基类,后面的两种是它的实现。</h2>
<hr>
<p><code>AbstractControlDirective</code>它的功能是提供对于<code>AbstractControl</code>属性的<strong>ReadOnly</strong>的访问,所以它的所有方法都是<code>get</code>。</p>
<ul>
<li>值相关的,
<ul>
<li><code>get value():any, </code></li>
<li><code>get control():AbstractControl|null</code></li>
</ul>
</li>
<li>状态相关的
<ul>
<li><code>get valid():boolean|null, get invalid():boolean|null</code></li>
<li><code>get pending():boolean|null, get disabled():boolean|null, get enabled():boolean|null</code></li>
<li>...</li>
</ul>
</li>
<li>值状态事件相关的
<ul>
<li><code>get valueChanges():Observable<any>|null;</code></li>
<li><code>get statusChanges(): Observable<any> | null;</code></li>
</ul>
</li>
<li>唯一的操作 API
<ul>
<li><code>reset(value?: any): void;</code></li>
</ul>
</li>
</ul>
<hr>
<p><code>ControlContainer</code>这个可以理解成是对于<code>FormGroup</code>的封装实现,提供访问<code>FormGroup</code>属性的 API。</p>
<ul>
<li><code>name:string|number|null</code></li>
<li><code>get formDirective():Form|null</code></li>
</ul>
<hr>
<p><code>NgControl</code>这个可以理解成对<code>FormControl</code>的封装,提供只读<code>FormControl</code>属性的 API。<code>NgModel</code>是这个类的实现。这个就是对应于<strong>FormControl</strong>。</p>
<ul>
<li><code>valueAccessor: ControlValueAccessor | null;</code>
<ul>
<li><code>ControlValueAccessor</code>这个是 Angular Form API 与 Dom 之间的桥梁,数据流从 Angular 到 DOM, 以及从 DOM 到 Angular 都是通过这个类实现的。它的定义如下,其实就是个接口。<br>
简单说明一下,我们自己定义的组件,只要实现了这个接口,对于 Angular(更确切的说就是 NgModel 或者 NgControl) 而言,它就是一个潜在的<strong>FormControlElement</strong>, <code>NgControl.valueAccessor</code>,如上所定义。Angular 是看不到 Dom 的,它看到的就是<code>ControlValueAccessor</code>这个抽象。当数据流从 Angular 到外部(比如 Dom),那么 Angular 会调用<code>ControlValueAccessor.writeValue(value)</code>这样外部就拿到值了。而在初始化的时候,Angular 会先把回调方法通过<code>ControlValueAccessor.registerOnChange(callbackFn)</code>,注册到 Angular 外部(比如 Dom),那么当外部值变化时,外部会主动调用<code>callbackFn</code>,这样 Angular 就拿到值了,</li>
</ul>
</li>
</ul>
<pre><code class="language-ts">interface ControlValueAccessor {
// Angular 到DOM
writeValue(obj: any): void;
// Dom到Angular
registerOnChange(fn: any): void;
registerOnTouched(fn: any): void;
setDisabledState?(isDisabled: boolean): void;
}
</code></pre>
<ul>
<li><code>abstract viewToModelUpdate(newValue: any): void;</code>这个是个新方法,未知</li>
</ul>
<h3 id="ngmodel这是我们经常用的ngmodelxxx常用于双向绑定它是-template-driven-的关键词是一个directive其中最重要的两个概念就是formcontrolcontrolvalueaccessor"><code>NgModel</code>这是我们经常用的<code>[(NgModel)]=xxx</code>,常用于双向绑定。它是 Template-driven 的关键词,是一个<strong>Directive</strong>。其中最重要的两个概念就是<strong>FormControl,ControlValueAccessor</strong>。</h3>
<ul>
<li>
<p><strong>FormControl</strong>,可以理解成 Angular 内部的抽象,进一步提供对业务组件的访问。</p>
</li>
<li>
<p><strong>ControlValueAccessor</strong>,可以理解成数据源,或者 DOM 封装</p>
</li>
<li>
<p>那么<strong>NgModel</strong>的作用就是沟通我们的客户段业务组件与用户的输入组件。它提供了丰富的 API 让我们同时可以操作 FormControl,或者改变 DOM 的值,同时<strong>NgModel</strong>也是一个 Directive,这边有点绕。</p>
</li>
<li>
<p><strong>NgModel</strong>是如何拿到 <code>readonly control: FormControl;</code> <code>valueAccessor: ControlValueAccessor | null;</code>,因为我们通常的使用场景就是最简单的<code><input name='hello' [(NgModel)]='val'/></code></p>
<ul>
<li><code>control: FormControl</code>:这个是<strong>NgModel</strong>自己定义的,这是源码:<code>public override readonly control: FormControl = new FormControl();</code></li>
<li><strong>valueAccessor</strong>是通过<strong>DI</strong>拿到的,看构造函数定义.<strong>control</strong>可能会添加到<strong>parent</strong>中。而<strong>valueAccessor</strong>则是直接来自于<strong>valueAccessors</strong>。</li>
</ul>
</li>
</ul>
<pre><code class="language-ts">constructor(
@Optional() @Host() parent: ControlContainer,
@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[]) {
super();
this._parent = parent;
this._setValidators(validators);
this._setAsyncValidators(asyncValidators);
this.valueAccessor = selectValueAccessor(this, valueAccessors);
};
</code></pre>
<ul>
<li>Angular 为我们提供了一个默认的<strong>DefaultValueAccessor</strong>,代码如下,大部分情况下,我们都是用的这是这个。可以看出来,它接管了<code>Input</code>事件。</li>
</ul>
<pre><code class="language-ts">export const DEFAULT_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DefaultValueAccessor),
multi: true,
};
@Directive({
selector:
"input:not(),textarea,input:not(),textarea,input:not(),textarea,",
// TODO: vsavkin replace the above selector with the one below it once
// https://github.com/angular/angular/issues/3011 is implemented
// selector: ',,',
host: {
"(input)": "$any(this)._handleInput($event.target.value)",
"(blur)": "onTouched()",
"(compositionstart)": "$any(this)._compositionStart()",
"(compositionend)": "$any(this)._compositionEnd($event.target.value)",
},
providers: ,
})
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);
}
}
</code></pre>
<hr>
<h3 id="我们来看看数据流是怎么流动的假设我们有如下的-html">我们来看看数据流是怎么流动的。假设我们有如下的 html</h3>
<pre><code class="language-html"><input type="text" [(ngModel)]="val" />
</code></pre>
<ul>
<li>当我们在这个文本框输入'H'时,HtMLInputElement 发出 input 事件,该事件会调用注册的方法,该方法时通过<code>setUpViewChangePipeline()</code> 添加的。这个方法内部会赋值给<code>FormControl</code>。同时有可能调用<code>updateControl()</code>方法,在它里面,先调用<code>control.setValue()</code>,这样,我们的<code>FormControl</code>就拿到了值。同时会让<code>NgModel</code>发出<code>(ngModelChange)</code>事件,这样我们的业务组件<code>val</code>就拿到了值。</li>
<li>如果我们通过改变<code>val</code>的值,那么在下一次<strong>CD</strong>里面(<strong>异步</strong>),<strong>ngModel</strong> 的<code>ngOnChanges</code>会发现变化,它会将该值赋值给<code>FormControl.setValue()</code>,然后在<code>setUpModelChangePipeline</code>里面会设置到<code>ControlValueAccessor</code>上,从而写入 HTMLInputElement 上面。</li>
<li>如果我们直接调用<code>FormControl.setValue()</code>,那么它会直接进入<code>setUpModelChangePipeline</code>,更新<code>ControlValueAccessor</code>,从而更新 HTMLInputElement, <code>FormControl.setValue()</code>,有个参数决定这个行为要不要发出<code>(ngModelChange)</code>,让 ngModel 知道。</li>
</ul>
<pre><code class="language-ts">// 注册方法,当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************************
</code></pre>
<hr>
<p><code>NgForm</code>这个是我们不怎么用的一个组件,但它一直存在。这个是它sourceCode<br>
下面是它源码的片段。得到如下结论</p>
<ul>
<li>为什么说它是<code>FormControl</code>背后的默默无闻,看他的<strong>selector</strong>,只要我们<code>import 'FormsModule'</code>,那么我们所有的<strong>Form</strong>就被接管了。</li>
<li>它继承了<code>ControlContainer</code>,而<code>ControlContainer</code>又继承了<code>AbstractControlDirective</code>,它们主要是提供值,status, validation, 以及 Control 的访问,都是读。</li>
<li>它实现了<code>Form</code>,这个类型的功能主要是对于其内部的<code>FormControl,FormGroup</code>,<strong>add/remove/getxxx</strong>的访问。</li>
<li>它的构造函数,会拿到<em>Form</em>上定义的 validator,asyncValidator,通过 DI 拿的。它的内部会直接定义最顶层的 Form。</li>
</ul>
<pre><code class="language-ts">@Directive({
selector: "form:not():not(),ng-form,",
providers: ,
host: { "(submit)": "onSubmit($event)", "(reset)": "onReset()" },
outputs: ["ngSubmit"],
exportAs: "ngForm",
})
export class NgForm extends ControlContainer implements Form, AfterViewInit {
constructor(
@Optional()
@Self()
@Inject(NG_VALIDATORS)
validators: (Validator | ValidatorFn)[],
@Optional()
@Self()
@Inject(NG_ASYNC_VALIDATORS)
asyncValidators: (AsyncValidator | AsyncValidatorFn)[]
) {
super();
this.form = new FormGroup(
{},
composeValidators(validators),
composeAsyncValidators(asyncValidators)
);
}
@Input("ngFormOptions") options!: { updateOn?: FormHooks };
form: FormGroup;
ngSubmit = new EventEmitter();
}
</code></pre>
<h3 id="第一个问题它是什么能干啥">第一个问题,它是什么,能干啥?</h3>
<p>它是<code>FormGroup</code>最顶层的引用,本质上也是一个<code>FormGroup</code>, 它提供了对于整个<strong>Form</strong>的一种全局访问,包括值,status, validators。</p>
<ul>
<li>我们可以通过<code>NgForm.form:FormGroup</code>拿到最顶层的的<strong>FormGroup</strong>,这就是为什么说它本质上就是一个<code>FormGroup</code>,</li>
<li>它可以操作它孩子级别的<code>FormGroup,Control</code>,方法名字例如<code>get/add/removexxx</code></li>
<li>值相关的,<code>updateModel,setValue,onSubmit()</code></li>
<li>最后一个,<strong>Form</strong>的<code>submit,reset</code>方法也被它给接管了。</li>
</ul><br><br>
来源:https://www.cnblogs.com/kongshu-612/p/15429698.html
頁:
[1]