Encountered an error while attempting to compare 'true' within the ngDoCheck() function in Angular2

Getting Started

Greetings! I am a novice in the world of Angular2, Typescript, and StackOverflow.com. I am facing an issue that I hope you can assist me with. I have successfully created a collapse animation for a button using ngOnChanges() when the button is clicked. Now, my goal is to make the menu collapse and hide whenever the user clicks anywhere on the page outside the displayed menu or away from the button managing the menu.

To achieve this, I want my collapse.animation.ts file to detect changes in the collapse property from my header.component.ts using ngDoCheck() when triggered by the clickOutsideEvent().

I attempted to implement events like (blur)="onBlur()" or (blur)="expression", but they did not function as intended due to focus on a single HTML element. Perhaps I failed to implement them correctly...

The exception message I am encountering is: angular2.dev.js:23730 EXCEPTION: Error trying to diff 'true' in [isCollapsed in HeaderComponent@16:59]

Apologies if my post is lengthy or unclear. Any assistance provided would be highly valued.

The Code in Progress

collapse.animation.ts: This is where I handle the collapse animation and attempt to detect changes in the collapse state to show/hide accordingly.

import {Directive, DoCheck, KeyValueDiffers , OnChanges, ElementRef, Input } from 'angular2/core';
import {AnimationBuilder} from 'angular2/src/animate/animation_builder';
import {CssAnimationBuilder} from 'angular2/src/animate/css_animation_builder';

@Directive({
    selector: '[collapse]',
    host: {
        '[attr.aria-expanded]': '!collapse',
        '[attr.aria-hidden]': 'collapse'
    }
})

export class Collapse implements OnChanges, DoCheck {
    @Input() duration: number = 200;            // Animation speed in ms (750 = 0.75 sec)
    @Input() collapse: any;                     // Boolean defining collapse state

    private _animation: CssAnimationBuilder;    // CSS Animation
    differ: any;                                // Property/attribute tracker.

constructor(private _animationBuilder: AnimationBuilder, private _element: ElementRef, private differs: KeyValueDiffers) {
    // Initialize the CSS animation.
    this._animation = _animationBuilder.css();
    // Initialize the tracker.
    this.differ = differs.find({}).create(null);
}

// Attempting to track changes...
ngDoCheck() {
    var changes = this.differ.diff(this.collapse);

    if (changes) {
        changes.forEachChangedItem((elt) => {
            if (elt.key === 'isCollapsed') {
                this.show();
            }
            else {
                this.hide();
            }
        });
    }
}

// Manage the collapse property
// Works when clicking the button
ngOnChanges(changes) {
    if (changes.collapse) {
        if (this.collapse) {
            this.hide();
        } else {
            this.show();
        }
    }
}

header.component.ts: My header component housing the menu

import { Component, Input } from 'angular2/core';
import { RouteConfig, RouterOutlet, ROUTER_DIRECTIVES, ROUTER_PROVIDERS} from "angular2/router";
import { Collapse } from './../../css/animations/collapse.animation';
import { ClickOutside } from './../directives/clickOutside.directive';

@Component({
    selector: 'headerComponent',
    templateUrl: 'app/master/header.component.html',
    styleUrls: ['app/master/header.component.css', 'app/master/menuNavigation.component.css', 'css/animations/collapse.animation.css'],
    directives: [
        ROUTER_DIRECTIVES,
        Collapse,
        ClickOutside
    ],
    providers: [
        ROUTER_PROVIDERS
    ],
})

export class HeaderComponent {
    @Input() collapse: boolean = false;

    private headerMenuClass: string = "header_menu";

    // Handle clickOutside method
    handleClickOutside(className) {
        // Detect "click" on the button linked to the menu with class "header_menu". No need to collapse the menu when clicking the button.
        let FirstClassName: string = className.split(" ")[0];
        if (FirstClassName != this.headerMenuClass) {
            console.log(this.collapse);
            // Trying to make my collapse.animation.ts detect this !!
            this.collapse = !this.collapse;
        }
    }
}

header.component.html: HTML template for my component

<div id="top_bar" class="top_bar">


    <!-- Button to display the menu -->

    <button type="button" class="header_menu nav_icon root_navitem" id="btn_menu_switch" (click)="isCollapsed = !isCollapsed">
        <img class="header_menu" src="css/App_Themes/VAL/48x48/m_bars.png" alt="" />
    </button>

    <button type="button" class="nav_icon" id="btn_home" [routerLink]="['Accueil']">
        <img src="css/App_Themes/VAL/48x48/m_home.png" alt="" />
    </button>
</div>

<!-- Menu to be collapsed at will -->

<div id="left_bar" tabindex="-1" class="left_bar collapse" [collapse]="isCollapsed"
     (clickOutside)="handleClickOutside( $event.target.className )">

