Як виявити, коли значення @Input () змінюється на Angular?


469

У мене є батьківський компонент ( CategoryComponent ), дочірній компонент ( videoListComponent ) та ApiService.

У мене більша частина цього працює добре, тобто кожен компонент може отримати доступ до json api та отримати відповідні дані за допомогою спостережуваних даних.

В даний час компонент списку відео просто отримує всі відео, я хотів би відфільтрувати це лише до відео в певній категорії, я досяг цього, передавши категоріюId дитині через @Input().

CategoryComponent.html

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

Це працює, і коли батьківська категорія CategoryComponent змінюється, тоді через нього передається значення categoryId, @Input()але мені потрібно виявити це у VideoListComponent та повторно запитати масив відео через APIService (з новою категорієюIdId).

У AngularJS я зробив би $watchзмінну. Який найкращий спосіб впоратися з цим?



дивіться зміни вхідних даних: - angulartutorial.net/2017/12/watch-input-changes-angular-4.html
Prashobh

для зміни масиву: stackoverflow.com/questions/42962394 / ...
dasfdsa

Відповіді:


718

Насправді, існує два способи виявлення та дії, коли вхід змінюється дочірньою складовою в 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
Demo Приклад: Подивіться на цей планкер

  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: АНГУЛЯРНЕ ВИДАЛЕННЯ ЗМІНИ МОЖЕ НЕ ПОЖЕЖИТЬСЯ ДІЯЛЬКИХ ОКРУГИ

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

  1. Якщо ви використовуєте вкладений об'єкт або масив (замість примітивного типу даних JS) для передачі даних від батьків до дитини, виявлення змін (використовуючи сетер або ngchanges) може не спрацювати, як це також було зазначено у відповіді користувача: muetzerich. Рішення шукайте тут .

  2. Якщо ви мутуєте дані за межами кутового контексту (тобто зовні), то кутовий не буде знати про зміни. Можливо, вам доведеться використовувати ChangeDetectorRef або NgZone у своєму компоненті для того, щоб дізнатись про зовнішні зміни і тим самим викликати виявлення змін. Зверніться до цього .


8
Дуже хороший момент щодо використання сетерів з введеннями, я оновлю це, щоб прийняти відповідь, оскільки воно є більш повним. Дякую.
Джон Кетмулл

2
@trichetriche, сеттер (метод встановлення) буде викликатися щоразу, коли батьків змінить вхід. Також те ж саме стосується і ngOnChanges ().
Алан CS

Ну, мабуть, ні ... Я спробую це зрозуміти, це, мабуть, щось не так.

1
Я щойно натрапив на це запитання, зазначив, що ви сказали, що використання сетера не дозволить порівнювати значення, однак це неправда, ви можете зателефонувати своєму doSomethingметоду і взяти 2 аргументи нових і старих значень, перш ніж фактично встановити нове значення, іншим способом буде зберігати старе значення перед встановленням і викликати метод після цього.
Т.Аукар

1
@ T.Aoukar Що я говорю, це те, що сеттер введення не підтримує порівнювати старі / нові значення, як, наприклад, ngOnChanges (). Ви завжди можете використовувати хаки для зберігання старого значення (як ви згадували), щоб порівняти його з новим значенням.
Алан CS

105

Використовуйте ngOnChanges()метод життєвого циклу у своєму компоненті.

ngOnChanges викликається відразу після перевірки властивостей, пов'язаних з даними, і перед тим, як перевіряти і переглядати діти та вміст, якщо хоча б один із них змінився.

Ось Документи .


6
Це працює, коли для змінної встановлено нове значення, наприклад MyComponent.myArray = [], але якщо вхідне значення змінено, наприклад MyComponent.myArray.push(newValue), ngOnChanges()не спрацьовує. Будь-які ідеї про те, як зафіксувати цю зміну?
Леві Фуллер

4
ngOnChanges()не викликається, коли вкладений об'єкт змінився. Можливо, ви знайдете тут
muetzerich

33

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

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

Редагувати:

