angular2 更改检测 - 如何检测 @Input() 值何时在 Angular 中更改



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

我的大部分工作正常,即每个组件都可以访问 json api 并通过可观察量获取其相关数据。

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

类别组件.html

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

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

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

实际上,有两种方法可以检测和处理 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(( 中一次获得所有输入的所有更改。使用此方法,您还可以比较已更改的输入的当前值和先前值,并采取相应的操作。

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

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

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

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

  2. 如果您在角度上下文之外(即外部(更改数据,则角度将不知道这些变化。您可能必须在组件中使用 ChangeDetectorRef 或 NgZone 来感知外部更改,从而触发更改检测。请参阅此处。

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

ngOnChanges 是在数据绑定属性被调用后立即调用的 选中并在查看和内容之前选中子项,如果至少 其中一个发生了变化。

以下是文档。

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

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

编辑:

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

ngOnChanges(changes: SimpleChanges) {
    console.log(changes['myInput'].currentValue);
}
@Input() set categoryId(categoryId: number) {
      console.log(categoryId)
}

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

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

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

服务网

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);
  }
}
<小时 />

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

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

更多信息: 角度文档

我只想补充一点,还有另一个名为 DoCheck 的生命周期钩子,如果@Input值不是基元值,它很有用。

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

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

我会坚持@alan-c-s建议的方法,但有一些修改。首先 - 我反对使用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>

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

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

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

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

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

  • 允许消费者利用 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]>> }>;
};

演示

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

当输入发生更改时,更改检测将调用ngOnChanges。 更改:SimpleChanges 对象已完成所有更改。检查 categoryId 是否是发生的更改之一。如果是,请执行您需要执行的操作。

您也可以将事件发射器作为输入传递。不太确定这是否是最佳实践...

CategoryComponent.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;
 }
}
似乎

没有一个答案是最好的解决方案。即使它们最终起作用,它们也过于复杂。

您需要做的就是在输入变量上使用 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;
    }
    ...

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

延伸阅读:

  • https://www.typescripttutorial.net/typescript-tutorial/typescript-getters-setters/
  • https://dev.to/deerawan/3-different-ways-to-use-input-decorator-4b3c
  • https://angular.io/guide/component-interaction#intercept-input-property-changes-with-a-setter

您可以在门面服务中使用BehaviorSubject,然后在任何组件中订阅该主题,并在事件发生时触发数据调用更改.next()。请确保在销毁生命周期挂钩内关闭这些订阅。

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();
}

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

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

设置器没有这个问题:您的 IDE 或编译器会通知您任何不匹配。

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

@Input() inputValue: string;
ngOnChanges(changes: SimpleChanges) {
    console.log(changes['inputValue'].currentValue);
}
如果您

正在处理使用@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;
 @Input() public set categoryId(id: number) {
    if (id) {
    console.log(id)
    }
 }

如果你不想使用ngOnChange实现 og onChange() 方法,你也可以通过valueChanges事件等订阅特定项目的更改。

myForm = new FormGroup({
  first: new FormControl(),
});
this.myForm.valueChanges.subscribe((formValue) => {
  this.changeDetector.markForCheck();
});

markForCheck()写是因为在此声明中使用:

changeDetection: ChangeDetectionStrategy.OnPush

相关内容

  • 没有找到相关文章

最新更新