我实现了一个共享/可重用的角度材质自动完成,它使用来自服务器的值作为对象返回。我可以将它集成到其他组件中。但是,如果我使用我的组件:
<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>
我想知道我做错了什么?我无法找到合适的可重用/共享自动完成示例。
您实际上需要使用您想要在模型中反映的值来调用您的
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()
以开始。这要简单得多,因为你不需要
订阅取消订阅(