角度2: 如何检测数组中的变化? (@input 属性)

我有一个使用 ajax 请求检索对象数组的父组件。

该组件有两个子组件: 一个以树结构显示对象,另一个以表格格式呈现其内容。父节点通过@input 属性将数组传递给子节点,并正确显示内容。一切都在预料之中。

当您更改对象中的某些字段时会出现问题: 没有通知子组件这些更改。只有在手动将数组重新分配给其变量时才会触发更改。

我习惯于使用 Knokout JS,我需要得到一个类似于可观察数组的效果。

我读过一些关于 DoCheck 的东西,但我不确定它是如何工作的。

110981 次浏览

You can use IterableDiffers

It's used by *ngFor

constructor(private _differs: IterableDiffers) {}


ngOnChanges(changes: SimpleChanges): void {
if (!this._differ && value) {
this._differ = this._differs.find(value).create(this.ngForTrackBy);
}
}


ngDoCheck(): void {
if (this._differ) {
const changes = this._differ.diff(this.ngForOf);
if (changes) this._applyChanges(changes);
}
}

OnChanges Lifecycle Hook will trigger only when input property's instance changes.

If you want to check whether an element inside the input array has been added, moved or removed, you can use IterableDiffers inside the DoCheck Lifecycle Hook as follows:

constructor(private iterableDiffers: IterableDiffers) {
this.iterableDiffer = iterableDiffers.find([]).create(null);
}


ngDoCheck() {
let changes = this.iterableDiffer.diff(this.inputArray);
if (changes) {
console.log('Changes detected!');
}
}

If you need to detect changes in objects inside an array, you will need to iterate through all elements, and apply KeyValueDiffers for each element. (You can do this in parallel with previous check).

Visit this post for more information: Detect changes in objects inside array in Angular2

Read following article, don't miss mutable vs immutable objects.

Key issue is that you mutate array elements, while array reference stays the same. And Angular2 change detection checks only array reference to detect changes. After you understand concept of immutable objects you would understand why you have an issue and how to solve it.

I use redux store in one of my projects to avoid this kind of issues.

https://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html

It's work for me:

@Component({
selector: 'my-component',
templateUrl: './my-component.component.html',
styleUrls: ['./my-component.component.scss']
})
export class MyComponent implements DoCheck {


@Input() changeArray: MyClassArray[]= [];
private differ: IterableDiffers;


constructor(private differs: IterableDiffers) {
this.differ = differs;
}


ngDoCheck() {
const changes = this.differ.find(this.insertedTasks);
if (changes) {
this.myMethodAfterChange();
}
}

You can always create a new reference to the array by merging it with an empty array:

this.yourArray = [{...}, {...}, {...}];
this.yourArray[0].yourModifiedField = "whatever";


this.yourArray = [].concat(this.yourArray);

The code above will change the array reference and it will trigger the OnChanges mechanism in children components.

This already appears answered. However for future problem seekers, I wanted to add something missed when I was researching and debugging a change detection problem I had. Now, my issue was a little isolated, and admittedly a stupid mistake on my end, but nonetheless relevant. When you are updating the values in the Array or Object in reference, ensure that you are in the correct scope. I set myself into a trap by using setInterval(myService.function, 1000), where myService.function() would update the values of a public array, I used outside the service. This never actually updated the array, as the binding was off, and the correct usage should have been setInterval(myService.function.bind(this), 1000). I wasted my time trying change detection hacks, when it was a silly/simple blunder. Eliminate scope as a culprit before trying change detection solutions; it might save you some time.

You can use an impure pipe if you are directly using the array in your components template. (This example is for simple arrays that don't need deep checking)

@Pipe({
name: 'arrayChangeDetector',
pure: false
})
export class ArrayChangeDetectorPipe implements PipeTransform {
private differ: IterableDiffer<any>;


constructor(iDiff: IterableDiffers) {
this.differ = iDiff.find([]).create();
}


transform(value: any[]): any[] {
if (this.differ.diff(value)) {
return [...value];
}
return value;
}
}
<cmp [items]="arrayInput | arrayChangeDetector"></cmp>

For those time travelers among us still hitting array problems here is a reproduction of the issue along with several possible solutions.

https://stackblitz.com/edit/array-value-changes-not-detected-ang-8

Solutions include:

  • NgDoCheck
  • Using a Pipe
  • Using Immutable JS NPM github

Instead of triggering change detection via concat method, it might be more elegant to use ES6 destructuring operator:

this.yourArray[0].yourModifiedField = "whatever";
this.yourArray = [...this.yourArray];