    <div id="left_bar_menu" class="left_bar_menu">
        <button type="button" class="left_bar_icon" id="btn_left_histo">
            <img src="css/App_Themes/VAL/48x48/m_history.png" alt="" />
        </button>
        <hr class="sep" />
        <button type="button" class="left_bar_icon" id="btn_left_board" [routerLink]="['Dashboard']">
            <img src="css/App_Themes/VAL/48x48/m_board.png" alt="" />
        </button>
        <hr class="sep" />
    </div>
</div>

clickOutside.directive.ts: Allows detection of clicks outside a component and retrieves information. While it may not be the most elegant solution, improvements will be made in the future.

import {Directive, EventEmitter, Input, Output } from 'angular2/core';

// Global variable within the class
var localEvent: any = null;

@Directive({
    selector: '[clickOutside]',
    host: {
        "(click)": "trackEvent( $event )",
        "(document: click)": "compareEvent( $event )"
    }
})

export class ClickOutside {
    @Output() clickOutside;

    constructor() {
        this.clickOutside = new EventEmitter();
    }

    // If the event at the document root matches the event target, it means the event started inside the target and bubbled up. If the document root event DOES NOT MATCH the last known target event, it originated from outside the target.
    compareEvent(event) {
        if (event !== localEvent) {
            this.clickOutside.emit(event);
        }
        localEvent = null;
    }

    // Track click event on bound target.
    trackEvent(event) {
        // Start tracking the event as it bubbles up the DOM tree when the user clicks inside the bound target. This allows determining if the event originated within the target.
        localEvent = event;
    }
}

Answer №1

It seems that utilizing ngDoCheck may not be the most effective solution in this scenario. Angular2 typically detects changes based on value for primitive types and by reference for objects, without identifying changes within arrays or objects.

If a custom detection method is required (as opposed to the default detection), then implementing ngDoCheck becomes necessary. This method depends on classes like KeyValueDiffers for objects and IterableDiffers for arrays. However, it's worth noting that using the KeyValueDiffers class with a boolean value isn't suitable.

In cases like this, relying on the default detection mechanism for the collapse input property should suffice. When its value changes, triggering your own processes can be achieved through the use of ngOnChanges.

To implement a collapse feature that can be triggered from any part of the application, consider utilizing a shared service such as MenuService. Within this service, a boolean property could represent the menu state, along with an observable/subject to signal updates:

export class MenuService {
  collapse: boolean;
  collapse$: Subject<boolean> = new Subject();

  toggleMenu() {
    this.collapse = !this.collapse;
    this.collapse$.next(this.collapse);
  }
}

Remember to include the service during application bootstrap:

bootstrap(AppComponent, [ MenuService ]);

Avoid redefining it within the components' providers attribute.

Subsequently, inject this service into your components/directives for updating the state or receiving notifications upon state changes.

Below is an example directive implementation:

@Directive({
  selector: '[collapse]',
  host: {
    '[attr.aria-expanded]': '!collapse',
    '[attr.aria-hidden]': 'collapse'
  }
})
export class Collapse implements OnChanges {
  @Input() duration: number = 200;
  @Input() collapse: any;

  private _animation: CssAnimationBuilder;    // CSS Animation

  constructor(private _animationBuilder: AnimationBuilder, private _element: ElementRef, private service: MenuService) {
    this._animation = _animationBuilder.css();
    this.service.collapse$.subscribe((collapse) => {
      this.updateMenu(collapse);
    });
  }

  ngOnChanges(changes) {
    if (changes.collapse) {
      this.updateMenu(changes.collapse);
    }
  }

  updateMenu(collapse: boolean) {
    if (this.collapse) {
        this.hide();
    } else {
        this.show();
    }
  }
}

The menu state can be updated anywhere within the application using the MenuService, even within components unrelated to the menu:

@Component({
  (...)
  template: `
    <div (click)="toggleMenu()">Toggle menu</div>
  `
})
export class SomeComponent {
  constructor(private service: MenuService) {
  }

