0

I've read this answer, this answer and this answer (along with many others), but I don't understand how to fix this error the way I'm doing it.

Basically, I'm making an event countdown timer, down to the second. Each event shows the largest two times remaining, (i.e. 1 year, 2 months. 4 days 12 hours. 10 minutes 25 seconds).

I have many events on the screen, all calling the same function. I will randomly get this error Error Expression has changed after it was checked if I have something counting down with seconds showing (and sometimes when the time changes in minutes, I get this error too).

my html looks like so:

<ion-header>
    <ion-navbar>
        <ion-title>
            <h2>{{theTime}}</h2>
        </ion-title>
    </ion-navbar>
</ion-header>

<ion-content padding-top padding-bottom>
    <ion-fab right bottom>
        <button ion-fab color="primary" (click)='goToEventUpsert()'>
            <ion-icon name="add"></ion-icon>
        </button>
    </ion-fab>
    <ion-card class="event" *ngFor="let event of events | OrderBy : 'dateTimeEpoch'" id="id-{{event.id}}">
        <div class="event__container">
            <div class="event__container__countdown" color="light" [innerHTML]="timeUntil(event.dateTime)"></div>
        <div class="event__container__details">
            ...
        </div>
    </ion-card>
</ion-content>

And the typescript is such:

export class HomePage {
    constructor(...) {
        this.getCurrentTime();
        setInterval(() => this.getCurrentTime(), 100);
    }

    getCurrentTime() {
        this.theTime = moment().format("h:mm a");
    }

    timeUntil(date) {
        var dur = moment.duration( moment(date).diff(moment()) );
        let yearsRemain = dur.years();
        let monthsRemain = dur.months();
        let daysRemain = dur.days();
        let hoursRemain = dur.hours();
        let minutesRemain = dur.minutes();
        let secondsRemain = dur.seconds();

        var dateArray = [
            yearsRemain,
            monthsRemain,
            daysRemain,
            hoursRemain,
            minutesRemain,
            secondsRemain
        ]

        for(var i = 0; i < dateArray.length; i++) {
            if(dateArray[i] > 0){
                var firstDur = dateArray[i] + this.typeOfTime(i, dateArray[i]);
                var secondDur = dateArray[i+1] !== 0 ? dateArray[i+1] + this.typeOfTime(i+1, dateArray[i+1]) : dateArray[i+2] + this.typeOfTime(i+2, dateArray[i+2]);
                return firstDur +  secondDur;
            } else if(dateArray[i] < 0) {
                return (dateArray[i] * (-1)) + this.typeOfTime(i, (dateArray[i] * (-1))) + " ago";
            } else {
                i++
            }
        }
    }

    typeOfTime(type, num) {
        var display;
        var plur = num === 1 ? "" : "s";
        switch(type) {
            case 0:
                display = "Year" + plur;
                break;
            case 1:
                display = "Month" + plur;
                break;
            case 2:
                display = "Day" + plur;
                break;
            case 3:
                display = "Hour" + plur;
                break;
            case 4:
                display = "Minute" + plur;
                break;
            case 5:
                display = "Second" + plur;
                break;
        }
        return display;
    }
}

So, as I've said, I read the answers, but I don't understand. Can anyone shed some light on this for me?

Community
  • 1
  • 1
ntgCleaner
  • 5,865
  • 9
  • 48
  • 86
  • When you are working with angular in development mode it makes a double check to check the state of the variables. Try running a production build, to see the difference, but you should change the code that is changing constantly to solve this issue. Seems to be your timeUntil function. – dlcardozo Feb 06 '17 at 18:38
  • @camaron, As far as I can tell, that's what I understand too. Though I'm foggy on `enableProdMode()` and where to put it. – ntgCleaner Feb 06 '17 at 18:55
  • the idea is to use the --prod flag, like ionic run android --prod, this will add enableProdMode(). But you can try to add the enableProdMode() on your main.ts file before the boostrap call – dlcardozo Feb 06 '17 at 19:09
  • @camaron, Thank you! I just tried this, though now I'm getting another error/issue with my SQLite functionality... Another problem to look into! – ntgCleaner Feb 06 '17 at 19:22
  • Happy coding!, I added the answer just for feature readers. – dlcardozo Feb 06 '17 at 19:30

2 Answers2

0

When you are working with angular in development mode it makes a double check to check the state of the variables. Try running a production build, to see the difference, but you should change the code that is changing constantly to solve this issue. Seems to be your timeUntil function.

To run in production mode the idea is to use the --prod flag, like ionic run android --prod, this will add enableProdMode().

But you can try to add the enableProdMode() on your main.ts file before the boostrap call.

This will solve this issue, but as I already said, It's not the best approach.

dlcardozo
  • 3,953
  • 1
  • 18
  • 22
0

Alright, I figured this out, though I'm not 100% sure on as to why.

As I have it written above, I am using angular to show a return value using [innerHTML]. For some reason (this is part I don't know why), the data binding can't handle a "return" value, rather it needs to take in a simple variable. I've changed it from

<div class="event__container__countdown" color="light" [innerHTML]="timeUntil(event.dateTime)"></div>

where the function timeUntil() used a return to show what I wanted: return firstDur + secondDur;

to

<div class="event__container__countdown" color="light">
    {{timePrimary}} {{timePrimaryType}} {{timeSecondary}} {{timeSecondaryType}}
</div>

Where, I fire my timeUntil() function inside of a setTimeout (as to always run) and change the variables.

timePrimary: number;
timePrimaryType: string;
timeSecondary: number;
timeSecondaryType: string;

constructor() {
    this.timeUntil(this.timeUntilValue);
    setInterval(_ => this.timeUntil(this.timeUntilValue));
}
timeUntil(date) {

    //...All the same variable declarations...

    for(var i = 0; i < dateArray.length; i++) {
        if(dateArray[i] > 0){
            this.timePrimary = dateArray[i];
            this.timePrimaryType = this.typeOfTime(i, dateArray[i]);
            this.timeSecondary = dateArray[i+1] !== 0 ? dateArray[i+1] : dateArray[i+2];
            this.timeSecondaryType = dateArray[i+1] !== 0 ? this.typeOfTime(i+1, dateArray[i+1]) : this.typeOfTime(i+2, dateArray[i+2]);
            break;
        } else if(dateArray[i] < 0) {
            this.timePrimary = dateArray[i] * (-1);
            this.timePrimaryType = this.typeOfTime(i, (dateArray[i] * (-1))) + " ago";
            break;
        } else {
            i++
        }
    }
}

So, for some reason, returning a value, rather than setting the value to a variable is no good in this situation.

ntgCleaner
  • 5,865
  • 9
  • 48
  • 86