Як зазначав Джон нижче, ви можете використовувати SimpleChangesпідпис при використанні нотацій дужок, а не крапкових позначень.

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

1
Ви імпортували SimpleChangesінтерфейс у верхній частині файлу?
Джон Кетмулл

@Jon - Так. Проблема полягала не в самому підписі, а в доступі до параметрів, які призначаються під час виконання об'єкту SimpleChanges. Наприклад, у своєму компоненті я прив’язав свій клас користувача до вхідних даних (тобто <my-user-details [user]="selectedUser"></my-user-details>). Доступ до цього властивості здійснюється з класу SimpleChanges, зателефонувавши changes.user.currentValue. Повідомлення userпов'язане під час виконання, а не є частиною об'єкта SimpleChanges
Дарсі

1
Ви пробували це хоч? ngOnChanges(changes: {[propName: string]: SimpleChange}) { console.log('onChanges - myProp = ' + changes['myProp'].currentValue); }
Джон Кетмулл

@Jon - Перехід на позначення дужок робить свою справу. Я оновив свою відповідь, щоб включити це як альтернативу.
Дарсі

1
імпорт {SimpleChanges} з '@ angular / core'; Якщо він знаходиться в кутових документах, а не в основному типі машинопису, то, ймовірно, це з кутового модуля, швидше за все, «кутовий / ядро»
BentOnCoding

13

Найбезпечніший вибір повинен піти із загальною службою замість частини з @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.name, він не буде?
йогібімбі

Ви повинні навести причину, чому ваше рішення є більш безпечним
Джошуа Кеммерер

1
@InputПараметр @JoshuaKemmerer не виявляє змін у складному вкладеному типі об'єктів, але це відбувається у простих нативних об’єктах.
Санкет Берде

6

Я просто хочу додати, що існує ще одна гачка життєвого циклу, яка називається, DoCheckщо корисно, якщо @Inputзначення не є примітивним значенням.

У мене є масив як Inputтакий, це не викликає OnChangesподії при зміні вмісту (тому що перевірка, яку робить Angular, є простою і не глибокою, тому масив все ще є масивом, навіть якщо вміст масиву змінився).

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


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

будь ласка, спробуйте скористатися цим методом. Сподіваюсь, це допомагає


4

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

2

Тут ngOnChanges запускається завжди, коли зміниться ваша властивість введення:

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

1

Це рішення використовує проксі- клас і пропонує такі переваги:

  • Дозволяє споживачеві використовувати сили RXJS
  • Більш компактні, ніж інші рішення, пропоновані до цих пір
  • Більш безпечний тип, ніж використанняngOnChanges()

Приклад використання:

@Input()
public num: number;
numChanges$ = observeProperty(this as MyComponent, 'num');

Утиліта:

export function observeProperty<T, K extends keyof T>(target: T, key: K) {
  const subject = new BehaviorSubject<T[K]>(target[key]);
  Object.defineProperty(target, key, {
    get(): T[K] { return subject.getValue(); },
    set(newValue: T[K]): void {
      if (newValue !== subject.getValue()) {
        subject.next(newValue);
      }
    }
  });
  return subject;
}

0

Якщо ви не хочете використовувати метод ngOnChange реалізації og onChange (), ви також можете підписатися на зміни певного елемента за допомогою події valueChanges , ETC.

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

});

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

маркаForCheck () написана через використання в цьому декларації:

  changeDetection: ChangeDetectionStrategy.OnPush

3
Це абсолютно відрізняється від питання, і хоча корисні люди повинні знати, що ваш код стосується іншого типу змін. Питання про зміну даних, що є від OUTSIDE компонента, а потім про те, щоб ваш компонент знав і відповідав на факт зміни даних. Ваша відповідь говорить про виявлення змін всередині самого компонента на таких речах, як входи у формі, яка є LOCAL для компонента.
rmcsharry

0

Ви також можете просто передати 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;
 }
}

Надайте корисні посилання, фрагменти коду для підтримки вашого рішення.
mishsx

Я додав приклад.
Седрик
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.