© Venkata Keerti Kotaru 2020
V. K. KotaruAngular for Material Designhttps://doi.org/10.1007/978-1-4842-5434-9_7

7. Angular: Services and Dependency Injection

Venkata Keerti Kotaru1 
(1)
Hyderabad, India
 

Angular provides features that help encapsulate reusable logic into a service. An Angular service does not have a view. It is the logic and the code that runs in the background (still in the browser).

Along with Angular services, this chapter introduces asynchronous constructs. It begins with simple callback functions in JavaScript, and then describes promises and observables. RxJS is used for observable implementation.

The chapter describes dependency injection in Angular and introduces Angular services, which are powerful and frequently used features.

Asynchronous Function Calls

Asynchronous programming and function calls are powerful and widely used. They help build applications that are fluid and responsive to user interactions. When building a user interface, a long-running process can adversely affect the user experience. It is important that the application is usable and responds to user clicks and keyboard input when a function call takes multiple seconds to finish.

A typical function executes a set of statements and returns a value. The calling function waits for the return value. This is a traditional and synchronous model. This model does not scale for long-running functions; for example, remote service calls (server-side API/HTTP calls) may take multiple seconds to return. As another example, user prompts that need user interaction and decisions could be time-consuming. It could be a prompt to confirm that a record was deleted. Users typically take a second or two to respond by clicking yes or no. In a synchronous model, the application does not run other statements while waiting for user input.

Before we get into the details of asynchronous programming in JavaScript and TypeScript, let’s discuss function callbacks. This is a widely used paradigm. JavaScript programming (including Angular and TypeScript) extensively uses callbacks, which are a function passed in by the calling function. The called function invokes a callback when the task is complete or a result is available. Listing 7-1 is an example of using callback functions. It uses the setTimeout() API, which runs after a prescribed number of milliseconds.
1. console.log("message 1");
2. setTimeout(
3.    function(){ console.log("callback invoked after 3 seconds") }
4.    , 3000);
5. console.log("message 2");
Listing 7-1

setTimeout, an Example of an Asynchronous Function Callback

Listing 7-1 has three JavaScript statements: lines 1, 2, and 5. The setTimeout function spans three lines: 2, 3, and 4.

We pass a function as a parameter in the setTimeout() function. It is a simple function that prints a console log message. The second parameter to the setTimeout is the number of milliseconds after which the callback function needs to be invoked. We pass 3000 milliseconds/3 seconds. setTimeout, the called function, asynchronously invokes a callback after the timeout. The called function does not wait for the timeout to occur. It executes lines 1, 2, and 5 and finishes the task. After three seconds, the callback is asynchronously invoked to print the final console message.

The result prints as follows.
  • message 1

  • message 2

  • callback invoked after 3 seconds

JavaScript Promises

Function callbacks allow you to run statements out of order, which opens the possibility that one or more lines of code can run when the data is ready. The statements do not need to run in order and block the remaining statements.

Promises are progressive succession in asynchronous programming. A function can return a promise object. The calling function assigns the returned value to a variable. The returned value is not the real value, rather it is a promise (object). It is a promise that the task will be attempted (see Listing 7-2).
let promiseRef: Promise = aTimetakingTask(false);
Listing 7-2

Promise Returned by a Function Call

Listing 7-3 shows the Promise constructor, which is natively supported by browsers.
Promise(executorCallback(resolve, reject));
Listing 7-3

Promise Constructor

Whenever a piece of code is run, there are two possibilities: the statements run successfully and the result is obtained, or there is an error and the function fails to run completely. An asynchronous function returning a promise either resolves the promise or rejects it. Listing 7-4 successfully resolves if the input parameter is true; otherwise, the promise fails with a reject.
1. aTimetakingTask (input){
2.    return new Promise( (resolve, reject) => {
3.      if(input){
4.        resolve("job well done");
5.      }else{
6.        reject("job failed");
7.      }
8.    })
9.  }
Listing 7-4

