在Angular应用程序中向Rxjs动态添加运算符 加载程序服务: MyFruitService MyAppleFruitComponent 更新1:

如何解决在Angular应用程序中向Rxjs动态添加运算符 加载程序服务: MyFruitService MyAppleFruitComponent 更新1:

在Angular应用程序中,我们有一个加载器组件,该组件通过输入setter属性接收可观察对象。

在设置器内部,我们首先将一个布尔isLoading设置为true,该模板内部开始显示加载微调器。 然后我们订阅可观察对象,然后在接收到数据时,将isLoading布尔值再次设置为false,使微调框消失:

// loading-component template:

<ng-container *ngIf="data; else errorOrLoading">
  ...
</ng-container>

<ng-template #errorOrLoading>
 ...
</ng-template>

// loading-component class (short version without unsubscribe):

  @Input() set data$(data$: Observable<any>) {
    if (data$) {
      this.data = null;
      data$
        .subscribe(data => {
          this.data = data;
        });
    }
  }

如果我们只有来自Observable的一个事件,那么这很好用。 但是,当Observable发出多个事件时,由于不会调用setter,因此isLoading不会再次设置为true。

有没有一种方法可以动态添加一个额外的tap运算符,从而允许在给定的Observable链开始发出新事件之前设置this.data = null?

例如,如果Observable是:

myService.onChanges$.pipe(
  switchMap(() => this.getBackendData())
);

我们可以动态添加一个tap操作符来将管道更改为:

myService.onChanges$.pipe(
  tap(() => this.data = null),switchMap(_ => this.getBackendData())
);

更新:我选择简化加载程序控制,并将所有可观察到的相关逻辑移至服务,这感觉方式更具可伸缩性和灵活性。

解决方法

更新#1

解决方案#1。使用shareReplay()

根据@Reactgular answer

您所要做的就是使用shareReplay()运算符:

class MyService {
    public data$: Observable<any>;
    public loaded$: Observable<boolean>;

    constructor(private dataService: DataService) {
        this.data$ = this.dataService.loadData().pipe(
            startWith(null),// data$ emit a null before data is ready followed by the API data.
            shareReplay(1);
        );
        this.loaded$ = this.data$.pipe(
           mapTo(true),startWith(false)
        );
    }
}

您必须调用myService.data $ .subscribe()来触发流的首次读取以使数据准备就绪。您可以在构造函数中执行此操作,但请记住,Angular在首次使用之前不会创建服务。如果您希望数据被紧急加载,请在路由中使用解析器,或将服务注入NgModule构造函数中并在其中进行订阅。


解决方案#2。使用专用服务

更好的解决方案是引入一个LoaderService来处理组件/视图数据的加载。

取决于您的项目需求,它可以是singletonshared

让我们假设我们的服务将仅处理当前视图(共享服务)的加载状态

加载程序服务:

export class LoaderService {
  private  readonly  _loading: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false)

  // returns value of `_loading` on the moment of accessing `isLoading`
  get isLoading(): boolean {
    return this._loading.getValue();
  }
  
  // returns value of `_loading` as Observable
  get isLoading$(): Observable<boolean> {
    return this._loading.asObservable();
  }
  
  // pushes `true` as a new value of `_loading` subject and notifies subscribers
  start(): void {
    this._loading.next(true);
  }
  
  // pushes `true` as a new value of `_loading` subject and notifies subscribers
  stop(): void {
    this._loading.next(false);
  }
  
}

假设我们有两项服务:

  • API-仅包含返回纯(未经修改的)http流的方法的声明
  • ComponentService-在将数据传递给表示组件之前准备数据的服务

MyFruitService


  constructor(
   ...,private api: MyFruitAPIService,private loader: LoaderService
  ) { ... }

  getApples(): Observable<T> {
    this.loader.start();
    
    return this.api.getApples()
     .pipe(
      finalize(() => this.loader.stop()) // finalize - will call a function when observable completes or errors
     );
  }
   

MyAppleFruitComponent


 readonly loading$ = this.loader.isLoading$.pipe(tap((loading) => {
   if (loading) {
    this.data = null;
   }
 }));

 constructor(private loader: LoaderService) { ... }

 <ng-container *ngIf="loading$ | async; else errorOrLoading">

 ...

 </ng-container>

,

简而言之,不。这是不可能的。这也不是一个好主意,因为这样做会破坏封装。

您也不能动态更改函数调用。如果doADance()为您跳舞,那么您实际上也无法动态地使其添加数字列表。函数的实现与其调用分开。尽管就您的观点而言,JavaScript实际上确实使人们使函数通过绑定不同的上下文等来动态地执行奇怪的事情。

RxJS还将实现与流的调用(订阅)分开。如果一个库进行了20次转换并将该流返回给您,则您不会真正获得转换列表,这只是库编写者可以在不引入重大更改的情况下进行更改的实现细节。

更新1:

是的,封装很重要,并且存在是有原因的。但是,我们 当然可以通过传递动态将数字列表添加到doADance() 该列表作为参数。也许是一种允许某种方式的受控方式 动态填充给定管道内的占位符 运算符会是一样的吗?

pipe中的占位符实际上没有任何意义,因为任何可管道运算符都容易转换为静态运算符,而任何一组运算符都容易转换为单个运算符。

您可以做的事情非常接近。例如,这不适用于从库返回的流,但是您可以设计流以允许自定义其处理方式。

为您服务:

function dataOnChange(): Observable<Data>{
  return myService.onChanges$.pipe(
    switchMap(() => this.getBackendData())
  );
}

function dataOnChangePlus(op): Observable<Data>{
  if(op == null) return dataOnChange();
  return myService.onChanges$.pipe(
    op,switchMap(() => this.getBackendData())
  );
}

其他地方:

this.service.dataOnChangePlus(
  tap(_ => this.data = null)
).subscribe(console.log);

在其他地方,做相同的事情,但有所不同:

this.service.dataOnChangePlus(
  st => st.pipe(
    mapTo(null),tap(val => this.data = val)
  )
).subscribe(console.log);

现在向dataOnChangePlus的使用者返回一个流,并且还可以帮助定义该流的构造方式。它不是动态添加运算符,但确实允许您推迟运算符的定义。

好处是每个调用都可以定义不同的内容。

如果需要,可以通过仅允许呼叫者访问特定类型的话务员来缩小呼叫者的工作范围。例如,仅让他们为tap运算符定义lambda:

function dataOnChangePlusTap(tapper): Observable<Data>{
  return myService.onChanges$.pipe(
    tap(tapper),switchMap(() => this.getBackendData())
  );
}

this.service.dataOnChangePlusTap(
  _ => this.data = null
).subscribe(console.log);

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-