Angular 11 Material - 国家、州和城市的级联自动完成输入

Angular 11 Material - Cascading Autocomplete input for country, state and city

我在使用 Angular 11 和 Angular/Material 的自动完成组件时遇到了一些困难。我需要级联搜索国家、城市和州,但我没有成功。

我找了一个例子,但没找到。

这些字段位于 FormArray 中。它是一个有 3 个块的固定数组。这些地址指的是发票、帐单和送货地址。

下面的代码是我可以开始工作的,仅适用于 Pais。我需要级联到城市和州。如果您 select 国家/地区,它会在城市字段中仅列出 selected 国家/地区的城市...

有人能帮我吗?

<mat-form-field class="full-width">
  <input type="text" placeholder="Pais..." aria-label="Pais" matInput
    formControlName="country" #country [matAutocomplete]="countryAuto" required>
  <button mat-button *ngIf="identificationGroup.get('country').value" matSuffix mat-icon-button
    aria-label="Clear" (click)="clearFieldControl(identificationGroup.get('country'))">
    <mat-icon>close</mat-icon>
  </button>
  <mat-autocomplete #countryAuto="matAutocomplete" [displayWith]="displayCountryFn">
    <mat-option *ngFor="let option of countryList" [value]="option">
      {{ option.name }}
    </mat-option>
  </mat-autocomplete>
  <mat-error *ngFor="let validation of validation_msgs.identificationGroup">
    <div *ngIf="identificationGroup.get('country').hasError(validation.type)">
      {{validation.message}}
    </div>
  </mat-error>
  <mat-error *ngIf="this.identificationGroup.get('country').invalid">
    <span [hidden]="!this.identificationGroup.get('country').errors.required">
      Campo é obrigatório.
    </span>
  </mat-error>
</mat-form-field>
this.filteredCountryOptions[i] = formGroup.controls.country.valueChanges
      .pipe(
        startWith<string | Country>(''),
        map(value => typeof value === 'string' ? value : value.name),
        map(name => name ? this._filterCountry(name) : this.countryList?.slice())
      );
displayCountryFn(country?: Country): string | undefined {
    return country ? country.name : undefined;
  }
private _filterCountry(name: string): Country[] {
    const filterValue = name?.toLowerCase();

    return this.countryList?.filter(option => option?.name?.toLowerCase().indexOf(filterValue) === 0);
  }

谢谢...

我输入的代码仅指一个字段。在本例中,Country 字段正常工作。

我需要的是让 State 和 City 字段正常工作。

我需要在其他字段自动完成“州”中选择国家列表时,列出与国家相关的列表,而在选择州时,列出与该州相关的城市。

我使用 Select 组件拥有此功能。