A Function Returning Promise

Line 2 returns the promise. The conditional logic in lines 4 and 6 either resolve or reject the promise.

Going back to the calling function in Listing 7-4 to retrieve the data out of promiseRef, we call a then() function. It expects two callback functions: one for resolve when the asynchronous function has been successful, and another for reject when the asynchronous function fails (see Listing 7-5).
1.  let promiseRef: Promise = this. aTimetakingTask (false);
2.  promiseRef.then(
3.      (result) => console.log(result),
4.      (error) => console.log(error)
5.      );
Listing 7-5

Promise Reference Used by the Calling Function

Alternatively, we may chain success and error handlers using the catch() function. Listing 7-5 is effectively similar to Listing 7-4. However, the then() function returns a promise. The catch() function is called on the promise returned by then(). In the earlier sample, the success and error handler are both defined on the then() function.

The alternative approach is useful to chain success and error handlers. It handles the errors not only in resolve or reject, but also in the success handler of then(). Consider Listing 7-6. If the error occurs between lines 4 and 7, the catch() function handles the error. Control is passed to line 9 because catch() is working on the promise returned by then().
 1.   promiseRef
 2.     .then(
 3.       (result) => {
 4.            console.log(result)
 5.            /* More lines of code
 6.                 making use of data returned by the promise. */
 7.      })
 8.     .catch(
 9.       (error) => console.log(error)
 10.     );
Listing 7-6

Error Handling with catch()

Figure 7-1 is a visual representation of using promises.
../images/475625_1_En_7_Chapter/475625_1_En_7_Fig1_HTML.jpg
Figure 7-1

Using promises

Reactive Programming with RxJS

RxJS (Reactive Extensions for JavaScript) is an open source library that uses reactive programming techniques in JavaScript and TypeScript. Angular extensively uses RxJS.

This section introduces RxJS and provides enough information for you to understand the Angular concepts described in the book. The concepts are vast, and complete coverage is out of the scope of this book.

Observable

Observable provides a collection of values asynchronously, as and when available. RxJS has an implementation of an observable. The observable streams values to the subscriber.

Note

A promise returns a value and the promise is closed. On the other hand, an observable can stream a series of values.

Where Do We See Observable Used in Angular?

The following are two common use cases for an observable in Angular.
  • Remote Service API. When an Angular application makes HTTP calls to a remote server, the API could take a long time to respond with results. We can create and return an observable. The calling function subscribes with a callback function. The callback is invoked as the results become available.

  • User prompts and dialog boxes. Angular components prompt data from the user, open model dialogs, create alerts, and so forth; the user responds with input or a choice. The parent component subscribes to an observable. The parent component does not stop and freezes the screen or the browser. The application continues to be fluid until the response is returned by the user.

Create an Observable

Import and use the Observable constructor to create an observable. In Listing 7-7, we import Observable from the rxjs module.
1.  import { Observable } from 'rxjs';
2.  observableSample(input): Observable<string>{
3.    return new Observable((observer) => {
4.      let values = ["value1", "value2", "value3", "value4"];
5.      if(input){
6.        values.map( value => observer.next(value));
7.      }else{
8.        observer.error("error");
9.      }
10.     observer.complete();
11.    });
12.  }
Listing 7-7

Create Observable

The observableSample function returns an observable of generic type String. The Observable constructor in line 3 accepts a callback function as a parameter. The callback has an observer parameter. Every time the observer invokes next(), a value from the collection is returned to the subscriber (see line 6). The observable streams the values.

In Listing 7-8, we continue with the promise code example from the previous section. For simplicity, if the input parameter value for the observableSample() function is false, observer errors out (see line 8).

Finally, in line 10, the observable completes (finished the job) for the subscriber.

