0%

angular-service

Angular Service

在Angular中,Service是一个可注入的类,用于封装可重用的功能。Service可以在任何Component中使用,也可以用于其他Service

Service可以完成以下工作,很多和UI无关的操作都可以用Service来完成,这样可以保持Component的简洁。

  • 调用API获取后台数据 - API Service
  • 验证用户输入 - Form Validation
  • 日志服务 - Logging Service
  • 数据库操作 - Database Service

创建Service

使用Angular CLI创建Service,下面的CLI命令生成一个OrderService

1
ng generate service order # or, ng g s order

该命令会在项目根目录下生成一个order.service.ts文件,我们在其中添加一个getOrder方法,用于获取订单信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// order.service.ts
import { Injectable } from '@angular/core';
import {Order} from "./Order";

@Injectable({
providedIn: 'root'
})
export class OrderService {
constructor() { }

getOrder():Promise<Order> {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
id: 1,
name: 'Order 1',
});
}, 1000);
});
}
}

Order类型定义如下

1
2
3
4
5
// order.ts
export interface Order {
id: number;
name: string;
}

可以看到,OrderService对应的Class是用@Injectable装饰器修饰的,这样Angular就可以将其注入到Component中。我们还给@Injectable传递了一个参数providedIn: 'root',这表示该Service是一个全局Service,可以在整个Application中使用。

使用Service

通过构造函数注入Service

ProductComponent中使用OrderService,我们需要在ProductComponent的构造函数中注入OrderService,然后调用OrderService的方法。(注意,由于OrderService是providedIn: 'root'的,所以使用者不需要在providers数组中声明它)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import {OrderService} from "../order.service";
import {Order} from "../Order";

export class ProductComponent implements OnInit {
order: Order | null;

// inject OrderService
constructor(private orderService: OrderService) {
this.order = null;
}

async ngOnInit() {
this.order = await this.orderService.getOrder();
console.log(this.order);
}
}

通过inject函数注入Service

Angular 14引入了一个新的函数inject, 可以用来注入Service,如下: 使用这种方式,我们不必再依赖构造函数的参数,可以在任何地方注入Service。

1
2
3
4
import {OrderService} from "../order.service";
export class ProductComponent implements OnInit {
orderService = inject(OrderService);
}

两种注入方式的区别

那么使用inject函数注入比使用constructor注入有何好处呢?主要体现在继承时,假设我有一个BaseComponent,其构造函数中注入了某个service, 另外一个组件ProductComponent继承自BaseComponent,则ProductComponent的构造函数也需要注入这个service才能调用super方法。

1
2
3
4
5
// base.component.ts
export class BaseComponent {
constructor(protected orderService: OrderService) {
}
}
1
2
3
4
5
6
// product.component.ts
export class ProductComponent extends BaseComponent {
constructor(override orderService: OrderService) {
super(orderService);
}
}

而使用inject函数注入则不需要。父组件的service会自动注入到子组件中。

1
2
3
4
// base.component.ts
export class BaseComponent {
orderService = inject(OrderService)
}
1
2
3
4
5
6
7
8
9
// product.component.ts
export class ProductComponent extends BaseComponent implements OnInit {
ngOnInit() {
// this.orderService is available here
this.orderService.getOrder().then(order => {
console.log(order);
})
}
}

providedIn vs providers

在Angular中,我们可以使用providedIn或者providers来指定Service的提供范围。providedIn是Angular 6中引入的新特性,用于替代providers

如果在定义Service时指定了providedIn: 'root',那么Angular会在应用启动时自动将该Service注入到根模块中,这样就可以在整个应用中使用该Service。在使用该Service的Component中,就不必再在providers中声明该Service。

如果定义Service时没有指定providedIn,那么就需要在使用该Service的Component中的providers中声明该Service。

1
2
3
4
5
6
7
8
@Component({
selector: 'app-product',
standalone: true,

templateUrl: './product.component.html',
styleUrl: './product.component.css',
providers: [OrderService] // <--- provide service here
})

当Angular找不到一个Service的提供者时,会抛出一个错误,相信大家都见过这个错误,从下面的错误可知,_OrderService没有提供者。

1
2
ERROR Error [NullInjectorError]: R3InjectorError(Environment Injector)[_OrderService -> _OrderService]: 
NullInjectorError: No provider for _OrderService!

更进一步,我们可以将Service的使用范围限定在某个Component中,这样其他Component就无法使用该Service。

1
2
3
4
5
6
7
// order.service.ts
@Injectable({
providedIn: ProductComponent, // --- only available in ProductComponent
})
export class OrderService {
// ...
}

注意,以上代码无法在应用启动时自动注入Service,使用者仍然需要在providers中声明该Service。

1
2
3
4
5
6
7
8
9
// product.component.ts
@Component({
selector: 'app-product',
standalone: true,

templateUrl: './product.component.html',
styleUrl: './product.component.css',
providers: [OrderService] // <--- provide service here
})

如果在其他Component中尝试使用该Service,会抛出一个错误,如下:

1
ERROR TypeError: Cannot read properties of undefined (reading 'provide')

Service的Scope

providedIn: ‘root’

providedIn: 'root' - 表示Service是一个全局Service,可以在整个应用中使用。Angular会在应用启动时自动将该Service注入到根模块中。

providedIn: ‘platform’

providedIn: 'platform' - 表示Service是一个平台Service,这种服务可以跨越多个Angular应用实例共享同一个实例,只要这些应用实例运行在同一页面上。这个主要在微前端项目中使用,单体Angular应用用不到。

providedIn: ‘any’

providedIn: 'any' - 这种方式下,每个lazy load的module都会有一个独立的Service实例。而所有的eager load的module共享一个Service实例。

lazy loading与Service实例

References

  1. https://angular.dev/api/core/Injectable#providedIn