使用 ngUpgrade 将 AngularJS 服务迁移到 Angular

在我们的 最后的指南中,我们涵盖了如何安装我们需要从 AngularJS 升级到 Angular 开始的一切,我们还涵盖了如何重写和降级组件。

在本指南中,您将与 ngUpgrade 项目中的服务合作,具体来说,您将:

  • 重写 AngularJS 服务为 Angular
  • 将可观察的服务转换为承诺
  • 降级服务以便它仍然与我们的 AngularJS 代码
  • 将承诺转换为可观察的服务

我们的起点

花一分钟在 GitHub 上克隆或折叠这个 样本项目(不要忘记在公共服务器文件夹中运行npm install)。

1git checkout 083ee533d44c05db003413186fbef41f76466976

我们有一个订单系统项目,我们可以通过 ngUpgrade 工作,它使用组件架构,TypeScript 和 Webpack (包括开发和生产的构件)。

(如果您错过了其中任何一个,我们将在全面的视频课程中涵盖这一切 Upgrading AngularJS

Upgrading Angular JS header image

一个快速的注意: 事情在 Angular 和 RxJS 快速变化. 如果你正在使用 Angular 4.3+ 或 5+,你会看到一些小差异,这里与样本项目相比。 样本项目使用Http在HTTP调用服务中,如GETPOST。 我们将使用新的HttpClient,它在版本 4.3+ 中被添加,这具有为本教程所需的功能性。

重写 AngularJS 服务

在执行 ngUpgrade 时,最好一次选择一个路线并从底部工作,您可以利用将 Angular 和 AngularJS 并行运行,而无需担心破坏应用程序。

由于我们在上一个指南中完成了主路线,我们现在已经准备好开始客户路线。我们将开始重新编写客户服务并降级,以使其可用于我们的 AngularJS 组件。

将 HttpClient 添加到 NgModule

在重新撰写客户服务之前,我们必须明确地将 Angular 的 HttpClientModule 导入我们的应用程序 NgModule(app.module.ts)以便进行 HTTP 呼叫。这与 Angular JS 不同,其中所有内容都包含在默认情况下。在 Angular,我们需要明确地说明我们想要使用哪些部分的 Angular。虽然这可能在一开始看起来不方便,但这很棒,因为它有助于通过不自动导入未使用的代码来减少我们的应用程序的足迹。

所以在第3行之后,我们会像这样导入它:

「從「@angular/common/http」輸入 { HttpClientModule };」

然后,我们需要在 UpgradeModule 上列12行后将该模块添加到我们的导入阵列中:

 1//app.module.ts
 2
 3@NgModule({
 4
 5    imports: [
 6
 7        BrowserModule,
 8
 9        UpgradeModule,
10
11        HttpClientModule
12
13    ],
14
15    declarations: [
16
17        HomeComponent
18
19    ],
20
21    entryComponents: [
22
23        HomeComponent
24
25    ]
26
27})

现在我们可以在整个应用程序中使用HttpClientModule,我们只需要导入一次,我们可以在整个应用程序中使用所有其他服务。

重写客户服务

现在我们已将 HttpClientModule 添加到我们的 Angular 应用模块中,我们已经准备好在 Angular 中重写 CustomerService。

我们要做的第一件事是将customerService.ts文件更名为customer.service.ts,以便遵循当前的命名惯例。

现在,让我们打开文件,你会看到我们已经使用 ES2015 类:

 1//customer.service.ts
 2
 3class CustomerService{
 4
 5    $http: any;
 6
 7    constructor($http) {
 8
 9        this.$http = $http;
10
11    }
12
13    getCustomers(){
14
15        return this.$http.get('/api/customers')
16
17            .then((response) => response.data);
18
19    }
20
21    getCustomer(id){
22
23        return this.$http.get(`/api/customers/${id}`)
24
25            .then((response) => response.data);
26
27    }
28
29    postCustomer(customer){
30
31        return this.$http.post('/api/customers', customer)
32
33            .then((data) => data);
34
35    }
36
37}
38
39CustomerService.$inject = ['$http'];
40
41export default CustomerService;

Angular 2+ 服务是我们出口的类,但我们添加了Injectable()标注。 试图记住工厂、服务、供应商以及如何创建它们的日子已经过去了。

编制代码

我们不再需要AngularJS$inject数组,而不是使用export default,我们将在类声明之前添加export关键字:

出口客户服务等。

现在我已经准备好从 Angular 中导入两个东西,第一个是前面提到的Injectable()注释:

’从‘@angular/core’导入 { Injectable };`

接下来我们需要HttpClient:

「從「@angular/common/http」輸入 { HttpClient };」

现在我们已经准备好将此变成一个 Angular 服务。

将服务类更新为 Angular

首先,让我们将注射( )的注释添加到我们的客户服务中,仅次于类:

可注射( )

没有选项对象被传入此注释。

接下来我们需要做的就是用 Angular 的 HttpClient 代替我们对 AngularJS 的$http服务的所有引用,我们会用http这个缩写来代替,在本文档中找到并更换,改变$httphttp,因为大多数的呼叫将大致相同:

现在我们需要改变一个关于我们的 http 属性是如何创建的。

 1//customer.service.ts
 2
 3class CustomerService{
 4
 5    http: any;
 6
 7    constructor(http) {
 8
 9        this.http = http;
10
11    }

...我们将删除声明http类型为任何的公共财产的第六行.相反,在我们的构建器中,让我们在http之前添加私密关键字,并指定它类型为HttpClient:

1//customer.service.ts
2
3export class CustomerService{
4
5    constructor(private http: HttpClient) {  }

借助Angular的依赖注入,我们在我们的客户服务中实例化了HttpClient服务的私人实例,您还可以看到,使用私人关键字,我们不需要将我们的类实例http设置为等于我们注入的实例(它为我们做后幕)。

我们现在所拥有的只是一个 Angular 服务的裸体骨头,但现在你会看到我们使用的.then的每一个角落下面的那些红色线条,你可以看到 IntelliSense 告诉我们,这个属性在可观察的响应类型上不存在:

Example of "Property does not exist" warning in the form of red squiggly line

到底发生了什么事?接下来就来处理一下吧。

将观察性转化为承诺

我們的客戶服務大多被重寫成 Angular 服務,但我們在這些 http 通話中嘗試使用 `.then' 有一點問題. 這是因為 Angular 中的 HttpClient 會返回 observable 而不是承諾。

  1. ** 实用方法:** 将这些响应转换为承诺,我们的应用程序的其余部分将同样工作,或
  2. ** 乐趣的方式:** 保持这些响应作为可观察和更新我们的组件

对于任何大规模的翻译器或升级,目标始终是在您的应用程序中尽可能少地浪费时间。建议的方法是首先将呼叫转换为承诺。这样,您可以确定应用程序的哪些组件和其他部分取决于服务及其呼叫。完成后,您可以一次次将呼叫转换为可观察的,并相应地更新每个组件。因此,首先,将服务转移到 Angular 并使其工作。

因此,让我们首先将呼叫转换为承诺,但不要担心 - 很快我们会做有趣的事情,并将呼叫转换为可观察的。

使用 toPromise 操作器

要将可观察数据转换为承诺,我们首先需要从处理可观察数据的库RxJS导入。

’ import { Observable } from ‘rxjs/ Observable’;`

这使我们能够使用RxJS提供的可观察对象的各种函数。

ToPromise方法允许我们将可观察的数据转换为承诺。它曾经是以前的RxJS版本的单独导入,但现在已经转变为可观察的。在RxJS中导入单个操作员是常见的模式,但了解你需要哪些操作员以及它们在图书馆中的位置可能有点令人不安。请确保通过RxJS提供的文档资源(https://github.com/ReactiveX/rxjs/tree/stable),以及RxJS的角度文档(https://angular.io/guide/rx-library)。

当你这样做时,你也会看到一个错误,说‘.data’不是一个存在于object类型的属性,这是因为响应已经返回了HTTP响应中的数据对象。

由于我们有TypeScript的优势,让我们将返回类型添加到这些函数中的每个函数中。在TypeScript中,最好在可能时指定类型,尽管从技术上来说,它不需要。

服务中完成的功能将看起来像这样:

 1//customer.service.ts
 2
 3getCustomers():Promise<any> {
 4
 5   return this.http.get('/api/customers')
 6
 7       .toPromise()
 8
 9        .then(response => response);
10
11}
12
13getCustomer(id):Promise<any> {
14
15    return this.http.get(`/api/customers/${id}`)
16
17        .toPromise()
18
19        .then(response => response);
20
21}
22
23postCustomer(customer):Promise<any> {
24
25    return this.http.post('/api/customers', customer)
26
27       .toPromise()
28
29       .then(data => data);
30
31}

通过这种方式,我们已经成功地将我们呼吁中的可观察的东西转化为承诺。

降低客户服务水平

现在,我们已经将我们的可观察数据转换为承诺,我们已经准备好下调客户服务,以便尚未迁移的AngularJS组件仍然可以使用它。

这个过程类似于我们在上一个指南中降级了主组件。我们需要做的第一件事是从 ngUpgrade 库中导入 downgradeInjectable 函数,就像我们为主组件导入 `downgradeComponent' 一样。

「從「@angular/upgrade/static」輸入 { downgradeInjectable };」

我们还需要声明一个名为角度的变量,就像我们在我们的家庭组件中一样。

declare var angular: angular.IAngularStatic;

然后,在我们的文件底部,我们将注册我们的服务作为一个下级的工厂,所以,在课堂结束后,我们将键入:

1angular.module('app')
2
3    .factory('customerService', downgradeInjectable(CustomerService));

We've downgraded the CustomerService to be available to AngularJS. Here's the finished service:

 1
 2服务 服务 服务
 3
 4从“@angular/core”导入
 5
 6从“@angular/common/http”到“HttpClient”
 7
 8从“rxjs/Observable”中导入 { Observable };
 9
10从“@angular/upgrade/static”中导入 { downgradeInjectable };
11
12标签名称: angular.IAngularStatic
13
14无线电( )
15
16出口类客户服务

constructor(private http: HttpClient) {}

getCustomers():Promise {

return this.http.get('/api/customers')

    .toPromise()

    .then(response => response);

}

getCustomer(id):Promise {

return this.http.get(`/api/customers/${id}`)

    .toPromise()

    .then(response => response);

}

postCustomer(customer):Promise {

return this.http.post('/api/customers', customer)

    .toPromise()

    .then((data) => data);

}

1
23
4主页 > 应用程序 > 应用程序

.factory('customerService', downgradeInjectable(CustomerService));

Moving the Service to the Angular Module

Our customer service has been rewritten to an Angular service and downgraded to be available to AngularJS. Now we need to remove our reference in our AngularJS module and add it to our Angular module.

Removing Content from the AngularJS Module

First, let's open up our AngularJS module (app.module.ajs.ts). You can remove line 22:

import CustomerService from './customers/customerService';

...as well as line 41:

.service('customerService', CustomerService)

Those are all the changes you need to make in this module.

Moving the Service to Angular Module

Now let's add our service to our NgModule in app.module.ts so that our Angular code can access it. The first thing we need to do is import the service after line seven:

import { CustomerService } from './customers/customer.service';

Now to register our customer service in our application, we need to add an array of providers to our NgModule after our entryComponents array and add our CustomerService there:

1
2//app.module.ts
3
4providers: [
5
6        CustomerService
7
8    ]

供应商范围是我们将在应用程序中注册所有服务的地方,现在我们已经在我们的 NgModule 注册了我们的客户服务,并准备好去。

A Quick Note on AOT 编译

这种降级方法 - 在服务文件中注册降级服务并从AngularJS模块文件中删除它 - 对于开发而言非常有效,或者如果你计划在部署之前快速重写你的应用程序。

降级是相同的,但相反,你会:

  • 在 app.module.ajs.ts 中导入 downgradeInjectable(你已经在里面有 angular,所以你不需要声明它)。
  • 更改 CustomerService 的导入到 import { CustomerService } from './customers/customer.service'; 因为我们已切换到命名出口
  • 更改服务登记到上面的相同工厂登记

测试应用程序的功能

我们最好确保我们的应用程序仍在运行。让我们启动我们的Express API,然后运行我们的Webpack开发服务器。打开终端并运行以下命令来启动Express:

1cd server
2
3npm start

然后打开另一个终端并运行以下命令来启动 Webpack:

1cd public
2
3npm run dev

你应该看到一切都编译和包装正确。

现在,打开一个浏览器,转到localhost:9000。让我们导航到我们的客户路线,看看服务是否工作:

Customers route example

我们可以通过进入Chrome开发人员工具中的源代码选项卡,导航到客户文件夹,然后点击客户服务源来检查我们是否正在使用重新编写的角度服务:

Chrome developers tools sources tab

我们已将服务更新为 Angular,但它正在客户组件和客户表组件中使用,这两个组件仍然在 AngularJS 中。

使用GetCustomers作为一个可观察的

现在我们已经下调了客户服务并工作了,让我们有点乐趣,并将getCustomers呼叫作为可观察的呼叫。这样我们就可以开始利用可观察的所有新功能。这将是有点麻烦的,因为我们正在使用客户组件和订单组件中的呼叫,这些组件都尚未重写到 Angular。不要担心 - 我将逐步向您展示如何做到这一点。

回到客户服务代码中,我们需要做的第一件事是将路线16的返回类型更改为可观察<任何>。当然,现在,TypeScript正在向我们抱怨,因为我们正在转换为Promise,所以我们只需要删除ToPromise然后函数。

1getCustomers():Observable<any> {
2
3      return this.http.get('/api/customers');
4
5}

现在我们需要更新我们的客户组件以使用可观察的,而不是承诺。

在客户组件中使用可观察的内容

我们的getCustomers呼叫现在返回可观察。让我们更新我们的客户组件(customers.ts)以使用可观察的组件,而不是承诺。客户组件仍然是一个AngularJS组件,这很好,我们还不需要打扰它,但让我们使用一点TypeScript来帮助我们。

’从‘./customer.service’导入 { CustomerService };`

现在我们已经导入了客户服务,我们可以在控制器函数定义中指定我们注入的客户服务类型:

1//customers.ts
2
3function customersComponentController(customerService: CustomerService){

现在我们有TypeScript抱怨我们的.then的优势,就像在我们的客户服务中一样。

我们在组件中使用可观察的内容,无论是 AngularJS 还是 Angular 组件,就是 subscribe 到它之前,我们需要像我们在服务中所做的那样导入可观察的内容

’ import { Observable } from ‘rxjs/observable’;`

这将使我们能够使用可观察的函数,包括订阅。所以,现在在我们$onInit函数内的18行中,我们可以简单地将然后更改为订阅,其他一切都可以保持不变。

让我们来看看浏览器,看看这是否按预期奏效了。如果您转到客户路线,您应该看到一切都是一样的。但是,如果我们转到订单选项卡,我们看到一个大问题:没有数据和TypeError:无法读取控制台的属性fullName of undefined`。

结果,订单组件 also 使用getCustomers呼叫,但它仍然试图将其作为一个承诺。

修复订单组件

当我们重写我们的 getCustomers 呼叫作为一个可观察的,而不是一个承诺时,我们意外破坏了我们的订单组件(‘orders/orders.ts’),该组件仍然在 AngularJS 中,这是因为在我们的 $onInit 函数中,我们正在使用 $q.all 等待两个承诺返回,然后我们将任何数据分配给我们的视图模型:

 1vm.$onInit = function() {
 2
 3        let promises = [orderService.getOrders(), customerService.getCustomers()];
 4
 5        return $q.all(promises).then((data) => {
 6
 7            vm.orders = data[0];
 8
 9            vm.customers = data[1];
10
11            vm.orders.forEach(function (order) {
12
13                var customer = _.find(vm.customers, function (customer) {
14
15                    return order.customerId === customer.id;
16
17                });
18
19                order.customerName = customer.fullName;
20
21            });
22
23        });
24
25    };

这在AngularJS中是一个常见的模式。

解决这个问题的一种解决方案是简单地将订单组件重写为 Angular,并重写订单服务,但在现实世界中,这并不总是可以立即实现的。

但是,如果订单组件更复杂,我们没有时间重新编写它呢?在这种情况下,我们有两个选择:我们可以将我们的getCustomers呼叫转换为订单组件中的承诺,或者我们可以将getOrders的承诺转换为可观察的。

要将getCustomers转换为组件中的承诺,我们只会做我们在服务中之前做的完全相同的事情 - 从 RxJS 导入可观察并在getCustomers后添加可承诺操作员。 这是很容易的,而且是一个方便的技巧,如果你只是无法没有时间重塑这个组件来使用可观察的,然而,这并不是完全可观察的,因为我们的长期目标是完全摆脱承诺,完全切换到可观察的。

getCustomers转换为承诺

让我们将getOrders转换为可观察的内容。我们要做的第一件事是将我们的客户服务导入到文件的顶部,就像我们在客户组件中一样:

「輸入客戶服務」從「../客戶/客戶服務」;

然后,我们可以在控制器函数定义中指定我们注入的客户服务类型:

1//orders.ts
2
3function ordersComponentController(orderService, customerService: CustomerService, $q) {

为了将getOrders调用转换为可观察的,我们将使用两个静态方法,称为fromPromiseforkJoinfromPromise方法允许我们将一个承诺转换为可观察的,而forkJoin允许我们订阅多个可观察的。

1import { fromPromise } from 'rxjs/observable/fromPromise';
2
3import { forkJoin } from 'rxjs/observable/forkJoin';.

现在我们可以在我们的$onInit函数中做一些工作。21行以上,让我们声明一个名为ordersData的变量,并使用从Promise方法:

let ordersData = fromPromise(orderService.getOrders());

现在让我们重写$q.all来使用forkJoin而不是forkJoin。所以,首先我们只会用forkJoin来代替forkJoin。我们需要通过一个数组,所以让我们把承诺数组移动,将ordersData添加到它的前面,然后就摆脱了承诺声明。最后,让我们把.then更改为.subscribe就像一个单一的可观察的数组一样。

 1vm.$onInit = function() {
 2
 3        let ordersData = fromPromise(orderService.getOrders());
 4
 5        forkJoin([ordersData, customerService.getCustomers()]).subscribe((data) => {
 6
 7            vm.orders = data[0];
 8
 9            vm.customers = data[1];
10
11            vm.orders.forEach(function (order) {
12
13                var customer = _.find(vm.customers, function (customer) {
14
15                    return order.customerId === customer.id;
16
17                });
18
19                order.customerName = customer.fullName;
20
21            });
22
23        });
24
25    };

让我们回顾一下我们在这里做过的事情。 首先,我们打电话给‘fromPromise’并将我们的‘getOrders’电话从一个承诺转换为一个可观察的电话。 然后,我们使用‘forkJoin’来订阅‘ordersData’和‘getCustomers’电话。 就像‘$q.all’一样,‘forkJoin’订阅者会返回我们列出的数据的序列。

我們可以從我們的「$inject」數列中的 16 行中移除「$q」依賴,並在我們的函數定義中移除 167 行。

确认应用程序正在工作

让我们再去看看浏览器一次,并确保这起作用. 您应该看到我们的应用程序编译和加载正确,所以检查订单卡:

Successful Orders tab example

这表明我们的数据正在正确地加载,现在你已经看到如何在承诺和可观察的之间翻译,这在你正在开发一个大型应用程序时非常有用,你不能一次地将一切转换为可观察的应用程序。

结论

从这里开始,使用本指南和最后一个转换customersTable组件和产品路线,您需要使用 Angular 模板语法学习一些新技巧,但否则您将拥有所需的一切。

Upgrading AngularJS header image

Published At
Categories with 技术
Tagged with
comments powered by Disqus