簡體   English   中英

使用 angular 異步 pipe 以完全反應式方式刷新數據

[英]Refresh Data in Fully Reactive Way using angular async pipe

問這個我覺得有點尷尬,但我確定我錯過了一些東西。 花了很長時間尋找/研究,但只通過mergeMap等提出需要主題或行為主題或pipe等的復雜而復雜的解決方案。

我正在從組件 Z4C4AD5FCA2E7A3F74DBB1CED00381AA4 中的命令式 HTTP 可觀察方法(手動訂閱)轉變為反應式 HTTP 可觀察方法(可觀察 $ | async)。

當頁面初始化時,讓它與異步 pipe 一起工作沒有問題。

但是,“即時”刷新模板的最佳方式是什么,即我知道我已將一條記錄插入到基礎數據庫中,並希望這反映在我的模板中。

使用命令式方法,我可以簡單地再次訂閱(例如從單擊處理程序)並將結果重新分配給與 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>
 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();
  }

只是跟進我最初的問題,這里有三個帶有解釋的工作解決方案。 感謝所有回復的人。

解決方案 1 - BehaviourSubject / SwitchMap

使用具有任意類型值和初始值的 RxJS BehaviorSubject 使用展平運算符SwitchMap將其與更高階的 observable 結合起來。

當單擊按鈕時, BehaviourSubject發出(下一個)一個值(在本例中為 0),該值被饋送到 HTTP 可觀察對象(忽略傳遞的值),從而導致內部可觀察對象發出一個值並啟動更改檢測。

當使用 BehaviourSubject 時,observable 會在訂閱時發出一個值。

在組件 HTML 模板中。

<button (click)="onClick()">Click Me</button>

在組件 Typescript 中。

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);
  }

解決方案 2 - 主題 / SwitchMap / initialValue

使用 RxJS Subject將其與使用展平運算符SwitchMap的高階可觀察對象結合起來。

當單擊按鈕時, Subject發出(下一個)一個值(在這種情況下未定義),該值被饋送到 HTTP 可觀察對象(忽略傳遞的值),從而導致內部可觀察對象發出一個值並啟動更改檢測。

Subject 需要startWith(0)否則當組件首次初始化並首次被異步 pipe 訂閱時,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);
  }
<button (click)="onClick()">Click Me</button>

請注意,在上述兩種情況下,可以直接在 HTML 組件模板中調用主題的下一個方法,因此主題具有公共訪問修飾符(無論如何都是默認值)。 這樣就不需要在 Typescript 中使用 onCLick 方法。

<button (click)="subject.next(0)">Click Me</button>

解決方案 3 - fromEvent

正如hanan所建議的那樣

將模板變量名稱添加到按鈕並使用@ViewChild獲取元素引用。 使用fromEvent創建一個 RxJS 可觀察對象,然后使用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),
          );
      }));
  }


.
.
.
<button #refreshButton>Click Me</button>
.
.
.

總之,如果用戶通過單擊隨意啟動刷新,則解決方案 3 最有意義,因為它不需要引入“虛擬”主題。

但是,主題方法是有意義的,如果可觀察對象應該以編程方式從任何地方發出......通過簡單地調用主題的下一個方法,可觀察對象可以根據命令發出值。

您知道,這個主題還有更多內容,真正的反應式方法可能會開始您關於使用共享可注入 singleton 服務和基於主題/行為主題/異步主題等的訂閱的思考過程。 不過,這些都是架構和設計決策。 感謝您的所有建議。

您可以從其單擊事件中獲取刷新 Btn 參考並創建 stream,然后使用虛擬條目啟動它。 現在你有一個刷新 stream,然后像這樣切換到你的數據 stream:

零件

@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),
      );
    }));
  }

模板

而不是使用 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>

但是要務實,不要花費太多精力來使其完全反應。 您之前的解決方案也很好,您只需要改進您的模板。

注意:在這種情況下,您可以使用*ngIf代替*ngLet

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM