📜 ⬆️ ⬇️

The book "Angular and TypeScript. Website building for professionals »

image Hello! Recently, we have released a new book describing working with complex and powerful tools of a web developer: Angular and TypeScript. Authors: Jacob Fain and Anton Moiseev explain the features of the framework, citing simple code examples, and several chapters set out how to create a one-page application for online auctions. Below we look at a section from the book on dependency injection.

Any Angular application is a collection of objects, directives and classes that can depend on each other. Despite the fact that each component can explicitly create instances of its dependencies, Angular is able to perform this task using the dependency injection mechanism.

We begin this chapter by defining what problems DI solves, and consider the advantages of DI as a design pattern. Next, we consider how this template is implemented in Angular using the example of the ProductComponent component, which depends on the ProductService. You will see how to write an injecting service and inject it into another element.

Next, we present an example application that demonstrates how Angular DI makes it easy to replace one dependency with another, changing just one line of code. After that we will get acquainted with a more advanced concept: the hierarchy of injectors.
')
At the end of the chapter, we will create a new version of the online auction, which uses the techniques described in this chapter.

Design patterns - recommendations for solving some common problems. A given design pattern can be implemented in different ways depending on the software used. In this section, we briefly look at two design patterns: Dependency Injection (DI) and Control Inversion (Inversion of Control, IoC).

4.1.1. Template "dependency injection"


If you have ever written a function that takes an object as an argument, you can say that you wrote a program that creates an object and injects it into a function. Imagine a complete filing center. An application that tracks sent products can create a product object and call a function that creates and saves a shipping record:

var product = new Product(); createShipment(product); 

The createShipment () function depends on the existence of an instance of the Product class. In other words, the createShipment () function has a dependency: Product. But by itself she does not know how to create objects of this type. The calling script must somehow create and transmit (that is, embed) this object as a function argument. Technically, you untie the place where the Product object is created from its place of use - but both previous lines of code are in the same scenario, so this unlinking cannot be called real. If you need to replace the Product type with the MockProduct type, you will need to make a small change to our simple example.

What if the createShipment () function has three dependencies (product, sending company, complete center), and each of them has its own dependencies? In this case, to create a different set of objects for the createShipment () function, you will need to make a large number of changes. Can I ask someone to create instances of dependencies (and their dependencies) for you?

Here we need the “dependency injection” pattern: if object A depends on an object of type B, then object A will not explicitly create object B (in the case of using the new operator, as in the previous example). Instead, object B will be embedded from the operating environment. Object A should simply declare the following: “I need an object of type B; Can someone give it to me? ”The word“ type ”is the most important here. Object A does not request a specific implementation of the object, and its request will be satisfied if the object being injected is of type B.

4.1.2. Control Inversion Template


The “Inversion of Control” pattern is more general than DI. Instead of using the framework (or software container) API in its application, the framework creates and sends the objects necessary for the application. The IoC pattern can be implemented in different ways, and DI is one of the ways to provide the required objects. Angular plays the role of an IoC container and can provide the required objects in accordance with the declarations made in your component.

4.1.3. Benefits of dependency injection


Before exploring the syntax for implementing the DI pattern in Angular, consider the advantages of embedding objects before creating them with the new operator. Angular offers a mechanism to help register and instantiate components for which there is a dependency. In short, DI allows you to write loosely coupled code, and also to reuse it and conduct better testing.

Weak binding and reuse


Suppose you have a ProductComponent component that obtains detailed information about a product using the ProductService class.

If you are not using DI, then the ProductComponent component must know how to create objects of the ProductService class. This can be done in several ways, for example, using the new operator, calling the getInstance () method for a singleton object, or calling the createProductService () method of some factory class. In any of the cases described, the ProductComponent component becomes closely (rigidly) associated with the ProductService class.
If you need to use the ProductComponent component in another application that uses another service to get detailed information about the product, then you should modify the code (for example, productService = new AnotherProductService ()). DI allows you to decouple application components, eliminating the need to know how to create dependencies.

Consider the following example of the ProductComponent component:

 @Component({ providers: [ProductService] }) class ProductComponent { product: Product; constructor(productService: ProductService) { this.product = productService.getProduct(); } } 

In applications, you register objects for DI by specifying vendors. A supplier is an instruction for Angular on how to create an instance of an object for later implementation in a target component or directive. In the previous code snippet, the providers: [ProductService] line is an abbreviation of the providers: [{provide: ProductService, useClass: ProductService}] line.

