为多个 'observable consumers' 创建一个订阅

Create one subscription for multiple 'observable consumers'

经理状态有为用户提供的商店服务。在商店中有一个 'select' 方法映射以获取用户 ID:

type User = { userId: number };

@Injectable({ providedIn: "root" })
export class SomeService {
  activeUsers$ = new BehaviorSubject<User[]>([]);

  selectUserIds = (): Observable<number[]> => {
    return this.activeUsers$
      .asObservable()
      .pipe(map((data) => data.map(({ userId }) => userId)));
  };
  // ...
}

在我调用该方法以查看哪个用户处于活动状态的组件中:

@Component({
  selector: "app-root",
  template: `
  <div>
    <h1>See console output</h1>
    <ng-container *ngFor="let user of users">
      <div [ngClass]="{'active': (activeUserIds$ | async).includes(user.userId)}">
        {{ user.userName }}
      </div>
    </ng-container>
  </div>`,
})
export class AppComponent implements OnInit {

  activeUserIds$: Observable<number[]>;

  users = [
    { userId: 1, userName: "John" },
    { userId: 2, userName: "Fred" },
    { userId: 3, userName: "Alex" }
  ];

  constructor(private someService: SomeService) {}

  ngOnInit(): void {
    this.activeUserIds$ = this.someService.selectUserIds().pipe(
      tap((data) => {
        console.log("data: ", data);
      }),
    );
  }
}

但是在控制台中,它在模板中被调用的次数(三次)发生了:

// console output:
// data: []
// data: []
// data: []

做三遍好像是多余的。但我确实希望它对状态更新做出反应。

我的想法是,通过在 OnInit 中创建 'activeUserIds$',我将可以观察到 1 并多次使用它。但它似乎订阅了多次。

只有 1 个订阅的方法是什么?

沙盒link:https://codesandbox.io/s/red-bird-n7xips?file=/src/app/app.component.ts

当您在 *ngFor 中使用 async 时,您正在创建三个订阅。它不知道 activeUserIds$ | async 会在您每次订阅它时发出相同的值。

一种解决方案是在模板中使用局部变量,并使用来自 NgRx 的 ngrxLet 等指令仅订阅一次。

在原版 Angular 中,最简单的方法(没有 *ngIf and this https://github.com/angular/angular/issues/15280)可能是将 shareReplay(1)take(1) 附加到链中,以便它立即完成并且不会订阅源 Observable:

this.activeUserIds$ = this.someService.selectUserIds().pipe(
  tap((data) => {
    console.log("data: ", data);
  }),
  shareReplay(1),
  take(1),
);

现场演示:https://stackblitz.com/edit/angular-ivy-woypne?file=src%2Fapp%2Fapp.component.ts