Как определить, когда значение @Input() изменяется в Angular?

У меня есть родительский компонент (CategoryComponent), дочерний компонент (videoListComponent) и ApiService.

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

В настоящее время компонент списка видео просто получает все видео, я хотел бы отфильтровать это только для видео в определенной категории, я достиг этого путем передачи categoryId ребенку через @Input().

CategoryComponent.html

<video-list *ngIf="category" [categoryId]="category.id"></video-list>

Это работает, и когда родительская категория CategoryComponent изменяется, тогда значение categoryId передается через @Input() но затем мне нужно обнаружить это в VideoListComponent и повторно запросить массив видео через APIService (с новым categoryId).

В AngularJS я бы сделал $watch для переменной. Каков наилучший способ справиться с этим?

Ответ 1

На самом деле, существует два способа обнаружения и реагирования на изменения входа в дочернем компоненте в angular2+:

  1. Вы можете использовать метод жизненного цикла ngOnChanges(), как также упоминалось в предыдущих ответах:
    @Input() categoryId: string;

    ngOnChanges(changes: SimpleChanges) {

        this.doSomething(changes.categoryId.currentValue);
        // You can also use categoryId.previousValue and 
        // categoryId.firstChange for comparing old and new values

    }

Ссылки на документацию: ngOnChanges, SimpleChanges, SimpleChange
Пример демо: Посмотрите на этот плункер

  1. В качестве альтернативы вы также можете использовать установщик входных свойств следующим образом:
    private _categoryId: string;

    @Input() set categoryId(value: string) {

       this._categoryId = value;
       this.doSomething(this._categoryId);

    }

    get categoryId(): string {

        return this._categoryId;

    }

Ссылка на документацию: смотрите здесь.

Пример демо: Посмотрите на этот плункер.

Какой подход вы должны использовать?

Если ваш компонент имеет несколько входов, то, если вы используете ngOnChanges(), вы получите все изменения сразу для всех входов в ngOnChanges(). Используя этот подход, вы также можете сравнить текущие и предыдущие значения входных данных, которые изменились, и принять соответствующие меры.

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

РЕДАКТИРОВАТЬ 2017-07-25: ОБНАРУЖЕНИЕ ИЗМЕНЕНИЯ УГЛОВОГО ИЗМЕНЕНИЯ МОЖЕТ БЫТЬ НЕ ПОЖАРОМ ПРИ НЕКОТОРЫХ ОБСТОЯТЕЛЬСТВАХ

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

  1. Если вы используете вложенный объект или массив (вместо примитивного типа данных JS) для передачи данных от Parent к Child, обнаружение изменений (с использованием либо setter, либо ngchanges) может не сработать, что также указано в ответе пользователя: muetzerich. Решения смотрите здесь.

  2. Если вы изменяете данные вне углового контекста (то есть извне), то angular не будет знать об изменениях. Возможно, вам придется использовать ChangeDetectorRef или NgZone в вашем компоненте, чтобы постоянно информировать о внешних изменениях и тем самым запускать обнаружение изменений. Обратитесь к этому.

Ответ 2

Используйте метод жизненного цикла ngOnChanges() в вашем компоненте.

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

Вот Docs.

Ответ 3

Я получал ошибки в консоли, а также компилятор и IDE при использовании типа SimpleChanges в сигнатуре функции. Чтобы предотвратить ошибки, используйте ключевое слово any в сигнатуре.

ngOnChanges(changes: any) {
    console.log(changes.myInput.currentValue);
}

EDIT:

Как указал Джон ниже, вы можете использовать подпись SimpleChanges при использовании нотной записи в виде скобок, а не в виде точечной нотации.

ngOnChanges(changes: SimpleChanges) {
    console.log(changes['myInput'].currentValue);
}

Ответ 4

Самый безопасный @Input - использовать совместно используемый сервис вместо параметра @Input. Кроме того, параметр @Input не обнаруживает изменения в сложном типе вложенных объектов.

Простой пример службы выглядит следующим образом:

Service.ts

import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';

@Injectable()
export class SyncService {

    private thread_id = new Subject<number>();
    thread_id$ = this.thread_id.asObservable();

    set_thread_id(thread_id: number) {
        this.thread_id.next(thread_id);
    }

}

Component.ts

export class ConsumerComponent implements OnInit {

  constructor(
    public sync: SyncService
  ) {
     this.sync.thread_id$.subscribe(thread_id => {
          **Process Value Updates Here**
      }
    }

  selectChat(thread_id: number) {  <--- How to update values
    this.sync.set_thread_id(thread_id);
  }
}

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

Ответ 5

  @Input()
  public set categoryId(categoryId: number) {
      console.log(categoryId)
  }

пожалуйста, попробуйте использовать этот метод. Надеюсь это поможет

Ответ 6

Я просто хочу добавить, что есть еще один крючок Lifecycle с именем DoCheck, который полезен, если значение @Input не является примитивным значением.

У меня есть Array как Input, поэтому он не запускает событие OnChanges, когда содержимое изменяется (потому что проверка того, что Angular делает это "просто" и не глубока, поэтому массив все еще является массивом, даже если содержимое в массиве изменилось).

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

Ответ 7

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

service.ts 
public categoryChange$ : ReplaySubject<any> = new ReplaySubject(1);

-----------------
CategoryComponent.ts
public onCategoryChange(): void {
  service.categoryChange$.next();
}

-----------------
videoListComponent.ts
public ngOnInit(): void {
  service.categoryChange$.subscribe(() => {
   // do your logic
});
}

Ответ 8

Здесь ngOnChanges будет срабатывать всегда, когда изменяется ваше входное свойство:

ngOnChanges(changes: SimpleChanges): void {
 console.log(changes.categoryId.currentValue)
}

Ответ 10

Вы также можете просто передать EventEmitter как Input. Не совсем уверен, что это лучшая практика...

CategoryComponent.ts:

categoryIdEvent: EventEmitter<string> = new EventEmitter<>();

- OTHER CODE -

setCategoryId(id) {
 this.category.id = id;
 this.categoryIdEvent.emit(this.category.id);
}

CategoryComponent.html:

<video-list *ngIf="category" [categoryId]="categoryIdEvent"></video-list>

И в VideoListComponent.ts:

@Input() categoryIdEvent: EventEmitter<string>
....
ngOnInit() {
 this.categoryIdEvent.subscribe(newID => {
  this.categoryId = newID;
 }
}

Ответ 11

Если вы не хотите использовать ngOnChange, реализуйте метод og onChange(), вы также можете подписаться на изменения определенного элемента с помощью события valueChanges, ETC.

 myForm= new FormGroup({
first: new FormControl()   

});

this.myForm.valueChanges.subscribe(formValue => {
  this.changeDetector.markForCheck();
});

markForCheck(), написанный из-за использования в этом объявлении:

  changeDetection: ChangeDetectionStrategy.OnPush