Handling search using the MusicService

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:

  1. 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'; 
  1. 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; 
  1. 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:

  1. Change the Search tag in the template to <Search @search-triggered="search($event)" @search-cleared="searchCleared()"></Search>.
  2. 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:

Notice that, in this case, we have decided not to add a type annotation to the artistsLoading field. We have omitted it because the compiler clearly sees that we immediately assign a boolean to value it, so adding a type annotation is useless in this case.

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:

  1. We call the findArtists method of our service and subscribe to the returned Observable (which we get immediately).
  2. 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).
  3. We pass an observer to the subscribe method, with the next, errorand complete methods (that is, the complete Observable contract).
  4. 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:

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.

We have kept both search operations independent; depending on the server and network conditions, one result will arrive before the other. In this case, we do not care about the order but know that RxJS observables can be coordinated/chained if needed. For example, you could decide to wait for all results to arrive before updating the view. There are many operators that you can use to help you, such as groupByhttps://www.learnrxjs.io/operators/transformation/groupby.html.

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.

The preceding code that handles RxJS observables/subscriptions could be improved and made safer by introducing and using vue-rx (https://github.com/vuejs/vue-rx), a library that adds support for RxJS in Vue.js. If you have time, then also take a look at vue-rx-decorators (https://github.com/MinuKang/vue-rx-decorators). We won't cover those libraries here due to space constraints, but they are great if you combine Vue.js and RxJS in your project. Check out this article to learn more: https://codeburst.io/combining-vue-typescript-and-rxjs-with-vue-rx-a084d60b6eac.

Here is a list of references: