如何检测Angular中@Input()值何时发生变化?

Jon*_*ull 382 angular2-changedetection angular

我有一个父组件(CategoryComponent),一个子组件(videoListComponent)和一个ApiService.

我有大部分工作正常,即每个组件可以访问json api并通过observable获取其相关数据.

目前视频列表组件只是获取所有视频,我想将其过滤为特定类别中的视频,我通过将categoryId传递给子项来实现此目的@Input().

CategoryComponent.html

<video-list *ngIf="category" [categoryId]="category.id"></video-list>
Run Code Online (Sandbox Code Playgroud)

这有效,当父CategoryComponent类别更改时,categoryId值将通过via传递,@Input()但我需要在VideoListComponent中检测到这一点并通过APIService(使用新的categoryId)重新请求视频数组.

在AngularJS中,我会对$watch变量做一个.处理这个问题的最佳方法是什么?

Ala*_* S. 576

实际上,有两种方法可以在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

    }
Run Code Online (Sandbox Code Playgroud)

文档链接:ngOnChanges, SimpleChanges, SimpleChange
演示示例:看看这个plunker

  1. 或者,您也可以使用输入属性设置器,如下所示:
    private _categoryId: string;

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

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

    }

    get categoryId(): string {

        return this._categoryId;

    }
Run Code Online (Sandbox Code Playgroud)

文档链接:请看这里.

演示示例:看看这个plunker.

您应该使用哪种方法?

如果您的组件有多个输入,那么,如果您使用ngOnChanges(),您将在ngOnChanges()中一次性获得所有输入的所有更改.使用此方法,您还可以比较已更改的输入的当前值和先前值,并相应地执行操作.

但是,如果您只想在特定的单个输入发生更改时执行某些操作(并且您不关心其他输入),那么使用输入属性设置器可能会更简单.但是,此方法不提供内置方法来比较已更改输入的先前值和当前值(您可以使用ngOnChanges生命周期方法轻松完成).

编辑2017-07-25:角度变化检测可能在某些情况下仍然不会发生火灾

通常,只要父组件更改传递给子节点的数据,就会触发setter和ngOnChanges的更改检测,前提是数据是JS原始数据类型(字符串,数字,布尔值).但是,在以下情况下,它不会触发,您必须采取额外的操作才能使其工作.

  1. 如果您使用嵌套对象或数组(而不是JS原始数据类型)将数据从Parent传递给Child,则更改检测(使用setter或ngchanges)可能不会触发,如用户的答案中所述:muetzerich.对于解决方案看这里.

  2. 如果您正在改变角度上下文之外的数据(即外部),则angular将不知道更改.您可能必须在组件中使用ChangeDetectorRef或NgZone来进行角度感知外部更改,从而触发更改检测.参考这个.

  • 关于使用带有输入的setter的非常好的观点,我会将其更新为接受的答案,因为它更全面.谢谢. (6认同)
  • @trichetrice,只要父更改输入,就会调用 setter(set 方法)。此外,ngOnChanges() 也是如此。 (2认同)

mue*_*ich 95

ngOnChanges()在组件中使用生命周期方法.

在检查数据绑定属性之后以及在检查视图和内容子项(如果其中至少有一个已更改)之前立即调用ngOnChanges.

这是Docs.

  • 这在变量设置为新值时有效,例如`MyComponent.myArray = []`,但如果输入值被更改,例如`MyComponent.myArray.push(newValue)`,则不会触发`ngOnChanges()`.关于如何捕捉这种变化的任何想法? (6认同)
  • 当嵌套对象发生更改时,不会调用`ngOnChanges()`.也许你找到了解决方案[这里](/sf/ask/2435783101/) (4认同)

Dar*_*rcy 27

SimpleChanges在函数签名中使用类型时,我在控制台以及编译器和IDE中遇到错误.要防止出现错误,请any改为使用签名中的关键字.

ngOnChanges(changes: any) {
    console.log(changes.myInput.currentValue);
}
Run Code Online (Sandbox Code Playgroud)

编辑:

正如Jon在下面指出的那样,您可以SimpleChanges在使用括号表示法而不是点符号时使用签名.

ngOnChanges(changes: SimpleChanges) {
    console.log(changes['myInput'].currentValue);
}
Run Code Online (Sandbox Code Playgroud)

  • 你试过这个吗?`ngOnChanges(changes: {[propName: string]: SimpleChange}) { console.log('onChanges - myProp = ' +changes['myProp'].currentValue); }` (2认同)

Tha*_*han 16

角度 ngOnChanges

ngOnChanges()是一种内置的 Angular 回调方法,在默认更改检测器检查数据绑定属性(如果至少有一个已更改)后立即调用。在查看和内容之前,先检查孩子们。

// child.component.ts

import { Component, OnInit, Input, SimpleChanges, OnChanges } from '@angular/core';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit, OnChanges {

  @Input() inputParentData: any;

  constructor() { }

  ngOnInit(): void {
  }

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

}
Run Code Online (Sandbox Code Playgroud)

了解更多:Angular 文档


Aks*_*put 15

@Input() set categoryId(categoryId: number) {
      console.log(categoryId)
}
Run Code Online (Sandbox Code Playgroud)

请尝试使用此方法。希望这可以帮助


Ste*_*evo 8