Angular applies the concept of tokens - arbitrary names representing the object being injected. Usually, the name of the token corresponds to the type of the object being injected; accordingly, in the previous code snippet, Angular is instructed to provide the ProductService token using the class with the same name. Using an object with the provide property allows you to associate one token with different values ​​or objects (for example, to emulate the functionality of the ProductService class when someone develops a real service class).

Now that you have added the providers property to the Component Component Component Component component annotation, the DI module provided by Angular will know that it must create an object of type ProductService. The ProductComponent component does not need to know what kind of implementation the ProductService type will use — it will apply any object specified as a supplier. A reference to an object of type ProductService will be embedded using the constructor argument; there is no need to explicitly create an object of type ProductService in the ProductComponent component. Simply use it as previous code that calls the getProduct () service method of an instance of the ProductService class created by Angular.

If you need to use the same ProductComponent component in different applications that have different implementations of the ProductService type, then change the providers string, as shown in the following example:

 providers: [{provide: ProductService, useClass: AnotherProductService}] 

Now Angular will create an instance of the class AnotherProductService, but the code using the ProductService type will not generate errors. In this example, using DI increases the reusability of the ProductComponent and breaks the tight association with the ProductService class. If one object is closely related to another, then at least one of them may require many changes.

Testability


DI allows you to better test components separately from each other. You can easily embed fake objects if their real implementations are unavailable or when you need to organize unit testing of code.

Suppose you need to add authorization to the application. You can create a LoginComponent component (to render the ID and password fields) using the LoginService component, which must connect to a specific authorization server and check the user's privileges. Authorization server must be provided by another department, but it is not ready yet. You finish writing the component code of the LoginComponent, but find it difficult to test it for reasons that you cannot control, for example, due to dependency or another component developed by other people.

When testing often used fake objects that mimic the behavior of real. In the case of using the DI framework, you can create a fake object, MockLoginService, which does not connect to the authorization server, but at the same time, the privileges assigned to users with certain identifier and password combinations are hard-coded. Using DI, you can write only one line in which MockLoginService will be embedded in the application's Login view, which will allow you not to wait for the authorization server to be ready. Further, when the server is ready, you can change the providers row so that Angular implements the real LoginService component, as shown in Figure. 4.1

image

4.2. Injectors and suppliers


Now that you’ve briefly reviewed the “Dependency Injection” template, let's move on to the details of implementing DI in Angular. In particular, we will look at concepts such as injectors and suppliers.

Each component can have an Injector object that can embed objects and primitive values ​​into an element or service. In any Angular application there is a root injector available to all its modules. To tell the injector what to implement, you specify the supplier. The injector will inject the object or value specified in the provider into the component's constructor.

Providers allow you to relate custom types (or tokens) to specific implementations of this type (or values). You can specify the providers either inside the decorator of the Component component, or as the @NgModule property, which was done in each piece of code presented so far.

You will use the ProductComponent component and the ProductService class in all of the code examples shown in this chapter. If your application has a class that implements a certain type (for example, ProductService), then you can specify a supplier object for this class during the preloading of the AppModule module, for example:

 @NgModule({ ... providers: [{provide:ProductService,useClass:ProductService}] }) 

If the name of the token matches the name of the class, then a shorter notation can be used to indicate the provider in the module:

 @NgModule({ ... providers: [ProductService] }) 

You can specify the providers property in the Component annotation. A short notation for the ProductService provider in Component looks like this:

 providers:[ProductService] 

No instance of the ProductService type has yet been created. The providers string tells the injector the following: "When you need to create an object that has an argument of type ProductService, create an instance of the registered class to embed into this object."

If you need to implement different implementations of a particular type, apply a longer notation:

 @NgModule({ ... providers: [{provide:ProductService,useClass:MockProductService}] }) 

So it looks at the component level:

 @Component({ ... providers: [{provide:ProductService, useClass:MockProductService}] }) 

It gives the injector the following instruction: "When you need to embed an object of type ProductService into a component, create an instance of the MockProductService class."

Thanks to the supplier, the injector knows what to implement; now you need to specify where to embed the object. In TypeScript, it all comes down to declaring a constructor argument with an indication of its type. The following line shows how to embed an object of type ProductService in the component's constructor:

 constructor(productService: ProductService) 

The constructor will remain the same no matter what specific implementation of the ProductService class will be specified as a supplier. In fig. 4.2 shows an exemplary sequence diagram of the implementation process.

image

4.2.1. How to declare suppliers


You can declare custom providers as an array of objects containing the provide property. Such an array can be specified in the providers property of the module or at the component level.
Consider an example of an array with one element, in which the supplier object is specified for the ProductService token:

 [{provide:ProductService, useClass:MockProductService}] 

