Как вызвать обнаружение изменений в Angular2?

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

У меня есть UserService, у которого есть свойство currentUser, которое является BehaviorSubject:

currentUser: Subject<User> = new BehaviorSubject<User>(new User(null));

И когда я хочу обновить пользователя в ответ на sdk javascript facebook, сообщающий мне, что пользователь вошел в систему или вышел из нее, я обновляю это и должен вызвать tick() на ApplicationRef:

updateUser(user: User) {
  console.log('UserService.updateUser:', user);
  this.currentUser.next(user);
  this.appRef.tick(); // UI does not update without this
}

constructor(facebook: Facebook) {
  this.facebook.facebookEvents.filter(x => x != null 
         && x.eventName == 'auth.authResponseChange')
      .subscribe((event) => {
        this.updateUser(new User(event.data));
      }
}

В моем компоненте я сохраняю «currentUser» из пользовательской службы в конструкторе и привязываюсь к свойству значения:

<h2>Logged into Facebook as {{currentUser.value.name}}</h2>
<p>Is this you? &nbsp; <img src="{{currentUser.value.profilePicUrl}}"></p>

Я делаю что-то неправильно? Есть ли лучший способ, чем вызывать ApplicationRef.tick() после изменения, вызванного внешней библиотекой?

Редактировать

Я попытался использовать NgZone, и это не сработало, используя другое событие, которое возвращает сообщения в ленте, поскольку сервисные страницы через них:

constructor(userService: UserService, private ref: ApplicationRef, private zone: NgZone)

...

this.postsSubject.subscribe((post) => {
  this.zone.runOutsideAngular(() => { // doesn't do anything
    this.posts.push(post);
    console.log('postsSubject POST, count is ', this.posts.length);
    ref.tick(); // required to update bindings
  });
}

Консоль показывает увеличение счетчика, но привязка html {{posts.length}} обновляется только в том случае, если я добавляю вызов ref.tick()...

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


person Jason Goemaat    schedule 20.02.2016    source источник
comment
Я не знаю, но если это может вам помочь, тогда хорошо, конструктор общих служб не выдает данные"> stackoverflow.com/questions/35500890/   -  person micronyks    schedule 20.02.2016
comment
currentUser — это BehaviorSubject. Вы должны подписаться на него, используя асинхронный канал {{ (currentUser | async)?.value?.name}}.   -  person Eric Martinez    schedule 20.02.2016


Ответы (3)


Я делаю что-то неправильно? Есть ли лучший способ, чем вызывать ApplicationRef.tick() после изменения, вызванного внешней библиотекой?

Это зависит. Если вы вызываете API Facebook вне Angular (т. е. регистрируете обработчики асинхронных событий вне Angular), вам потребуется вручную запускать обнаружение изменений как часть обработки любых событий. Вы также можете

  • ввести NgZone, а затем вызвать run() для этого объекта, который выполнит переданную (обратный вызов) функцию внутри зоны Angular, а затем автоматически вызовет ApplicationRef().tick(), который запустит обнаружение изменений для всего приложения, или
  • ввести ApplicationRef и вызвать tick() самостоятельно, или
  • введите ChangeDetectorRef и вызовите detectChanges() самостоятельно - это запустит обнаружение изменений только для одного компонента и его дочерних элементов.

Обратите внимание: если вы вводите NgZone, вы не хотите использовать runOutsideAngular(). Это выполнит переданную (обратный вызов) функцию за пределами зоны Angular и не вызовет ApplicationRef().tick(), поэтому обнаружение изменений не будет выполнено.

Если вы вызываете API Facebook внутри Angular, вы можете избежать всего вышеперечисленного, потому что тогда Angular (благодаря использованию Zone.js) должен будет исправлять асинхронные вызовы событий. Затем, когда ваши события сработают, ApplicationRef.tick() будет автоматически вызываться. Подробнее об этом подходе см. на странице https://stackoverflow.com/a/34593821/215945.

person Mark Rajcok    schedule 20.02.2016
comment
Так что, может быть, лучший способ избежать обнаружения изменений во всем приложении — это использовать ChangeDetectorRef для компонента, подписаться на событие изменения пользователя и вызвать его оттуда? - person Jason Goemaat; 21.02.2016
comment
@JasonGoemaat, если нужно обновить представление только одного компонента, то это, вероятно, самый эффективный способ сделать это. (Обратите внимание, что компонент и его дочерние элементы обнаруживаются с помощью detectChanges(). Я обновлю свой ответ.) - person Mark Rajcok; 21.02.2016

Я не уверен, что это лучше. Есть еще несколько способов, о которых я могу думать.

Использование NgZone

Вы можете внедрить NgZone и выполнить метод запуска с обратным вызовом:

 NgZone.run(()=>this.currentUser.next(user));

Использование setTimeout

setTimeout автоматически активирует обнаружение изменений:

setTimeout(()=>this.currentUser.next(user));
person pixelbits    schedule 20.02.2016
comment
Правильный — ngZone, у нас была похожая проблема с GoogleMaps, размещающим булавки на карте. - person VuesomeDev; 20.02.2016
comment
вы не можете использовать NgZone.run, нужно ввести его экземпляр. - person e-cloud; 01.06.2017
comment
И использование setTimeout неправильно, оно запускает только внешнюю зону (она же Zone.current) экземпляра NgZone. Таким образом, он не будет запускать обнаружение изменений angular. - person e-cloud; 01.06.2017
comment
@e-cloud, ты в этом уверен? setTimeout определенно запускается внутри зоны Angular (forkInnerZoneWithAngularBehavior) и после выхода истощает микро-/макро-задачи и запускает обнаружение изменений. - person pixelbits; 11.07.2018

Не могли бы вы рассказать нам, как вы инициализируете поставщика Facebook и свою службу в своем компоненте?

Вариант 1

Ваше поведение заставляет меня изменить, что код Facebook работает за пределами зоны Angular, поэтому обнаружение изменений Angular не запускается при возникновении события. Ты

Вы также можете вручную запустить обнаружение изменений:

import { Component, ChangeDetectorRef } from 'angular2/core';

@Component({
  (...)
})
export class MyComponent {
  constructor(private cdr:ChangeDetectorRef) {
  }

  someMethod() {
    this.cdr.detectChanges();
  }
}

Смотрите эти вопросы:

Вариант 2

Возможно, ваше событие запускается до того, как компонент зарегистрирует обратный вызов для темы currentUser. В этом случае вы можете инициировать событие позже...

person Thierry Templier    schedule 20.02.2016