Forward Advance Learning

Services and Dependency Injection with TypeScript

A TypeScript service is just a plain old TypeScript class, which ultimately is a newable. In Angular 1, services needed to be special Angular objects because Angular 1 had it's own module system and string based DI.

In Angular 2, and class or newable function can be a service.

@Injectable()
export class CatService {
get() {
return ['Manny', 'Francis', 'Terry Wogan'];
}
}

The @injectable decorator at the top is a blank decorator. It's only purpose is to trick TypeScript into storing type data about the object. The presence of a decorator means that type info might be required, so Angular holds onto it using Reflect.Metadata.

Make it available in your module

Tell your ngModule about the service using the providers attribute.

@NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent, CatListComponent ],
bootstrap: [ AppComponent ],
providers: [ CatService ]
})
export class AppModule { }

Import the service

We can then inject this into a component, simply by receiving it in the constructor like this:

import { Component } from '@angular/core';
import { CatService } from './cats';
@Component({
selector: 'app',
template:`
{{cats | json}}
`,
})
export class AppComponent {
constructor(cats: CatService) {
this.cats = cats.get();
}
}

Dependency Injection

Our Component receives a CatService, and yet we never manually made one. Angular has made one for us. We call this Dependency Injection.

Notice the class constructor. It receives a catService of type CatService. This is enough for Angular to know that it must make a CatService and pass it in.

This works because TypeScript saves type information in metadata when it compiles. In our tsconfig, we have enabled the emitDecoratorMetadata flag. Any object with a decorator saves its type, and its constructor signature in a global object called Relect.metadata. Angular uses this object to work out how to instantiate objects.

Simple Exercise - A Service

In your exercise folder you'll find an app that renders a list of cats using ng-for. The cats are currently hardcoded into the component.

To build this application:

  1. First download the dependencies with npm install.
  2. Can compile this application by typing: webpack -w.

Convert this to use a service instead, so that the list of cats comes from a service.

I would like the component to receive cats:Cats in the constructor, and then call this.cats = cats.get() to get the cats`

Escape the Dungeon Exercise - Let's actually make it winnable

We have an object called LocationModel that contains a single location. Let's expand this and create a location service that holds a collection of locations, and lets us navigate between them.

We might change our LocationModel to look like this:

export class LocationModel {
name:string = ""
description:string = ""
items:Array<any> = []
north:LocationModel
south:LocationModel
east:LocationModel
west:LocationModel
removeItem(item:any) {
this.items.splice(this.items.indexOf(item), 1)
}
placeItem(item:any) {
this.items.push(item)
}
}

We might also Create a location service:

import {Injectable} from '@angular/core';
import {LocationModel} from './location.model'
var dungeon = new LocationModel()
var corridor = new LocationModel()
dungeon.north = corridor;
@Injectable()
export class LocationService {
currentLocation:LocationModel = dungeon
moveNorth() {
if (this.currentLocation.north) {
this.currentLocation = this.currentLocation.north
}
}
...
}

We would then have a location service which we could call to get the current location, and to move between locations.

Look in the exercise start point, you'll find an implemented location service. Use this as a start point to create an InventoryService that holds a list of ItemModels. Let the user store items and drop them.

Extension

Crete a Monster Service with a fight method. Pass it a monster from the location and a weapon from the inventory and have it return an outcome.