In Listing 7-8, line 1 sets a returned Observable reference to a variable. It invokes the subscribe() function on the observable. The subscribe() function has three callback functions: line 3 is a success callback, line 4 is an error callback, and line 5 is when the observable has completed and closed.
1.    let observableRef: Observable<string> = this.observableSample(true);
2.    observableRef.subscribe(
3.        (result) => console.log(result),
4.        (error) => console.log(error),
5.        () => console.log("complete")
Listing 7-8

Observable Subscription

Angular Services

Services are reusable units of code in an Angular application. The feature could be a value, a function, or a class. Services are separate from components and other directives. They do not have a user interface or a view. The code runs behind it, but still in the browser.

Let’s review some use cases for a service in Angular.
  • Consider a class that abstracts the logic to obtain superhero data from the components. It could be reused throughout the application, and hence, it is a potential service.

  • A function that aggregates and transforms data objects could be a service. We might receive data shown in a component from multiple sources. A service could stitch the data together and provide a structure that a component can instantly show on the screen (with data binding features).

We could build services to encapsulate cross-cutting concerns like logging, auditing, and so forth. This reuses the feature across the application.

Create a Service

To create a service with Angular CLI, run the command shown in Listing 7-9.
ng g service data-services/superhero-data
Listing 7-9

Create a Service with Angular CLI

Note

The first parameter of the Angular CLI command, g, stands for generate. You may alternatively run this command as ng generate service my-service-name.

The command creates a service file under the data-services folder. In the superheroes code sample, data-services is not a module; it is just a folder under which we created the new service. If a module with the same name existed, the service would have been added to the module.

Dependency Injection in Angular

One of the salient features of Angular has been dependency injection. Angular creates and maintains instances of classes and types. This process defines the reusability of an instance.

What is a dependency? A component uses a service for reusable functionality. As discussed, a Superhero List component could use a superhero data service to get a list of superhero objects. Here, the service is a dependency for the component.

Angular uses the metadata defined in the decorators as instructions for dependency injection. Angular with TypeScript uses decorators almost everywhere with modules, components, services, and so forth.

Provider for a Service

Angular, the framework, creates injectors. They are typically application wide. The injector creates and maintains instances. Provider is another object that instructs an injector on how to create and obtain an instance of the service to be reused.

The metadata gives us choices to “provide” a service at any of the following levels. Again, it creates an instance at this level.
  • Root: For a service provided at root, the instance is created once and is reused throughout the application.

  • Module: The service instance is reused within the module.

  • Component: The service instance is specific to the component. Angular creates as many instances of the service as that of the component.

In Figure 7-2, the Service-1 instance is reused throughout the entire application, without reinstantiating. The Service-2 instance is reused in Component-1 and Component-2 of Module A. The Service-3 instance is specific to Component-3.
../images/475625_1_En_7_Chapter/475625_1_En_7_Fig2_HTML.jpg
Figure 7-2

Service provided at various levels

By default, the Angular CLI command in Listing 7-10 creates and provides the service at the root level. Refer to Listing 7-10 for the new service in which we add a getSuperheroes() function that returns a list of superhero objects.

Note

getSuperheroes() returns a list of superheroes hardcoded in this function. It is for the simplicity of the code sample. A real-world application might invoke an HTTP service and a server-side API that returns a list of superheroes. You learn to do this in Chapter 16.

1. import { Injectable } from '@angular/core';
2. @Injectable({
3.  providedIn: 'root'
4.})
5. export class SuperheroDataService {
6.  constructor() { }
7.  getSuperheroes(): Array<Superhero>{
8.    return [
9.      {
10.         // Superhero objects go here.
11.         // removed code for brevity
12.      },
13.      {  }, {
14.      }
15.    ];
16.  }
17. }
Listing 7-10

Service Created by Angular CLI

The decorator injectable is imported from @angular/core in line 1 and used on top of the class. This marks the class as a service that is provided at the root level. A single instance is reused throughout the entire application. It also enables the injector to not create an instance unnecessarily if the service is not used.

As depicted in Figure 7-3, let’s provide the service at various levels. To inject the service at the module level, consider removing providedIn: ‘root’ and doing so in the superheroes Material Design module in the @NgModule() decorator. Consider Listing 7-11 and note the providers array in line 10. With this change, the service is available at the module level.
import { SuperheroDataService } from '../data-services/superhero-data.service';
1. @NgModule({
2.  declarations: [
3.     // Removed code for brevity
4.  ],
5.  imports: [
6.  ],
7.  exports: [
8.  ],
9.  providers:[
10.    SuperheroDataService,
11.  ],
12.  entryComponents: [
13. // removed code for brevity
14.  ]
15. })
16. export class SuperheroesMaterialDesignModule { }
Listing 7-11

Provide at the Module Level

We may inject the service at the component level. Consider including it in the @component decorator for the component (see line 7 in Listing 7-11).

Whichever level the component has been provided, it needs to be injected into a component or another service. In Listing 7-12, it is injected into the constructor so that a reference is available for the component to use. At this point, the service is a dependency for the component.

We use the service reference in line 14 by calling the getSuperheroes() function.
1. import { Component, OnInit, ViewChild } from '@angular/core';
2. import { SuperheroDataService } from 'src/app/data-services/superhero-data.service';
3. @Component({
4.  selector: 'app-superhero-new-list',
5.  templateUrl: './superhero-new-list.component.html',
6.  styleUrls: ['./superhero-new-list.component.css'],
7.  providers: [SuperheroDataService]
8. })
9. export class SuperheroNewListComponent implements OnInit {
10.  private heroes: Array<Superhero>;
11.  constructor(private bottomsheet: MatBottomSheet,
12.    private heroData: SuperheroDataService) { }
13.  ngOnInit() {
14.    this.heroes = this.heroData.getSuperheroes();
15.  }
16.}
Listing 7-12

Provide Service at the Component Level

If we remove providedIn at all levels in the application, it results in the error seen in Figure 7-3.
../images/475625_1_En_7_Chapter/475625_1_En_7_Fig3_HTML.jpg
Figure 7-3

Removing a provider results in an error

Typically, data is retrieved from a remote service, which is a time-consuming task. Hence, we return an observable. For simplicity, we return the hard-coded data wrapped in an observable. Listing 7-13 returns superhero data from the getSuperheroes() service function through an observable.
  getSuperheroes(): Observable<Array<Superhero>>{
    return new Observable<Array<Superhero>>( observer => {
      observer.next([
        {
          name: "Chhotta Bheem",
          email: "[email protected]",
          details: "A hero in Dholakpur village",
          country: "India",
          cardImage: "/assets/chhottabheem.png",
          specialPowers: ["a very strong boy!"],
          favFood: ["Laddu"]
        },
        {
          // removed code for brevity
        },
        {
        }
      ]);
      observer.complete();
    })
  }
Listing 7-13

Return Observable from the Service

The return type function is updated to Observable<Array<Superhero>>. A new Observable object is created and returned. The hard-coded data is successfully sent to the subscriber with the next() function.

Listing 7-14 uses this data by assigning it to a class variable in ngOnInit(). The “heroes” class variable is used in the HTML template, so that the result is shown in the component.
  ngOnInit() {
    this.heroData
      .getSuperheroes()
      .subscribe( data => this.heroes = data);
  }
Listing 7-14

Component Uses the Data from the Observable

An Example to Differentiate “Providing” at the Module Level vs. the Component Level

Let’s use an example to better understand the difference by creating a counter TypeScript class to maintain a hit count for a component or a page. It is a simple class with a class field to maintain the counter. We can increment the value as we go (see Listing 7-15).
Import { Injectable } from ‘@angular/core’;
@Injectable()
export default class HitCounter {
   private _counter: number;
   constructor() {
       this._counter = 0;
   }
   // It’s a getter on the class. Notice there is no setter, that means the class
   // controls changing the private variable _counter.
   // We cannot set counter value from outside the class.
   get counter(): number {
       return this._counter;
   }
   incrementCounter() {
       this._counter += 1;
   }
}
Listing 7-15

A Counter Implementation

We will begin by providing the counter service at the module level. An instance of the class is created at the module level. The same counter value will be used by all the components in the module (see Listing 7-16).
import HitCounter from './utilities/hit-counter';
@NgModule({
 /* For brevity removed unrelated blocks of code */
 exports: [
 ],
 providers: [HitCounter]
})
export class SuperheroesMaterialDesignModule { }
Listing 7-16

Hit Counter Provided at the Module Level

We will use the HitCounter component in the SuperheroProfileComponent component (see Listing 7-17). We inject HitCounter in the constructor.
import HitCounter from "../utilities/hit-counter.service"
@Component({
 selector: 'app-superhero-profile',
 templateUrl: './superhero-profile.component.html',
 styleUrls: ['./superhero-profile.component.css'],
})
export class SuperheroProfileComponent implements OnInit {
 constructor(public hitCounter: HitCounter) {
 }
}
Listing 7-17

Counter Injected into the Component

We show the value of the counter in the template (see Listing 7-18). The double curly braces syntax is interpolation data binding. It shows the value of the variable in the component. Also notice (click)="incrementCounter()" on the button. It calls the incrementCounter function in the TypeScript class component.
<mat-card class="custom-style">
  <div>Superhero profile.</div>
  <button (click)="incrementCounter()">Increment Counter</button>
  <div>You have visited {{hitCounter.counter}} times.</div>
</mat-card>
Listing 7-18

Template with Counter Values

When the user clicks the Increment Counter button, the function shown in Listing 7-19 is invoked. It is defined in the component class. In turn, it invokes the function on the HitCounter service class instance, which increments the private variable.
  incrementCounter(){
    this.hitCounter.incrementCounter()
  }
Listing 7-19

Increment Counter on the Click of a Button

Let’s create two instances of SuperheroProfile components on a page. When the user clicks the button in one component, it updates the value for the second component as well. Remember, the hit counter is provided at the module level. One instance is shared for both components (see Figure 7-4).
../images/475625_1_En_7_Chapter/475625_1_En_7_Fig4_HTML.jpg
Figure 7-4

Clicking the Increment Counter button anywhere, the first or second instance of the component will increment both the values

Provide the TypeScript class at the component level (see Listing 7-20). With this change, an instance of HitCounter is maintained at each component level.
@Component({
 selector: 'app-superhero-profile',
 templateUrl: './superhero-profile.component.html',
 styleUrls: ['./superhero-profile.component.css'],
 providers: [HitCounter]
})
export class SuperheroProfileComponent {
}
Listing 7-20

Provided at the Component Level

Figure 7-5 shows how the behavior of the component changes. There is a different counter value maintained for each instance of the component. The counter value is shown in Figure 7-5.
../images/475625_1_En_7_Chapter/475625_1_En_7_Fig5_HTML.jpg
Figure 7-5

HitCounter provided at the component level

The injected instance is used by all the child components in the tree. The state is maintained for all SuperheroProfile child components.

Conclusion

Angular services are one of the building blocks of an Angular application. Angular services help bring reusability to an Angular application and abstract complex functionalities from rest of the application.

Angular services extensively use the dependency injection, which is a powerful and highly used feature in the framework. The feature helps abstract object creation logic and extensively reuses service instances.

This chapter introduced asynchronous programming constructs, and described callbacks, promises, and observables (with the help of RxJS library). It also explained how to create a service with Angular CLI and the various possibilities of providing a service at the root, module, or component level.

The chapter used an asynchronous, reactive, programming construct observable with the service to obtain superhero data for a component.

Exercise

Create a new data service for the dinosaur data. Use Angular CLI to create the service. Update the components showing the dinosaur data; use the newly created service by injecting into it. Provide this service at the module level.

Ensure that the dinosaur data is returned asynchronously with a promise or an observable.

Create a logger service with Angular CLI that logs the given object information to the console. Provide this service at the root level.

References

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.127.37