使用角度异步管道以完全反应方式刷新数据

Rob*_*ebb 8 asynchronous observable rxjs angular

我觉得问这个有点尴尬,但我确定我错过了一些东西。花了很多年的时间寻找/研究,但只提出需要主题或行为主题或通过合并映射等管道的复杂和令人费解的解决方案。

我正在从组件 HTML 模板中的命令式 HTTP 可观察方法(手动订阅)转向反应式 HTTP 可观察方法(observable$ | async)。

当页面初始化时,让它与异步管道一起工作没有问题。

但是,“即时”刷​​新模板的最佳方法是什么,即我知道我已将一条记录插入到基础数据库中并希望将其反映在我的模板中。

使用命令式方法,我可以简单地再次订阅(例如从单击处理程序)并将结果重新分配给与 ngfor 指令关联的属性,并且更改检测将触发并更新 DOM。

我想出的唯一解决方案是简单地再次从服务中获取 observable,这会触发更改检测。

只是为了让这里更清楚的是代码的发挥(非常简单只是专注于解决方案)。

必须有一个简单的解决方案吗?

<div *ngIf="(model$ | async)?.length > 0" class="card mb-3 shadow">

  <div class="card-header">Actions Notes</div>

  <div class="card-body">

    <mat-list *ngFor="let action of model$ | async">
      <div class="small">
        <strong>
          {{action.createdDate | date:'dd/MM/yyyy HH:mm'}}
          {{action.createdBy}}
        </strong>
      </div>
      <div>{{action.notes}}</div>
    </mat-list>

  </div>
</div>

<button (click)="onClick()">name</button>
Run Code Online (Sandbox Code Playgroud)
 model$: Observable<any>;

  constructor(
    private changeTransferActionsService: ChangeTransferActionsService
  ) {}

  ngOnInit(): void {
    this.getObservable();
  }

  private getObservable(): void {
    this.model$ = this.changeTransferActionsService
      .getActionsWithNotesById(this.model.id)
      .pipe(
        map((x) => x.result),
        share()
      );
  }

  onClick(): void {

    //insert row into database here (via HTTP post service call)

    this.getObservable();
  }
Run Code Online (Sandbox Code Playgroud)

只是跟进我原来的问题,这里有三个工作解决方案和解释。感谢所有回复的人。

解决方案 1 - BehaviourSubject / SwitchMap

使用BehaviorSubject具有任意类型值和初始值的 RxJS 。使用展平运算符将其与高阶 observable 结合起来SwitchMap

单击按钮时,BehaviourSubject 会发出(下一个)一个值(在本例中为 0),该值被馈送到 HTTP observable(忽略传递的值),从而导致内部 observable 发出一个值并启动更改检测。

当使用 BehaviourSubject 时,observable 会在订阅时发出一个值。

在组件 HTML 模板中。

<button (click)="onClick()">Click Me</button>
Run Code Online (Sandbox Code Playgroud)

在组件打字稿中。

import { BehaviorSubject, Observable } from 'rxjs';
.
.
.
subject = new BehaviorSubject(0);
.
.
.

this.model$ = this.subject.asObservable().pipe(
      // startWith(0),
      switchMap(() => this.changeTransferActionsService
        .getActionsWithNotesById(this.model.id)
        .pipe(
          map((x) => x.result)
        )));
.
.
.
onClick(): void {
    this.subject.next(0);
  }
Run Code Online (Sandbox Code Playgroud)

解决方案 2 - 主题 / SwitchMap / initialValue

使用 RxJS 将Subject其与使用展平运算符的高阶可观察对象相结合SwitchMap

当单击按钮时,Subject 会发出(下一个)一个值(在这种情况下未定义),该值被馈送到 HTTP observable(忽略传递的值),从而导致内部 observable 发出一个值并启动更改检测。

Subject 需要一个,startWith(0)否则当组件第一次初始化并第一次被异步管道订阅时,observable 不会发出任何值。

