[英]How to detect when an @Input() value changes in Angular?
I have a parent component ( CategoryComponent ), a child component ( videoListComponent ) and an ApiService.我有一个父组件 ( CategoryComponent )、一个子组件 ( videoListComponent ) 和一个 ApiService。
I have most of this working fine ie each component can access the json api and get its relevant data via observables.我的大部分工作都很好,即每个组件都可以访问 json api 并通过 observables 获取其相关数据。
Currently video list component just gets all videos, I would like to filter this to just videos in a particular category, I achieved this by passing the categoryId to the child via @Input()
.目前视频列表组件只获取所有视频,我想将其过滤为特定类别中的视频,我通过
@Input()
将 categoryId 传递给孩子来实现这一点。
CategoryComponent.html类别组件.html
<video-list *ngIf="category" [categoryId]="category.id"></video-list>
This works and when the parent CategoryComponent category changes then the categoryId value gets passed through via @Input()
but I then need to detect this in VideoListComponent and re-request the videos array via APIService (with the new categoryId).这有效,当父 CategoryComponent 类别发生变化时,categoryId 值通过
@Input()
传递,但我需要在 VideoListComponent 中检测到这一点,并通过 APIService 重新请求视频数组(使用新的 categoryId)。
In AngularJS I would have done a $watch
on the variable.在 AngularJS 中,我会对变量执行
$watch
。 What is the best way to handle this?处理这个问题的最佳方法是什么?
Actually, there are two ways of detecting and acting upon when an input changes in the child component in angular2+ :实际上,当 angular2+ 的子组件中的输入发生变化时,有两种方法可以检测和采取行动:
@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
}
Documentation Links: ngOnChanges, SimpleChanges, SimpleChange文档链接: ngOnChanges、 SimpleChanges、 SimpleChange
Demo Example: Look at this plunker演示示例:看看这个 plunker
private _categoryId: string;
@Input() set categoryId(value: string) {
this._categoryId = value;
this.doSomething(this._categoryId);
}
get categoryId(): string {
return this._categoryId;
}
Documentation Link: Look here .文档链接:看这里。
Demo Example: Look at this plunker .演示示例:看看这个 plunker 。
WHICH APPROACH SHOULD YOU USE?您应该使用哪种方法?
If your component has several inputs, then, if you use ngOnChanges(), you will get all changes for all the inputs at once within ngOnChanges().如果您的组件有多个输入,那么,如果您使用 ngOnChanges(),您将在 ngOnChanges() 中一次获得所有输入的所有更改。 Using this approach, you can also compare current and previous values of the input that has changed and take actions accordingly.
使用这种方法,您还可以比较已更改的输入的当前值和以前的值,并采取相应的措施。
However, if you want to do something when only a particular single input changes (and you don't care about the other inputs), then it might be simpler to use an input property setter.但是,如果您只想在特定的单个输入更改时执行某些操作(并且您不关心其他输入),那么使用输入属性设置器可能会更简单。 However, this approach does not provide a built in way to compare previous and current values of the changed input (which you can do easily with the ngOnChanges lifecycle method).
但是,这种方法没有提供一种内置方法来比较更改输入的先前值和当前值(您可以使用 ngOnChanges 生命周期方法轻松完成)。
EDIT 2017-07-25: ANGULAR CHANGE DETECTION MAY STILL NOT FIRE UNDER SOME CIRCUMSTANCES编辑 2017-07-25:在某些情况下,角度变化检测可能仍然不会触发
Normally, change detection for both setter and ngOnChanges will fire whenever the parent component changes the data it passes to the child, provided that the data is a JS primitive datatype(string, number, boolean) .通常,只要父组件更改它传递给子组件的数据,setter 和 ngOnChanges 的更改检测就会触发,前提是数据是 JS 原始数据类型(string, number, boolean) 。 However, in the following scenarios, it will not fire and you have to take extra actions in order to make it work.
但是,在以下情况下,它不会触发,您必须采取额外的措施才能使其工作。
If you are using a nested object or array (instead of a JS primitive data type) to pass data from Parent to Child, change detection (using either setter or ngchanges) might not fire, as also mentioned in the answer by user: muetzerich.如果您使用嵌套对象或数组(而不是 JS 原始数据类型)将数据从父级传递给子级,则更改检测(使用 setter 或 ngchanges)可能不会触发,正如用户回答中提到的:muetzerich。 For solutions look here .
对于解决方案看这里。
If you are mutating data outside of the angular context (ie, externally), then angular will not know of the changes.如果您在角度上下文之外(即外部)改变数据,那么角度将不知道这些变化。 You may have to use ChangeDetectorRef or NgZone in your component for making angular aware of external changes and thereby triggering change detection.
您可能必须在组件中使用 ChangeDetectorRef 或 NgZone 以从角度感知外部变化,从而触发变化检测。 Refer to this .
参考这个。
Use the ngOnChanges()
lifecycle method in your component.在组件中使用
ngOnChanges()
生命周期方法。
ngOnChanges is called right after the data-bound properties have been checked and before view and content children are checked if at least one of them has changed.
ngOnChanges 在数据绑定属性被检查之后和视图和内容子元素被检查之前被调用,如果它们中的至少一个已经改变。
I was getting errors in the console as well as the compiler and IDE when using the SimpleChanges
type in the function signature.在函数签名中使用
SimpleChanges
类型时,我在控制台以及编译器和 IDE 中遇到错误。 To prevent the errors, use the any
keyword in the signature instead.为防止出现错误,请改用签名中的
any
关键字。
ngOnChanges(changes: any) {
console.log(changes.myInput.currentValue);
}
EDIT:编辑:
As Jon pointed out below, you can use the SimpleChanges
signature when using bracket notation rather than dot notation.正如 Jon 在下面指出的,您可以在使用括号表示法而不是点表示法时使用
SimpleChanges
签名。
ngOnChanges(changes: SimpleChanges) {
console.log(changes['myInput'].currentValue);
}
@Input() set categoryId(categoryId: number) {
console.log(categoryId)
}
please try using this method.请尝试使用此方法。 Hope this helps
希望这可以帮助
The safest bet is to go with a shared service instead of a @Input
parameter.最安全的选择是使用共享服务而不是
@Input
参数。 Also, @Input
parameter does not detect changes in complex nested object type.此外,
@Input
参数不会检测复杂嵌套对象类型的变化。
A simple example service is as follows:一个简单的示例服务如下:
Service.ts
服务.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
组件.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);
}
}
You can use a similar implementation in other components and all your compoments will share the same shared values.您可以在其他组件中使用类似的实现,并且您的所有组件都将共享相同的共享值。
Angular ngOnChanges角 ngOnChanges
The ngOnChanges()
is an inbuilt Angular callback method that is invoked immediately after the default change detector has checked data-bound properties if at least one has changed. ngOnChanges()
是一种内置的 Angular 回调方法,在默认更改检测器检查数据绑定属性(如果至少有一个已更改)后立即调用该方法。 Before the view and content, children are checked.在查看和内容之前,检查孩子。
// 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);
}
}
for more: Angular Docs更多信息: Angular 文档
I just want to add that there is another Lifecycle hook called DoCheck
that is useful if the @Input
value is not a primitive value.我只想补充一点,如果
@Input
值不是原始值,还有另一个名为DoCheck
的生命周期钩子很有用。
I have an Array as an Input
so this does not fire the OnChanges
event when the content changes (because the checking that Angular does is 'simple' and not deep so the Array is still an Array, even though the content on the Array has changed).我有一个数组作为
Input
,因此当内容更改时它不会触发OnChanges
事件(因为 Angular 所做的检查是“简单”且不深入,因此数组仍然是一个数组,即使数组上的内容已更改)。
I then implement some custom checking code to decide if I want to update my view with the changed Array.然后我实现了一些自定义检查代码来决定是否要使用更改后的数组来更新我的视图。
Here ngOnChanges will trigger always when your input property changes:当您的输入属性更改时,这里 ngOnChanges 将始终触发:
ngOnChanges(changes: SimpleChanges): void {
console.log(changes.categoryId.currentValue)
}
You can also , have an observable which triggers on changes in the parent component(CategoryComponent)
and do what you want to do in the subscribtion in the child component.您还可以拥有一个触发父
component(CategoryComponent)
更改的可观察对象,并在子组件的订阅中执行您想要执行的操作。 ( videoListComponent
) (
videoListComponent
)
service.ts服务.ts
public categoryChange$ : ReplaySubject<any> = new ReplaySubject(1);
CategoryComponent.ts类别组件.ts
public onCategoryChange(): void {
service.categoryChange$.next();
}
videoListComponent.ts视频列表组件.ts
public ngOnInit(): void {
service.categoryChange$.subscribe(() => {
// do your logic
});
}
I'd stick to approach, suggested by @alan-cs, but with some modifications.我会坚持采用@alan-cs 建议的方法,但要进行一些修改。 First - I'm against using
ngOnChanges
.首先 - 我反对使用
ngOnChanges
。 Instead, I propose to move all what needs to be changed under one object.相反,我建议将所有需要更改的内容移到一个对象下。 And use
BehaviorSubject
to track it changes:并使用
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>
You could also just pass an EventEmitter as Input.你也可以只传递一个 EventEmitter 作为输入。 Not quite sure if this is best practice tho...
不太确定这是否是最佳实践...
CategoryComponent.ts:类别组件.ts:
categoryIdEvent: EventEmitter<string> = new EventEmitter<>();
- OTHER CODE -
setCategoryId(id) {
this.category.id = id;
this.categoryIdEvent.emit(this.category.id);
}
CategoryComponent.html:类别组件.html:
<video-list *ngIf="category" [categoryId]="categoryIdEvent"></video-list>
And in VideoListComponent.ts:在 VideoListComponent.ts 中:
@Input() categoryIdEvent: EventEmitter<string>
....
ngOnInit() {
this.categoryIdEvent.subscribe(newID => {
this.categoryId = newID;
}
}
This solution uses a proxy class and offers the following advantages:此解决方案使用代理类并提供以下优势:
ngOnChanges()
ngOnChanges()
更安全Example usage:示例用法:
@Input()
public num: number;
numChanges$ = observeProperty(this as MyComponent, 'num');
Utility function:实用功能:
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;
}
You can use a BehaviorSubject
within a facade service then subscribe to that subject in any component and when an event happens to trigger a change in data call .next()
on it.您可以在外观服务中使用
BehaviorSubject
,然后在任何组件中订阅该主题,当事件发生时触发数据调用.next()
的更改。 Make sure to close out those subscriptions within the on destroy lifecycle hook.确保在 on destroy 生命周期钩子中关闭这些订阅。
data-api.facade.ts数据-api.facade.ts
@Injectable({
providedIn: 'root'
})
export class DataApiFacade {
currentTabIndex: BehaviorSubject<number> = new BehaviorSubject(0);
}
some.component.ts一些.component.ts
constructor(private dataApiFacade: DataApiFacade){}
ngOnInit(): void {
this.dataApiFacade.currentTabIndex
.pipe(takeUntil(this.destroy$))
.subscribe(value => {
if (value) {
this.currentTabIndex = value;
}
});
}
setTabView(event: MatTabChangeEvent) {
this.dataApiFacade.currentTabIndex.next(event.index);
}
ngOnDestroy() {
this.destroy$.next(true);
this.destroy$.complete();
}
You can use the ngOnChanges() lifecycle method您可以使用ngOnChanges() 生命周期方法
@Input() inputValue: string;
ngOnChanges(changes: SimpleChanges) {
console.log(changes['inputValue'].currentValue);
}
Basically both suggested solutions work fine in most cases.基本上,两种建议的解决方案在大多数情况下都可以正常工作。 My main negative experience with ngOnChange() is the lack of type safety.
我对ngOnChange()的主要负面体验是缺乏类型安全性。
In one of my projects I did some renaming, following which some of the magic strings remained unchanged, and the bug of course took some time to surface.在我的一个项目中,我做了一些重命名,之后一些魔术字符串保持不变,当然这个错误需要一些时间才能浮出水面。
Setters do not have that issue: your IDE or compiler will let you know of any mismatch. Setter 没有这个问题:您的 IDE 或编译器会让您知道任何不匹配的情况。
If you don't want use ngOnChange
implement og onChange()
method, you can also subscribe to changes of a specific item by valueChanges
event , ETC.如果您不想使用
ngOnChange
实现 og onChange()
方法,您也可以通过valueChanges
事件等订阅特定项目的更改。
myForm = new FormGroup({
first: new FormControl(),
});
this.myForm.valueChanges.subscribe((formValue) => {
this.changeDetector.markForCheck();
});
the markForCheck()
writen because of using in this declare:由于在此声明中使用而编写的
markForCheck()
:
changeDetection: ChangeDetectionStrategy.OnPush
If you're dealing with the case that your are using @Input
to share data between parent and child component, you can detect @Input
data changes by using the lifecycle method: ngOnChanges
如果您正在处理使用
@Input
在父组件和子组件之间共享数据的情况,您可以使用生命周期方法检测@Input
数据更改: ngOnChanges
ngOnChanges(changes: SimpleChanges) {
if (!changes.categoryId.firstChange) {
// only logged upon a change after rendering
console.log(changes.categoryId.currentValue);
}
}
And I'm advising that you should care of the change strategy implemented for the child component you should add ChangeDetectionStrategy.OnPush for some performance reason :而且我建议您应该注意为子组件实施的更改策略,出于某种性能原因,您应该添加 ChangeDetectionStrategy.OnPush :
Example-Code :示例代码:
@Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class VideoListComponent implements OnChanges {
@Input() categoryId: string;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.