See Advanced Angular Tutorial 1 (Routing + Forms) for this tutorial.
Introduction to dependency injection and HTTP
Why use a service?
Component \color{# 0ABB3c}{component} Components should not directly fetch or store data. They should focus on presenting the data and delegate the responsibility for data access and processing to a service \color{# 0ABB3c}{service} service. What about dependencies between components and services? Angular introduces the dependency injection framework (DI) to solve this problem.
Dependency Injection (DI)
Dependency (service/object) injection is a design pattern in which a class requests dependencies \color{# 0ABb3c}{request dependencies} from an external source rather than creating them. Angular’s DI framework provides dependencies on a class when it is instantiated, improving modularity and flexibility. Before we look at dependency injection, let’s look at three concepts that are at the heart of dependency injection:
Injector: Provides a series of interfaces for creating dependent object instances. (Think of it as a cook.)
Provider: Used to configure the injector to create instances of the dependent object. Providers map tokens to list objects and provide a runtime dependency that is created using this method. (Think of it as a recipe in the hands of the chef, where the Token is the name of the dish)
Dependence: Specifies the type of the dependent object and the injector creates the corresponding object based on that type.
Use of dependency injection
- Create injectable service:
import { Injectable } from '@angular/core';
// The @Injectable() decorator tells Angular that the service is Injectable. The injector creates the service instance and injects it into the class. The metadata providedIn: 'root' indicates that the HeroService is visible throughout the application.
@Injectable({
providedIn: 'root',})export class GoodsListService {
constructor(){}}Copy the code
The Injectable class decorator can be omitted if you create a service that does not depend on other services. The Injectable decorator is used when the service needs to inject dependent objects into the constructor. But we always add this decorator as we go through development.
- Into service
Inject dependencies (services) into the component’s constructor()
constructor(goodsListService: GoodsListService)
Copy the code
Common ways to inject services
Inject services into components
If you define providers on the component \color{#0abb3c}{component} metadata \color{#0abb3c}{metadata} metadata, Angular creates an injector for that component. Color {#0abb3c}{component’s child} component’s child also shares the injector, if not defined, The component creates its dependencies by looking up the appropriate injector level by level in the component tree.
// Each component instance is registered with its own injector. (Multiple components can have multiple injectors)
@Component({
selector: 'app-goods-list'.providers: [GoodsListService]}) : [GoodsListService]}) : [GoodsListService]}selector: 'app-goods-list'.providers: [{ provide: GoodsListService, useClass: GoodsListService } ]
// The provide attribute can be understood as the Provider's unique identifier, which is used to locate the dependency value, i.e. the service name used in the application
// The useClass attribute represents which service class is used to create the instance
})
Copy the code
Inject the service into the module
The service injected in the root component \color{#0abb3c}{root component} can be shared in all subcomponents \color{#0abb3c}{child component}{share}. Of course, the same result can be achieved by injecting a service into the module \color{#0abb3c}{module}. We import the foreign module through imports\color{# 0ABb3c}{imports}imports. The services of the foreign module are injected into the injectors of your module
To complement the above: Because Angular starts a root module and loads other modules it depends on when it launches an application, it generates a global root injector that creates dependency injection objects visible at the entire application level and shares an instance. So there is no module level area in Angular, only component level and application level area. Injection at the module level is equivalent to application level.
// The service can be configured in this way (the @Injectable() decorator is also required in the service class).
// Without route lazy loading, this injection is the same as in the service class.
@NgModule({
providers: [ GoodsListService ],
})
Copy the code
Points to note: Although the dependencies injected into a module are quite application-level, Angular initializes a new execution context for a lazy-loaded module and creates a new injector where the dependencies injected are only visible inside the module. This is a special module-level scope.
Inject the service in the service class
// This injection tells Angular to register the service in the root injector, which is the default for cli-generated services.
// This way you don't need to write providers in the @NgModule decorator, and when the code is compiled and packaged, you can perform tree Shaking optimization, removing all services that are not used in the application. It is recommended to register the service in this way.
@Injectable({
providedIn: 'root'
})
Copy the code
How do you choose between a root component or a child component for service injection?
This depends on whether you want the injected dependent service to be global or local
There are four ways to create dependent objects (just know):
-
UseClass: Specifies dependencies based on the identity
-
UseValue: Dependent objects do not have to be classes. They can also be constants, strings, objects, and other data types
-
UseExisting: You can configure multiple identifiers in a Provider that correspond to objects pointing to the same instance, enabling multiple dependencies and one object instance
-
UseFactory: dynamically generates dependent objects
The introduction of Http
Most front-end applications need to communicate with the server over HTTP to download or upload data and access other back-end services. Angular provides an HTTP client API for applications, @angular/common/ HTTP \color{#0abb3c}{@angular/common/ HTTP}@angular/common/ HTTP HttpClient\color{#0abb3c}{HttpClient}HttpClient service class.
Use HttpClient
- HttpClient is typically imported under the root module
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
/ / import HttpClientModule
import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
HttpClientModule,
],
exports: [].providers: [].bootstrap: [AppComponent]
})
export class AppModule {}Copy the code
- Dependency injection in the service class (need to communicate in the service class via HttpClient)
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class GoodsListService {
constructor(private http: HttpClient){}}Copy the code
- HttpClient\color{#0abb3c}{HttpClient} Returns observable services. So we also need to import the RxJS observables \color{# 0ABb3c}{observable} observables and possible operators \color{# 0ABb3c}{operator} operators in the service class.
import { Observable } from 'rxjs';
import { pluck } from 'rxjs/operators'; // This operator is used to get the contents of a field
Copy the code
Common request methods
- Request data from the server httpClient.get ()
// Encapsulate the methods that communicate with the server in the service class
public getHttpResult(code: string, name: string): Observable<any> {
const url: string = ' '; // This is the requested address
return this._http.get(url, { params: { code, name } });
}
Copy the code
- Send data to the server httpClient.post ()
public postHttpResult(body: any): Observable<any> {
const url: string = ' '; // This is the requested address
return this._http.post(url, body);
}
Copy the code
Error handling
When an interface request fails or an error occurs, the front end displays error messages as follows:
this._goodsListService.getHttpResult('12'.'zs')
.subscribe((res) = > { // Since httpClient returns an Observable, it must be subscribed to execute and return results
console.log(res);
}, (error) = > { // This is where the interface error is handled
console.log(error);
});
Copy the code
RxJS field introduction
What is a RxJS
RxJS is a library for asynchronous streams. Angular introduced RxJS to make asynchronous streams easier and more manageable. Reactive Programming is a Programming approach that uses stream \color{#0abb3c}{stream}.
What is flow?
Stream/color{#0abb3c}{stream /stream} is the whole of data based on events. Color {#0abb3c}{streamdirection}{streamdirection}{streamdirection} a river can be divided into many tributaries, and many small tributaries can also be combined into a river. So in RxJS, streams can also use the operator \color{#0abb3c}{operator} to implement the summary \color{#0abb3c}{summary} summary and shitter \color{#0abb3c}{shitter} shitter.
Core concepts in RxJS (Observable, Observer, Subscription, Subject)
When we call an interface in An Angular project, we use:
this._goodsListService.getHttpResult
.subscribe((res) = > {
console.log(res)
})
/ / this. _goodsListService. GetHttpResult is returned observables, he can be the API calls, calls can be event and so on
Copy the code
We can abstract the above invocation as Observable. subscribe(observer)\color{#0abb3c}{observable.subscribe(observer)} Observable. subscribe(observer The objects are Observable and Observer, and the return object of this method call, which returns an instantiation of the Subscription object. Let’s walk through these core concepts one by one.
Observable
Observable is the most core concept in RxJS. Its essence is “Observable is a function to generate values”. First, it is a function \color{#0abb3c}{function} function, that is to say, it is the data source. Observable is the data producer \color{#0abb3c}{data producer} data source, is the data producer, usually we add $at the end of the variable to indicate an Observable.
// This function defines a setInterval that generates a value every two seconds
const observable$ = (observer) = > {
let counter = 0;
const id = setInterval(() = > observer.next(counter++), 2000);
}
Copy the code
Observable is an object, so it needs to be called
observable$({ next: (val) = > console.log(val) });
Copy the code
The generation of value is defined in the function, and observer.next performs the behavior defined in the observer when the function is called, such as counter++ in the above example. Observable features can be found as follows:
- Must be called (subscribed) to be executed
- An Observable must be able to close after it is called, otherwise it keeps running
- It doesn’t matter where we subscribe to the same Observable. This is consistent with function being executed multiple times with no relation to each other.
Observer (Understand)
It is the observer, the data user, the data consumer, the observer, the data user, the data consumer. Color {#0abb3c}{object} object with three callbacks, Each callback corresponds to three Observable notification types (Next, Error, and complete). The observer represents how the result of a sequence is handled. In real development, if we provide a callback function \color{#0abb3c}{a callback function} a callback function as an argument, SUBSCRIBE takes the function argument we provide as a callback handler for next\color{#0abb3c}{next}next. Next decides what data to pass to the observer.
let observer = {
next: data= > console.log('data'); // next indicates normal data flow,
error: err= > console.log('err'); // error indicates an error in the stream
complete: () = > console.log('complete') // complete indicates the end of the stream
}
// Error and complete only trigger one, but there can be multiple next
Copy the code
Subject
Subject is a special Observable; color{#0abb3c}{special Observable} Special Observable: We can subscribe to Subject like any Observable. Color {#0abb3c}{observer} observer: It has the next(v), error(e), and complete() methods, and if we need to supply the subject with a new value, we simply call next(v), which will multicast the value to observers registered to listen on the subject.
So: The Subject is both an Observable and an observer (multiple)
The difference between Subject and Observable:
- Color {#0abb3c}{#0abb3c}{#0abb3c}
- Observble (Observble) {color{#0abb3c}{unicast}} (Observble) {color{#0abb3c}{unicast} (Observble) {color{#0abb3c}{unicast} (Observble) {color{#0abb3c}{unicast} (Observble)
The common role of Subject in Angular:
Angular uses services to transfer values between components or between modules
// Define a public service for data storage. The file name is (eg: xampleStore.service.ts).
@Injectable()
export class ExampleStoreService {
private currentTabNumber$ = new Subject<number>();
}
// the logic of this data change can be applied wherever it is needed to change the corresponding value of next. The file name is (eg:a.com ponent.ts).
this.ExampleStoreService.currentTabNumber$.next(1);
// The subscription receives the data change and does the next logical processing. The file name is (eg: b.component.ts).
this.ExampleStoreService.currentTabNumber$
.subscribe((res: number) = > {
this.currentIndex = res;
})
Copy the code
An introduction to RxJS operators
Operators is a pure function \color{#0abb3c}{pure function} that takes an Observable and returns an Observable. The essence of Operators is to describe the relationship between one data stream and another, that is, the transition between an Observer and an Observable, much like Lodash. There are close to 100 operators in RxJS, but only a dozen are commonly used during development.
Common operators include map, filter, concat, FlatMap, SwitchMap, and Forkjoin
ForkJoin and switchMap are the only operators we can look at here.
// When the user does not care about the return order of the interface
Forkjoin is used only when multiple interfaces return at the same time
forkJoin([
this._goodsListService.getHttpResultOne('12'.'zs'),
this._goodsListService.getHttpResultTwo('12'.'zs')])
.subscribe(resArr= > {
// The results are placed in an array in order
const oneData = resArr[0];
const TwoData = resArr[1];
});
Copy the code
// When the user is concerned about the return order of the interface
// switchMap ensures that the interface data of getHttpResultOne is returned first, and then the result of getHttpResultTwo is returned
this._goodsListService.getHttpResultOne('12'.'zs')
.pipe(
switchMap((resultOne: any) = > {
console.log(resultOne);
return this._goodsListService.getHttpResultTwo('12'.'zs');
})
)
.subscribe((resultTwo: any) = > {
console.log(resultTwo);
});
Copy the code
How do I unsubscribe from a project
According to observabled’s feature, “An Observable that is called must be able to be closed, otherwise it keeps running.” So we use subscribe\color{#0abb3c}{subscribe} in the component, Color {# 0ABB3c}{unsubscribe} are required to be unsubscribed during the component’s destruction phase to optimize project performance and efficiency. There are three steps to unsubscribe:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
export class GoodsListComponent implements OnInit.OnDestroy {
// First define an array of the Subscription type to hold all observabled subscriptions in this component
public subscriptions: Subscription[] = [];
constructor( private _goodsListService: GoodsListService ) {}
public ngOnInit(): void {
// Push the subscribed observabLED into an array of defined Subscription arrays and wait to unsubscribe
this.subscriptions.push(
this._goodsListService.getHttpResultOne('12'.'zs')
.subscribe((res) = > {
// console.log(res);
}, (error) = > {
// console.log(error);}); ; } public ngOnDestroy():void {
// ③ In the component destruction phase, the observabLED of the subscription in the array is unsubscribed by calling the unsubscribe() method
this.subscriptions.forEach((subscription) = >subscription.unsubscribe()); }}Copy the code