Асинхронный канал не получает значения, пока я не подпишусь на наблюдаемый - PullRequest
0 голосов
/ 11 июня 2019

Я столкнулся со странным случаем, когда асинхронный канал не работает должным образом, пока я не подпишусь на наблюдаемое.Источники:

Вид компонента:

<div *ngIf="loading$ | async" fxLayout="row" fxLayoutAlign="center">
    <span class="spinner">Loading...</span>
</div>

<h1>{{data$ | async}}</h1>

Компонент:

@Component({
  selector: './tm-fake',
  changeDetection: ChangeDetectionStrategy.OnPush,
  templateUrl: './fake.component.html'
})
export class FakeComponent implements OnInit {
  constructor(
    private _fs: FakeService
  ) {}

  loading$ = new BehaviorSubject<boolean>(false);
  data$ = this._fs.data$.pipe(indicateUntilNULL(this.loading$));

  ngOnInit(): void {
    this._fs.requestData();

    //this.data$.subscribe(e => {});
  }
}

Сервис:

@Injectable()
export class FakeService {
  requestData(): void {
    timer(2000).pipe(map(e => "data")).subscribe(e => this._data.next(e));
  }

  private readonly _data = new BehaviorSubject<string>(null);
  readonly data$ = this._data.asObservable();
}

Оператор:

export function indicateUntilNULL<T>(indicator: BehaviorSubject<boolean>): (source: Observable<T>) => Observable<T> {
  return source => source.pipe(
    tap(e => indicator.next(!e))
  );
}

Я вижу data после 2-секундной задержки, как и ожидалось, но я не видел счетчика во время этой задержки.Но если я раскомментирую строку this.data$.subscribe(e => {});, она начнет работать нормально.И я не могу найти причину.Есть идеи?

Примечание: loading$ выдает правильные значения, даже если эта строка закомментирована.

РЕШЕНИЕ

Большое спасибо @theMayer и его сотрудникамсовет.Я просто хочу добавить статью , где проблема подробно объясняется.И согласно этой статье самое элегантное решение - это оператор delay(0):

export function indicateUntilNULL<T>(indicator: BehaviorSubject<boolean>): (source: Observable<T>) => Observable<T> {
  return source => source.pipe(
    delay(0),
    tap(e => indicator.next(!e))
  );
}

Ответы [ 2 ]

0 голосов
/ 11 июня 2019

Вам нужно share() Вот мой рабочий пример:

public $route!: Observable<{}>;

constructor(
    private route: ActivatedRoute
) { }

public ngOnInit(): void {
    this.$route = this.route.data.pipe(share());
}

Тогда в поле зрения

<ng-container *ngIf="$route | async as route; else loading">
    ...
</ng-container>
0 голосов
/ 11 июня 2019

ОК, после просмотра вашего кода, я думаю, я понимаю вопрос. Вы не видите обновленное значение true, испускаемое через наблюдаемую loading$, обнаруженное в DOM.

Вот что произойдет в вашем коде:

  1. Объекты будут построены, а субъект поведения _data будет инициализирован нулевым значением.

  2. Шаблон Angular будет отображаться и проходить обнаружение изменений. Он получит false для значения loading и null для data. Загрузочный спиннер не будет вращаться.

  3. Позже в цикле обнаружения изменений труба async вызовет значение true, которое будет выдано loading. Это происходит синхронно; поскольку обнаружение изменений было установлено на OnPush, я подозреваю, что это изменение будет пропущено, потому что уже был установлен цикл обнаружения изменений, когда значение было установлено (обычно это может быть зафиксировано обычной стратегией обнаружения изменений и увеличено как ошибка).

  4. Тогда ваш data будет излучать после двухсекундной задержки.

Обратите внимание, что при использовании регулярного обнаружения изменений такая ситуация приведет к ExpressionChangedAfterItHasBeenCheckedError. Правильный подход заключается в обеспечении внутренней согласованности вашего компонента до цикла обнаружения изменений, а не во время обнаружения изменений, которые вызывают дальнейшие изменения.

Быстрый ответ состоит в том, чтобы изначально установить для вашего значения «loading» значение true, поскольку код всегда будет пытаться загрузить данные при инициализации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...