Angular ngOnInit 中的Async/Await使用 TypeScript 装饰器
很多时候,需要在页面加载或类初始化之前使用 API 的 Promises 加载数据。
为了实现这一点,我看到我的许多开发人员在 ngOnInit
上使用 async
,因此他们可以在数据获取 API 方法上使用 await
。
async ngOnit {
this.movies = await this.service.getMovies();
}
但是如果你仔细看,没有人在等待 ngOnInit
,即使你想也不能 await ngOnInit
。
它将运行 async
函数,但它不会等待它完成,它只允许我们使用 await
关键字,但它不是 aysnc
函数,尽管有 async
关键字。
它看起来有点尴尬,有点难以理解,似乎不合常规。
理想情况下,方法是使用路由解析器,以便在路由完成导航之前加载数据,并且我可以在加载视图之前知道数据可用。
许多 Stack Overflow 的答案都指向了一种更易读的方法,无需向 ngOnInit 添加 async 关键字,但解决方案的行为相同。
ngOnInit() {
(async () => {
this.movies = await this.service.getMovies();
});
}
这不仅适用于 Angular 组件。 想象一个简单的类,它加载了一些数据并且还具有该数据的 getter 和 setter。
class MovieService {
private movies: Movie[];
constructor(){
this.loadMovies();
}
getMovieById(id: number) {
return this.movies.find(movie => movie.id === id);
}
getAllMovies() {
return this.movies;
}
private async loadMovies(){
this.movies = await MovieStore.getTodos();
}
}
在上面的例子中,数据将在类的实例创建后立即开始加载,并且在完全获取数据之前不能阻塞构造函数。 因此,如果在请求仍处于挂起状态时调用 getter,它可能会以默认的空或未定义的电影对象结束。
因此,我们只能希望在调用任何 getter 时加载数据。 转过来就是添加一些延迟加载并让访问器等待加载请求,如下所示。
async getMoviesById(id: number){
if(!this.movies){
await this.loadMovies();
}
return this.movies.find(movie => movie.id === id);
}
看起来不错,但是现在这段代码变成了重复的代码,并且加载请求可能会被触发不止一次,前提是我们可以找到一种方法来用其他东西抽象所有这些连接。
TypeScript 装饰器
装饰器是一种特殊的声明,可以附加到类声明、方法、访问器、属性或参数上。
简洁明了,装饰器是一个函数,它接受另一个函数并扩展后者函数的行为,而无需显式修改它。
抽象接线的一个好方法是在我们可以等待调用所有方法的地方使用装饰器,这些方法首先要运行和完成,然后运行依赖于这些方法的方法。 如下所示:
class MoviesService {
private movies: Movies[];
@waitForInit
async getMoviesById(id: number) {
return this.movies.find(user => user.id === id);
}
@waitForInit
async getAllMovies() {
return this.movies;
}
@init
private async loadMovies() {
this.users = await myMoviesStore.getUsers();
}
}
以下是如何使这些装饰器工作。 我们可以将每个装饰器添加到任意数量的方法中。
const INIT_METHODS = new Map<any, string[]>();
type InitMethodDescriptor = TypedPropertyDescriptor<() => void>
| TypedPropertyDescriptor<() => Promise<void>>;
export function init(target: any, key: string, _descriptor: InitMethodDescriptor) {
if (!INIT_METHODS.has(target)) {
INIT_METHODS.set(target, []);
}
INIT_METHODS.get(target)!.push(key);
}
const INIT_PROMISE_SYMBOL = Symbol.for("init_promise");
export function waitForInit(target: any, _key: string, descriptor: PropertyDescriptor) {
const method = descriptor.value!;
descriptor.value = function(...args: any[]) {
if (!Object.getOwnPropertySymbols(this).includes(INIT_PROMISE_SYMBOL)) {
if (!INIT_METHODS.has(target)) {
this[INIT_PROMISE_SYMBOL] = Promise.resolve();
} else {
const promises = INIT_METHODS.get(target)!.map(methodname => {
return Promise.resolve(this[methodname]());
});
this[INIT_PROMISE_SYMBOL] = Promise.all(promises);
}
}
return this[INIT_PROMISE_SYMBOL].then(() => method.apply(this, args));
};
return descriptor;
}
给刚接触装饰器的人的代码解释
要了解 init/waitForInit
装饰器代码,我们首先必须了解装饰器是如何计算的。
让我们以下面的例子来理解:
function first() {
console.log("first(): factory evaluated");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("first(): called");
};
}
function second() {
console.log("second(): factory evaluated");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("second(): called");
};
}
class ExampleClass {
@first()
@second()
method() {}
}
和上面一样,我们所有的 init
装饰方法都将首先被评估,在 MoviesService 案例中,loadMovies 将被注册为 INIT_METHODS 之一,然后当任何一个用 waitForInit 装饰的方法(如 getAllMovies)被调用时,它首先会被调用 先运行 loadMovies Promise,再运行对应的调用函数。
不用担心,我们为大家提供保障。 以下是如何在 ngOnInit
上使用相同的 init/waitForInit
装饰器。
这是在 ngOnInit 上演示上述装饰器的 Stackblitz 示例。
import { Component } from '@angular/core';
import { init, waitForInit } from './init';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
movies: any[];
@init
private async loadMovies() {
this.movies = await Promise.resolve([
'Toy Story',
'Bahubali',
'Terminator',
'Iron Man',
]);
}
@waitForInit
ngOnInit() {
console.log('Initializing view template with pre-loaded data', this.movies);
}
}
最后,我们简要讨论了使用 TypeScript 装饰器在 Angular 中的 ngOnInit 中预加载数据或在模板初始化之前在任何类中预加载数据的实现。
相关文章
在 Angular 中上传文件
发布时间:2023/04/14 浏览次数:71 分类:Angular
-
本教程演示了如何在 Angular 中上传任何文件。我们还将介绍如何在文件上传时显示进度条,并在上传完成时显示文件上传完成消息。
Angular 中所有 Mat 图标的列表
发布时间:2023/04/14 浏览次数:91 分类:Angular
-
本教程演示了在哪里可以找到 Angular 中所有 Mat 图标的列表以及如何使用它们。
Angular 2 中的复选框双向数据绑定
发布时间:2023/04/14 浏览次数:139 分类:Angular
-
本教程演示了如何一键标记两个复选框。这篇有 Angular 的文章将着眼于执行复选框双向数据绑定的不同方法。
在 AngularJS 中重新加载页面
发布时间:2023/04/14 浏览次数:142 分类:Angular
-
我们可以借助 windows.location.reload 和 reload 方法在 AngularJS 中重新加载页面。
在 AngularJs 中设置 Select From Typescript 的默认选项值
发布时间:2023/04/14 浏览次数:78 分类:Angular
-
本教程提供了在 AngularJs 中从 TypeScript 中设置 HTML 标记选择的默认选项的解释性解决方案。
在 AngularJS 中启用 HTML5 模式
发布时间:2023/04/14 浏览次数:150 分类:Angular
-
本文讨论如何在 AngularJS 应用程序上启用带有深度链接的 HTML5 模式。
在 AngularJs 中加载 spinner
发布时间:2023/04/14 浏览次数:107 分类:Angular
-
我们将介绍如何在请求加载时添加加载 spinner,并在 AngularJs 中加载数据时停止加载器。
在 Angular 中显示和隐藏
发布时间:2023/04/14 浏览次数:78 分类:Angular
-
本教程演示了 Angular 中的显示和隐藏。在开发商业应用程序时,我们需要根据用户角色或条件隐藏一些数据。我们必须根据该应用程序中的条件显示相同的数据。
在 Angular 中下载文件
发布时间:2023/04/14 浏览次数:104 分类:Angular
-
本教程演示了如何在 angular 中下载文件。我们将介绍如何通过单击按钮在 Angular 中下载文件并显示一个示例。