我有一个父组件( CategoryComponent ),一个子组件( videoListComponent )和一个ApiService。
我的大部分工作正常,即每个组件都可以访问json api并通过observable获取相关数据。
目前视频列表组件只是获取所有视频,我想将其过滤为特定类别中的视频,我通过@Input()
将categoryId传递给孩子来实现此目的。
CategoryComponent.html
<video-list *ngIf="category" [categoryId]="category.id"></video-list>
这有效,当父CategoryComponent类改变时,categoryId值通过@Input()
传递,但我需要在VideoListComponent中检测到这一点并通过APIService(使用新的categoryId)重新请求视频数组。
在AngularJS中,我会对变量做$watch
。处理这个问题的最佳方法是什么?
答案 0 :(得分:455)
实际上,有两种方法可以在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
}
文档链接:ngOnChanges, SimpleChanges, SimpleChange
演示示例:查看this plunker
private _categoryId: string;
@Input() set categoryId(value: string) {
this._categoryId = value;
this.doSomething(this._categoryId);
}
get categoryId(): string {
return this._categoryId;
}
文档链接:查看here。
演示示例:查看this plunker。
您应该使用哪种方式?
如果您的组件有多个输入,那么,如果您使用ngOnChanges(),您将在ngOnChanges()中一次性获得所有输入的所有更改。使用此方法,您还可以比较已更改的输入的当前值和先前值,并相应地执行操作。
但是,如果您只想在特定的单个输入发生更改时执行某些操作(并且您不关心其他输入),那么使用输入属性设置器可能会更简单。但是,此方法不提供内置方法来比较已更改输入的先前值和当前值(您可以使用ngOnChanges生命周期方法轻松完成此操作)。
编辑2017-07-25:角度变化检测可能在某些情况下仍未消除
通常,只要父组件更改传递给子项的数据,就会触发setter和ngOnChanges的更改检测,只要数据是JS原始数据类型(字符串,数字,布尔值) 。但是,在以下情况下,它不会触发,您必须采取额外的操作才能使其正常工作。
答案 1 :(得分:25)
在函数签名中使用SimpleChanges
类型时,我在控制台以及编译器和IDE中遇到错误。要防止出现错误,请改为使用签名中的any
关键字。
ngOnChanges(changes: any) {
console.log(changes.myInput.currentValue);
}
修改强>
正如Jon在下面指出的那样,使用括号表示法而不是点符号时,可以使用SimpleChanges
签名。
ngOnChanges(changes: SimpleChanges) {
console.log(changes['myInput'].currentValue);
}
答案 2 :(得分:8)
最安全的选择是使用@Input
参数的共享service instead。
此外,@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);
}
}
您可以在其他组件中使用类似的实现,并且您的所有组件将共享相同的共享值。
答案 3 :(得分:2)
我只想补充说,如果DoCheck
值不是原始值,那么另一个名为@Input
的Lifecycle钩子很有用。
我有一个数组作为Input
,因此当内容发生变化时,这不会触发OnChanges
事件(因为Angular所做的检查是简单的&#39;而不是很深即使数组中的内容已更改,Array仍然是一个数组。
然后,我实现了一些自定义检查代码,以决定是否要使用更改的Array更新我的视图。
答案 4 :(得分:2)
您还可以使用一个observable触发父组件(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
});
}
答案 5 :(得分:2)
此解决方案使用proxy类,并具有以下优点:
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;
}
答案 6 :(得分:1)
@Input()
public set categoryId(categoryId: number) {
console.log(categoryId)
}
请尝试使用此方法。希望这会有所帮助
答案 7 :(得分:1)
您可以在外观服务中使用BehaviorSubject
,然后在任何组件中订阅该主题,并在事件发生时触发其上的数据调用.next()
的更改。确保关闭on destroy生命周期挂钩中的那些订阅。
data-api.facade.ts
@Injectable({
providedIn: 'root'
})
export class DataApiFacade {
currentTabIndex: BehaviorSubject<number> = new BehaviorSubject(0);
}
some.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();
}
答案 8 :(得分:1)
基本上这两种建议的解决方案在大多数情况下都可以正常工作。 我对 ngOnChange() 的主要负面体验是缺乏类型安全。
在我的一个项目中,我进行了一些重命名,之后一些魔法字符串保持不变,当然这个错误需要一些时间才能浮出水面。
Setter 没有这个问题:您的 IDE 或编译器会让您知道任何不匹配。
答案 9 :(得分:1)
您可以使用 ngOnChanges() 生命周期方法
@Input() inputValue: string;
ngOnChanges(changes: SimpleChanges) {
console.log(changes['inputValue'].currentValue);
}
答案 10 :(得分:0)
如果您不想使用ngOnChange工具或onChange()方法,还可以通过 valueChanges事件(即ETC)订阅特定项目的更改。
myForm= new FormGroup({
first: new FormControl()
});
this.myForm.valueChanges.subscribe(formValue => {
this.changeDetector.markForCheck();
});
由于在此声明中使用而写的markForCheck():
changeDetection: ChangeDetectionStrategy.OnPush
答案 11 :(得分:0)
当输入属性更改时,ngOnChanges将始终触发:
ngOnChanges(changes: SimpleChanges): void {
console.log(changes.categoryId.currentValue)
}
答案 12 :(得分:0)
您也可以只传递一个EventEmitter作为输入。不太确定这是否是最佳做法, 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;
}
}
答案 13 :(得分:0)
如果您正在处理使用 @Input
在父组件和子组件之间共享数据的情况,您可以检测 @Input
> 使用生命周期方法更改数据:ngOnChanges
ngOnChanges(changes: SimpleChanges) {
if (!changes.categoryId.firstChange) {
// only logged upon a change after rendering
console.log(changes.categoryId.currentValue);
}
}
我建议您应该注意为子组件实施的更改策略,出于某些性能原因,您应该添加 ChangeDetectionStrategy.OnPush :
示例代码:
@Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class VideoListComponent implements OnChanges {
@Input() categoryId: string;
答案 14 :(得分:0)
Angular 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);
}
}
了解更多:Angular Docs