<mat-expansion-panel class="mat-elevation-z4" [expanded]="true">
            <mat-expansion-panel-header>
              <mat-panel-title>
                <mat-icon fontSet="fa" fontIcon="fa-map-marker" fxLayoutAlign="start center"></mat-icon>
                <span>Endereços</span>
              </mat-panel-title>
            </mat-expansion-panel-header>
            <ng-template>Localização</ng-template>
            <mat-slide-toggle (change)="checkCopyValuesAddress($event)" [color]="'primary'">Considerar
              os
              mesmos dados do endereço de faturamento.</mat-slide-toggle>
            <mat-tab-group mat-stretch-tabs id="main">
              <mat-tab *ngFor="let tab of getFormArray(identificationFormGroup, 'addresses');let i= index"
                [label]="i === 0 ? 'Faturamento' : i === 1 ? 'Cobrança' : 'Entrega'" formArrayName="addresses"
                #addresses>

                <div [formGroupName]="i">
                  <div class="row">
                    <div class="col-sm-4">
                      <mat-form-field class="full-width">
                        <mat-label>Pais...</mat-label>
                        <mat-select [compareWith]="objectComparisonFunction" formControlName="country" #country
                          required>
                          <mat-option>-- Selecione --</mat-option>
                          <mat-option *ngFor="let country of countryList" [value]="country">
                            {{country.name}} - {{country.initialsCode}}
                          </mat-option>
                        </mat-select>
                      </mat-form-field>

                    </div>
                    <div class="col-sm-4">
                      <mat-form-field class="full-width">
                        <mat-label>Estado...</mat-label>
                        <mat-select [compareWith]="objectComparisonFunction" formControlName="state" #state
                          required>
                          <mat-option>-- Selecione --</mat-option>
                          <mat-option *ngFor="let state of stateList" [value]="state">
                            {{state.name}}
                          </mat-option>
                        </mat-select>
                      </mat-form-field>
                    </div>
                    <div class="col-sm-4">
                      <mat-form-field class="full-width">
                        <mat-label>Cidade...</mat-label>
                        <mat-select [compareWith]="objectComparisonFunction" formControlName="city" #city required>
                          <mat-option>-- Selecione --</mat-option>
                          <mat-option *ngFor="let city of cityList" [value]="city">
                            {{city.name}}
                          </mat-option>
                        </mat-select>
                      </mat-form-field>
                    </div>
                    <div class="col-md-9">
                      <mat-form-field class="full-width">
                        <input matInput placeholder="Endereço... " formControlName="address" #address
                          maxlength="100" required>
                        <button mat-button
                          *ngIf="(address.value && i == 0) || ((address.value && i > 0) && !copyValueAddress)"
                          [disabled]="address.disabled" matSuffix mat-icon-button aria-label="Clear"
                          (click)="clearFieldArrayControl(identificationFormGroup, 'addresses', 'address', i)">
                          <mat-icon>close</mat-icon>
                        </button>
                        <mat-hint align="end">
                          {{address.value.length}}/100.
                        </mat-hint>
                      </mat-form-field>
                    </div>
                    <div class="col-sm-3">
                      <mat-form-field class="full-width">
                        <input matInput placeholder="Número..." formControlName="addressNumber" #addressNumber
                          maxlength="7" required>
                        <button mat-button
                          *ngIf="(addressNumber.value && i == 0) || ((addressNumber.value && i > 0) && !copyValueAddress)"
                          [disabled]="addressNumber.disabled" matSuffix mat-icon-button aria-label="Clear"
                          (click)="clearFieldArrayControl(identificationFormGroup, 'addresses', 'addressNumber', i)">
                          <mat-icon>close</mat-icon>
                        </button>
                        <mat-hint align="end">
                          {{this.addressNumber.value.length}}/7.
                        </mat-hint>
                      </mat-form-field>
                    </div>
                    <div class="col-md-3">
                      <mat-form-field class="full-width">
                        <input #cep matInput placeholder="CEP... " formControlName="addressZipCode" #addressZipCode
                          minlength="8" mask="00.000-000" required>
                        <button mat-button
                          *ngIf="(addressZipCode.value && i == 0) || ((addressZipCode.value && i > 0) && !copyValueAddress)"
                          [disabled]="addressZipCode.disabled" matSuffix mat-icon-button aria-label="Clear"
                          (click)="clearFieldArrayControl(identificationFormGroup, 'addresses', 'addressZipCode', i)">
                          <mat-icon>close</mat-icon>
                        </button>
                        <mat-hint align="end">
                          {{this.addressZipCode.value.replace('.', '').replace('-', '').length}}/8.
                        </mat-hint>
                      </mat-form-field>
                    </div>
                    <div class="col-md-6">
                      <mat-form-field class="full-width">
                        <input matInput placeholder="Complemento... " formControlName="addressComplement"
                          #addressComplement maxlength="100">
                        <button mat-button
                          *ngIf="(addressComplement.value && i == 0) || ((addressComplement.value && i > 0) && !copyValueAddress)"
                          [disabled]="addressComplement.disabled" matSuffix mat-icon-button aria-label="Clear"
                          (click)="clearFieldArrayControl(identificationFormGroup, 'addresses', 'addressComplement', i)">
                          <mat-icon>close</mat-icon>
                        </button>
                        <mat-hint align="end">
                          {{this.addressComplement.value.length}}/100.
                        </mat-hint>
                      </mat-form-field>
                    </div>
                    <div class="col-sm-3">
                      <mat-form-field class="full-width">
                        <input matInput placeholder="Bairro..." formControlName="addressNeighborhood"
                          #addressNeighborhood maxlength="25" required>
                        <button mat-button
                          *ngIf="(addressNeighborhood.value && i == 0) || ((addressNeighborhood.value && i > 0) && !copyValueAddress)"
                          [disabled]="addressNeighborhood.disabled" matSuffix mat-icon-button aria-label="Clear"
                          (click)="clearFieldArrayControl(identificationFormGroup, 'addresses', 'addressNeighborhood', i)">
                          <mat-icon>close</mat-icon>
                        </button>
                        <mat-hint align="end">
                          {{this.addressNeighborhood.value.length}}/25.
                        </mat-hint>
                      </mat-form-field>
                    </div>
                    <hr>
                    <div class="col-md-4">
                      <mat-form-field class="full-width">
                        <input matInput placeholder="Contato... " formControlName="contactName" #contactName
                          maxlength="100" required>
                        <button mat-button
                          *ngIf="(contactName.value && i == 0) || ((contactName.value && i > 0) && !copyValueAddress)"
                          [disabled]="contactName.disabled" matSuffix mat-icon-button aria-label="Clear"
                          (click)="clearFieldArrayControl(identificationFormGroup, 'addresses', 'contactName', i)">
                          <mat-icon>close</mat-icon>
                        </button>
                        <mat-hint align="end">
                          {{contactName.value.length}}/100.
                        </mat-hint>
                      </mat-form-field>
                    </div>
                    <div class="col-md-4">
                      <mat-form-field class="full-width">
                        <input matInput placeholder="E-Mail... " formControlName="contactEmail" #contactEmail
                          maxlength="100" required>
                        <button mat-button
                          *ngIf="(contactEmail.value && i == 0) || ((contactEmail.value && i > 0) && !copyValueAddress)"
                          [disabled]="contactEmail.disabled" matSuffix mat-icon-button aria-label="Clear"
                          (click)="clearFieldArrayControl(identificationFormGroup, 'addresses', 'contactEmail', i)">
                          <mat-icon>close</mat-icon>
                        </button>
                        <mat-hint align="end">
                          {{contactEmail.value.length}}/100.
                        </mat-hint>
                        <!-- input field error -->
                        <mat-error
                          *ngIf="identificationFormGroup.get('addresses')['controls'][i].value?.contactEmail?.invalid">
                          <span
                            [hidden]="!identificationFormGroup.get('addresses')['controls'][i].value?.contactEmail?.errors?.email">
                            Formato inválido para o campo.
                          </span>
                        </mat-error>
                      </mat-form-field>
                    </div>
                    <div class="col-md-4">
                      <mat-form-field class="full-width">
                        <input matInput placeholder="Telefone... " formControlName="contactPhone" #contactPhone
                          maxlength="100" [mask]="phoneMask" required>
                        <button mat-button
                          *ngIf="(contactPhone.value && i == 0) || ((contactPhone.value && i > 0) && !copyValueAddress)"
                          [disabled]="contactPhone.disabled" matSuffix mat-icon-button aria-label="Clear"
                          (click)="clearFieldArrayControl(identificationFormGroup, 'addresses', 'contactPhone', i)">
                          <mat-icon>close</mat-icon>
                        </button>
                        <mat-hint align="end">
                          {{contactPhone.value.length}}/100.
                        </mat-hint>
                      </mat-form-field>
                    </div>
                    <div class="col-md-12">
                      <mat-form-field class="full-width">
                        <textarea matInput placeholder="Observação..." rows="6" formControlName="observation"
                          #observation maxlength="500"></textarea>
                        <button mat-button
                          *ngIf="(observation.value && i == 0) || ((observation.value && i > 0) && !copyValueAddress)"
                          [disabled]="observation.disabled" matSuffix mat-icon-button aria-label="Clear"
                          (click)="clearFieldArrayControl(identificationFormGroup, 'addresses', 'observation', i)">
                          <mat-icon>close</mat-icon>
                        </button>
                        <mat-hint align="end">
                          {{observation.value.length}}/500.
                        </mat-hint>

                      </mat-form-field>
                    </div>
                  </div>
                </div>
              </mat-tab>
            </mat-tab-group>

          </mat-expansion-panel>

