Angular application is built from components – organized in modules with hierarchy. The component represent a user interface element with properties, methods, input and output parameters and more. When we want interaction between components we use input and output parameters but if the interaction is not between parent and child (for example 2 brothers components share functionality) it can be complex to make it using the parent. this is where Angular services help
The main idea behind a service is to provide an easy way to share code and data between components. Using dependency injection you can control how the service instances are shared
Lets start with a simple service to understand the basic concepts
To create a service use Angular CLI
# ng g s Math
Lets implement a simple random function:
@Injectable() export class MathService { constructor() { } lowlim:number = 10; uplim:number = 20; getRand():number{ let range:number; range = this.uplim - this.lowlim; return +Math.floor(Math.random()*range) + +this.lowlim; } }
To inject the service to the app component add it to the providers list in the component decorator. Declare an object and use it:
@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], providers: [MathService] }) export class AppComponent { title = 'app'; num:number; num2:number; constructor(private _ser:MathService) { this._ser.lowlim = 100; this._ser.uplim = 200; } fn() { this.num = this._ser.getRand(); } }
This will inject a service instance to the component
Note that if you declare 2 objects , you will get the same instance
Now lets create 2 child components Comp2, Comp3:
import { Component, OnInit } from '@angular/core'; import {MathService} from "../math.service"; @Component({ selector: 'comp3', template: ` <input type="text" [(ngModel)]="low"/> <input type="text" [(ngModel)]="high"/> <button (click)="setlim()">Set Limits</button> <button (click)="gennum()">Generate</button> {{num}} `, styles: [], providers:[MathService] }) export class Comp2Component { constructor(private _ser:MathService) { } low:number; high:number; num:number setlim(){ this._ser.uplim = this.high; this._ser.lowlim = this.low; } gennum(){ this.num = this._ser.getRand(); } }
The component generate 2 text fields for the random limit and a button to generate a random number in the range. We inject the service to the component decorator and also do the same with comp3 that has the same code , if we put the components in the parent we get 2 different instances
If we declare the provider on the parent component or on the Module we will get the same instance and in this way we can share also data between the components:
@NgModule({ declarations: [ AppComponent, Comp2Component ], imports: [ BrowserModule, FormsModule ], providers: [MathService], bootstrap: [AppComponent] }) export class AppModule { }
In this example we set a different range on the components but when we generate random we get numbers from the second range (the last we set)
Asynchronous Operations
It is very common to use a service for asynchronous tasks – for example, creating http request. You can use a Promise object or (and better) observable
Observable is an implementation of publisher subscriber pattern, it is like a stream that you can sent multiple events and subscribe to multiple events. It is part of rxjs
Simple example
import {Observable} from "rxjs"; let t= Observable.interval(1000); t.subscribe(i => console.log("hello "+i));
In the above example, we create an observable that generate an event every 1 second. We need to subscribe to see something happen – in this example we print a simple message and we get an index as a parameter (incremented counter)
The previous example runs forever, to limit the interval to 5 times add take:
let t= Observable.interval(1000).take(5);
to add other operation add do:
let t= Observable.interval(1000).take(5).do(i => console.log("hello "+i));
Note that you still have to subscribe to make things happens, we can subscribe more than once
t.subscribe(); //subscribe without additional tasks t.subscribe(j => console.log("more:" + j)); // add more task
You can also apply some operations on the observable :
let t= Observable.interval(1000).take(10).filter(i => i%2==0); t.subscribe(i => console.log("hello "+i)); t.subscribe(i => console.log("bye "+i));
Working With HTTP
To work with http we need to create http requests, with http we can generate:
GET request – select data (return records as Json object)
localhost:3000/ins localhost:3000/ins/100 localhost:3000/ins?id=400 localhost:3000/ins?rate=3
POST – insert new data
Header: Content-Type: application/json Body: { "id": 500, "Name": "noya", "Price": 444, "rate": 3 }
PUT – update data
localhost:3000/ins/500 Content-Type: application/json { "id": 500, "Name": "noyale", "Price": 4443, "rate": 3 }
DELETE – delete data
localhost:3000/ins/200
To make http requests, first we generate a new service
# ng g s Data
Add the HttpModule to the app.module.ts file
imports: [ BrowserModule, FormsModule, HttpModule ],
Create the service
import { Injectable } from '@angular/core'; import {Http} from "@angular/http"; import {Book} from "./Book"; import {Observable} from "rxjs/Observable"; @Injectable() export class DataService { constructor(private _http:Http) { } getData():Observable<Book[]>{ return this._http.get("http://localhost:3000/books").map(r => <Book[]>r.json()); } }
http module provides methods for all HTTP request types – here we use get, we convert the result from Response object to a json object and cast it Book array
declare a class:
export class Book{ id:number; Name:string; Price:number; rate:number; }
Build the template:
<button (click)="load()">load</button> <table border='1' align="center" width='70%' *ngIf='books && books.length > 0'> <tbody> <tr *ngFor='let b of books'> <td> {{b.id}} </td> <td> {{b.Name }} </td> <td> {{b.Price }} </td> <td> {{b.rate}} </td> </tr> </tbody> </table>
Add the code in the component class:
books:Book[] = []; load(){ this._data.getData().subscribe(b => this.books = b); }
The new HttpClient module
One new feature of Angular 5 is the HttpClient Module that is in release version (previously was beta). It make the use of http services much more easy
Add HttpClientModule to import section in app.module
imports: [ BrowserModule, FormsModule, HttpClientModule ],
Change the service:
import {HttpClient} from "@angular/common/http"; @Injectable() export class DataService { constructor(private _htc:HttpClient) { } getData():Observable<Book[]>{ return this._htc.get<Book[]>("http://localhost:3000/books"); } }
As you can see we don’t need to map the result and the generic function makes the casting so the code is much more clear