Home > Net >  Countdown timer error when changing inside my web page using Angular, TypeScript
Countdown timer error when changing inside my web page using Angular, TypeScript

Time:12-12

I set a countdown timer in typescript to my webpage on the home page. But when I change to another page I get a console error every second.

.TS

countDown() {
    var countDownDate = new Date("Jan 1, 2022 00:00:00").getTime();

    // Update the count down every 1 second
    var x = setInterval(function () {
      var now = new Date().getTime();

      // Find the distance between now an the count down date
      var distance = countDownDate - now;

      var days = Math.floor(distance / (1000 * 60 * 60 * 24));
      var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
      var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
      var seconds = Math.floor((distance % (1000 * 60)) / 1000);

      // Display the result in an element with id="demo"
      var contadorElement =  document.getElementById("contador") as HTMLElement; // <- Line 37
      contadorElement.innerHTML = days   "d "   hours   "h "
          minutes   "m "   seconds   "s ";

      if (distance < 0) {
        clearInterval(x);
        contadorElement.innerHTML = "EXPIRED";
      }
    }, 1000);
  }

.HTML

<p id="contador" style="font-size:30px"></p>

The code above works under 'localhost:4200/'. However if I go to 'localhost:4200/anotherStuf' (for example) I get this console error every second:

core.js:6456 ERROR TypeError: Cannot set properties of null (setting 'innerHTML')
    at home.component.ts:37
    at timer (zone.js:2561)
    at ZoneDelegate.invokeTask (zone.js:406)
    at Object.onInvokeTask (core.js:28661)
    at ZoneDelegate.invokeTask (zone.js:405)
    at Zone.runTask (zone.js:178)
    at invokeTask (zone.js:487)
    at ZoneTask.invoke (zone.js:476)
    at data.args.<computed> (zone.js:2541)

Any thoughts?

CodePudding user response:

When you change your page, the part of the DOM that your contador element resides in gets destroyed. Since you do not clean up your setInterval in the ngOnDestroy hook, your code is still running, even when you navigate away (i.e. your component gets destroyed). This would lead to an error along the lines of cannot read undefined of contadorElement (reading: innerHTML) or something similiar.

The solution is simple: clean up your setInterval, which you can achieve by calling clearInterval in the ngOnDestroy hook, using the return value of your setInterval as the parameter.

private interval;

countDown() {
   this.interval = setTimeout(() => { /* Omitted for readability */}, 1000);
}

ngOnDestroy() {
   clearInterval(this.interval);
}

Since you are clearing the interval already when the distance reaches 0, you might want to polish this a bit by setting the variable to undefined or null after clearing the interval and only calling clearInterval when you did not already stop it.

private interval;

countDown() {
   this.interval = setTimeout(() => { 
      /* Omitted for readability */
      
      if (distance < 0) {
        this.stopInterval();
        contadorElement.innerHTML = "EXPIRED";
      }
   }, 1000);
}

ngOnDestroy() {
   clearInterval(this.interval);
}

private stopInterval() {
   if (this.interval) {
      clearInterval(this.interval);
      this.interval = null;
   }
}

If you can't stop the timer in ngOnDestroy (if, for example, your code resides inside a service and you don't want to call a stopCountDown method in ngOnDestroy), you will have to check if the element exists in each setInterval loop and possibly destroy the interval if it does not exist anymore.

countDown() {

    // Update the count down every 1 second
    var x = setInterval(function () {
      /* Omitted for readability */
       
      var contadorElement =  document.getElementById("contador") as HTMLElement; 

      if (contadorElement) {
         contadorElement.innerHTML = days   "d "   hours   "h "   minutes   "m "   seconds   "s ";
      } else {
         clearInterval(x);
      }

      if (distance < 0) {
        clearInterval(x);
        contadorElement.innerHTML = "EXPIRED";
      }
    }, 1000);
  }
  • Related