The Services are one suitable solution that provides you specific options and that can be used in different components separately.
In this chapter, you will create own service. This will happen through DI (Dependency Injection).
There are different approaches to create services. We will demonstrate one of the popular approaches and after that we will create a service for the started example of Heroes
.
With services we can easily share some information with different classes without they knowing about each other.
There are 3
important steps to create an Angular Service.
@Injectable
Decorator.The app.service.ts
example:
import { Injectable } from '@angular/core';
()
export class MyService {
//Do Something
MyMethod() {}
}
Here we should follow 4
important steps.
providers
in the metadata of the component. Also, it is possible to include it through @NgModule
decorator to be accessible from all components.The app.component.ts
example:
import { Component } from '@angular/core';
import { MyService } from './app.service';
export class AppComponent {
result: any = null;
constructor(private _myService: MyService) {
}
onClickMe() {
this.result = this._myService.MyMethod();
}
}
Heroes
project.We will use the familiar approach with commands through the 'terminal' to create a service named hero
.
ng generate service hero
This command (ng generate service hero) will generate a HeroService
class in the src/app/hero.service.ts
. It should look as follows:
import { Injectable } from '@angular/core';
@Injectable()
export class HeroService {
constructor() { }
}
You could see that the Angular @Injectable
decorator is added into this file.
This decorator tells Angular that this service might itself have injected dependencies.
Implement getHeroes()
method into the HeroService
. In this way we could get data from anywhere - a web service, local storage, or a mock data source.
This method will deliver the mock heroes.
Import the Hero
class and HEROES
list.
import { Hero } from './hero';
import { HEROES } from './mock-heroes';
Now add the getHeroes()
method as follow:
getHeroes(): Hero[] {
return HEROES;
}
It is almost done. We need to include our service to the @NgModule.providers
array of the AppModule
in the app.module.ts
file:
providers: [
HeroService
],
We can use the Angular command to do this instead of us:
ng generate service hero --module=app
Import the HeroService
into the HeroesComponent
.
import { HeroService } from '../hero.service';
Now we can remove the HEROES
import because we do not need it anymore.
We will get all needed data from the HeroService
. Also, replace the definition of the heroes
property with a simple declaration.
heroes: Hero[];
We should include a private heroService
property of type HeroService
to the constructor.
constructor(private heroService: HeroService) { }
The DI system sets the heroService
parameter as a singleton instance of the HeroService
at the moment when Angular creates a HeroesComponent
.
The best practices suggest creating a function that will retrieve the heroes from the service.
getHeroes(): void {
this.heroes = this.heroService.getHeroes();
}
getHeroes()
method in the ngOnInit
It is possible to invoke this method into the constructor
but this is not the best practice. That is used for the simple initialization such as wiring constructor parameters to properties.
We will invoke this function inside the ngOnInit
Our "heroes.component.ts
" file should look like this below:
import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
({
selector: 'app-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
heroes: Hero[];
constructor(private heroService: HeroService) { }
ngOnInit() {
this.getHeroes();
}
getHeroes(): void {
this.heroes = this.heroService.getHeroes();
}
selectedHero: Hero;
rowdetails(event: any): void {
let args = event.args;
let rowData = args.row.bounddata;
this.selectedHero = { id: rowData.Id, name: rowData.Name };
}
}
After we save these changes our solution still will work as before.
The implementation of our service to fetch 'heroes' is synchronous.
The HeroesComponent
consumes the result of the getHeroes()
method synchronously - this.heroes = this.heroService.getHeroes();
.
If we imagine the real case where the data is retrieved from the server, this fetching operation is asynchronous.
In this case, it will be better to prepare our service to work asynchronously.
HeroService
to work with Observable
Observable
is one of the key classes in the RxJS library.
In this paragraph we will use RxJS Observable
s and also, for the demo purpose to simulate this fetching data from the server we will use RxJS of()
method.
Including these new changes into the HeroService
placed in src/app/hero.service.ts
and import as follows:
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
The next step is to replace the current implementation of the getHeroes()
method as follows:
getHeroes(): Observable<Hero[]> {
return of(HEROES);
}
The of(HEROES)
will return an observable from this type Observable<Hero[]>
. This will throw an error because the heroes
variable that should get this data is not of the same type.
getHeroes()
method
We should make changes in the getHeroes
method of the HeroesComponent
. Currently it returns data with wrong type - Observable<Hero[]>
but it expect Hero[]
type.
For this purpose we should transform our implementation for this situation.
We will use Observable.subscribe()
which is totally different from the previous approach.
Replace the getHeroes()
method of the HeroesComponent
as below:
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes);
}
The page will load these records simultaneously now or just a moment after that. The big plus of the subscribe
is that the data will be emitted when it is all ready.
We will repeat some of the actions to become more confident. We will add a new component MessagesComponent
, new service MessageService
and you will know how to inject one service into another.
Again we will use the Angular's commands to create the MessagesComponent
.
ng generate component messages
Everything should be bound automatically. Now we have new folder src/app/messages
with the new component MessagesComponent
.
Open the AppComponent
and add the selector of this component into the template of the app.component.html
:
<h1>{{title}}</h1>
<app-heroes></app-heroes>
<app-messages></app-messages>
MessageService
The next step is to create a MessageService
in src/app
.
Again we will use a command to achieve this and use --module=app
option to provide this service in the AppModule
.
ng generate service message --module=app
After we have MessageService
created, we will implement two new methods there.
The add()
method will add a message and the clear()
method which will clear our messages.
Make the following changes:
import { Injectable } from '@angular/core';
export class MessageService {
messages: string[] = [];
add(message: string) {
this.messages.push(message);
}
clear() {
this.messages = [];
}
}
MessageService
into the HeroService
We will import
the MessageService
to the HeroService
.
import { MessageService } from './message.service';
After that we should determine a property relevant to this service in the constructor of the HeroService
.
constructor(private messageService: MessageService) { }
We now know how to inject one service MessageService
into another HeroService
which will be injected into the HeroesComponent
. This is a typical "service-in-service" scenario.
Modify the getHeroes()
method of the HeroService
that will show a message when the heroes are fetched.
getHeroes(): Observable<Hero[]> {
this.messageService.add('HeroService: fetched heroes');
return of(HEROES);
}
The message that we create is collected into the messages
property of the instance of the MessageService
.
In the next step we will import the MessageService
into the MessagesComponent
and we will repeat the previous steps.
Import that service into the src/app/messages/messages.component.ts
.
import { MessageService } from './message.service';
Add the public messageService
property because it will be bound into the template.
constructor(public messageService: MessageService) {}
We will add a new jQWidgets - jqxButtonComponent. Need to follow this steps bellow:
tsconfig.json
:"node_modules/jqwidgets-scripts/jqwidgets-ts/angular_jqxbuttons.ts"
"files": [
"src/app/app.module.ts",
"node_modules/jqwidgets-scripts/jqwidgets-ts/angular_jqxinput.ts",
"node_modules/jqwidgets-scripts/jqwidgets-ts/angular_jqxgrid.ts",
"node_modules/jqwidgets-scripts/jqwidgets-ts/angular_jqxbuttons.ts"
]
The next step is to import jqxButtonComponent
into the AppModule
:
import { jqxButtonComponent } from 'jqwidgets-scripts/jqwidgets-ts/angular_jqxbuttons';
...
@NgModule({
declarations: [
...
jqxButtonComponent,
...
],
})
Open the MessagesComponent
template and replace it as following:
<div *ngIf="messageService.messages.length">
<h2>Messages</h2>
<jqxButton (click)="goBack()" [theme]="'material'">go back</jqxButton>
<div *ngFor='let message of messageService.messages'> {{message}} </div>
</div>
We use the *ngIf
and *ngFor
Angular's directives to visualize the template of the MessagesComponent
and also, event binding to the button to invoke MessageService.clear()
method.
The message can be stylized in suitable way to look better.
HeroService
.HeroService
class.HeroService
in the root AppModule
so that it can be injected anywhere.HeroService
class.Observable
and the RxJS Observable library and used it to make an asynchronous signature.of()
to return an Observable of mock heroes (Observable<Hero[]>
).ngOnInit
lifecycle hook calls the HeroService
method, not the constructor.MessageService
for loosely-coupled communication between classes.MessageService
within other service HeroService
, which is injected into a component.