The provide property allows to associate a token with the method that creates the object being injected. In this example, you instruct Angular to create an object of the MockProductService class where the ProductService token is used as a dependency. But the creator of an object (the Angular injector) can use a class, a factory function, a string, or a special class OpaqueToken to create an object or embed it.
• To relate a token to a class implementation, use an object that has a useClass property, as shown in the previous example.
• If you have a factory function that creates objects based on certain criteria, use an object with the useFactory property. It allows you to specify a factory function (or an anonymous arrow expression) that knows how to create the required objects. Such a function may have an optional argument with dependencies, if they exist.
• To provide a string with a simple embed value (for example, a service URL), refer to an object with the useValue property.

4.3. An example of an application that uses Angular DI


Now that you have seen some code fragments associated with Angular DI, create a small application that brings all the pieces together. We want to prepare you for using DI in an online auction application.

4.3.1. Product service implementation


Let's create a simple application that uses the ProductComponent component to draw product information and the ProductService service that provides product data. If you are using the downloadable code supplied with the book, then this application is located in the main-basic.ts file in the di_samples directory. In this subsection, you will create an application that generates the page shown in Figure 2. 4.3.

image

The ProductComponent component can request the implementation of the ProductService object by declaring a constructor argument with the type:

 constructor(productService: ProductService) 

In fig. 4.4 shows an example of an application that uses these components.

image

The AppModule module preloads AppComponent, which contains a component dependent on ProductService. Pay attention to the import and export operators. The definition of a ProductService begins with an export statement, which allows other elements to access its contents. The ProductComponent component contains an import statement that provides the name of the class (ProductService) and the module being imported (located in the product-service.ts file).

The providers attribute, defined at the component level, instructs Angular to provide an instance of the ProductService class on demand. This class can communicate with any server, requesting detailed information about the product selected on the web page. But we will now omit this part and concentrate on how the specified service can be implemented in the ProductComponent. We realize the components shown in fig. 4.4.

In addition to index.html, you will create the following files:
• the main-basic.ts file will contain the code required to load the AppModule module, which contains the AppComponent component hosting the ProductComponent;
• the ProductComponent component will be implemented in the product.ts file;
• the ProductService service will be implemented in the product-service.ts file.

Each of these files is fairly simple. The main-basic.ts file, shown in Listing 4.1, contains the module code and the root component that hosts the child component of the ProductComponent. This module imports and declares this component.

Listing 4.1. Contents of the main-basic.ts file

 import {Component} from '@angular/core'; import ProductComponent from './components/product'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; @Component({ selector: 'app', template: `<h1> Basic Dependency Injection Sample</h1> <di-product-page></di-product-page>` }) class AppComponent {} @NgModule({ imports: [ BrowserModule], declarations: [ AppComponent, ProductComponent], bootstrap: [ AppComponent ] }) class AppModule { } platformBrowserDynamic().bootstrapModule(AppModule); 

Based on the <di-product-page> tag, it is easy to guess that there is an element with a selector that has this value. This selector is declared in the ProductComponent component, whose dependency (ProductService) is implemented using a constructor (Listing 4.2).

Listing 4.2. Contents of the product.ts file

image

In Listing 4.2, the type name is the same as the class name, ProductService, so you can use a short notation without having to explicitly correlate the provide and useClass properties. When specifying suppliers, the name (token) of the object being injected is separated from its implementation. In this case, the name of the token will be the same as the name of the type: ProductService. The implementation of this service itself can be in the classes ProductService, OtherProductService, or somewhere else. Replacing one implementation with another comes down to changing the providers string.

The constructor of the ProductComponent component calls the getProduct () method for the service and places a reference to the returned Product object in the product class variable, which is used in the HTML template. Using double braces, in Listing 4.2, you can link the title, description, and price properties of the Product class.

The product-service.ts file contains the definition of two classes: Product and ProductService (Listing 4.3).

image

In real-world applications, the getProduct () method will need to obtain product information from an external data source, for example, by sending an HTTP request to a remote server.
To run this example, open a command prompt in the project directory and execute the npm start command. The live server will open the window as shown earlier in Figure. 4.3. An instance of the ProductService class is embedded in the ProductComponent component, which draws product information provided by the server.

»More information about the book can be found on the publisher's website.
» Table of Contents
» Excerpt

For Habrozhiteley a 20% discount on the coupon - Angular

Source: https://habr.com/ru/post/347364/


All Articles