Reputation: 3243
I know why this error is thrown, but I don't understand how to organize my code to fix it. Here is the issue
@Component({
selector: 'article',
templateUrl: 'article.html',
moduleId: module.id,
directives: [Toolbar]
})
export class Article {
public toolbar: Array<IToolbarItem>;
constructor() {
this.toolbar = [
{
css: 'ic-save',
action: (item) => { },
visible: false
},
<IDropdownItem>{
css: 'ic-edit',
items: [
{
css: 'ic-edit',
label: 'Edit Article',
action: (item) => { }
},
{
css: 'ic-edit',
label: 'Edit Content',
action: (item) => {
this.toolbar[0].visible = true;
}
}
]
}
];
}
}
and the toolbar component and template
@Component({
selector: 'toolbar',
moduleId: module.id,
templateUrl: 'toolbar.html',
styleUrls: ['toolbar.css'],
pipes: [VisiblePipe],
encapsulation: ViewEncapsulation.None
})
export class Toolbar {
@Input() items: Array<IToolbarItem>;
}
<div class="container">
<div class="toolbar">
<div class="toolbar-item" *ngFor="#i of (items | visible)">
.
.
.
and finally the VisiblePipe
pipe
@Pipe({
name: 'visible',
pure: false
})
export class VisiblePipe implements PipeTransform {
transform(value) {
return (<Array<any>>value).filter(v => v.visible !== false);
}
}
So article component uses the toolbar component to which the toolbar array is passed, this is in turn uses the visible pipe to filter out items which have the visible property set to false.
When the VisiblePipe
pipe is run, the error is thrown. So for some reason the pipe transform code is running after the change detection? Why?
Edit
So I've updated my VisiblePipe
pipe as per the suggestion by Gunter, and it is working
export class VisiblePipe implements PipeTransform {
private previousValue: Array<IVisibleItem>;
private cacheResult: Array<IVisibleItem>;
transform(value: Array<IVisibleItem>) {
if (!this.previousValue || !compareArrays(this.previousValue, value)) {
this.previousValue = value.map(i => { return { visible: i.visible } });
this.cacheResult = value.filter(v => v.visible !== false);
}
return this.cacheResult;
}
}
function compareArrays(arrayOne: Array<IVisibleItem>, arrayTwo: Array<IVisibleItem>) {
if (arrayOne.length !== arrayTwo.length) {
return false;
}
for (let i = 0, l = arrayOne.length; i < l; i++) {
let arrayOneEntry = arrayOne[i];
let arrayTwoEntry = arrayTwo[i];
if (arrayOneEntry.visible !== undefined &&
arrayTwoEntry.visible !== undefined &&
arrayOneEntry.visible !== arrayTwoEntry.visible) {
return false;
}
}
return true;
}
interface IVisibleItem {
visible: boolean
}
is this really the best/only way? It feels like I'm handling some aspect of the change detection myself!
Upvotes: 3
Views: 1721
Reputation: 657318
The error is caused because in devMode
Angular runs change detection twice for each turn and your pipe returns a different array instance for two subsequent calls even though the input value hasn't changes.
This is not even "allowed" when is set to pure: false
.
To fix it ensure your pipe returns the same array instance for subsequent calls when the input didn't change.
@Pipe({
name: 'visible',
pure: false
})
export class VisiblePipe implements PipeTransform {
cached:any;
transform(value) {
if(value == this.value && this.resultCached) {
return this.resultCached;
}
this.value = value;
this.resultCached = (<Array<any>>value).filter(v => v.visible !== false);
return this.resultCached;
}
}
If the array can be modified from the outside (without creating a new instance) then you need to take care of this as well. You then need to check if the contents of the array has changed since the last call.
You can use the IterableDiffers to check if items in the array were added or removed or replaced. This still doesn't cover property changes in the items contained in the array.
Upvotes: 3