
Implementing a custom pipe - SecondsToTimePipe
SecondsToTimePipe, as the name suggests, should convert a numeric value into the hh:mm:ss format.
Create a folder shared in the workout-runner folder and from the shared folder invoke this CLI command to generate the pipe boilerplate:
ng generate pipe seconds-to-time
Copy the following transform function implementation into seconds-to-time.pipe.ts(the definition can also be downloaded from the Git branch checkpoint.2.4 on the GitHub site at http://bit.ly/nng6be-2-4-seconds-to-time-pipe-ts):
export class SecondsToTimePipe implements PipeTransform { transform(value: number): any { if (!isNaN(value)) { const hours = Math.floor(value / 3600);
const minutes = Math.floor((value - (hours * 3600)) / 60);
const seconds = value - (hours * 3600) - (minutes * 60);
return ('0' + hours).substr(-2) + ':'
+ ('0' + minutes).substr(-2) + ':'
+ ('0' + seconds).substr(-2); } return; } }
In an Angular pipe, the implementation logic goes into the transform function. Defined as part of the PipeTransform interface, the preceding transform function transforms the input seconds value into an hh:mm:ss string. The first parameter to the transform function is the pipe input. The subsequent parameters, if provided, are the arguments to the pipe, passed using a colon separator (pipe:argument1:arugment2..) from the view.
For SecondsToTimePipe, while Angular CLI generates a boilerplate argument (args?:any), we do not make use of any pipe argument as the implementation does not require it.
The pipe implementation is quite straightforward, as we convert seconds into hours, minutes, and seconds. Then, we concatenate the result into a string value and return the value. The addition of 0 on the left for each of the hours, minutes, and seconds variables is done to format the value with a leading 0 in case the calculated value for hours, minutes, or seconds is less than 10.
The pipe that we just created is just a standard TypeScript class. It's the Pipe decorator (@Pipe) that instructs Angular to treat this class as a pipe:
@Pipe({ name: 'secondsToTime' })
The pipe definition is complete, but to use the pipe in WorkoutRunnerComponent the pipe has to be declared on WorkoutRunnerModule. Angular CLI has already done this for us as part of the boilerplate generation (see the declaration section in workout-runner.module.ts).
Now we just need to add the pipe in the view. Update workout-runner.component.html by adding the highlighted fragment:
<div class="exercise-pane" class="col-sm-6"> <h4 class="text-center">Workout Remaining - {{workoutTimeRemaining | secondsToTime}}</h4>
<h1 class="text-center">{{currentExercise.exercise.title}}</h1>
Surprisingly, the implementation is still not complete! There is one more step left. We have a pipe definition, and we have referenced it in the view, but workoutTimeRemaining needs to update with each passing second for SecondsToTimePipe to be effective.
We have already initialized WorkoutRunnerComponent's workoutTimeRemaining property in the start function with the total workout time:
start() { this.workoutTimeRemaining = this.workoutPlan.totalWorkoutDuration(); ... }
Now the question is: how to update the workoutTimeRemaining variable with each passing second? Remember that we already have a setInterval set up that updates exerciseRunningDuration. While we can write another setInterval implementation for workoutTimeRemaining, it will be better if a single setInterval setup can take care of both the requirements.
Add a function called startExerciseTimeTracking to WorkoutRunnerComponent; it looks as follows:
startExerciseTimeTracking() {
this.exerciseTrackingInterval = window.setInterval(() => {
if (this.exerciseRunningDuration >= this.currentExercise.duration) {
clearInterval(this.exerciseTrackingInterval);
const next: ExercisePlan = this.getNextExercise();
if (next) {
if (next !== this.restExercise) {
this.currentExerciseIndex++;
}
this.startExercise(next);
}
else {
console.log('Workout complete!');
}
return;
}
++this.exerciseRunningDuration;
--this.workoutTimeRemaining;
}, 1000);
}
As you can see, the primary purpose of the function is to track the exercise progress and flip the exercise once it is complete. However, it also tracks workoutTimeRemaining (it decrements this counter). The first if condition setup just makes sure that we clear the timer once all the exercises are done. The inner if conditions are used to keep currentExerciseIndex in sync with the running exercise.
This function uses a numeric instance variable called exerciseTrackingInterval. Add it to the class declaration section. We are going to use this variable later to implement an exercise pausing behavior.
Remove the complete setInterval setup from startExercise and replace it with a call to this.startExerciseTimeTracking();. We are all set to test our implementation. If required, refresh the browser and verify the implementation:

The next section is about another inbuilt Angular directive, ngIf, and another small enhancement.