我只想补充一点DoCheck,如果该@Input值不是原始值,那么还有一个称为 Lifecycle 的钩子很有用。

我有一个数组,Input所以OnChanges当内容更改时这不会触发事件(因为 Angular 所做的检查是“简单”而不是深入的,因此即使 Array 上的内容已更改,该数组仍然是一个数组)。

然后我实现一些自定义检查代码来决定是否要使用更改后的数组更新我的视图。


San*_*rde 8

最安全的办法就是与共享服务,而不是一个@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);
    }

}
Run Code Online (Sandbox Code Playgroud)

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

您可以在其他组件中使用类似的实现,并且您的所有组件将共享相同的共享值.

  • 谢谢桑克特。这是一个非常好的观点,并且在适当的情况下是更好的方法。我将保留已接受的答案,因为它回答了我有关检测 @Input 更改的具体问题。 (2认同)

Ale*_*der 8

我会坚持使用 @alan-cs 建议的方法,但需要进行一些修改。首先 - 我反对使用ngOnChanges. 相反,我建议将所有需要更改的内容移至一个对象下。并使用BehaviorSubject来跟踪它的变化:

  private location$: BehaviorSubject<AbxMapLayers.Location> = new BehaviorSubject<AbxMapLayers.Location>(null);

  @Input()
  set location(value: AbxMapLayers.Location) {
    this.location$.next(value);
  }
  get location(): AbxMapLayers.Location {
    return this.location$.value;
  }

<abx-map-layer
    *ngIf="isInteger(unitForm.get('addressId').value)"
    [location]="{
      gpsLatitude: unitForm.get('address.gpsLatitude').value,
      gpsLongitude: unitForm.get('address.gpsLongitude').value,
      country: unitForm.get('address.country').value,
      placeName: unitForm.get('address.placeName').value,
      postalZip: unitForm.get('address.postalZip').value,
      streetName: unitForm.get('address.streetName').value,
      houseNumber: unitForm.get('address.houseNumber').value
    }"
    [inactive]="unitAddressForm.disabled"
    >
</abx-map-layer>
Run Code Online (Sandbox Code Playgroud)


Gan*_*ade 8

export class ChildComponent implements OnChanges {
  @Input() categoryId: string;

  ngOnChanges(changes: SimpleChanges) {
    if (changes.categoryId) { // also add this check 
      console.log('Input data changed:', this.categoryId);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

当输入更改时,changeDetection 会调用 ngOnChanges。更改:SimpleChanges 对象已完成所有更改。检查categoryId 是否是发生的更改之一。如果是,请做您需要做的事情。


小智 6

这里 ngOnChanges 将始终在您的输入属性更改时触发:

ngOnChanges(changes: SimpleChanges): void {
 console.log(changes.categoryId.currentValue)
}
Run Code Online (Sandbox Code Playgroud)


Jos*_*osf 5

您还可以拥有一个可在父组件中触发更改的可观察对象,component(CategoryComponent)并在子组件的订阅中执行您想要执行的操作。( videoListComponent)

服务.ts

public categoryChange$ : ReplaySubject<any> = new ReplaySubject(1);
Run Code Online (Sandbox Code Playgroud)

类别组件.ts

public onCategoryChange(): void {
  service.categoryChange$.next();
}
Run Code Online (Sandbox Code Playgroud)

视频列表组件.ts

public ngOnInit(): void {
  service.categoryChange$.subscribe(() => {
   // do your logic
  });
}
Run Code Online (Sandbox Code Playgroud)


Ste*_*aul 5

该解决方案使用代理类并具有以下优点:

  • 允许消费者利用RXJS的强大功能
  • 比迄今为止提出的其他解决方案更紧凑
  • 比 using更安全ngOnChanges()
  • 您可以通过这种方式观察任何类字段。

用法示例:

@Input()
num: number;
@Input()
str: number;
fields = observeFields(this); // <- call our utility function

constructor() {
  this.fields.str.subscribe(s => console.log(s));
}
Run Code Online (Sandbox Code Playgroud)

实用功能:

import { BehaviorSubject, Observable, shareReplay } from 'rxjs';

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

export const observeFields = <T extends object>(target: T) => {
  const subjects = {} as { [key: string]: Observable<any> };
  return new Proxy(target, {
    get: (t, prop: string) => {
      if (subjects[prop]) { return subjects[prop]; }
      return subjects[prop] = observeField(t, prop as keyof T).pipe(
        shareReplay({refCount: true, buffer:1}),
      );
    }
  }) as Required<{ [key in keyof T]: Observable<NonNullable<T[key]>> }>;
};
Run Code Online (Sandbox Code Playgroud)

演示


jxn*_*jxn 5

所有答案似乎都不是最佳解决方案。即使它们最终起作用,它们也过于复杂。

您需要做的就是在输入变量上使用 get-set 模式(也称为“自动属性”),如下所示:

    ...
    private _categoryId: number;

    @Input()
    set categoryId(value: number) {
      this._categoryId = value;
      // Do or call whatever you want when this input value changes here
    }
    get categoryId(): number {
      return this._categoryId;
    }
    ...
Run Code Online (Sandbox Code Playgroud)

然后代码中的其他任何地方都可以引用 this,this.categoryId因为在 TypeScript 中引用 getter 时会自动调用 getter。

进一步阅读: