Lukas Marx
September 15, 2019
Angular

Angular Services Tutorial

In this tutorial, you will learn what angular services are and how they are used.

We will take a look at the benfits of services by using simple examples.

That way you will learn about dependency injection, how to provide a serive and what singleton services are.

Let's get started!

angular-question

What are services?

Services solve one big problem: They prevent us from copying logic over and over. Instead, they centralize business logic.

Also, they are very useful everywhere in our application.

That is because they can be easily requested via Dependency Injection.

They are also very useful if you want to use the same instance of a class everywhere in your class.

Services are just classes

After all, services are just classes. Other than components, services may only contain logic. They should be completely separated from the view (anything visual). They also should only fulfill one purpose, following the single responsibility principle.

Use Cases

The most common use case is I/O (Input/Output). To get more specific: HTTP requests. Generally, all HTTP requests in Angular are wrapped by a service. Why? Because with the help of Dependency Injection, our code stays highly maintainable. Here is an example:

Imagine you changed the route of your REST-Endpoint. Imagine you called that route in a billion different places. Good luck finding and replacing them all!

By wrapping our Http calls in a service, we know exactly where the change has to be made. And it is only one line affected.

angular-compiler-banner

Caching example

My favorite example for a service is a cached HTTP service. So let's build one together!

So let's say we want to create a wrapper service for a specific REST-Endpoint. Furthermore, we only want to hit the web once. Every additional response is served from a cache.

First, we define what kind of object our endpoint returns. In this case, we do so, by defining the Whatever interface.

src/app/services/cached.http.service.ts
interface Whatever {
  id: string
}

In this case, our response object only contains an id. Of course, this object can look however you want.

Next, we create our service. It is a simple injectable with the public getWhatever method. It also has a cache object, where we save the responses we got. Finally, we have a private getWhateverHttpRequest that does the actual Http request.

src/app/services/cached.http.service.ts
import { Injectable } from '@angular/core'
import { Observable } from 'rxjs/Observable'
import { HttpClient } from '@angular/common/http'

// of-operator has to be imported separatly
import 'rxjs/add/observable/of'
import 'rxjs/add/operator/first'

interface Whatever {
  id: string
}

@Injectable()
export class CachedHttpService {
  private cache = {}

  // Note: I'm using the 4.3 Http client here.
  constructor(private http: HttpClient) {}

  public getWhatever(id: string): Observable<Whatever> {
    if (this.cache[id] == null) {
      // If we have no response in chache, reach out to the web
      const observable = this.getWhateverHttpRequest(id)

      // We need to subscribe to the result, to write the result to our cache
      let subscription = observable.first().subscribe(response => {
        // Wite the response to cache
        this.cache[id] = response
      })

      console.log('Cached Http: Read from server')
      return observable
    } else {
      //If we have the response in our cache already, just serve that response
      console.log('Cached Http: Read from cache')
      return Observable.of(this.cache[id])
    }
  }

  private getWhateverHttpRequest(id: string): Observable<Whatever> {
    // Only for test purposes
    return Observable.of({ id: 'result' })
    // return this.http.get<Whatever>("anyurl.com/api/any/" + id);
  }
}

Every class (or service) served via Dependency Injection, has to be provided somewhere. We do so in our AppModule. We also need to import the HttpClientModule here, if we actually want to make any requests.

I'm using the HttpClient of Angular version 4.3+. If you are on older versions, you can use the old client, as well. However, the syntax might look a little different.

src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser'
import { NgModule } from '@angular/core'

import { AppComponent } from './app.component'
import { HttpClientModule } from '@angular/common/http'
import { CachedHttpService } from './services/cached.http.service'

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, HttpClientModule],
  providers: [CachedHttpService], // provide our service here
  bootstrap: [AppComponent],
})
export class AppModule {}

All that is left now, is to actually use our service somewhere. We do so in our AppComponent.

src/app/app.component.ts
import { Component, OnInit } from '@angular/core'
import { CachedHttpService } from './services/cached.http.service'

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
  title = 'app'
  constructor(private cachedHttpService: CachedHttpService) {}

  ngOnInit(): void {
    this.cachedHttpService.getWhatever('1').subscribe(result => {
      console.log('Received Response: ' + result.id)
    })

    this.cachedHttpService.getWhatever('1').subscribe(result => {
      console.log('Received Response: ' + result.id)
    })

    this.cachedHttpService.getWhatever('1').subscribe(result => {
      console.log('Received Response: ' + result.id)
    })
  }
}

Output

As you can see, only the first request is actually hitting the web. Exactly what we wanted to achieve:

Cached Http: Read from server
Received Response: result

Cached Http: Read from cache
Received Response: result

Cached Http: Read from cache
Received Response: result

Singleton services

If a service is a singleton, there is only one instance of that service for the whole app.

With singleton services there is an alternative wayof providing the service. Instead of using the providers-array of the @NgModule, we can tell the @Injectable where the service should be provided.

This is done by passing the provideIn option to the @Injectable decorator:

import { Injectable } from '@angular/core'

@Injectable({
  providedIn: 'root',
})
export class CachedHttpService {}

In this case, the service is provided in "root". That means it is provided applicaition-wide. It can also be provided in a specific module.

The provideIn feature is available in Angular 6.0.0 or higher.

Using provideIn over the regular aproach allows angular to tree-shake the service, resulting in a potentially smaller bundle size.

Conclusion

In this tutorial, we learned what angular services are and how to use them.

I hope you enjoyed this read.

If you did please share this post!

Thanks for reading!


Leave a comment

We save your email address, your name and your profile picture on our servers when you sing in. Read more in our Privacy Policy.