Angular Services

From bibbleWiki
Jump to navigation Jump to search

Introduction

A Service is

  • Reusable piece of functionality
  • Responsibility for sing piece of function

Create a Service if

  • Not required by the view
  • Business logic not used across components
  • Share logic across components

Parts of a Service

  • Class
  • Injectable Decorator
  • Provider, defined in the module as an array

Delivering to a component is simple

Basic Service

Service Code

This is the old way to register a service. We specify the @Injectable decorator with no arguments

import {Injectable} from '@angular/core'
@Injectable()
export class LoggerService {

  log(message: string) : void {
    const timeString : string = new Date().toLocaleTimeString()
    console.log(`${message} (${timeString})`)
  }

  error(message: string) :void {
    console.error(`ERROR: ${message}`)
  }
}

The recommended way

import {Injectable} from '@angular/core'
@Injectable({
   providedIn: 'root'
})
export class LoggerService {
...
}

Registering the Service

This is not required provided you use the providedIn property in the @Injectable declaration and the benefit of not specifying it in the providers array is that the service is not compiled in if it is not determined as used.

...
import { LoggerService } from './services/logger.service';
...
@NgModule({
  declarations: [
...
  ],
  imports: [
...  ],
  providers: [LoggerService],
...

Usage Of Service Code

class AComponent {
...
   constructor(private loggerService : LoggerService) {}
..
   this.loggerService.log("Whoopee!")

Using CLI to create a Service

ng g s services/data --spec false

Dependency Injection

There are four methods to create instances of services

  • UseClass
  • UseExisting
  • UseValue
  • UseFactory

UseClass

A provider tells an injector how to create a service, for the long hand version of this it would look like this.

@NgModule({
...
  providers: [
     { provide: LoggerService, useClass: LoggerService}
  ]
..

UseExisting

We can specify an existing class in the hierarchy provided it has the correct interface.

@NgModule({
...
  providers: [
     PlainLogger,
     { provide: LoggerService, useExisting: PlainLogger}
  ]
..

UseValue

We implement these on the fly.

@NgModule({
...
  providers: [
     PlainLogger,
     { provide: LoggerService, useValue: {
       log: (message) => console.log(`MESSAGE": ${message}`),
       error: (message) => console.error(`ERROR": ${message}`)
     }}
  ]
..

UseFactory

We implement this by providing factory function to build an service. The deps is and array of parameters which may be required for the factory function. The function

import { LoggerService } from "./logger.service";
import { DataService } from "./data.service";

export function dataServiceFactory(logger: LoggerService) {

  let dataService = new DataService(logger)

  logger.log(`Creating new Data Service with factory function`)

  return dataService
}

And the service creation

@NgModule({
...
  providers: [
    LoggerService,
    { provide: DataService, useFactory: dataServiceFactory, deps: [LoggerService] }
  ],
..

Finally

TypeScript Setting

We must use the emitDecoratorMeta flag in the tsconfig.json for dependency injection to work. The values used on examples is provided below.

{
  "compileOnSave": false,
  "compilerOptions": {
    "outDir": "./dist/out-tsc",
    "baseUrl": "./src",
    "sourceMap": true,
    "declaration": false,
    "moduleResolution": "node",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es5",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2016",
      "dom"
    ],
    "module": "es2015"
  }
}

Dependency Hierarchy

Each provider, provides a separate instance of the service. To understand which your code is using you need to understand the hierarchy. The search goes up the tree to find the nearest function. This is why it is recommend you put yours providers as near to the root as possible especially if have data in the service. Pure function services are ok but of course will make your packages larger. Angular-2-Injector-Tree.png

Errors with Dependency

Given the above hierarchy, one of the errors you may encounter is that the service has been injected in a component in a module but not your component. The code will compile and run but you will see "No Provider for myService"

Angular DI error.png

Calling Services Async

Example Of Promise

    this.productService.getProduct(id)
      .then(
        (product: Product) => this.displayProduct(product),
        (reason: string) => console.log(`Rejected: ${reason}`)
      )
      .catch(
        (error: err) => this.errorMessage = err
      );

Example Of async/await

private async getAuthor(readerID: number) Promise<void> {

    try {
      let author:  string = await this.dataService.getAuthorRecomendation(readerID)
      this.loggerService.log(author)
    }
    catch(error)(
        this.loggerService.error(error)
    )
}

// And usage could be
ngOnInit() {
...
   this.getAuthor(1);
...
}

Example of Observable

    this.productService.getProduct(id)
      .subscribe({
        next: (product: Product) => this.displayProduct(product),
        error: err => this.errorMessage = err
      });