
Component lifecycle hooks
The life of an Angular component is eventful. Components get created, change state during their lifetime, and finally, they are destroyed. Angular provides some lifecycle hooks/functions that the framework invokes (on the component) when such an event occurs. Consider these examples:
- When a component is initialized, Angular invokes ngOnInit
- When a component's data-bound properties change, Angular invokes ngOnChanges
- When a component is destroyed, Angular invokes ngOnDestroy
As developers, we can tap into these key moments and perform some custom logic inside the respective component.
The hook we are going to utilize here is ngOnInit. The ngOnInit function gets fired the first time the component's data-bound properties are initialized, but before the view initialization starts.
Update the ngOnInit function to the WorkoutRunnerComponent class with a call to start the workout:
ngOnInit() {
... this.start(); }
Angular CLI as part of component scaffolding already generates the signature for ngOnInit. The ngOnInit function is declared on the OnInit interface, which is part of the core Angular framework. We can confirm this by looking at the import section of WorkoutRunnerComponent:
import {Component,OnInit} from '@angular/core'; ... export class WorkoutRunnerComponent implements OnInit {
Time to run our app! Open the command line, navigate to the trainer folder, and type this line:
ng serve --open
The code compiles, but no UI is rendered. What is failing us? Let's look at the browser console for errors.
Open the browser's dev tools (common keyboard shortcut F12) and look at the console tab for errors. There is a template parsing error. Angular is not able to locate the abe-workout-runner component. Let's do some sanity checks to verify our setup:
- WorkoutRunnerComponent implementation complete - check
- Component declared in WorkoutRunnerModule- check
- WorkoutRunnerModule imported into AppModule - check
Still, the AppComponent template cannot locate the WorkoutRunnerComponent. Is it because WorkoutRunnerComponent and AppComponent are in different modules? Indeed, that is the problem! While WorkoutRunnerModule has been imported into AppModule, WorkoutRunnerModule still does not export the new WorkoutRunnerComponent that will allow AppComponent to use it.
Let's export WorkoutRunnerComponent by updating the export array of the WorkoutRunnerModule declaration to the following:
declarations: [WorkoutRunnerComponent],
exports:[WorkoutRunnerComponent]
This time, we should see the following output:

The model data updates with every passing second! Now you'll understand why interpolations ({{ }}) are a great debugging tool.
We are not done yet! Wait long enough on the page and we realize that the timer stops after 30 seconds. The app does not load the next exercise data. Time to fix it!
Update the code inside the setInterval function:
if (this.exerciseRunningDuration >= this.currentExercise.duration) { clearInterval(intervalId); const next: ExercisePlan = this.getNextExercise(); if (next) { if (next !== this.restExercise) { this.currentExerciseIndex++; } this.startExercise(next);} else { console.log('Workout complete!'); } }
The if condition if (this.exerciseRunningDuration >= this.currentExercise.duration) is used to transition to the next exercise once the time duration of the current exercise lapses. We use getNextExercise to get the next exercise and call startExercise again to repeat the process. If no exercise is returned by the getNextExercise call, the workout is considered complete.
During exercise transitioning, we increment currentExerciseIndex only if the next exercise is not a rest exercise. Remember that the original workout plan does not have a rest exercise. For the sake of consistency, we have created a rest exercise and are now swapping between rest and the standard exercises that are part of the workout plan. Therefore, currentExerciseIndex does not change when the next exercise is rest.
Let's quickly add the getNextExercise function too. Add the function to the WorkoutRunnerComponent class:
getNextExercise(): ExercisePlan { let nextExercise: ExercisePlan = null; if (this.currentExercise === this.restExercise) { nextExercise = this.workoutPlan.exercises[this.currentExerciseIndex + 1]; } else if (this.currentExerciseIndex < this.workoutPlan.exercises.length - 1) { nextExercise = this.restExercise; } return nextExercise; }
The getNextExercise function returns the next exercise that needs to be performed.
The implementation is quite self-explanatory. If the current exercise is rest, take the next exercise from the workoutPlan.exercises array (based on currentExerciseIndex); otherwise, the next exercise is rest, given that we are not on the last exercise (the else if condition check).
With this, we are ready to test our implementation. The exercises should flip after every 10 or 30 seconds. Great!
We have done enough work on the component for now, let's build the view.