迹忆客 专注技术分享

当前位置:主页 > 学无止境 > WEB前端 > Angular >

Angular ngOnInit 中的Async/Await使用 TypeScript 装饰器

作者:迹忆客 最近更新:2022/10/31 浏览次数:

很多时候,需要在页面加载或类初始化之前使用 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 中预加载数据或在模板初始化之前在任何类中预加载数据的实现。

转载请发邮件至 1244347461@qq.com 进行申请,经作者同意之后,转载请以链接形式注明出处

本文地址:

相关文章

在 Angular 中上传文件

发布时间:2023/04/14 浏览次数:71 分类:Angular

本教程演示了如何在 Angular 中上传任何文件。我们还将介绍如何在文件上传时显示进度条,并在上传完成时显示文件上传完成消息。

Angular 2 中的复选框双向数据绑定

发布时间:2023/04/14 浏览次数:139 分类:Angular

本教程演示了如何一键标记两个复选框。这篇有 Angular 的文章将着眼于执行复选框双向数据绑定的不同方法。

在 AngularJs 中加载 spinner

发布时间:2023/04/14 浏览次数:107 分类:Angular

我们将介绍如何在请求加载时添加加载 spinner,并在 AngularJs 中加载数据时停止加载器。

在 Angular 中显示和隐藏

发布时间:2023/04/14 浏览次数:78 分类:Angular

本教程演示了 Angular 中的显示和隐藏。在开发商业应用程序时,我们需要根据用户角色或条件隐藏一些数据。我们必须根据该应用程序中的条件显示相同的数据。

在 Angular 中下载文件

发布时间:2023/04/14 浏览次数:104 分类:Angular

本教程演示了如何在 angular 中下载文件。我们将介绍如何通过单击按钮在 Angular 中下载文件并显示一个示例。

扫一扫阅读全部技术教程

社交账号
  • https://www.github.com/onmpw
  • qq:1244347461

最新推荐

教程更新

热门标签

扫码一下
查看教程更方便