简体   繁体   English

*ngIf 带有多个异步管道变量

[英]*ngIf with multiple async pipe variables

Been trying to combine two observables into one *ngIf and show the user interface when both have emitted.一直试图将两个 observable 组合成一个*ngIf并在两者都发出时显示用户界面。

Take:拿:

<div *ngIf="{ language: language$ | async, user: user$ | async } as userLanguage">
    <b>{{userLanguage.language}}</b> and <b>{{userLanguage.user}}</b>
</div>

From: Putting two async subscriptions in one Angular *ngIf statement来自: 将两个异步订阅放在一个 Angular *ngIf 语句中

This works as far as it compiles however in my case language$ and user$ would be from two HTTP requests and it seems user$ throws runtime errors like TypeError: _v.context.ngIf.user is undefined .这在编译时有效,但是在我的情况下, language$user$将来自两个 HTTP 请求,并且似乎user$抛出运行时错误,如TypeError: _v.context.ngIf.user is undefined

Essentially what I really want is (this doesn't work):基本上我真正想要的是(这不起作用):

<div *ngIf="language$ | async as language && user$ | async as user">
    <b>{{language}}</b> and <b>{{user}}</b>
</div>

Is the best solution:是最好的解决方案:

  • Subscribe inside the component and write to variables在组件内部订阅并写入变量
  • To combine the two observables inside the component with say withLatestFrom将组件内的两个 observable 与 say withLatestFrom结合起来
  • Add null checks {{userLanguage?.user}}添加空检查{{userLanguage?.user}}

This condition should be handled with nested ngIf directives:这种情况应该用嵌套的ngIf指令处理:

<ng-container *ngIf="language$ | async as language">
  <div *ngIf="user$ | async as user">
    <b>{{language}}</b> and <b>{{user}}</b>
  </div>
<ng-container>

The downside is that HTTP requests will be performed in series.缺点是 HTTP 请求将按顺序执行。

In order to perform them concurrently and still have language and user variables, more nesting is required:为了同时执行它们并且仍然有languageuser变量,需要更多的嵌套:

<ng-container *ngIf="{ language: language$ | async, user: user$ | async } as userLanguage">
  <ng-container *ngIf="userLanguage.language as language">
    <ng-container *ngIf="userLanguage.user as user">
      <div><b>{{language}}</b> and <b>{{user}}</b></div>
    </ng-container>
  </ng-container>
</ng-container>

More efficient way way to do this is to move logic from template to component class at this point and create a single observable, eg with withLatestFrom更有效的方法是此时将逻辑从模板移动到组件类并创建单个可观察对象,例如withLatestFrom

You can also use the following trick.您还可以使用以下技巧。 You will need one additional nesting.您将需要一个额外的嵌套。

<ng-container *ngIf="{a: stream1$ | async, b: stream2$ | async, c: stream3$ | async} as o">
  <ng-container *ngIf="o.a && o.b && o.c">
    {{o.a}} {{o.b}} {{o.c}}
  </ng-container>
</ng-container>

The object o is ever truthy, therefore the first *ngIf is simple used to save the stream values.对象 o 永远是真实的,因此第一个 *ngIf 很简单,用于保存流值。 inside you have to namespace your variables with o.在里面你必须用 o 命名你的变量。

That's depend what do you want but I think forkJoin operator with a loaded flag, could be a good idea.这取决于您想要什么,但我认为带有加载标志的 forkJoin 运算符可能是一个好主意。

https://www.learnrxjs.io/operators/combination/forkjoin.htmlhttps://www.learnrxjs.io/operators/combination/forkjoin.html

The forkJoin wait that all Observable are completed to return their values in its subscribe forkJoin 等待所有 Observable 完成以返回其订阅中的值

Observable.forkJoin(
  Observable.of("my language").delay(1000),
  Observable.of("my user").delay(1000),
).subscribe(results => {
  this.language = results[0]
  this.user = results[1]
})

You can catch errors into onError of the subscribe and display it.您可以将错误捕获到订阅的 onError 中并显示它。

I use a custom function that combines all class observables providing the following advantages:我使用了一个自定义函数,它结合了所有类可观察对象,具有以下优点:

  • Reduces async pipe count to 1 without the boilerplate proposed in other answers .async管道数减少到 1,而无需其他答案中提出的样板
  • Allows you to synchronously read all observables in your component code允许您同步读取组件代码中的所有 observable
  • Allows you to view the current value of your observables in the Angular devtools extension .允许您在Angular devtools 扩展中查看您的 observables 的当前值

COMPONENT CODE :组件代码

@Component({
  template: `
    selector: 'app-component',
    <ng-container *ngIf="observables$ | async; let observe;">
      <div *ngIf="observe.language$ && observe.user$">
        <b>{{observe.language$}}</b> and <b>{{observe.user$}}</b>
      </div>
    </ng-container>
  `
})
export class MyComponent {

  language$ = of('English');
  user$ = of('John');
  observables$ = combineComponentObservables<MyComponent>(this);

  ngAfterViewInit() {
    // We can read values synchronously from this field
    const value = this.observables$.value.language;
  }
}

UTILITY CODE :实用程序代码

import { EventEmitter } from '@angular/core';
import { combineLatest, Observable } from 'rxjs';
import { map } from 'rxjs/operators';

type FunctionParameter<T> = T extends (arg: infer H) => any ? H : never;
type ClassObservables<T> = {
  [I in keyof T]: T[I] extends Observable<any> ? FunctionParameter<Parameters<T[I]['subscribe']>[0]> : never;
};
type SubType<Base, Condition> = Pick<Base, {
  [Key in keyof Base]: Base[Key] extends Condition ? Key : never
}[keyof Base]>;
export type Observables<T> = ClassObservables<SubType<Omit<T, 'templateObservables$' | '$observables'>, Observable<any>>>;

export const combineComponentObservables = <T>(component: T): Observable<Observables<T>> & { value: Observables<T> } => {
  const keysOfObservableMembers = Object.keys(component)
    .filter(key => component[key] instanceof Observable && !(component[key] instanceof EventEmitter));
  const res = combineLatest(
    keysOfObservableMembers.map(key => component[key] as Observable<any>)
  ).pipe(
    map(observers => {
      const result = {};
      observers.forEach((obs, idx) => result[keysOfObservableMembers[idx]] = obs);
      (component as any).$observables = result;
      (res as any).value = result;
      return result as Observables<T>;
    })
  );
  return res as Observable<Observables<T>> & { value: Observables<T> };
};

NOTES:笔记:

  • To get template type-checking, You will need to be using a recent version of Angular with Ivy enabled.要进行模板类型检查,您需要使用启用了 Ivy 的最新版本的 Angular。 Also ensure that you have the Language Service enabled and that you enable strictTemplates in the angularCompilerOptions within your tsconfig.json .还要确保您启用了语言服务,并在angularCompilerOptions strictTemplates中启用了tsconfig.json
  • The content within the ng-container will only display once all observables have resolved. ng-container的内容只会在所有 observables 解析后显示。 Therefore you will experience issues if any observables that depend on dom elements.因此,如果有任何依赖于 dom 元素的可观察对象,您将遇到问题。 That being said, all other answers posted here suffer from the same problem.话虽如此,此处发布的所有其他答案都存在同样的问题。 To prevent this from being an issue, I tend to use RouteResolvers to pre-fetch all API data.为了防止这成为一个问题,我倾向于使用 RouteResolvers 来预取所有 API 数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM