CRUD操作后如何更新Async Pipe的数据/状态?求优化方案
问题
我有一个从API获取用户数据的Observable users$,还有一个在CRUD操作时通过scan操作符封装状态的Observable usersWithAction$。目前用merge操作符实现的方案能正常运行,但感觉不够优雅,想找更合理的实现方式,恳请提供建议。
现有代码
data.service.ts
interface User { id: number; name: string; phone: string; } type ActionDelete = { action: 'delete'; id: number }; type ActionAdd = { action: 'add'; user: User }; @Injectable({ providedIn: 'root' }) export class DataService { constructor(private http: HttpClient) {} private actionSubject = new BehaviorSubject<'' | ActionAdd | ActionDelete>( '' ); action$ = this.actionSubject.asObservable(); users$ = this.http .get<User[]>('https://jsonplaceholder.typicode.com/users') .pipe(shareReplay(1)); usersWithAction$ = merge(this.action$, this.users$).pipe( scan((acc, value) => { if (Array.isArray(value)) return [...value]; else { if (value && value.action === 'delete') { return acc.filter((user) => user.id !== value.id); } if (value && value.action === 'add') { return [...acc, value.user]; } } }, []), tap((data) => console.log(data, 'data')), shareReplay(1) ); deleteUser(id: number) { // 执行相关API请求 this.actionSubject.next({ action: 'delete', id: id }); } addUser() { // 执行相关API请求 this.actionSubject.next({ action: 'add', user: { id: 123, name: 'John Doe', phone: '234234' }, }); } }
app.component.ts
export class AppComponent { name = 'Angular ' + VERSION.major; constructor(private dataService: DataService) {} users$ = this.dataService.usersWithAction$; deleteUser(id: number) { this.dataService.deleteUser(id); } addUser() { this.dataService.addUser() } }
app.component.html
<div *ngFor="let user of users$ | async" class="user-container"> <p>{{ user.name }}</p> <p>{{ user.phone }}</p> <button type="button" (click)="deleteUser(user.id)">Delete</button> </div> <button type="button" (click)="addUser()">Add</button>
优化建议
1. 统一Action类型,消除无效初始值
原代码中actionSubject的初始值是空字符串,属于无效Action,建议统一Action类型并使用合法的初始标识,让类型定义更严谨:
type Action = | { action: 'init' } | { action: 'add'; user: User } | { action: 'delete'; id: number }; private actionSubject = new BehaviorSubject<Action>({ action: 'init' });
2. 改用concat保证初始数据优先加载
merge会让多个Observable的事件并行触发,存在Action先于初始用户数据发出的风险(比如API请求延迟时),改用concat可以确保先获取初始用户数据,再处理后续操作:
usersWithAction$ = concat(this.users$, this.action$).pipe( scan((acc, value) => { if (Array.isArray(value)) { return [...value]; } else { switch(value.action) { case 'add': return [...acc, value.user]; case 'delete': return acc.filter(user => user.id !== value.id); default: return acc; } } }, []), shareReplay(1) );
3. 拆分状态处理逻辑,提升可读性
把每个Action对应的状态更新逻辑抽成独立函数,让scan内部逻辑更简洁、易维护:
private handleAdd(acc: User[], user: User): User[] { return [...acc, user]; } private handleDelete(acc: User[], id: number): User[] { return acc.filter(user => user.id !== id); } usersWithAction$ = concat(this.users$, this.action$).pipe( scan((acc, value) => { if (Array.isArray(value)) return [...value]; switch(value.action) { case 'add': return this.handleAdd(acc, value.user); case 'delete': return this.handleDelete(acc, value.id); default: return acc; } }, []), shareReplay(1) );
4. 直接将初始数据作为scan的初始状态(可选)
如果能确保初始用户数据可提前获取,也可以将users$的结果作为scan的初始值,配合switchMap实现更直观的状态流转:
usersWithAction$ = this.users$.pipe( switchMap(initialUsers => this.action$.pipe( scan((acc, action) => { switch(action.action) { case 'add': return [...acc, action.user]; case 'delete': return acc.filter(user => user.id !== action.id); default: return acc; } }, initialUsers) )), shareReplay(1) );
内容的提问来源于stack exchange,提问作者Noob
相关产品推荐
相关产品推荐

