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

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

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

类别组件.html

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

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

在AngularJS中,我会对变量进行$watch。处理此问题的最佳方法是什么?


当前回答

如果您正在处理使用@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;

其他回答

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

ngOnChanges在数据绑定财产在检查视图和内容子项之前,如果至少其中一个已经改变了。

这是文件。

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

允许消费者利用RXJS的力量比目前提出的其他解决方案更紧凑比使用ngOnChanges()更安全您可以通过这种方式观察任何类字段。

示例用法:

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

constructor() {
  this.fields.str.subscribe(s => console.log(s));
}

实用程序功能:

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]>> }>;
};

Demo

您还可以在父组件(CategoryComponent)中的更改时触发一个可观察值,并在子组件的子属性中执行您想要执行的操作。(视频列表组件)

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

此处ngOnChanges将在输入属性更改时始终触发:

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

实际上,当输入以角度2+表示的子分量发生变化时,有两种方法可以检测和处理:

您可以使用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演示示例:看看这个punker

或者,也可以使用输入属性设置器,如下所示:

    private _categoryId: string;
    
    @Input() set categoryId(value: string) {
    
       this._categoryId = value;
       this.doSomething(this._categoryId);
    
    }
    
    get categoryId(): string {
    
        return this._categoryId;
    
    }

文档链接:查看此处。

演示示例:看看这个punker。

您应该使用哪种方法?

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

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

编辑2017-07-25:在某些情况下,角度变化检测可能仍然无法启动

通常,每当父组件更改传递给子组件的数据时,setter和ngOnChanges的更改检测都会启动,前提是数据是JS原始数据类型(字符串、数字、布尔值)。然而,在以下场景中,它不会启动,您必须采取额外的行动才能使其工作。

如果您使用嵌套对象或数组(而不是JS基元数据类型)将数据从Parent传递到Child,则更改检测(使用setter或ngchanges)可能不会启动,正如用户在回答中提到的:muetzerich。有关解决方案,请参阅此处。如果您在角度上下文之外(即外部)更改数据,那么角度将不知道这些更改。您可能必须在组件中使用ChangeDetectorRef或NgZone,以便对外部变化进行角度感知,从而触发变化检测。请参阅。