零、需要剖析

前置条件:本文基于Angular动静组件
https://angular.cn/guide/dyna...

这是一个有着三层组件的动静表单,因为表单项是动静生成的,与父组件不共用FromControl,因而对于必填项的拦挡无奈反馈到最外层组件的submit上。

为了不便交换,咱们把最外层的模态框页面称为第一层
把Angular动静组件加载器称为第二层
把表单项子组件称为第三层。

一、第一层和第二层之间传值

这里用到的是一般的组件间传值。
先创立一个变量:

// 第一层:TS// 以后组件按钮是否能够点击  submitButtonActive: boolean;

在第一层的提交按钮上加上[disabled]="!submitButtonActive"

// 第一层:HTML// Submit按钮<button [disabled]="!submitButtonActive" class="btn btn-primary" (click)="onSubmit()" type="button">确认</button>

这样button就指向了一个变量,以便前面在第二层数据变动时,扭转第一层的变量从而管制按钮是否激活。

而后在第二层组件建设一个弹射器

// 第二层:TS@Output()  isFormGroupValid = new EventEmitter<boolean>();

因为第二层组件有一堆子组件,这些子组件的输出是否非法由第二层决定,第二层只负责向第一层弹射一个计算后的最终值即可。

当第二层弹射新的值时,将会更新第一层提交按钮的激活状况。

二、第三层向第二层传值

接下来就是思考第二层和第三层传递数据的问题了。

这段内容基于Angular动静组件。
先回顾一下动静组件是怎么生成的:
首先须要一个接口,接口规定了第二层组件加载器和第三层组件间能够传递什么值;
而后动静组件加载器就能够生成组件并向接口规定的变量中传值了。

基于此形式,如果向让子组件向父组件传值,咱们还须要两个变量:
一个输出值index,用来标记以后子组件在父组件中是第几个被加载进去的,即地位索引。
一个输入值isValid,用来通知父组件,以后子组件的信息是否非法。

当输入值isValid变动时,将变动值弹射给父组件(也就是第二层组件加载器),第二层校验所有组件的表单值是否全副非法,校验实现后向第一层弹射。

批改动静组件实现的接口,减少两个变量,

// 接口// 组件索引,类型:number@Input()  index: number;  // 以后组件的表单值是否非法,类型:弹射器@Output()  isValid: EventEmitter<{index: number, isValid: boolean}>;

并且所有成员实现这两个变量:

// 第三层:TS@Input()  index: number;  @Output()  isValid = new EventEmitter<{index: number, isValid: boolean}>();

在第二层减少赋值和订阅:

// 第二层:TS 加载动静组件时componentRef.instance.index = i;  componentRef.instance.isValid.subscribe(({index, isValid}) => {    console.log(index);    console.log(isValid);  })

在第三层组件上,减少对formControl的订阅,输入valid属性:

// 第三层:TS , ngOninit办法中this.formControl.valueChanges.subscribe(value => {    this.formItemValue.value = value;    // 打印表单值是否非法  console.log(this.formControl.valid);  })

此时,一旦把输入框的内容全副删掉,就会打印false属性:

接下来咱们删掉第三层的console.log,在valid变动后把它弹射进来:

// 第三层:TS , ngOninit办法中// 弹射内容包含以后索引和表单合法性this.formControl.valueChanges.subscribe(value => {    this.formItemValue.value = value;    // 弹射表单值合法性  this.isValid.emit({index: this.index, isValid: this.formControl.valid});  })

此时,第三层没有输入语句了,而它的弹射会触发第二层的输入语句,咱们来验证下成果:

每次值变动都会向第二层输入以后组件的索引和以后组件表单值是否非法。

接下来就是在第二层创立一个map,用来贮存所有的组件信息:

// 第二层:TS/**   * 组件映射,用来贮存每个组件的索引和表单值是否非法   * 当值发生变化时,会统计以后映射的所有boolean,全副非法则弹射非法,否则弹射不非法   */  componentMap = new Map<number, boolean>();

在加载动静组件时,每一次循环都向map中增加一个值。
有几个组件,就有几个对键值对,后面是索引,前面是合法性:

// 第二层:TS 加载动静组件时// 向map中增加以后组件的信息this.componentMap.set(i, false);

批改第二层订阅时的行为,去掉console.log,批改map的值。
并且统计map中所有的值,将统计好的后果弹射到第一层:

// 第二层:TS 加载动静组件时componentRef.instance.isValid.subscribe(({index, isValid}) => {    // 批改map中本组件的值    this.componentMap.set(index, isValid);    // 统计map中所有的值,并向外弹射    let isValidResult = true;     // 只有有一个是false,最终后果就是false  this.componentMap.forEach((_isValid) => {      if (_isValid === false) {        isValidResult = false;      }    });    // 弹射  this.isValid.emit(isValidResult);  })

这样数据就到了第一层,只发送了一个boolean数据。

三、补全第一层对于第二层传值的解决

在第一层减少对第二层的订阅,也就是在对于第二层组件的援用上减少(isValid)="isValidChange($event)",并实现这个办法。

// 第一层组件的HTML<app-form-item [formItems]="formItems" (isValid)="isValidChange($event)" [type]="'edit'" [formItemValues]="formItemValues"></app-form-item>
// 第一层组件的TS/**   * 当子组件弹射出表单项是否非法时的行为   * @param isValid   */  isValidChange(isValid: boolean) {    console.log(isValid);  }

再次测试,其余层曾经不再有输入语句,因而只会显示True或False。
全副抉择后,将会变成true,其余状况都是false:

还差最初一步,就是让变动的isValid作用到按钮上:

/**   * 当子组件弹射出表单项是否非法时的行为   * @param isValid   */  isValidChange(isValid: boolean) {    // 在变动时取isValid和以后formControl的交加,从而扭转按钮激活状态    this.submitButtonActive = isValid && this.formGroup.valid;  }

终于功败垂成!

四、总结

本文基于Angular动静组件,所以须要有对于动静组件的理解作为前置条件。

核心内容就是组件间传值@Input @Output的应用办法,以及创立动静组件时,如何调用子组件属性的问题,在此整顿一下。

创立动静组件时,父组件只能获取到子组件的援用componentRef,而不能像动态组件间传值那样看到子组件的HTML代码,因而不能用相似[formItems]="formItems" (isValid)="isValidChange($event)"的写法去绑定输入输出。

而是这样用:
对于子组件的@input属性,用这种写法:

// 对应子组件的inputcomponentRef.instance.index = i;  

对于子组件的@output弹射器,用这种订阅的写法:

// 对应子组件的outputcomponentRef.instance.isValid.subscribe(({index, isValid}) => {    console.log(index);    console.log(isValid);  })

把握这些核心内容后,动静组件的校验就和一般动态父子组件传值的难度差不多了,只不过有很多数据通过多层传递能力达到,看上去略显麻烦。