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);
}