Before we can use the MusicService in our Home view, we need to inject it. Since we have already configured InversifyJS, we can do that really quickly:
- First of all, open the src/views/Home.vue file and add the following imports:
import {MusicService} from '@/services'; import {TYPES} from '@/ioc/types'; import {lazyInject} from '@/ioc/config';
- Then, create a new field for the service inside the class and decorate it with @lazyInject as follows:
@lazyInject(TYPES.MUSIC_SERVICE) private musicService!: MusicService;
- We can verify that our service correctly gets injected by modifying the mounted life cycle hook:
public mounted() { console.log('Home component mounted'); console.log('Music service instance: ', this.musicService); }
With this done, we can now easily get the information we need using the service. Actually, we'll only need to interact with it when events are emitted by the search component.
Let's implement the event handlers:
- Change the Search tag in the template to <Search @search-triggered="search($event)" @search-cleared="searchCleared()"></Search>.
- Then, don't forget to implement the two following functions:
public search(searchText: string): void { console.log('Handling search: ', searchText); } public searchCleared(): void { console.log('Handling search cleared'); }
If you try now, you should see that our event handlers are indeed invoked when we type something in the search input, click the button, or clear the input. Great!
Now it is time to use the service and to display some actual results.
Add these to the class:
- import { Artist } from '@/domain';
- private artists: Artist[] = [];
- private artistsLoading = false;
Then, adapt the search method as follows:
public search(searchText: string): void { console.log('Handling search: ', searchText); this.artistsLoading = true; const artistsSearchSubscription =
this.musicService.findArtists(searchText).subscribe({ next: (artists: Artist[]) => { console.log(`Artists search result received. Found
${artists.length} artists`); this.artists = artists; this.artistsLoading = false; }, error: (err: any) => { console.error('Error: ', err); this.artistsLoading = false; artistsSearchSubscription.unsubscribe(); }, complete: () => { this.artistsLoading = false; artistsSearchSubscription.unsubscribe(); }, }); }
This code leverages the Observable instance returned by our service layer.
This reactive programming approach takes some getting used to, but is very elegant:
- We call the findArtists method of our service and subscribe to the returned Observable (which we get immediately).
- We keep a reference to the Subscription object returned by the subscribe method call so that we can later unsubscribe (and avoid introducing a memory leak).
- We pass an observer to the subscribe method, with the next, error, and complete methods (that is, the complete Observable contract).
- When either complete or error is called, we unsubscribe. Alternatively, we could use the vue-rx library to transparently handle the subscriptions for us (more about it later)
We have also kept track of the loading state. Thanks to this, and with the help of Element, we can easily display a loading indicator on the page.
To render the artists list and handle the loading state, go ahead and add the following code to the template, right below the Search tag:
<el-divider></el-divider> <el-row class="lf-home-view-results"> <el-col :span="12"> <el-container class="lf-home-view-results-artists"
direction="vertical"> <h3>Artists</h3> <el-container v-loading="artistsLoading"> <ul> <li v-for="artist in artists" :key="artist.id">
{{artist.name}}</li> </ul> </el-container> </el-container> </el-col> </el-row>
If you test the application now, you should see that it works and that we even get a nice loading icon while the search operation is ongoing, thanks to the v-loading directive set on the container. We can build upon this basic structure to also search for songs using the same input.
Add two more fields to the class:
- private songs: Song[] = [];
- private songsLoading = false;
Of course, don't forget to add the necessary import for the Song class:
import {Song} from '@/domain';
Then, add the following code to the search method:
this.songsLoading = true; const songsSearchSubscription = this.musicService.findSongs(searchText).subscribe({ next: (songs: Song[]) => { console.log(`Songs search result received. Found ${songs.length}
artists`); this.songs = songs; this.songsLoading = false; }, error: (err: any) => { console.error('Error: ', err); this.songsLoading = false; songsSearchSubscription.unsubscribe(); }, complete: () => { this.songsLoading = false; songsSearchSubscription.unsubscribe(); }, });
Indeed, this code has the exact same structure as before but searches for songs instead of artists.
To continue, modify the template to display the retrieved songs by adding a new el-col (similar to the previous one, but for songs) below the first el-col element:
<el-col :span="12"> <el-container class="lf-home-view-results-songs"
direction="vertical"> <h3>Songs</h3> <el-container v-loading="songsLoading"> <ul> <li v-for="song in songs" :key="song.id">{{song.name}}</li> </ul> </el-container> </el-container> </el-col>
Finally, adapt the style tag as follows:
<style scoped> .lf-artists > h3 { color: var(--font-color); } .lf-artists ul { text-align: initial; } .lf-home-view-results { color: var(--font-color); } </style>
Let's not worry too much about the styling at this point. If you're motivated, though, feel free to improve the display before moving on.
Here is a list of references:
- Loading indicators: https://element.eleme.io/#/en-US/component/loading