RxJS可复用操作符:Angular路由Resolver通用逻辑复用实现求助
看起来你想把多个Angular路由Resolver里的重复逻辑抽离出来,用RxJS实现复用,这个需求很常见,我来给你几个实用的方案,帮你彻底解决重复代码的问题。
首先先拆解你示例里的重复逻辑:
- 启动/结束
ngProgress进度条 - 从
ActivatedRouteSnapshot获取slug参数 - 调用对应服务获取数据
- 错误处理(比如导航到404)
下面分两种方式实现复用,你可以根据场景选择:
方案一:用RxJS自定义操作符封装进度条逻辑
如果只是进度条的启动/结束逻辑重复,可以单独封装成可复用的RxJS操作符,这样不仅能在Resolver里用,其他地方的Observable也能复用。
首先创建操作符:
// src/app/shared/operators/progress.operator.ts import { OperatorFunction, finalize } from 'rxjs'; import { NgProgress } from 'ngx-progressbar'; export function withProgress(ngProgress: NgProgress): OperatorFunction<any, any> { return (source$) => { // 订阅前启动进度条 ngProgress.start(); // 无论成功还是失败,最终都结束进度条 return source$.pipe(finalize(() => ngProgress.complete())); }; }
再封装一个参数提取的工具函数(处理参数不存在的情况):
// src/app/shared/utils/route-utils.ts import { ActivatedRouteSnapshot } from '@angular/router'; export function getRequiredParam(route: ActivatedRouteSnapshot, paramName: string): string { const param = route.paramMap.get(paramName); if (!param) { throw new Error(`Route parameter "${paramName}" is required`); } return param; }
现在你的Resolver可以简化成这样:
// fetch-abc.resolver.ts import { Injectable } from '@angular/core'; import { Resolve, ActivatedRouteSnapshot, Router } from '@angular/router'; import { NgProgress } from 'ngx-progressbar'; import { catchError, EMPTY } from 'rxjs'; import { withProgress } from '../shared/operators/progress.operator'; import { getRequiredParam } from '../shared/utils/route-utils'; import { AbcService } from './abc.service'; @Injectable() export class FetchAbcResolver implements Resolve<any> { constructor( private router: Router, private ngProgress: NgProgress, private abcService: AbcService ) { } resolve = (route: ActivatedRouteSnapshot) => { const slug = getRequiredParam(route, 'slug'); return this.abcService.getAbc(slug).pipe( withProgress(this.ngProgress), catchError(() => { // 请求失败时导航到404 this.router.navigate(['/404']); return EMPTY; }) ); } }
方案二:用高阶函数封装整个Resolver逻辑
如果想把**所有重复逻辑(参数提取、进度条、错误处理)**都封装起来,高阶函数是更彻底的方案,每个Resolver只需要传入差异化的部分(比如数据获取方法)。
创建一个Resolver工厂函数:
// src/app/shared/factories/resolver.factory.ts import { ResolveFn } from '@angular/router'; import { ActivatedRouteSnapshot, Router } from '@angular/router'; import { NgProgress } from 'ngx-progressbar'; import { catchError, finalize, Observable } from 'rxjs'; import { EMPTY } from 'rxjs'; // 定义数据获取函数的类型 type DataFetcher = (paramValue: string) => Observable<any>; export function createParamResolver( paramName: string, ngProgress: NgProgress, router: Router, dataFetcher: DataFetcher ): ResolveFn<any> { return (route: ActivatedRouteSnapshot) => { // 启动进度条 ngProgress.start(); // 获取参数 const paramValue = route.paramMap.get(paramName); if (!paramValue) { // 参数不存在时导航到404并结束进度条 router.navigate(['/404']); ngProgress.complete(); return EMPTY; } // 调用数据获取函数,处理进度条和错误 return dataFetcher(paramValue).pipe( finalize(() => ngProgress.complete()), catchError(() => { router.navigate(['/404']); return EMPTY; }) ); }; }
现在你的Resolver会变得非常简洁,只需要传入对应的参数名和数据获取方法:
// fetch-abc.resolver.ts import { Injectable } from '@angular/core'; import { Resolve } from '@angular/router'; import { NgProgress } from 'ngx-progressbar'; import { Router } from '@angular/router'; import { createParamResolver } from '../shared/factories/resolver.factory'; import { AbcService } from './abc.service'; @Injectable() export class FetchAbcResolver implements Resolve<any> { constructor( private router: Router, private ngProgress: NgProgress, private abcService: AbcService ) { } // 直接复用工厂函数生成的resolve逻辑 resolve = createParamResolver( 'slug', this.ngProgress, this.router, (slug) => this.abcService.getAbc(slug) ); }
方案对比
- 自定义操作符:更灵活,适合只复用某一段Observable管道逻辑(比如进度条),可以在项目其他Observable场景复用。
- 高阶工厂函数:彻底封装Resolver的所有重复逻辑,Resolver代码量极少,适合多个Resolver逻辑高度一致的场景。
你可以根据自己的项目情况选择,两种方式都能很好地解决你的复用问题。
内容的提问来源于stack exchange,提问作者Azriz
相关产品推荐
相关产品推荐

