NGRX Data自定义PUT请求实现合理性及内存泄漏等问题咨询
NgRx Data 自定义PUT请求实现答疑(电影添加标签场景)
自定义MovieDataService代码
export class MovieDataService extends DefaultDataService<typeof Movie> { constructor(http: HttpClient, httpUrlGenerator: MovieHttpUrlGenerator, logger: Logger) { super('Movie', http, httpUrlGenerator); logger.log('Created custom Movie EntityDataService'); } // CUSTOM METHODS APART FROM AUTO-CRETED BY COLLECTION SERVICE // Further handled in collection service addTagToMovie(movieId: number, tagId: number) { return this.execute( 'PUT', `https://external-api.com/movie/${movieId}/add_tag/${tagId}/`, ) } }
CollectionService代码
constructor( EntityCollectionServiceFactory: EntityCollectionServiceFactory, private movieDataService: movieDataService, ) { this.movieCollectionService = EntityCollectionServiceFactory.create<typeof Movie>('Movie'); } getMovies() { this.movieCollectionService.getAll(); } addTagToMovie(movieId: number, tagId: number) { if (movieId && tagId) { this.movieCollectionService.setLoaded(false) this.movieCollectionService.setLoading(true) this.movieDataService.addTagToMovie(movieId, tagId).pipe( tap((updatedMovie: typeof Movie) => { this.movieCollectionService.updateOneInCache(updatedMovie) this.movieCollectionService.setLoading(false) this.movieCollectionService.setLoaded(true) }) ).subscribe() } }
问题解答
1. 当前实现方式是否恰当?
整体思路没问题,但有几个可优化的细节:
MovieDataService的execute调用建议加上泛型声明:this.execute<Movie>(...),明确返回值类型,避免后续类型推断混乱。- 手动维护
loading/loaded状态时,要补上请求失败的处理逻辑——现在只处理了成功场景,失败时loading会一直处于true状态,导致UI一直显示加载中。 - 用
updateOneInCache更新缓存时,要确保返回的updatedMovie包含实体主键(比如id),否则NgRx Data无法定位到要更新的缓存条目。
2. 该subscribe是否会导致内存泄漏?
会。直接调用.subscribe()且未主动取消订阅的话,若组件销毁时请求仍在进行,订阅会一直驻留内存,引发泄漏。
解决方式:
- 若在组件中调用该方法,可保存订阅对象,在
ngOnDestroy钩子中调用unsubscribe():// 组件内定义 private addTagSub?: Subscription; // 调用方法时 this.addTagSub = this.collectionService.addTagToMovie(1, 2).subscribe(); // 组件销毁时 ngOnDestroy() { this.addTagSub?.unsubscribe(); } - 或者用
takeUntil操作符结合销毁信号自动取消订阅:// 组件内定义 private destroy$ = new Subject<void>(); // 调用方法时 this.collectionService.addTagToMovie(1, 2).pipe(takeUntil(this.destroy$)).subscribe(); // 组件销毁时 ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); }
3. 能否像getAll一样无需订阅即可触发?
可以,两种实现思路:
方式一:整合到NgRx Data的Action数据流
- 定义自定义
EntityAction(比如AddTagToMovie)。 - 创建Effect监听该Action,调用
MovieDataService的addTagToMovie方法,完成后触发NgRx Data内置的UpdateAction,让框架自动更新缓存和状态。 - 之后在CollectionService里只需dispatch这个自定义Action,就像调用
getAll一样无需手动订阅,由NgRx数据流自动处理。
方式二:让方法返回Observable,由调用方处理订阅
修改CollectionService的方法,不在内部订阅,而是返回Observable:
addTagToMovie(movieId: number, tagId: number): Observable<Movie> { if (!movieId || !tagId) return EMPTY; this.movieCollectionService.setLoaded(false); this.movieCollectionService.setLoading(true); return this.movieDataService.addTagToMovie(movieId, tagId).pipe( tap((updatedMovie) => { this.movieCollectionService.updateOneInCache(updatedMovie); this.movieCollectionService.setLoading(false); this.movieCollectionService.setLoaded(true); }), catchError((err) => { this.movieCollectionService.setLoading(false); return throwError(() => err); }) ); }
这样组件调用时可以用async管道自动处理订阅,和getAll的用法完全一致。
内容的提问来源于stack exchange,提问作者grx00
相关产品推荐
相关产品推荐

