3

本中要写到一个页面需要进行级联查询
图片.png
图片.png
当前建筑是属于服务区下的,所以我们选择了服务区才能继续根据所选择的服务区,也就是说如果我们选择完 服务区一,再选择建筑一,如果我们此时更换选择的服务区里所应当应该去除所选择的建筑。
按照之前的逻辑写的话只要在传入新的服务区后获取到对应的建筑从而更改选择列表项,所选内容就会自动重置为空,如下所示:

 @Input()
  set serviceAreaId(serviceId: number) {
    this.buildingId.setValue(null);
    if (Number.isInteger(serviceId)) {
      this.listOfOption = [];
      this.serviceAreaService.getAllBuildingByServiceAreaId(serviceId)
        .subscribe(buildings => {
          this.buildings = buildings;
          buildings.forEach(building => {
            this.listOfOption.push(
              {
                label: building.name,
                value: building.id
              }
            )
          })
        })
    } else {
      this.listOfOption = [];
    }
  }

但是这在<thy-custom-select>中并不适用,也就是说就算列表已经更新但是并不会重置或是删除所选项目。这很明显不符合我们的需求,所以首先就是要找官方文档中有没有给出调用清空所选项的API。
thy-custom-select官方网站
但是很可惜,并没有给出清空选项的API,那么就只能找其他办法。
起初认为是子组件没有及时重新渲染导致的,后来根据这些关键词搜索,并尝试在其获取完数据后对其进行重新渲染,但是并没有作用。


constructor(private serviceAreaService: BuildingService,
              private cdr: ChangeDetectorRef) {
  }

 this.serviceAreaService.getAllBuildingByServiceAreaId(serviceId)
        .subscribe(buildings => {
          this.buildings = buildings;
          buildings.forEach(building => {
            this.listOfOption.push(
              {
                label: building.name,
                value: building.id
              }
            )
          })
          //数据绑定变更的时候用ChangeDetectorRef驱动angular更新视图
          //当输入已更改或视图中发生了事件时,组件通常会标记为脏的(需要重新渲染)。调用此方法会确保即使那些触发器没有被触发,也仍然检查该组件
          this.cdr.markForCheck();
          //从该组件到各个子组件执行一次变化检测 检查该视图及其子视图
          this.cdr.detectChanges();
        })

之后尝试改为angular原生的选择组件,虽然可以实现想要的效果,但是样式与<thy-custom-select>有差别,风格不统一

<select [formControl]="buildingId" class="form-control">
  <option [ngValue]="null">请选择</option>
  <option *ngFor="let building of buildings" [ngValue]="building.id">
    {{building.name}}
  </option>
</select>

图片.png
后来又想到了一种更加简便直接的方法,子组件是根据*ngif来判断是否调用的,只要在在服务区选择改变时取消其显示(即销毁目前子组件)并且在很短的延迟后重新让其显示(即根据服务区新建子组件)便可以达到我们想要的效果;

ngOninit(): void {
 .   .   .   
this.queryForm.get(this.formKeys.serviceArea)?.valueChanges
      .subscribe(
        ()=> {
          this.resetChildForm();
        }
      )
} 

 resetChildForm(){
    this.showBuildingSelect= false;

    setTimeout(() => {
      this.showBuildingSelect = true
    }, 10);
  }
<div class="ml-10" *ngIf="queryForm.get(formKeys.serviceArea)?.value && showBuildingSelect">
              <app-building-select [serviceAreaId]="queryForm.get(formKeys.serviceArea)?.value" [formControlName]="formKeys.buildingId"></app-building-select>
            </div>

但是这种写法的缺点就是可读性不高,并且集成度不高即调用完选择组件还需要这些来配合使用,但是考虑到使用这种查询方式的地方并不多,这样解决也可以接受。

第二个问题

使用场景=》建筑新建
图片.png
如图所示我们主要需要解决的问题就是如何解决分部工程的传值,首先是如何完成选择框选择情况的传递。

我们此处是直接获得所有的分部工程模板然后让用户进行选择,也就是说条数不是固定的,那么这也就不能用我们所熟悉的form表单传值。就算解决了这方面的问题还需要考虑其附属的多结构的选择情况能否传递过来。

对于选择框的处理参考了之前项目的解决方法:每个选择框都是一个组件,全选框是另一个组件,通过在前台实体中加入 ”是否被选中“ 属性来实现在提交时提交被选中的分部工程。
单选:
父组件:

<app-check-single [checked]="divisionalWorksTemplate._checked"
  (beChange)="onSingleChange($event,divisionalWorksTemplate)">
</app-check-single>
  onSingleChange($event: boolean, divisionalWorksTemplate: DivisionalWorksTemplate) {
    divisionalWorksTemplate._checked = $event;
    //用于通知全选组件选项的变化
    this.singleCheckboxChangeSubject.next();
  }

