You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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内置的Update Action,让框架自动更新缓存和状态。
  • 之后在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 07:55:34