Refre sh Data в полностью реактивном режиме с использованием канала angular asyn c - PullRequest
0 голосов
/ 10 июля 2020

Мне немного неловко спрашивать об этом, но я уверен, что что-то упускаю. Потратил много времени на поиски / исследования, но придумываю только сложные и запутанные решения, требующие субъектных или поведенческих объектов или конвейера через mergeMap, и т. Д. реактивный HTTP-наблюдаемый подход (наблюдаемый $ | asyn c) в шаблоне компонента HTML.

Нет проблем с его работой с конвейером asyn c при инициализации страницы.

Однако как лучше всего обновить sh шаблон «на лету», т.е. я знаю, что вставил запись в базовую базу данных и хочу, чтобы это отразилось в моем шаблоне.

При императивном подходе я мог бы просто снова подписаться (например, из обработчика кликов) и переназначить результат свойству, связанному с директивой ngfor, и обнаружение изменений сработало бы и DOM обновился.

Единственное решение, которое у меня есть придумать - просто снова получить наблюдаемое из службы, и это вызывает обнаружение изменений.

Чтобы было понятнее, вот фрагмент кода (очень простой, чтобы сосредоточиться на решении).

Должно быть простое решение?

<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

Используйте Rx JS BehaviorSubject с произвольным значением типа и начальным значением. Объедините это с наблюдаемым более высокого порядка, используя оператор сглаживания SwitchMap.

При нажатии кнопки BehaviourSubject испускает (следующее) значение (в данном случае 0), которое передается наблюдаемому HTTP ( переданное значение игнорируется), в результате чего внутренний наблюдаемый объект испускает значение и срабатывает обнаружение изменений.

При использовании BehaviourSubject наблюдаемый объект выдает значение при подписке.

В компоненте 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

Используйте Rx JS Subject Объедините это с наблюдаемым более высокого порядка с использованием оператора сглаживания SwitchMap.

При нажатии кнопки Subject выдает (следующее) значение (в данном случае не определено), которое передается наблюдаемому HTTP (переданное значение игнорируется), что приводит к тому, что внутренний наблюдаемый объект испускает значение, и срабатывает обнаружение изменений.

Субъект требует startWith(0), иначе наблюдаемое не будет выдавать никаких значений, когда компонент сначала инициализируется и сначала подписывается с помощью asyn c pipe.

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 субъект имел модификатор доступа publi c (в любом случае по умолчанию). Это избавило бы от необходимости иметь метод onCLick в Typescript.

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

Решение 3 - fromEvent

Как было предложено hanan

Добавьте имя переменной шаблона к кнопке и используйте @ViewChild, чтобы получить ссылку на элемент. Создайте наблюдаемый Rx JS, используя 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),
          );
      }));
  }


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

В заключение, решение 3 имеет наибольший смысл, если refre sh является запускаться пользователем по желанию с помощью щелчка, поскольку для этого не требовалось вводить «фиктивный» объект.

Тем не менее, субъектный подход имеет смысл в том, должен ли наблюдаемый излучать программно из любого места ... просто вызвав следующий метод по этому предмету, наблюдаемое может выдавать значения по команде.

Вы знаете, в этом топе c есть гораздо больше, и действительно реактивный подход может начать ваш мыслительный процесс об использовании общих вводимых одноэлементных сервисов и подписок, основанных на предметах / поведенческих объектах / асинхронных объектах и ​​так далее. Но это архитектурные и дизайнерские решения. Спасибо за все ваши предложения.

1 Ответ

0 голосов
/ 10 июля 2020

Вы можете получить ссылку refre sh Btn и создать поток из его события щелчка, а затем запустить его с фиктивной записи. Теперь у вас есть поток refre sh, затем переключите его в поток данных следующим образом:

Component

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

Template

Вместо использования 2 асинхронных c каналов используйте только 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>

НО Будьте прагматичны c, не прилагайте слишком много усилий, чтобы сделать его полностью реактивным. Ваше предыдущее решение также было хорошим, вам просто нужно улучшить свой шаблон.

Примечание. В этом случае вы можете использовать *ngIf вместо *ngLet

...