I'm calling a C# API that's returning the following types to my Angular application.
export interface Auction {
reservePrice: number
currentHighBid: number
auctionEnd: string
id: string
}
I created an AuctionListComponent that will ngFor
a list of auction to the screen and in the list I want to add a countdown timers. So I created another component for that called TimerComponent. The parent component is passing a UTC string date to the child. The child timer component needs a type of number for the ngx-countdown timer to work. I'm no pro at Angular so am I missing something simple here?
I've also looked over this similar questions as well and didn't help me. similar question
(Parent) AuctionListComponent.html
<div class="m-3" *ngFor="let auction of auctions">
<p class="card-text">
End Date: {{ auction.auctionEnd | date }}
</p>
<app-timer [childTimer]="auction.auctionEnd"></app-timer>
</div>
(Child) TimerComponent.html
<p>
From the parent: <b>{{ childTimer | date : 'MM d, y, h:mm:ss a' }}</b>
UTC: <b>{{ childTimer }}</b>
</p>
<p>ngx-countdown Counter</p>
<countdown [config]="config"> </countdown>
TimerComponent.TS
import { Component, Input } from '@angular/core';
import { CountdownConfig, CountdownEvent } from 'ngx-countdown';
@Component({
selector: 'app-timer',
templateUrl: './timer.component.html',
styleUrl: './timer.component.scss'
})
export class TimerComponent {
@Input() childTimer: string = ''; //This doesn't work because it's the wrong type
@Input() childTimer: any; //This compiles and give an error
//ERROR Error: Unable to convert "Invalid Date" into a date
config: CountdownConfig = { leftTime: this.childTimer, format: 'd:H:M:ss'};
}
ngx-countdown
expects leftTime
to be a number representing seconds left. So to make it work you need to find a difference between auctionEnd
which is a UTC string and the current Date and convert it into seconds.
Here is how your component could do this:
export class TimerComponent {
config: CountdownConfig = {
leftTime: 0,
format: 'd:H:M:ss'
};
// Turn this into a setter so it updates the config if input changes
// make sure you use ChangeDetectionStrategy.OnPush to avoid redundant updates
// or change this as you wish
@Input() set childTimer(utcDate: string) {
this.config = {
...this.config,
leftTime: this.calculateSecondsLeft(utcDate)
};
}
private calculateSecondsLeft(utcDate: string): number {
// Both `parse` and `now` return a timestamp
const diff = Date.parse(utcDate) - Date.now();
return diff / 1000;
}
}
Update
I digged into documentation of ngx-countdown
and there is stopTime
option in the config that makes it even simpler as it accepts "end time" as timestamp, so the example could be simplified to:
export class TimerComponent {
config: CountdownConfig = {
format: 'd:H:M:ss'
};
@Input() set childTimer(utcDate: string) {
this.config = {
...this.config,
stopTime: Date.parse(utcDate)
};
}
}
Regarding d
in a format showing 1 when timeLeft
> 24h. It seems that the library uses formatting for Date, while the data is more like Duration
. When a diff
is calculated it is a date relative to Jan 1 1970
and d
is a "day of month" so it shows 1
. It is better explained here. As we cannot subtract 1 day from Jan 1 1970
there is no easy fix for it that could handle leap days and other Date related edge cases.
Here is an example of how you can add a custom formatter via formatDate
configuration, and intervalToDuration
from date-fns
library:
config: CountdownConfig = {
formatDate: ({ date }) => {
const { days, hours, minutes, seconds } = intervalToDuration({
start: 0,
end: date
});
return `${days ?? 0}:${hours ?? 0}:${minutes ?? 0}:${seconds ?? 0}`;
},
};