import {Observable, Subject } from 'rxjs';
.
.
.
subject = new Subject();
.
.
.
 this.model$ = this.subject.asObservable().pipe(
      startWith(0),
      switchMap(() => this.changeTransferActionsService
        .getActionsWithNotesById(this.model.id)
        .pipe(
          map((x) => x.result)
        )));
.
.
.
onClick(): void {
    this.subject.next(0);
  }
Run Code Online (Sandbox Code Playgroud)
<button (click)="onClick()">Click Me</button>
Run Code Online (Sandbox Code Playgroud)

请注意,在上述两种情况下,主题的 next 方法可以直接在 HTML 组件模板中调用,因此主题具有公共访问修饰符(无论如何都是默认值)。这将不需要在 Typescript 中使用 onCLick 方法。

<button (click)="subject.next(0)">Click Me</button>
Run Code Online (Sandbox Code Playgroud)

解决方案 3 - fromEvent

正如hanan所建议的

为按钮添加模板变量名称并用于@ViewChild获取元素引用。创建一个 RxJS 可观察对象fromEvent,然后使用switchMap更高阶的可观察对象方法。

.
.
.
 @ViewChild('refreshButton') refreshButton: ElementRef;
.
.
.

 ngAfterViewInit(): void {
    const click$ = fromEvent(this.refreshButton.nativeElement, 'click');

    this.model$ = click$.pipe(
      startWith(0),
      switchMap(_ => {
        return this.changeTransferActionsService
          .getActionsWithNotesById(this.model.id)
          .pipe(
            map((x) => x.result),
          );
      }));
  }


Run Code Online (Sandbox Code Playgroud)
.
.
.
<button #refreshButton>Click Me</button>
.
.
.
Run Code Online (Sandbox Code Playgroud)

总之,如果用户通过单击随意启动刷新,则解决方案 3 最有意义,因为它不需要引入“虚拟”主题。

然而,主题方法是有意义的,如果 observable 应该从任何地方以编程方式发出……通过简单地调用主题的 next 方法,observable 可以根据命令发出值。

您知道,这个主题还有更多内容,真正的反应式方法可能会开始您关于使用基于主题/行为主题/异步主题等的共享可注入单例服务和订阅的思考过程。不过,这些都是架构和设计决策。感谢您的所有建议。

han*_*nan 3

您可以获取刷新 Btn 引用并从其单击事件创建流,然后使用虚拟条目启动它。现在您有了刷新流,然后将其切换到数据流,如下所示:

成分

@ViewChild('refreshButton') refreshBtn:ElementRef;  
  model$: Observable<any>;

  ngAfterViewInit() {
    let click$ = fromEvent(this.refreshBtn.nativeElement, 'click')
    this.model$ = click$.pipe(startWith(0),
    switchMap(_=> {
       return this.changeTransferActionsService
      .getActionsWithNotesById(this.model.id)
      .pipe(
        map((x) => x.result),
      );
    }));
  }
Run Code Online (Sandbox Code Playgroud)

模板

不使用 2 个异步管道,而只使用 1 个,这样ngLet可以简化您的模板,并且您不必使用share()

<button #refreshButton >Refresh</button>

<ng-container *ngLet="(model$ | async) as model">

<div *ngIf="model.length > 0" class="card mb-3 shadow">

  <div class="card-header">Actions Notes</div>

  <div class="card-body">

    <mat-list *ngFor="let action of model">
      <div class="small">
        <strong>
          {{action.createdDate | date:'dd/MM/yyyy HH:mm'}}
          {{action.createdBy}}
        </strong>
      </div>
      <div>{{action.notes}}</div>
    </mat-list>

  </div>
</div>

<button (click)="onClick()">name</button>

</ng-container>
Run Code Online (Sandbox Code Playgroud)

要务实,不要花费太多精力来使其完全反应。您之前的解决方案也很好,您只需要改进模板即可。

*ngIf注意:在这种情况下您可以使用*ngLet