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

228

У меня есть родительский компонент (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 комментарий
Теги:
angular
input
event-handling
angular2-changedetection

9 ответов

347
Лучший ответ

На самом деле, существует два способа обнаружения и реагирования на изменения входа в дочернем компоненте в 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
    Очень хороший момент относительно использования сеттеров со входами, я обновлю это, чтобы быть принятым ответом, поскольку это более полно. Благодарю.
  • 0
    Это нормально, что сеттер работает только один раз?
Показать ещё 7 комментариев
88

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

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

Вот Docs.

  • 5
    Это работает, когда для переменной задано новое значение, например, MyComponent.myArray = [] , но если изменяется входное значение, например, MyComponent.myArray.push(newValue) , ngOnChanges() не запускается. Есть идеи о том, как запечатлеть это изменение?
  • 4
    ngOnChanges() не вызывается, когда вложенный объект изменился. Может быть, вы найдете решение здесь
23

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

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

EDIT:

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

ngOnChanges(changes: SimpleChanges) {
    console.log(changes['myInput'].currentValue);
}
  • 1
    Вы импортировали интерфейс SimpleChanges вверху файла?
  • 0
    @Джон - Да. Проблема была не в самой подписи, а в доступе к параметрам, которые назначаются во время выполнения объекту SimpleChanges. Например, в моем компоненте я связал свой класс User со входом (то есть <my-user-details [user]="selectedUser"></my-user-details> ). Доступ к этому свойству осуществляется из класса SimpleChanges путем вызова changes.user.currentValue . Обратите внимание, что user связан во время выполнения и не является частью объекта SimpleChanges
Показать ещё 5 комментариев
5

Самый безопасный @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);
  }
}

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

  • 1
    Спасибо, Санкет. Это очень хорошая точка зрения, и, где это уместно, лучший способ сделать это. Я оставлю принятый ответ в том виде, в каком он есть, поскольку он отвечает на мои конкретные вопросы, касающиеся обнаружения изменений @Input.
  • 1
    Параметр @Input не обнаруживает изменения внутри сложного вложенного типа объекта, но если сам объект изменяется, он сработает, верно? Например, у меня есть входной параметр «parent», поэтому, если я полностью изменю parent, обнаружение изменений сработает, но если я изменю parent.name, не будет?
Показать ещё 2 комментария
1

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

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

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

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

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

0

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

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

});

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

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

  changeDetection: ChangeDetectionStrategy.OnPush
0
0

Вы также можете иметь наблюдаемое, которое инициирует изменения в родительском компоненте (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
});
}

Ещё вопросы

Сообщество Overcoder
Наверх
Меню