Using component life cycle events
The injected MyAudioDirective in WorkoutAudioComponent, shown as follows, is not available till the view is initialized:
<audio #ticks="MyAudio" loop src="/assets/audio/tick10s.mp3"></audio>
<audio #nextUp="MyAudio" src="/assets/audio/nextup.mp3"></audio>
...
We can verify it by accessing the ticks variable inside the constructor; it will be null. Angular has still not done its magic and we need to wait for the children of WorkoutAudioComponent to be initialized.
The component's life cycle hooks can help us here. The AfterViewInit event hook is called once the component's view has been initialized and hence is a safe place from which to access the component's child directives/elements. Let's do it quickly.
Update WorkoutAudioComponent by adding the interface implementation, and the necessary imports, as highlighted:
import {..., AfterViewInit} from '@angular/core'; ... export class WorkoutAudioComponent implements OnInit, AfterViewInit { ngAfterViewInit() { this.ticks.start(); }
Go ahead and test the app. The app has come to life with full-fledged audio feedback. Nice!
While everything looks fine and dandy on the surface, there is a memory leak in the application now. If, in the middle of the workout, we navigate away from the workout page (to the start or finish page) and again return to the workout page, multiple audio clips play at random times.
It seems that WorkoutRunnerComponent is not getting destroyed on route navigation, and due to this, none of the child components are destroyed, including WorkoutAudioComponent. The net result? A new WorkoutRunnerComponent is being created every time we navigate to the workout page but is never removed from the memory on navigating away.
The primary reason for this memory leak is the event handlers we have added in WorkoutAudioComponent. We need to unsubscribe from these events when the audio component unloads, or else the WorkoutRunnerComponent reference will never be dereferenced.
Another component lifecycle event comes to our rescue here: OnDestroy Add this implementation to the WorkoutAudioComponent class:
ngOnDestroy() { this.subscriptions.forEach((s) => s.unsubscribe()); }
Also, remember to add references to the OnDestroy event interface as we did for AfterViewInit.
Hope the subscription array that we created during event subscription makes sense now. One-shot unsubscribe!
This audio integration is now complete. While this approach is not an awfully bad way of integrating the two components, we can do better. Child components referring to the parent component seems to be undesirable.