Skip to main content

Angular Services

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.

Comments

Popular posts from this blog

4 Ways to Communicate Across Browser Tabs in Realtime

1. Local Storage Events You might have already used LocalStorage, which is accessible across Tabs within the same application origin. But do you know that it also supports events? You can use this feature to communicate across Browser Tabs, where other Tabs will receive the event once the storage is updated. For example, let’s say in one Tab, we execute the following JavaScript code. window.localStorage.setItem("loggedIn", "true"); The other Tabs which listen to the event will receive it, as shown below. window.addEventListener('storage', (event) => { if (event.storageArea != localStorage) return; if (event.key === 'loggedIn') { // Do something with event.newValue } }); 2. Broadcast Channel API The Broadcast Channel API allows communication between Tabs, Windows, Frames, Iframes, and  Web Workers . One Tab can create and post to a channel as follows. const channel = new BroadcastChannel('app-data'); channel.postMessage(data); And oth...

Certbot SSL configuration in ubuntu

  Introduction Let’s Encrypt is a Certificate Authority (CA) that provides an easy way to obtain and install free  TLS/SSL certificates , thereby enabling encrypted HTTPS on web servers. It simplifies the process by providing a software client, Certbot, that attempts to automate most (if not all) of the required steps. Currently, the entire process of obtaining and installing a certificate is fully automated on both Apache and Nginx. In this tutorial, you will use Certbot to obtain a free SSL certificate for Apache on Ubuntu 18.04 and set up your certificate to renew automatically. This tutorial will use a separate Apache virtual host file instead of the default configuration file.  We recommend  creating new Apache virtual host files for each domain because it helps to avoid common mistakes and maintains the default files as a fallback configuration. Prerequisites To follow this tutorial, you will need: One Ubuntu 18.04 server set up by following this  initial ...

Working with Node.js streams

  Introduction Streams are one of the major features that most Node.js applications rely on, especially when handling HTTP requests, reading/writing files, and making socket communications. Streams are very predictable since we can always expect data, error, and end events when using streams. This article will teach Node developers how to use streams to efficiently handle large amounts of data. This is a typical real-world challenge faced by Node developers when they have to deal with a large data source, and it may not be feasible to process this data all at once. This article will cover the following topics: Types of streams When to adopt Node.js streams Batching Composing streams in Node.js Transforming data with transform streams Piping streams Error handling Node.js streams Types of streams The following are four main types of streams in Node.js: Readable streams: The readable stream is responsible for reading data from a source file Writable streams: The writable stream is re...