反应式表单 - Select 前的下拉选项,数据来自 API

Reactive Form - Pre-Select Dropdown Option with Data from API

在 Angular 7 中,我有一个用于创建和编辑现有对象的响应式表单。创建效果很好。

编辑时,API 调用 returns 一个用于初始化 FormGroup 的对象。其中一些属性是下拉 selection 的结果。我如何预先 select 使用 FormGroup 数据的下拉列表?

在这个例子中,我模拟了一个我们正在编辑的面板或订单项目,它来自 "Server",这个面板有一个基板 属性。将基材想象成汽车。为了准确知道汽车 (Tundra SR5),您首先必须选择品牌 (Toyota),然后是 trim (SR5)。同样,基板具有两个定义属性,尺寸 (4x8) 和厚度 (0.75)。

期望的效果是,在加载表单时,下拉菜单应该已经具有正确的尺寸、厚度和基材 selected 在下拉菜单中,与 "Server" 面板上的内容相匹配

我创建了一个 StackBlitz,其中包含该功能的精简版。

StackBlitz:https://stackblitz.com/edit/angular-sfusar

component.html

<form [formGroup]="panelForm" *ngIf="!loading">
  <section class="form-block">
    <div class="form-group">
      <label for="select_size">Size</label>
      <div class="select">
        <select id="select_size" name="select_size" formControlName="size" (ngModelChange)="sizeChange($event)"
            required>
          <option *ngFor="let s of sizes" [ngValue]="s">{{ s.width }} x {{ s.height }}</option>
        </select>
      </div>
    </div>

    <div class="form-group">
      <label for="select_thickness">Thickness</label>
      <div class="select">
        <select id="select_thickness" name="select_thickness" formControlName="thickness" (ngModelChange)="thicknessChange($event)"
            required>
          <option *ngFor="let t of thicknesses" [ngValue]="t">{{t.value}}</option>
        </select>
      </div>
    </div>

    <div class="form-group">
      <label for="select_substrate">Material</label>
      <div class="select">
        <select id="select_substrate" name="select_substrate" formControlName="substrate"
            required>
          <option *ngFor="let s of substrates" [ngValue]="s">{{s.name}}</option>
        </select>
      </div>
    </div>
  </section>
</form>

component.ts

import { Component, OnInit } from '@angular/core';

import { OrderItem } from '../classes/order-item';
import { Size } from '../classes/size';
import { Thickness } from '../classes/thickness';
import { SubstrateService } from '../services/substrate.service';
import { OrderItemService } from '../services/order-item.service';
import { SizeService } from '../services/size.service';
import { ThicknessService } from '../services/thickness.service';
import { Substrate } from '../classes/substrate';
import {
  FormGroup,
  FormControl,
  Validators,
  FormArray,
  FormBuilder
} from '@angular/forms';

@Component({
  selector: 'app-order-item-form',
  templateUrl: './order-item-form.component.html',
  styleUrls: ['./order-item-form.component.scss']
})
export class OrderItemFormComponent implements OnInit {        
  panel: OrderItem;
  panelId: number;
  loading: boolean;
  isEdit: boolean;

  sizes = new Array<Size>();
  thicknesses = new Array<Thickness>();
  substrates = new Array<Substrate>();

  selectedSize: Size;
  selectedThickness: Thickness;

  panelForm: FormGroup;

  constructor(
    private substrateService: SubstrateService,
    private sizeService: SizeService,
    private thicknessService: ThicknessService,
    private orderItemService: OrderItemService,
    private fb: FormBuilder
  ) {}

  ngOnInit() {
    this.loading = true;

    // set this to === 1 to invoke the "Edit" panel form and isEdit === true
    this.panelId = 1;

    const sizePromise = this.sizeService.getList().toPromise();
    const thicknessPromise = this.thicknessService.getList().toPromise();

    Promise.all([
      sizePromise,
      thicknessPromise
    ]).then(response => {
        this.sizes = response[0];
        this.thicknesses = response[1];

        this.isEdit = this.panel != null;

        this.orderItemService.getSingle(this.panelId).subscribe(response => {
          this.panel = response;
          this.buildForms();

          console.log(this.panel);
          this.loading = false;
        });        
    });
  }

