網站搜尋

javascript - 如何在 Angular 8 中取消選取 select 元素中的已選取 option 元素


我有以下要求:

  • 我有兩個 FormControl 物件,分別用於下拉選單 mainSelect 和 subSelect,它們都是 required 的。
  • subSelect 會根據 mainSelect 的值而改變。
  • 當 mainSelect 改變為一個 subSelect 的值未包含的值時,subSelect 需要變成 invalid,這樣兩個 FormControl 所屬的 FormGroup 也會變成 invalid。
  • 但是,如果來自 subSelect 的值被包含,subSelect 需要保持它的實際值。

(在 StackBlitz 連結之後描述了一個具體例子。)

我解決這個需求的問題:

如果 mainSelect 的值改變,而 subSelect 的值不包含在其中,subSelect 將取列表中的第一個值,而不是變為 null/invalid。

所以,解決方案將是在瀏覽器中,如果 'subSelect' 的選擇值變為 null 且沒有選擇任何值時。

到目前為止我嘗試的:

我試著創建一個元件並實作 ControlValueAccessor 介面。看起來問題可能出在這裡。我想我並不完全理解它是如何運作的。

我在 YouTube 上觀看了以下影片,並閱讀了與 ControlValueAccessor 相關的文章(1, 2),但仍然無法解決我的問題。

這是我程式碼的一部分:

你也可以在 StackBlitz 上找到它

範例

如果在瀏覽器中,MainSelect 的值是 thirdMainSelect,而 SubSelect 的值是 fifthSubSelect,然後 MainSelect 將其值更改為 firstMainSelect,則 SubSelect 應該沒有選中任何值。

select.component.ts

export class SomeObject {
  value: string;
  parameters: {[parameterName: string]: string} = {};
}

@Component({
  selector: "app-select",
  templateUrl: "./select.component.html",
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: SelectComponent,
    multi: true
  }]
})
export class SelectComponent implements ControlValueAccessor, OnChanges {
  @ViewChild("select", {static: true}) select: ElementRef;
  @Input() tableId: string;
  @Input() filter: { [parameterName: string]: string};

  returnedTable: SomeObject[];

  onChange: (_: any) => void;
  onTouched: () => void;

  selected: string;

  constructor(private tableService: TableService) { }

  loadTable(): void {
    this.tableService.getTable(this.tableId, this.filter)
      .subscribe(table => {
        this.returnedTable = table;
      });
  }

  ngOnChanges(): void {
    this.loadTable();
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  writeValue(value: string): void {
    this.selected = value;
  }
}

select.component.html

<select class="form-control" #select (change)="onChange($event.target.value)">
  <option *ngFor="let item of returnedTable" [value]="item.value" [selected]="selected === item.value">{{item.value}}</option>
</select>

app.component.ts

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.scss"]
})
export class AppComponent implements OnInit {

  form: FormGroup;
  containerObject: ContainerObject;

  selectedMainValue: string;

  constructor(private tableService: TableService,
              private formBuilder: FormBuilder) {

  }

  ngOnInit(): void {
    this.tableService.getContainerObject()
      .subscribe(containerObject => {
        this.containerObject = containerObject;
        this.selectedMainValue = containerObject.mainSelect;
        this.initForm();
      });
  }

  private initForm(): void {
    this.form = this.formBuilder.group({
      mainSelect: [this.containerObject.mainSelect, Validators.required],
      subSelect: [this.containerObject.subSelect, Validators.required]
    });
    this.subscribeToMainSelectChanged();
    this.subscribeToSubSelectChanged();
  }

  onSubmit(): void {
    if (this.form.valid) {
      this.containerObject.mainSelect = this.form.get("mainSelect").value;
      this.containerObject.subSelect = this.form.get("subSelect").value;
      this.tableService.saveContainerObject(this.containerObject);
    }
  }

  private subscribeToMainSelectChanged() {
    this.form.get("mainSelect").valueChanges
      .subscribe(mainSelect => {
        this.selectedMainValue = mainSelect;
        console.log(this.form.status);
      });
  }

  private subscribeToSubSelectChanged() {
    this.form.get("subSelect").valueChanges
      .subscribe(() => {
        console.log(this.form.status);
      });
  }
}

app.component.html

<div>
  <form id="wrapper" [formGroup]="form" (ngSubmit)="onSubmit()">
    <div id="left" class="form-group row">
      <label for="mainSelect" class="col-form-label col-sm-2">MainSelect</label>
      <div class="col-sm-6">
        <app-select
          id="mainSelect"
          formControlName="mainSelect"
          [tableId]="'mainSelectTable'"
          [filter]="{firstType: 'firstParameter'}"
        ></app-select>
      </div>
    </div>
    <div id="right" class="form-group row">
      <label for="subSelect" class="col-form-label col-sm-2">SubSelect</label>
      <div class="col-sm-6">
        <app-select
          id="subSelect"
          formControlName="subSelect"
          [tableId]="'firstParameter'"
          [filter]="{firstType: 'firstParameter', secondType: selectedMainValue}"></app-select>
      </div>
    </div>
    <p></p>
    <button id="button" type="submit">Submit</button>
  </form>
</div>

答案#1

我認為它只使用 valuesChange。如果你有兩個陣列 data 和 subdata,以及一個表單像

form = new FormGroup({
    prop1: new FormControl(),
    prop2: new FormControl()
  });

簡單的

this.form.get("prop1").valueChanges.subscribe(res => {
  this.dataService.getData(res).subscribe(data=>{
    this.subdata=data;
    if (!this.subdata.find(x=>x.value==this.form.get("prop2").value))
      this.form.get("prop2").setValue(null);
  }).unsubscribe()
});

應該夠了,看看 stackblitz

答案#2

在嘗試僅使用 SelectComponent 尤其是 writeValue 來解決這個問題一段時間後,以下代碼完成了工作:

我將 select.component.ts 修改如下:

export class SomeObject {
  value: string;
  parameters: {[parameterName: string]: string} = {};
}

@Component({
  selector: "app-select",
  templateUrl: "./select.component.html",
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: SelectComponent,
    multi: true
  }]
})
export class SelectComponent implements ControlValueAccessor, OnChanges {
  @ViewChild("select", {static: true}) select: ElementRef;
  @Input() tableId: string;
  @Input() filter: { [parameterName: string]: string};

  returnedTable: SomeObject[];

  onChange: (_: any) => void;
  onTouched: () => void;

  selected: string;

  constructor(private tableService: TableService) { }

  loadTable(): void {
    this.tableService.getTable(this.tableId, this.filter)
      .subscribe(table => {
        this.returnedTable = table;
        if (!!this.select && !!this.select.nativeElement.value) {
          this.writeValue(this.select.nativeElement.value);
          this.onChange(this.selected);
        }
      });
  }

  ngOnChanges(): void {
    this.loadTable();
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  writeValue(value: string): void {
    if (!!this.returnedTable && !this.returnedTable.some(item => item.value === value)) {
      this.selected = null;
    } else {
      this.selected = value;
    }
  }
}

而 select.component.html 如下所示:

<select class="form-control" #select (change)="onChange($event.target.value)">
  <option hidden *ngIf="!selected" value=""></option>
  <option *ngFor="let item of returnedTable" [value]="item.value" [selected]="selected === item.value">{{item.value}}</option>
</select>