Использование асинхронного канала Angular и обнаружение изменений onPush из сторонних библиотек

В последнее время я пытаюсь понять асинхронный канал Angulars и наблюдаемые объекты rxjs, и мне приходится возвращаться к подписке на наблюдаемые в файле .ts и вызову обнаружения изменений вручную при использовании BehaviorSubjects из сторонних библиотек.

Я пытаюсь создать список, используя подобную настройку, но BehaviorSubject возвращает массив из объекта из сторонней библиотеки.

<app-list-item
    [innerItem]="item"
      *ngFor="let item of (3rdPartyComponent.itemBehaviorSubject | async)">
</app-list-item>

Мой вопрос: возможно ли использовать асинхронные каналы, подобные этому, из модуля вне angular, или обнаружение изменений просто не срабатывает? И если это правда, что я могу сделать, чтобы привести эту стороннюю библиотеку в соответствие с angular, чтобы асинхронный канал выполнял свою работу?


person Martin Nielsen    schedule 25.05.2021    source источник
comment
Что вы подразумеваете под сторонними библиотеками? У вас есть доступ к теме или нет? Можете ли вы создать экземпляр предмета? Вся концепция канала async заключается в том, что он будет автоматически обрабатывать рендеринг. Вы всегда можете создать subcription и избежать асинхронного канала, если хотите.   -  person StPaulis    schedule 25.05.2021
comment
Я имею в виду, что создается экземпляр BehaviorSubject, и его следующий метод вызывается внутри другого модуля, импортированного из NPM в package.json. Насколько я понимаю, обнаружение изменений происходит внутри зоны zone.js. Если метод BehaviorSubject.next вызывается за пределами угловой зоны, не пропустит ли его обнаружение изменений?   -  person Martin Nielsen    schedule 25.05.2021
comment
Каждый используемый вами пакет npm вы импортируете в модуль. Либо вы объявляете компонент или канал, либо предоставляете услугу. После этого все работает как ваш код.   -  person StPaulis    schedule 25.05.2021
comment
Я не понимаю, в чем именно ваша проблема.. Вы видите в логах, что в сообщениях есть данные, но элемент не отображается? Вы пытались напечатать на экране свою тему в формате json?   -  person StPaulis    schedule 25.05.2021
comment
Да, я пробовал регистрировать, где вызывается BehaviorSubject.next. Поскольку я пытаюсь использовать асинхронный канал, я не могу войти в систему подписчика, так как это делается с помощью |async внутри цикла ngFor. Но данные есть, и список ОТОБРАЖАЕТСЯ, только не при его обновлении. Он отображается, когда что-то еще запускает обнаружение изменений позже.   -  person Martin Nielsen    schedule 25.05.2021
comment
Но где именно реализовано обнаружение изменений? Если это реализовано в angulars zone.js, то асинхронные вызовы из зоны по умолчанию не должны запускать обнаружение изменений, верно?   -  person Martin Nielsen    schedule 25.05.2021
comment
Создайте Observable из своего BehaviorSubject. Subscribe на нем и console.log выбросы.   -  person StPaulis    schedule 25.05.2021
comment
Я только что сделал. Наблюдаемый регистрирует изменения, как и ожидалось, когда они происходят. И если я введу ChangeDetectorRef и вызову detectChanges(), тогда представление обновится, когда субъект выдает новое значение. Вопрос в том, почему асинхронный канал не запускает обнаружение изменений.   -  person Martin Nielsen    schedule 25.05.2021
comment
Нет, zone.js — это сторонняя библиотека, которая генерирует асинхронные события и Angular, получает эти события и решает, когда выполнять рендеринг. Когда вы находитесь в компоненте OnPush, только асинхронные каналы и изменения ввода заставляют компонент отображать. В противном случае рендеринг будет происходить после каждой асинхронной задачи или изменения.   -  person StPaulis    schedule 25.05.2021
comment
Когда я удаляю onPush, рендеринг все равно не происходит. Единственный работающий способ — это использование ChangeDetectorRef, но почему тогда асинхронность не определяет, что данные были отправлены?   -  person Martin Nielsen    schedule 25.05.2021
comment
Я не уверен, но, возможно, если вы замените BehoviorSubject на Observable, это сработает. В любом случае, вы можете заменить Subject фактическим значением, создав подписку. В следующий раз попробуй устроить стекблиц, это действительно поможет...   -  person StPaulis    schedule 25.05.2021
comment
Это не имеет смысла, BehaviorSubject IS Observable.   -  person Martin Nielsen    schedule 25.05.2021
comment
Нет, это не так... Вот почему у него есть метод расширения, называемый .asObservable()   -  person StPaulis    schedule 25.05.2021
comment
(3rdPartyComponent.itemBehaviorSubject.asObservable() | async) ничего не улучшает   -  person Martin Nielsen    schedule 25.05.2021
comment
Если вы сделали (3rdPartyComponent.itemBehaviorSubject | async) и это сработало, то это НЕ BehaviorSubject, я думаю, вам нужно предоставить больше кода, чтобы мы могли помочь   -  person noririco    schedule 25.05.2021
comment
(3rdPartyComponent.itemBehaviorSubject.asObservable() | async) Это не то, что я предлагаю. Я считаю, что вы должны закрыть тему на данный момент...   -  person StPaulis    schedule 25.05.2021


