Home > Mobile >  Expression has changed after it was checked. Previous value: 'a few seconds ago'. Current
Expression has changed after it was checked. Previous value: 'a few seconds ago'. Current

Time:06-17

I've been trying to implement some pipes (angular 13) to be able to show dates as twitter or other web platforms, where instead of the date I show a message like: "1 day ago", "Few seconds ago"...

I was trying to use https://www.npmjs.com/package/ngx-pipes but the same issue with the Expression has changed after it was checked appears the same.

I have a table that receives a lot of data changes trhough signalr. And I was able to instead of using that library making a custom timeAgo pipe with the base of the ngx-pipe timeAgo code.

The original code for that pipe is (the one I copy to make my custom pipe)

import { ChangeDetectorRef, NgZone, OnDestroy, Pipe, PipeTransform } from "@angular/core";


@Pipe({
  name: "dateAgo",
  pure: false
})
export class DateAgoPipe implements PipeTransform, OnDestroy {
  changeDetectorRef: ChangeDetectorRef;
  ngZone: NgZone;
  timer: any;

  constructor(changeDetectorRef: ChangeDetectorRef, ngZone: NgZone) {
    this.changeDetectorRef = changeDetectorRef;
    this.ngZone = ngZone;
  }

  ngOnDestroy() {
    this.removeTimer();
  }

  transform(value: any, args?: any): any {
    this.removeTimer();
    const d = new Date(value);
    const now = new Date();
    const seconds = Math.round(Math.abs((now.getTime() - d.getTime()) / 1000));
    const timeToUpdate = (Number.isNaN(seconds)) ? 1000 : this.getSecondsUntilUpdate(seconds) * 1000;
    this.timer = this.ngZone.runOutsideAngular(() => {
      if (typeof window !== 'undefined') {
        return window.setTimeout(() => {
          this.ngZone.run(() => this.changeDetectorRef.markForCheck());
        }, timeToUpdate);
      }
      return null;
    });
    const minutes = Math.round(Math.abs(seconds / 60));
    const hours = Math.round(Math.abs(minutes / 60));
    const days = Math.round(Math.abs(hours / 24));
    const months = Math.round(Math.abs(days / 30.416));
    const years = Math.round(Math.abs(days / 365));
    if (Number.isNaN(seconds)) {
      return '';
    } else if (seconds <= 45) {
      return 'a few seconds ago';
    } else if (seconds <= 90) {
      return '1 minute ago';
    } else if (minutes <= 45) {
      return minutes   ' minutes ago';
    } else if (minutes <= 90) {
      return 'an hour ago';
    } else if (hours <= 22) {
      return hours   ' hours ago';
    } else if (hours <= 36) {
      return 'a day ago';
    } else if (days <= 25) {
      return days   ' days ago';
    } else if (days <= 45) {
      return 'a month ago';
    } else if (days <= 345) {
      return months   ' months ago';
    } else if (days <= 545) {
      return 'a year ago';
    } else {
      // (days > 545)
      return years   ' years ago';
    }
  }

  removeTimer() {
    if (this.timer) {
      window.clearTimeout(this.timer);
      this.timer = null;
    }
  }

  getSecondsUntilUpdate(seconds: any) {
    const min = 60;
    const hr = min * 60;
    const day = hr * 24;

    if (seconds < min) {
      // less than 1 min, update every 2 secs
      return 2;
    } else if (seconds < hr) {
      // less than an hour, update every 30 secs
      return 30;
    } else if (seconds < day) {
      // less then a day, update every 5 mins
      return 300;
    } else {
      // update every hour
      return 3600;
    }
  }
}

But this code was throwing exceptions a lot specially when changing from few seconds ago to 1 minute and the x minutes ago. So I made this change which make a great improvement on not showing the error that often so this is the code with changes that right now i have implemented

if (Number.isNaN(seconds)) {
      return '';
    } else if (seconds <= 45) {
      return 'a few seconds ago';
    } else if (minutes <= 45 && seconds > 45) {
      return minutes === 1 ? minutes   ' minute ago' : minutes   ' minutes ago';
    } else if (minutes <= 90 && minutes > 45) {
      return 'an hour ago';
    } else if (hours <= 22 && minutes > 90) {
      return hours   ' hours ago';
    } else if (hours <= 36 && hours > 22) {
      return 'a day ago';
    } else if (days <= 25 && hours > 36) {
      return days   ' days ago';
    } else if (days <= 45 && days > 25) {
      return 'a month ago';
    } else if (days <= 345 && days > 45) {
      return months   ' months ago';
    } else if (days <= 545 && days > 345) {
      return 'a year ago';
    } else {
      // (days > 545)
      return years   ' years ago';
    }

But I'm still having some errors while letting the app run for some time

Expression has changed after it was checked. Previous value: 'a few seconds ago'. Current value: '1 minute ago' (this one appears the most)

Expression has changed after it was checked. Previous value: '1 minute ago'. Current value: '2 minutes ago'

Expression has changed after it was checked. Previous value: '3 hours ago'. Current value: '4 hours ago'..

Does anyone can notice any possible change to improve this behaviour? I made pure as false, because i read that some people resolve a lot of issues with this. But still happens the same.

The implementation on the component is

<span>{{data.time | dateAgo }}</span>

And I have imported the pipe on the component main module inside @NgModule in the declarations section

CodePudding user response:

A pipe is not supposed to hold logic related to the view it is in.

What I mean by that is that the pipe should not have a timer and handle all the value changes itself : the pipe is only there to display the value in a way that is different than the one it receives.

So, if you wish to make something like that, you shuold rather let the component update the view, with a timer and a change detection trigger.

Don't forget to put your pipe as impure, since the input will not change (or you can take the other way around and let the component update the value, so taht your pipe is pure).

Here is an example

  • Related