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.
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.
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.
Promise Returned by a Function Call
Promise Constructor
A Function Returning Promise
Line 2 returns the promise. The conditional logic in lines 4 and 6 either resolve or reject the promise.
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.
Error Handling with catch()
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?
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
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.
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.
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
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.
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.
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.
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.
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.
Provide Service at the Component Level
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.
Component Uses the Data from the Observable
An Example to Differentiate “Providing” at the Module Level vs. the Component Level
A Counter Implementation
Hit Counter Provided at the Module Level
Counter Injected into the Component
Template with Counter Values
Increment Counter on the Click of a Button
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
Angular documentation (https://angular.io/)
RxJS documentation (https://rxjs-dev.firebaseapp.com/guide/observable)
Documentation on promises (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
Article: “JavaScript promises, mastering the asynchronous” (www.codingame.com/playgrounds/347/javascript-promises-mastering-the-asynchronous/the-catch-method)