  buildForms() {
    this.panelForm = new FormGroup({
      size: new FormControl(this.panel.substrate.size, Validators.required),
      thickness: new FormControl(this.panel.substrate.thickness, Validators.required),
      substrate: new FormControl(this.panel.substrate, Validators.required)
    });
  }

  sizeChange(size: Size) {
    if (size != null) {
      this.selectedSize = size;

      if (this.selectedThickness != null) {
        this.getSubstrates();
      }
    } else {
      this.selectedSize = null;
    }
  }

  thicknessChange(thickness: Thickness) {
    if (thickness != null) {
      this.selectedThickness = thickness;

      if (this.selectedSize != null) {
        this.getSubstrates();
      }
    } else {
      this.selectedThickness = null;
    }
  }

  getSubstrates() {
    this.substrateService.filter(this.selectedSize._id, this.selectedThickness._id)
      .toPromise().then(response => {
        this.substrates = response;
      })
      .catch();
  }


  get substrate() {
    return this.panelForm.get('substrate').value as Substrate;
  }

}

服务

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { Thickness } from '../classes/thickness';
import { Size } from '../classes/size';
import { Substrate } from '../classes/substrate';
import { OrderItem } from '../classes/order-item';

@Injectable({
  providedIn: 'root'
})
export class ThicknessService {
    thicknesses: Thickness[] = JSON.parse( `[{ "_id": 1, "value": 0.75 }, 
                                   { "_id": 2, "value": 1 }, 
                                   { "_id": 3, "value": 1.25 }]`);

    public getList(): Observable<Thickness[]> {
      return of(this.thicknesses);
    }

}

@Injectable({
  providedIn: 'root'
})
export class SizeService {
    sizes: Size[] = JSON.parse( `[{ "_id": 1, "height": 8, "width": 4 }, 
                                   { "_id": 2, "height": 10, "width": 5 }, 
                                   { "_id": 3, "height": 12, "width": 5 }]`);

    public getList(): Observable<Size[]> {
      return of(this.sizes);
    }

}

@Injectable({
  providedIn: 'root'
})
export class SubstrateService {
    substrates: Substrate[] = JSON.parse( `[{
        "_id": 1,
        "name": "MDF",
        "size": { "_id": 1, "height": 8, "width": 4 },
        "thickness": { "_id": 1, "value": 0.75 }
    },
    {
        "_id": 2,
        "name": "PB",
        "size": { "_id": 2, "height": 10, "width": 5 },
        "thickness": { "_id": 2, "value": 1 }
    },
    {
        "_id": 3,
        "name": "WB",
        "size": { "_id": 3, "height": 12, "width": 5 },
        "thickness": { "_id": 3, "value": 1.25 }
    }
]`);

    public filter(sizeId: number, thicknessId: number): Observable<Substrate[]> {
      return of(this.substrates.filter(s => s.size._id === sizeId && s.thickness._id === thicknessId));
    }
}

@Injectable({
  providedIn: 'root'
})
export class OrderItemService {
    orderItem: OrderItem = JSON.parse( `{ "substrate": {
        "_id": 2,
        "name": "PB",
        "size": { "_id": 2, "height": 10, "width": 5 },
        "thickness": { "_id": 2, "value": 1 }
    } }`);

    newItem = new OrderItem();

    public getSingle(id: number): Observable<OrderItem> {
      if (id === 1) return of(this.orderItem);
      return of(this.newItem);
    }
}

SubstrateSizeThickness 是 类。这些 类 的实例不同于例如Substrate 在你的 OrderItem 中。因此,当您像现在这样绑定 [ngValue] 并随后在模型中设置表单值时,它们将不匹配。

我建议对您的代码进行以下更改以使其正常工作。该示例仅适用于 Size,但可以应用于所有三种类型:

<option *ngFor="let s of sizes" [ngValue]="s._id">{{ s.width }} x {{ s.height }}</option>
this.panelForm = new FormGroup({
  size: new FormControl(this.panel.substrate.size._id, Validators.required),
});

当然,如果需要,您稍后需要将 _id 转换回对象(通过在 sizes 数组中查找)。