即使使用 ControlValueAccessor,可重用/共享的 Angular 材质自动完成也会返回 null

问题描述 投票:0回答:1

我实现了一个共享/可重用的角度材质自动完成,它使用来自服务器的值作为对象返回。我可以将它集成到其他组件中。但是,如果我使用我的组件:

<app-positions-input [formControl]="form.controls.position"></app-positions-input>

form.control.position
返回
null
。但如果我像这样整合:

<app-positions-input #positionComponent></app-positions-input>

positionComponent.control.value
返回所选值。我无法使这个可重用的自动完成功能与其余的反应形式集成。
positionComponent
基本上是这样的:父级中的
@ViewChild(PositionsComponent) positionComponent: PositionsComponent;
。但这效果不佳,因为我无法验证表单,因为控件不是反应表单的一部分。

import { Component, EventEmitter, OnDestroy, OnInit, Output, forwardRef } from '@angular/core';
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
import { Observable, Subject } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
import { PositionService } from '../position.service';
import { MaterialModule } from '@myapp/client/material';
import { AsyncPipe } from '@angular/common';
import { PositionGetDto as GetDto } from '@myapp/dtos';

@Component({
  selector: 'app-positions-input',
  standalone: true,
  imports: [ReactiveFormsModule, MaterialModule, AsyncPipe],
  templateUrl: './positions.component.html',
  styleUrl: './positions.component.scss',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => PositionsComponent),
      multi: true,
    }
  ]
})

export class PositionsComponent implements OnInit, OnDestroy, ControlValueAccessor {
  @Output() selected = new EventEmitter<GetDto>();

  control = new FormControl<GetDto | undefined>(undefined);
  items: GetDto[] = [];
  filteredItems: Observable<GetDto[]>;

  private ngUnsubscribe = new Subject<void>();

  private onChangeCallback: (_: GetDto | undefined) => void = () => {};
  private onTouchedCallback: () => void = () => {};

  constructor(private service: PositionService) {
    this.filteredItems = this.control.valueChanges
      .pipe(
        // tap((term) => { console.log("Term: %s", term); }),
        startWith(''),
        map(item => (item ? this._filter(item) : this.items.slice()))
      );
  }

  ngOnInit() {
    this.service.getAll().subscribe((items) => {
      this.items = items;
    });
  }

  ngOnDestroy() {
    this.ngUnsubscribe.next();
    this.ngUnsubscribe.complete();
  }
  
  writeValue(item: GetDto | undefined): void {
    this.control.setValue(item);
  }

  registerOnChange(fn: any): void {
    this.onChangeCallback = fn;
  }
  registerOnTouched(fn: any): void {
    this.onTouchedCallback = fn;
  }
  setDisabledState?(isDisabled: boolean): void {
    //this.disabled = isDisabled;
  }

  private _filter(term: any): GetDto[] {
    let filteredItems: GetDto[];

    if (typeof term === 'object' && 'id' in term && 'name' in term) {
      filteredItems = this.items;
      return filteredItems;
    } else if (typeof term === 'string') {
      const lowerCaseTerm = term.toLowerCase();
      filteredItems = this.items.filter((item) =>
        item.name.toLowerCase().includes(lowerCaseTerm)
      );
      return filteredItems;
    }
    return this.items;
  }

  display(item: GetDto): string {
    return item ? item.name : '';
  }

  setValue(item: GetDto | undefined) {
    this.selected.emit(item);
    this.control.setValue(item);
  }
}
<mat-form-field>
  <mat-label>Position: </mat-label>
  <input matInput aria-label="Position" placeholder="Please put a position" [formControl]="control"
    [matAutocomplete]="auto">
  <mat-autocomplete #auto="matAutocomplete" [displayWith]="display">
    @for (item of filteredItems | async; track item) {
    <mat-option [value]="item">{{item.name}}</mat-option>
    }
  </mat-autocomplete>
</mat-form-field>

我想知道我做错了什么?我无法找到合适的可重用/共享自动完成示例。

angular angular-material components reusability
1个回答
0
投票

您实际上需要使用您想要在模型中反映的值来调用您的

onChangesCallback()
。所以你可以添加这样的方法:

  onOptionSelected(option: GetDto) {
    this.onChangeCallback(option);
  }

并像这样从模板中触发它(或任何您想要发出值的适当事件):

<mat-autocomplete (optionSelected)="onOptionSelected($event.option.value)">

这是一个 StackBlitz 示例。


此外,您可以使用新的

takeUntilDestroyed()
运算符,而不是使用自己的“取消订阅主题”。但更简单的是,您可以将
items
保留为可观察值,以避免显式订阅:

  items$: Observable<GetDto[]> = this.service.getAll();

  filteredItems$: Observable<GetDto[]> = this.items$.pipe(
    switchMap(allItems => this.control.valueChanges.pipe(
      startWith(''),
      map(term => (term ? this._filter(allItems, term) : allItems.slice()))
    ))
  );

注意这里,我们可以只声明一个发出项目的可观察量,然后删除

service.getAll()
以开始。
这要简单得多,因为你不需要

订阅

取消订阅(
    也不需要主题来触发
  • 实现 ngOnInit 实现 ngOnDestroy
  • 这是一个简化的
  • StackBlitz
  • 演示;

© www.soinside.com 2019 - 2024. All rights reserved.