Dev error occurs due to a change in Angular2 pipe causing the message "has changed after it was checked"

I understand the reason for this error being thrown, but I am struggling with organizing my code to resolve it. Here is the problem:

@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 here is the toolbar component along with its 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)">
        .
        .
        .

Lastly, let's take a look at the VisiblePipe pipe:

@Pipe({
    name: 'visible',
    pure: false
})
export class VisiblePipe implements PipeTransform {
    transform(value) {
        return (<Array<any>>value).filter(v => v.visible !== false);
    }
}

The article component uses the toolbar component to which the toolbar array is passed, and then the visible pipe filters out items with the visible property set to false.

An error occurs when the VisiblePipe pipe runs. It seems like the pipe transform code is executing after the change detection process. Why could that be?

Edit

Following Gunter's suggestion, I have made updates to my VisiblePipe pipe and now it's functioning correctly:

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 truly the best or only way? It feels like I'm taking control of some part of the change detection process myself!

Answer №1

An issue arises due to the fact that in devMode, Angular performs change detection twice for each turn, and your pipe generates a different array instance for consecutive calls even when the input value remains unchanged. Even when set to pure: false, this behavior is not ideal.

To resolve this error, ensure that your pipe consistently returns the same array instance for subsequent calls when the input has not changed.

@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 externally modified (without creating a new instance), then you must handle this scenario as well. You will need to verify if the contents of the array have changed since the last call.

You can utilize the IterableDiffers to determine if items have been added, removed, or replaced within the array. However, this approach does not address changes to properties within the array's items.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Utilizing Angular 6 mergeMap for handling nested API requests

My goal is to retrieve a list of clients along with their accounts using the observe/subscribe pattern. Each client should have a list of their accounts associated with their client id. This is how I attempted it: this.httpService.getClients().subscribe( ...

How can I turn off automatic ellipsis on my IOS device?

Currently, I am working on a web application that involves displaying location descriptions retrieved from an API. The issue I am encountering is that the description is being cut off with an ellipsis after a certain number of lines when viewed on an iPhon ...

Importing SCSS files dynamically with TypeScript

Recently, I utilized Create React App (CRA) to create a new project and then included node-sass in order to import SCSS files. An example: import "./App.scss"; Although this method works without any issues, I encountered a problem when trying t ...

The ion-list will only load image URLs once the height has been established

I've been dealing with a technical issue that I don't have much experience in, and I'm struggling to find a solution. While I now understand the root cause of the problem, I am unsure how to resolve it. The webpage I am working on sends a r ...

Travis CI's TypeScript build process detects errors before Mocha has a chance to catch them

Instead of a bug, the TypeScript compiler is doing its job but causing my Travis builds to fail. In my package, I have a function named completeRound which accepts a number as its first argument and 3 optional arguments. Since it's in TypeScript, I s ...

The SunEditor onChange event does not reflect updated state information

software version next: 12.0.7 suneditor: 2.41.3 suneditor-react: 3.3.1 const SunEditor = dynamic(() => import("suneditor-react"), { ssr: false, }); import "suneditor/dist/css/suneditor.min.css"; // Import Sun Editor's CSS Fi ...

The 'undefined' type cannot be assigned to the '(number | null)[]' type

I recently encountered an issue with the following code snippet: const data = values?.map((item: PointDTO) => item.y); const chartData: ChartData = { labels, datasets: [{ data }], }; The error message I received is as follows: Type '(number | ...

How is it possible for a TypeScript function to return something when its return type is void?

I find the book Learning JavaScript to be confusing. It delivers conflicting information regarding the use of void as a return type in functions. const foo = (s: string): void => { return 1; // ERROR } At first, it states that if a function has a re ...

Angular: controller's property has not been initialized

My small controller is responsible for binding a model to a UI and managing the UI popup using semantic principles (instructs semantic on when to display/hide the popup). export class MyController implements IController { popup: any | undefined onShow(con ...

"When a class extends another class and utilizes properties within a static property, it essentially becomes

I have been encountering challenges with generics in TypeScript for quite some time now. My current setup is as follows: First, there is a generic class defined as: class Entity { public static schema = {}; } Then, there is a class that extends the ...

Accessing the map in an Angular 6 service via Leaflet

Embedding a map into my Angular 6 app service has been a bit tricky. Currently, I'm passing it as an argument when calling an init function in the service and providing it via Subject from the component after fetching data from the store. However, som ...

Experiencing issues with importing .scss files after upgrading Angular and Bootstrap versions

After creating a jhipster application, I initially used Angular version 14. However, I later decided to upgrade it to version 16. Upon running "ng build," an error occurred in the following code snippet: @import '~bootswatch/dist/materia/variables&apo ...

What is the best way to standardize complex nested data within the ngrx/store?

Currently, I am utilizing ngrx/store with Angular 6. Within the store, there exists a deeply nested structure which I have concerns about in terms of its organization: const state = [ { aliases: ['alias1', 'alias2'], isRequir ...

Conversation with form component in angular2

I am currently using ng2-bootstrap-modal. For adding a sample form to the example Confirm Dialog, check out ng2-bootstrap-modal. To change the template: <div class="modal-dialog"> <div class="modal-content"> <form [formGroup]="login ...

Customize Angular Material styles uniquely across various components

Within my application, I am utilizing two components that contain tab groups. The first component serves as the main page, where I have adjusted the CSS to enlarge the labels by using ViewEncapsulation.None. The second component is a dialog, and I aim to m ...

Utilizing the index of the .map function in conjunction with internal methods

After running my code, I encountered the following error message: Warning: Encountered two children with the same key, `classroom-1278238`. Keys are required to be unique so that components can maintain their identity during updates. Having non-unique keys ...

The functionality of ZoneAwarePromise has been modified within the Meteor framework

After updating to the latest Angular 2.0.1 release on Meteor 1.4.1.1, I'm facing an error that says: Zone.js has detected that ZoneAwarePromise (window|global).Promise has been overwritten I've attempted running meteor update and meteor reset, b ...

Enhancing Angular Material: requiring more user engagement for rendering to occur

Encountering an unusual issue with Angular Material: certain components require an additional event, like a click or mouse movement on the targeted div, to trigger the actual rendering process. For instance, when loading new rows in mat-table, some empty ...

Occasionally, the translate.get function may return the key instead of the intended

I recently encountered an issue while working on a web application built with Angular 12 and ASP.NET Core 5. The application utilizes ngx-translate for internationalization, but I faced a problem with translating content in a new component that I added to ...

Having trouble compiling Angular CLI version 8.3.21 with the command ng serve

Upon trying to compile my first Angular app, I encountered an error when running ng serve: ERROR in ./src/styles.css (./node_modules/@angular-devkit/build-angular/src/angular-cli-files/plugins/raw-css-loader.js!./node_modules/postcss-loader/src??embedded! ...