这是我当前使用的 valueChange 所在的函数。

createAddress(i: number, data: CustomerAddressData = {
id: null,
companyId: null,
addressType: null,
city: null,
state: null,
country: null,
address: null,
addressNeighborhood: null,
addressZipCode: null,
addressNumber: null,
addressComplement: null,
contactName: null,
contactEmail: null,
contactPhone: null,
observation: null
}): FormGroup {
const formGroup = this.formBuilder.group({
  id: [data.id === null ? 0 : data.id],
  companyId: [data.companyId === null ? 0 : data.companyId],
  addressType: [data.addressType === null ? i : 0],
  city: [data.city, [Validators.required]],
  state: [data.state, [Validators.required]],
  country: [data.country, [Validators.required]],
  address: [data.address, [Validators.required, Validators.maxLength(100)]],
  addressNeighborhood: [data.addressNeighborhood, [Validators.required, Validators.maxLength(25)]],
  addressZipCode: [data.addressZipCode, [Validators.required, Validators.minLength(8), Validators.minLength(8)]],
  addressNumber: [data.addressNumber, [Validators.required, Validators.maxLength(7)]],
  addressComplement: [data.addressComplement, [Validators.maxLength(100)]],
  contactName: [data.contactName, [Validators.required, Validators.maxLength(100)]],
  contactEmail: [data.contactEmail, [Validators.required, Validators.maxLength(100), Validators.email]],
  contactPhone: [data.contactPhone, [Validators.required, Validators.maxLength(100)]],
  observation: [data.observation, [Validators.maxLength(500)]],
});

formGroup.controls.addressZipCode.valueChanges
  .pipe(
    map(x => x !== undefined ? x : x !== null ? x : 0),
    map(x => x !== null ? x : 0)
  ).subscribe();

formGroup.controls.addressZipCode.statusChanges
  .pipe(
    distinctUntilChanged(),
    switchMap(status => status === 'VALID' ?
      this.findZipCodeService.findZipCode(formGroup.controls.addressZipCode.value)
      : empty()
    ),
    catchError(err => throwError(err))
  ).subscribe(resp => {
    if ('erro' in resp) {
      this.toastAlertService.showError(
        `CEP ${formGroup.controls.addressZipCode.value} informado não encontrado!`, 'Consulta de CEP.'
      );
    }
    else {
      this.auxiliaryDataService.getStates()
        .subscribe(stateResult => {
          if (resp['localidade'] !== undefined) {
            const state = stateResult['data']?.filter(f => f.initialsCode === resp['uf']).map(m => m);

            this.auxiliaryDataService.getCityByFilter(state ? state[0].id : 0, null, null, null, true)
              .subscribe(s => {
                const city = s['data']?.filter(f => f.ibgeCode.toString() === resp['ibge'].toString()).map(m => m);
                const country = this.countryList.filter(f => f.initialsCode === 'BR').map(m => m);

                formGroup.patchValue({
                  state: state[0],
                  city: city[0],
                  country: country[0],
                  address: resp['logradouro'],
                  addressNeighborhood: resp['bairro']
                });
              });
          }
        });
    }
  });

formGroup.controls.state.valueChanges
  .pipe(
    switchMap((value: number) => {
      this.auxiliaryDataService.getCityByFilter(value ? value['id'] : 0, null, null, null, true)
        .subscribe(s => {
          this.cityList = s['data'];
        }, (error) => {
          console.log(error);
          observableOf(null);
        });
      return of('');
    }),
  ).subscribe();

formGroup.controls.country.valueChanges
  .subscribe(value => {
    this.phoneMask = '(00) 0000-0000 || (00) 00000-0000';
      if ((value ? value['initialsCode'] : 'BR') !== 'BR') {
        this.phoneMask = '';
      }

    this.auxiliaryDataService.getStateByFilter(value ? value['id'] : 0, null, null, null, true)
      .subscribe(s => {
        this.stateList = s['data'];
      }, (error) => {
        console.log(error);
        observableOf(null);
      });
    return of('');
  });

formGroup.controls.id.setValue(data.id === null ? 0 : data.id);
formGroup.controls.companyId.setValue(data.companyId === null ? 0 : data.companyId);
formGroup.controls.addressType.setValue(data.addressType === null ? i : 0);
formGroup.controls.city.setValue(data.city);
formGroup.controls.state.setValue(data.state);
formGroup.controls.country.setValue(data.country);
formGroup.controls.address.setValue(data.address);
formGroup.controls.addressNeighborhood.setValue(data.addressNeighborhood);
formGroup.controls.addressZipCode.setValue(data.addressZipCode);
formGroup.controls.addressNumber.setValue(data.addressNumber);
formGroup.controls.addressComplement.setValue(data.addressComplement);
formGroup.controls.contactName.setValue(data.contactName);
formGroup.controls.contactEmail.setValue(data.contactEmail);
formGroup.controls.contactPhone.setValue(data.contactPhone);
formGroup.controls.observation.setValue(data.observation);

return formGroup;
}

我需要将 Select 组件更改为自动完成。我无法在 3 个自动完成组件之间级联。