Angular Services: Difference between revisions
Line 184: | Line 184: | ||
(error: err) => this.errorMessage = err | (error: err) => this.errorMessage = err | ||
); | ); | ||
</syntaxhighlight> | |||
==Example Of async/await== | |||
<syntaxhighlight lang="ts"> | |||
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); | |||
... | |||
} | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Latest revision as of 04:24, 8 September 2020
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.
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"
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
});