Injecting a parent component into a child component
Add the WorkoutAudioComponent to the WorkoutRunnerComponent view just before the last closing div:
<abe-workout-audio></abe-workout-audio>
Next, inject WorkoutRunnerComponent into WorkoutAudioComponent. Open workout-audio.component.ts and add the following declaration and update the constructor:
private subscriptions: Array<any>; constructor( @Inject(forwardRef(() => WorkoutRunnerComponent)) private runner: WorkoutRunnerComponent) { this.subscriptions = [ this.runner.exercisePaused.subscribe((exercise: ExercisePlan) => this.stop()), this.runner.workoutComplete.subscribe((exercise: ExercisePlan) => this.stop()), this.runner.exerciseResumed.subscribe((exercise: ExercisePlan) => this.resume()), this.runner.exerciseProgress.subscribe((progress: ExerciseProgressEvent) => this.onExerciseProgress(progress)),
this.runner.exerciseChanged.subscribe((state: ExerciseChangedEvent) => this.onExerciseChanged(state))]; }
And remember to add these imports:
import {Component, ViewChild, Inject, forwardRef} from '@angular/core'; import {WorkoutRunnerComponent} from '../workout-runner.component'
Let's try to understand what we have done before running the app. There is some amount of trickery involved in the construction injection. If we directly try to inject WorkoutRunnerComponent into WorkoutAudioComponent, it fails with Angular complaining of not being able to find all the dependencies. Read the code and think carefully; there is a subtle dependency cycle issue lurking. WorkoutRunnerComponent is already dependent on WorkoutAudioComponent, as we have referenced WorkoutAudioComponent in the WorkoutRunnerComponent view. Now by injecting WorkoutRunnerComponent in WorkoutAudioComponent, we have created a dependency cycle.
Cyclic dependencies are challenging for any DI framework. When creating a component with a cyclic dependency, the framework has to somehow resolve the cycle. In the preceding example, we resolve the circular dependency issue by using an @Inject decorator and passing in the token created using the forwardRef() global framework function.
Once the injection is done correctly, inside the constructor, we attach a handler to the WorkoutRunnerComponent events, using the subscribe function of EventEmitter. The arrow function passed to subscribe is called whenever the event occurs with a specific event argument. We collect all the subscriptions into a subscription array. This array comes in handy when we unsubscribe, which we need to, to avoid memory leaks.
A bit about EventEmitter: the EventEmmiter subscription (subscribe function) takes three arguments:
subscribe(generatorOrNext?: any, error?: any, complete?: any) : any
- The first argument is a callback, which is invoked whenever an event is emitted
- The second argument is an error callback function, invoked when the observable (the part that is generating events) errors out
- The final argument takes a callback function that is called when the observable is done publishing events
We have done enough to make audio integration work. Run the app and start the workout. Except for the ticking audio, all the \ audio clips play at the right time. You may have to wait some time to hear the other audio clips. What is the problem?
As it turns out, we never started the ticking audio clip at the start of the workout. We can fix it by either setting the autoplay attribute on the ticks audio element or using the component life cycle events to trigger the ticking sound. Let's take the second approach.