子组件:

  @Input()
  set checked(checked: boolean) {
    this.formControl.setValue(checked);
  }

  @Output()
  beChange = new EventEmitter<boolean>();

  formControl = new FormControl(false);

  constructor() {
  }

  ngOnInit(): void {
    this.formControl.valueChanges
      .subscribe(value => {
        this.beChange.emit(value);
      });
  }

明白各个参数的功能后整个组件就很好理解了,当我们点击选择时子组件检测到formcontrol的变化就会向父组件弹射表单的Value父组件接收到弹射就会改变列表中分部工程的_checked字段为弹射值,从而使父组件得知该项已被选中/取消选中。

并且通知全选组件来及时更改自身的状态即如果我们选中了所有选项全选应该自动被选中,否则全选应不被选中。
而对于全选组件则是和单选原理相同,不同的是全选组件传入了所有的分部工程而不是一个,从而实现对所有单选框状态的控制。此处就不再展开说明,并且原项目中后来将这些组件改为了指令,待了解指令后再仔细说明。

之后我们要解决的问题就是如何做到多结构的选择,很明显这里也不能采用formcomtrol进行绑定,所以仍是采取了单选组件的处理方法——在前台实体中加入以下类型来获取选择情况。

 /**
   * 所选择的分部工程类型
   */
  _checkedDivisionalWorksType: DivisionalWorksType[]

处理方式同单选组件,当选项值改变时弹出值,将所选选项加入到_checkedDivisionalWorksType中。

之后值得说的就是提交时的处理:
此处需要解决的问题是从上面的图片中我们也可以看到有些分部工程没有多结构,他们实际上固定有一个多结构,结构为默认。但是我们在选择时忽略了他们,这也就导致如果完全按照_checkedDivisionalWorksType来处理的话很明显是不合理的,所以要对筛选出来的被选择的分部工程进行进一步的筛选从而使其正常处理这些无多结构的结构工程并且当我们没有对存在多结构的结构工程选择但是却选择了此结构工程的情况作出处理。

onSubmit(formGroup: FormGroup, divisionalWorksTemplates: DivisionalWorksTemplate[]) {
    //是否允许被提交,默认为true
    let permit = true;
    //所要提交的分部工程列表
    const checkedDivisionalWorksTemplates = [] as DivisionalWorksTemplate[];
    divisionalWorksTemplates.forEach((value) => {
      if(value._checked) {
        let checkedDivisionalWorksTemplate = {} as DivisionalWorksTemplate;
        checkedDivisionalWorksTemplate.id = value.id;

        if(value._checkedDivisionalWorksType && value._checkedDivisionalWorksType?.length !=0) {
           //当选择项不为空时将选择项赋给我们要提交的结构工程
          checkedDivisionalWorksTemplate.divisionalWorksType = value._checkedDivisionalWorksType;
        } else if(value.divisionalWorksType[0].structure === STRUCTURE.default.value && value.divisionalWorksType?.length === 1){
            //当选择项目为空但是其选择总项目仅有一条并且为默认项时将总项赋给我们要提交的结构工程
          checkedDivisionalWorksTemplate.divisionalWorksType = value.divisionalWorksType;
        } else {
        //其余情况则为选择项目为空但是总项有多个,弹出错误指导用户为其配置具体结构
          this.notifyService.error('新增失败', "请为分部工程" + value.name +"选择具体结构");
          //不允许提交
          permit = false;
        }
        //将检查合格项加入所要提交的分部工程列表中
        checkedDivisionalWorksTemplates.push(checkedDivisionalWorksTemplate);
      }
    })
    if(permit) {
      const newBuilding = {} as Building;
      const serviceArea = {
        id: formGroup.get(this.formKeys.serviceArea)?.value
      } as ServiceArea
      newBuilding.name = formGroup.get(this.formKeys.name)?.value;
      newBuilding.serviceArea = serviceArea;
      newBuilding.divisionalWorksTemplate = checkedDivisionalWorksTemplates;
      this.buildingService.save(newBuilding)
        .subscribe((value) => {
          this.notifyService.success("新增成功");
          this.isFinish.emit(value);
        }, (error) => {
          this.notifyService.error("新增失败", error);
        })
    }

  }

这些方法主要的思想就是通过前台实体本身来进行传值从而使得我们可以进行如上这种形式的输入,并且不需要form绑定。

但是上面的做法虽然可以达成想要效果但是由于没有和formcontrol进行绑定,所以只能在提交时检查是否符合提交条件,若不符合则给出相应提示,而我们想要的是和其他信息输入时一样,在提交前就可以得知所提交信息是否合格并给出提示并且提交按钮为disable。

等项目基本完成后仍需进行改动。


李明
441 声望18 粉丝