问题
So, I have the following service in an Angular 6 codebase:
export class GenericService<T> {
public endpoint: string;
constructor(private http: HttpClient) {}
public create(entity: T): Observable<T> {
return this.http.post<T>(`${environment.apiUrl}/api/${this.endpoint}`, entity);
}
}
As you can see, the observable create(entity: T)
method returns the entity it creates. In the component, it is handled thus:
@Component({
selector: 'app-brand-dialog',
templateUrl: './brand-dialog.component.html',
styleUrls: ['./brand-dialog.component.css']
})
export class BrandDialogComponent implements OnInit {
public addresses$ = Observable<Address[]>;
readonly separatorKeysCodes: number[] = [ENTER, COMMA]
constructor(private service: GenericService<Address>, @Inject(MAT_DIALOG_DATA) private brand: Brand){}
ngOnInit(): void {
this.addresses$ = this.service.getAll();
}
addAdress(address: Address) {
this.service.create(address).subscribe((address) => {
//address handling code would go here
})
}
}
And in the template:
<h2 mat-dialog-title>{{brand.name}}</h2>
<form (submit)="editbrand()">
<mat-dialog-content>
<mat-form-field>
<input matInput placeholder="name" name="name" [(ngModel)]="brand.name" required>
</mat-form-field><br />
<mat-form-field *ngIf="brand.id">
<mat-chip-list #chipList>
<mat-chip *ngFor="let address of addresses$ | async" [selectable]="true" [removable]="true" (removed)="removeAddress(address)">
{{address.name}}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input
matInput
placeholder="New address..."
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
[matChipInputAddOnBlur]="false"
(matChipInputTokenEnd)="addaddress($event)"
>
</mat-chip-list>
</mat-form-field>
</mat-dialog-content>
<div mat-dialog-actions>
<button mat-button (click)="onNoClick()">Cancel</button>
<button mat-button type="submit" cdkFocusInitial>OK</button>
</div>
</form>
As you can see, the Observable I use for the Addresses Angular Material chips is handled by Angular's async pipe, so I don't subscribe to it directly. However, my REST API returns a 201 Created with the newly created entity; I want to add it to addresses$ so that the async pipe catches and adds it to the chips list without having to do the whole request again. How would I go about it?
回答1:
The way I do it is something like this. It's just a POC, but it's very flexible. You can easily handle reloading all items, caching, etc. The idea is that the service handles the store of the items and the service also knows when a new item was added/removed/etc. to update the array. You can also add transaction support so that fake items appear that are not commited yet, undo support, etc.
export class GenericService<T> {
private events$ = new ReplaySubject<Event>();
public readonly items$ = concat(this.loadAllItems(), this.events$)
.pipe(
scan((items, event: Event) => {
switch (event.type) {
case 'loaded':
return event.items;
case 'added':
return [...items, event.item];
}
return items;
}, [] as T[]),
shareReplay(1),
);
public endpoint: string; // where does this come from? you could use an interceptor...
constructor(private http: HttpClient) {
}
public create(entity: T): Observable<T> {
return this.http
.post<T>(`${environment.apiUrl}/api/${this.endpoint}`, entity)
.pipe(
tap(_ => this.events$.next({
type: 'added',
item: entity, // maybe you want the id from the response, or even the entire response...
}))
);
}
private loadAllItems() {
return this.http
.get<T>(`${environment.apiUrl}/api/${this.endpoint}`)
.pipe(
map(items => ({ type: 'loaded', items } as LoadedEvent))
);
}
}
type Event = LoadedEvent | AddedEvent;
interface AddedEvent {
type: 'added';
item: any;
}
interface LoadedEvent {
type: 'loaded';
items: any[];
}
来源:https://stackoverflow.com/questions/52722740/how-do-i-add-an-item-to-an-observableentity-type-from-an-api-response-if-th