Angular 6 View не обновляется после изменения переменной в подписке

Почему представление не обновляется при изменении переменной в подписке?

У меня есть этот код:

example.component.ts

testVariable: string;

ngOnInit() {
    this.testVariable = 'foo';

    this.someService.someObservable.subscribe(
        () => console.log('success'),
        (error) => console.log('error', error),
        () => {
            this.testVariable += '-bar';

            console.log('completed', this.testVariable);
            // prints: foo-Hello-bar
        }
    );

    this.testVariable += '-Hello';
}

example.component.html

{{testVariable}}

Но отображается представление: foo-Hello.

Почему он не будет отображаться: foo-Hello-bar?

Если я вызову ChangeDetectorRef.detectChanges() в подписке, он отобразит правильное значение, но зачем мне это делать?

Я не должен называть этот метод из каждой подписки, или вообще (угловой должен справиться с этим). Правильно ли?

Я что-то пропустил в обновлении от Angular/rxjs 5 до 6?

Прямо сейчас у меня есть угловая версия 6.0.2 и rxjs 6.0.0. Тот же код работает нормально в Angular 5.2 и rxjs 5.5.10 без необходимости вызова detectChanges.

Ответ 1

Насколько я знаю, Angular обновляет только вид, если вы меняете данные в "Angular zone". Асинхронный вызов в вашем примере не подходит для этого. Но если вы хотите, вы можете поместить его в угловую зону или использовать rxjs или извлечь часть кода в новый компонент для решения этой проблемы. Я объясню все:

ОБНОВЛЕНИЕ: Кажется, что не все решения работают больше. Для большинства пользователей первое решение "Угловая зона" делает свою работу.

1 угловая зона

Наиболее распространенным использованием этого сервиса является оптимизация производительности при запуске работы, состоящей из одной или нескольких асинхронных задач, которые не требуют, чтобы Angular обновлял пользовательский интерфейс или обрабатывал ошибки. Такие задачи могут быть запущены с помощью runOutsideAngular, и при необходимости эти задачи могут повторно войти в угловую зону с помощью run. https://angular.io/api/core/NgZone

Ключевой частью является функция "запустить". Вы можете внедрить NgZone и поместить обновление значения в обратный вызов run объекта NgZone:

constructor(private ngZone: NgZone ) { }
testVariable: string;

ngOnInit() {
   this.testVariable = 'foo';

   this.someService.someObservable.subscribe(
      () => console.log('success'),
      (error) => console.log('error', error),
      () => {
      this.ngZone.run( () => {
         this.testVariable += '-bar';
      });
      }
   );
}

Согласно этому ответу, все приложение будет обнаруживать изменения, в то время как подход ChangeDetectorRef.detectChanges будет обнаруживать только изменения в вашем компоненте и его потомках.

2 RxJS

Другой способ - использовать rxjs для обновления представления. Когда вы впервые подписываетесь на ReplaySubject, вам будет предоставлено последнее значение. BehaviorSubject в основном то же самое, но позволяет вам определить значение по умолчанию (имеет смысл в вашем примере, но не обязательно всегда будет правильным выбором). После этой первоначальной эмиссии это в основном нормальный предмет воспроизведения:

this.testVariable = 'foo';
testEmitter$ = new BehaviorSubject<string>(this.testVariable);


ngOnInit() {

   this.someService.someObservable.subscribe(
      () => console.log('success'),
      (error) => console.log('error', error),
      () => {
         this.testVariable += '-bar';
         this.testEmitter.next(this.testVariable);
      }
   );
}

По вашему мнению, вы можете подписаться на тему, используя асинхронный канал:

{{testEmitter$ | async}}

3 Извлечение кода в новый компонент

Если вы отправите строку в другой компонент, она также будет обновлена. Вам придется использовать селектор @Input() в новом компоненте.

Итак, новый компонент имеет такой код:

@Input() testVariable = '';

И testVariable назначается в HTML, как и раньше, с фигурными скобками.

В родительском HTML-представлении вы можете затем передать переменную этого элемента в дочерний элемент:

<app-child [testVariable]="testVariable"></app-child>

Таким образом, вы находитесь в angular зоне.

4 Личные предпочтения

Мое личное предпочтение - использовать rxjs или компонентный путь. Использование Detechanges или NGZone кажется мне более хакерским.