  toggleMenu() {
    this.service.toggleMenu();
  }
}

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

Managing user sessions in Angular 5: Best practices and tips

Using the Spring Framework with 'Shiro' for authentication on the backend, And Angular 5 on the frontend. Calling the login API from Postman results in the same user session until the logout API is used, which is expected behavior. Postman UI ...

How can I restrict the highest possible date selection in reactjs?

I am working on a project that requires users to select dates using datetime-local, but I want to limit the selection to only three months ahead of the current date. Unfortunately, my current implementation is not working as expected. Any assistance woul ...

Encountering error message "Module not found '@angular/compiler-cli/ngcc'" while attempting to run "ng serve" for my application

Encountering an error while trying to run my app, I have attempted various solutions available online. These include uninstalling and reinstalling angular/cli, verifying the correct version in package.json (ensuring it is "@angular/cli" and not "@angular-c ...

Peer dependencies in NPM refer to dependencies that your project

I am currently in the process of upgrading from Angular 4 to Angular 5 and encountered some warnings along the way. Some of the warnings I received include: npm WARN @angular/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="88eb ...

Error message "NoSuchKey" encountered on CloudFront when accessing an Angular application

I recently developed an Angular application and successfully uploaded it to an S3 bucket. To make my website accessible, I deployed a CloudFront distribution. However, when trying to access a specific route on the website (such as /login), I encountered an ...

What is the best way to modify an object within a pure function in JavaScript?

Currently, I am exploring different strategies to ensure that a function remains pure while depending on object updates. Would creating a deep copy be the only solution? I understand that questions regarding object copying are quite common here. However, ...

Share the component with css and font via npm

I am looking to release an angular 2 component on NPM. This particular component utilizes CSS that references certain fonts. I have figured out how to publish the .ts files, but I am unsure about how to handle the CSS and font files. Here is what I curren ...

Is it true that Node.js can be used to run Angular and Ionic frameworks

During a conversation about performance, the following question came up: Do Angular and Ionic require Node.js to be served, or is it sufficient to just serve the dist folder on the client side app? Is Node.js purely a development tool, or is it also used ...

What is the best approach to implement a recursive intersection while keeping refactoring in consideration?

I'm currently in the process of refactoring this code snippet to allow for the reuse of the same middleware logic on multiple pages in a type-safe manner. However, I am encountering difficulties when trying to write a typesafe recursive type that can ...

What makes it impossible to use var instead of let in ngFor?

My understanding is that in JavaScript, we typically use var and let for variable declarations. The main difference between the two is that var is scoped to the current function, while let is scoped to the current block. Therefore, theoretically I should b ...

Establish a connection with MongoDB and make changes to the data

I am facing an issue while trying to update values stored in MongoDB. I thought of using mongoose to view and edit the data, but it seems like I'm encountering an error along the way. Has anyone successfully implemented this kind of task before? impo ...

Encountering issues with Semver while working on building an Angular 4 IMAP client

I am currently working on integrating an Imap Client into my Angular 4 app. I came across a helpful node module for implementing Imap using npm: Repository However, I encountered an issue. After adding the following line in angular-cli.json: "scripts": ...

Angular 2 router generates incorrect URLpaths

When navigating through my routes, I encountered an issue with the following routing setup: const routes: Routes = [ { path: '', component : HomeComponent, children: [] }, { path: 'login', ...

Using TypeScript, leverage bracket notation to access a property of an object using a variable

I am working with an object that has an interface and I am interested in accessing values dynamically using property keys. const userData: IUser = { username: "test", namespace: "test", password: "test" } Object.keys(userData).forEach(propert ...

Using System.import in my code is triggering a cascade of errors in my console

I incorporate the System module in my component. export class LoginComponent { constructor() { System.import('app/login/login.js'); } } The file loads successfully, however, TypeScript compiler raises an error Error:(10, 9) TS2 ...

Vuetify's v-data-table is experiencing issues with displaying :headers and :items

I'm facing an issue while working on a Vue project with typescript. The v-data-table component in my Schedule.vue file is not rendering as expected. Instead, all I can see is the image below: https://i.sstatic.net/AvjwA.png Despite searching extensi ...

What methods can be used to create a responsive height in iOS applications using WebView with Nativescript?

I am facing an issue with the WebView not dynamically loading height on iOS (it works on Android). My content is dynamic and can grow in height, so setting a fixed height won't work for me. Can anyone provide assistance? <CardView *ngFor="let itin ...

Prevent auth0 express middleware from causing server crashes by handling failures silently

I am currently integrating auth0 into a node project for authentication using JWTs. Each request to an authenticated endpoint requires a token, and auth0 provided me with this middleware function: import {auth} from 'express-oauth2-jwt-bearer'; i ...

Angular 2 routing malfunctioning

I'm encountering an issue while setting up routing in my application. The error displayed in the console is as follows: angular2-polyfills.js:138 Error: XHR error (404 Not Found) loading http://localhost:9000/angular2/router.js(…) Below is the co ...

I recently downloaded a project from Github, but I'm encountering issues as the npm start command is not

This is the latest update for my project: https://github.com/rietesh/Hyperledgerfabric-Airline-App.git Upon running npm start, the following error message is displayed: The serve command should only be executed within an Angular project, but no project de ...