705

我有一个父组件 ( CategoryComponent )、一个子组件 ( videoListComponent ) 和一个 ApiService。

我的大部分工作都很好,即每个组件都可以访问 json api 并通过 observables 获取其相关数据。

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

类别组件.html

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

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

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

4

16 回答 16

1126

实际上,当 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
演示示例:看看这个 plunker

  1. 或者,您还可以使用输入属性设置器,如下所示:
    private _categoryId: string;
    
    @Input() set categoryId(value: string) {
    
       this._categoryId = value;
       this.doSomething(this._categoryId);
    
    }
    
    get categoryId(): string {
    
        return this._categoryId;
    
    }

文档链接:看这里

演示示例:看看这个 plunker

您应该使用哪种方法?

如果您的组件有多个输入,那么,如果您使用 ngOnChanges(),您将在 ngOnChanges() 中一次获得所有输入的所有更改。使用这种方法,您还可以比较已更改的输入的当前值和以前的值,并采取相应的措施。

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

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

通常,只要父组件更改它传递给子组件的数据,setter 和 ngOnChanges 的更改检测就会触发,前提是数据是 JS 原始数据类型(string, number, boolean)。但是,在以下情况下,它不会触发,您必须采取额外的措施才能使其工作。

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

  2. 如果您在角度上下文之外(即外部)改变数据,那么角度将不知道这些变化。您可能必须在组件中使用 ChangeDetectorRef 或 NgZone 以从角度感知外部变化,从而触发变化检测。参考这个

于 2017-06-21T20:53:35.733 回答
119

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

ngOnChanges 在数据绑定属性被检查之后和视图和内容子元素被检查之前被调用,如果它们中的至少一个已经改变。

这是文档

于 2016-07-25T15:31:56.297 回答
53

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

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

编辑:

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

ngOnChanges(changes: SimpleChanges) {
    console.log(changes['myInput'].currentValue);
}
于 2016-10-06T19:47:43.597 回答
19

最安全的选择是使用共享服务而不是参数@Input。此外,@Input参数不会检测复杂嵌套对象类型的变化。

一个简单的示例服务如下:

服务.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);
    }

}

组件.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);
  }
}

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

于 2017-11-30T11:24:50.747 回答
19
@Input() set categoryId(categoryId: number) {
      console.log(categoryId)
}

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

于 2018-12-12T12:47:02.753 回答
8

我只想补充一点,如果值不是原始值,还有另一个 Lifecycle 钩子DoCheck很有用。@Input

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

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

于 2017-08-14T11:09:06.463 回答
6

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

ngOnChanges(changes: SimpleChanges): void {
 console.log(changes.categoryId.currentValue)
}
于 2019-04-15T11:06:53.367 回答
6

角 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 文档

于 2021-08-10T17:40:48.060 回答
5

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

服务.ts

public categoryChange$ : ReplaySubject<any> = new ReplaySubject(1);

类别组件.ts

public onCategoryChange(): void {
  service.categoryChange$.next();
}

视频列表组件.ts

public ngOnInit(): void {
  service.categoryChange$.subscribe(() => {
   // do your logic
  });
}
于 2017-09-19T22:39:31.023 回答
3

此解决方案使用代理类并提供以下优势:

  • 允许消费者利用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;
}
于 2020-04-20T18:30:41.823 回答
2

你也可以只传递一个 EventEmitter 作为输入。不太确定这是否是最佳实践...

类别组件.ts:

categoryIdEvent: EventEmitter<string> = new EventEmitter<>();

- OTHER CODE -

setCategoryId(id) {
 this.category.id = id;
 this.categoryIdEvent.emit(this.category.id);
}

类别组件.html:

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

在 VideoListComponent.ts 中:

@Input() categoryIdEvent: EventEmitter<string>
....
ngOnInit() {
 this.categoryIdEvent.subscribe(newID => {
  this.categoryId = newID;
 }
}
于 2019-09-20T09:01:07.967 回答
1

您可以BehaviorSubject在外观服务中使用 a ,然后在任何组件中订阅该主题,并在发生事件时触发.next()对其进行数据调用的更改。确保在 on destroy 生命周期钩子中关闭这些订阅。

数据-api.facade.ts

@Injectable({
  providedIn: 'root'
})
export class DataApiFacade {

currentTabIndex: BehaviorSubject<number> = new BehaviorSubject(0);

}

一些.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();
}
于 2020-08-07T15:39:41.037 回答
1

基本上,两种建议的解决方案在大多数情况下都可以正常工作。我对ngOnChange()的主要负面体验是缺乏类型安全性。

在我的一个项目中,我做了一些重命名,之后一些魔术字符串保持不变,当然这个错误需要一些时间才能浮出水面。

Setter 没有这个问题:您的 IDE 或编译器会让您知道任何不匹配的情况。

于 2021-01-29T22:34:28.033 回答
1

您可以使用ngOnChanges() 生命周期方法

@Input() inputValue: string;

ngOnChanges(changes: SimpleChanges) {
    console.log(changes['inputValue'].currentValue);
}
于 2021-03-21T18:10:09.017 回答
0

如果不想使用ngOnChangeimplement ogonChange()方法,也可以通过valueChangesevent、ETC订阅特定item的变化。

myForm = new FormGroup({
  first: new FormControl(),
});

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

markForCheck()因为在这个声明中使用而写的:

changeDetection: ChangeDetectionStrategy.OnPush
于 2018-12-10T05:34:17.960 回答
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;
于 2021-07-13T22:12:00.943 回答