Ответы (1)


Проблема

Мне кажется, то, что вы делаете, должно сработать. В прошлом я решал эту же проблему с асинхронным каналом и не сталкивался с какими-либо проблемами.

Поиск проблемы

Я написал небольшую утилиту, которая позволяет вам выходить из/входить в ngZone Angular в наблюдаемом объекте. Вы можете попробовать это, хотя мой опыт показывает, что вам не должно это понадобиться с асинхронным каналом.

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

Это довольно старое, так что вам может понадобиться настроить его? Точно сказать не могу.

// Inject ngZone
constructor(private ngZone: NgZone) { }

/*****
 * Wrap every emission of an observable (next, complete, & error)
 * With a callback function, effectively removing the invocation
 * of these emissions from the observable to the callback.
 *
 * This isn't really too useful except for as a helper function for
 * our NgZone Operators where we leverage this wrapper to run an
 * observable in a specific JavaScript environment.
 *****/
callBackWrapperObservable<T>(
  input$: Observable<T>, 
  callback: (fn: (v) => void) => void
): Observable<T> {
  const callBackBind = fn => (v = undefined) => callback(() => fn(v));
  const bindOn = (ob, tag) => callBackBind(ob[tag].bind(ob));
  return new Observable<T>(observer => {
    const sub = input$.subscribe({
      next: bindOn(observer, "next"),
      error: bindOn(observer, "error"),
      complete: bindOn(observer, "complete")
    });
    return { unsubscribe: () => sub.unsubscribe() };
  });
}

ngZoneEnterObservable<T>(input$: Observable<T>): Observable<T> {
  return this.callBackWrapperObservable(input$, this.ngZone.run.bind(this.ngZone));
}
ngZoneEnter<T>(): MonoTypeOperatorFunction<T> {
  return this.ngZoneEnterObservable;
}

ngZoneLeaveObservable<T>(input$: Observable<T>): Observable<T> {
  return this.callBackWrapperObservable(input$, this.ngZone.runOutsideAngular.bind(this.ngZone));
}
ngZoneLeave<T>(): MonoTypeOperatorFunction<T> {
  return this.ngZoneLeaveObservable;
}

Вы можете попробовать это так:

В вашем .ts:

items$ = ngZoneEnterObservable(
  3rdPartyComponent.itemBehaviorSubject 
);

or

items$ = 3rdPartyComponent.itemBehaviorSubject.pipe(
  ngZoneEnter()
);

Тогда вместо 3rdPartyComponent.itemBehaviorSubject | async можно просто написать items$ | async

Вы также можете использовать оператор tap как быстрое и грязное средство для проверки того, сработала ли наблюдаемая

/***
 * Curried function that logs the message (msg) and
 * value (val)
 ***/
function logMsg(msg: string) {
  return (val: any = null) => 
    msg == null || msg.length < 1 ? 
      console.log(val) :
      val == null ? 
        console.log(msg) :
        console.log(`${msg}: `, val);
}

/***
 * RxJS Pipeable Operator:
 * Optionally prepend a message before logging 
 * the current value to the console.
 ***/
function log<T>(msg = ""): MonoTypeOperatorFunction<T> {
  return pipe(tap(logMsg(msg)));
}

items$ = 3rdPartyComponent.itemBehaviorSubject.pipe(
  log("Before ngZoneEnter"),
  ngZoneEnter(),
  log("After ngZoneEnter"),
);
person Mrk Sef    schedule 25.05.2021