codecamp

Angular 更关注性能的升级方式

更关注性能的升级方式

Angular 是当前以及未来的 Angular 名称。
AngularJS 特指 Angular 的所有 1.x 版本。

本指南介绍了一些用来将 AngularJS 项目高效地逐块迁移到 Angular 平台上的工具。 本章和从 AngularJS 升级很像,但是这里会用辅助函数 ​downgradeModule()​ 取代 ​UpgradeModule​。这会影响到应用如何启动,以及变更检测事件如何在两个框架之间传播。 它能让你逐步升级,并提高混合式应用的运行速度,并让你能在升级过程中尽早用上 Angular 中的最新特性。

准备工作

在讨论你应该如何用 ​downgradeModule()​ 来创建混合式应用之前,你可以先采取一些措施来简化升级过程,甚至在开始升级之前就可以做。 无论你用哪种方式升级,这些步骤都是一样的,请参考从 AngularJS 升级的准备工作部分

使用 ngUpgrade 升级

使用 Angular 中的 ​ngUpgrade ​库,你可以通过构建混合式应用来逐步升级现有的 AngularJS 应用。在这些混合式应用中,你可以混用 AngularJS 和 Angular 的组件与服务,并让它们天衣无缝地进行互操作。 这意味着你不用一次性完成迁移工作,因为在过渡阶段两个框架可以自然共存。

ngUpgrade 的工作原理

无论选择 ​downgradeModule()​ 还是 ​UpgradeModule​,升级的基本原则都是一样的:无论是混合式应用背后的心智模型,还是 ​upgrade/static​ 的用法。 要了解更多,参阅从 AngularJS 升级的 ngUpgrade 工作原理部分

从 AngularJS 升级中的变更检测部分仅仅适用于使用 ​UpgradeModule ​的应用。 虽然你处理变更检测的方式和 ​downgradeModule()​(本章的重点)不同,不过读一下变更检测部分还是能为后续内容提供一些有用的上下文知识。

使用 downgradeModule() 进行变更检测

如前所述,​downgradeModule()​ 和 ​UpgradeModule ​之间的一个关键区别,就是如何进行变更检测,以及检测结果如何在两个框架之间传播。

使用 ​UpgradeModule​,两套变更检测系统绑得更紧密一些。 一旦应用中的 AngularJS 部分发生了某些变化,变更检测就会自动在 Angular 部分触发它,反之亦然。 这很方便,因为它保证了任何一个框架都不会丢失重要的变更。不过,其实大多数情况下并不需要运行这些额外的变更检测。

而 ​downgradeModule()​ 会避免显式触发变更检测,除非它确信应用的其它部分对此感兴趣。 比如,如果被降级的组件定义了 ​@Input()​,当那个值发生变化时,应用就可能需要知道。 因此,​downgradeComponent()​ 就会自动在该组件上触发变更检测。

但是,大多数情况下,应用的其它地方并不会关心某个组件中进行的局部更改。 比如,如果用户点击了某个表单的提交按钮,通常会由组件自行处理这个操作的结果。 话虽如此,但在某些情况下,你可能希望把这些变化传播到应用中由另一个框架控制的部分。 这时候,你就有责任通过手动触发变更检测来通知相关方。

如果你希望某些代码片段在应用的 AngularJS 部分触发变更检测,就要把它包在 scope.$apply() 中。 同样,要想在 Angular 中触发变更检测,就要调用 ​ngZone.run()​。

很多情况下,是否运行额外的变更检测可能并不重要。不过,在较大或变更检测较多的应用中,它们可能会产生显著地影响。 通过让你更精细的控制变更检测的传播方式,​downgradeModule()​ 可以让你的混合式应用达到更好地性能。

使用 downgradeModule()

AngularJS 和 Angular 都有自己的模块概念,来帮你把应用按功能组织成内聚的代码块。

它们在架构和实现方面的细节有很大不同。在 AngularJS 中,你可以用 angular.module() 指定名字和依赖,以创建一个模块。 然后,你可以使用它的各种方法添加资产。在 Angular 中,你要创建一个带有 ​NgModule ​装饰器的类,靠这个装饰器的元数据来描述这些资产。

在混合式应用中,你同时运行着两个框架。这意味着你至少需要一个来自 AngularJS 的模块和一个来自 Angular 的模块。

大多数情况下,你可以使用与常规应用程序相同的方式来指定模块。然后,使用 ​upgrade/static​ 辅助函数来让两个框架了解对方使用的资产。这叫做"升级(upgrading)"和"降级(downgrading)"。

定义
  • 升级:让 AngularJS 中的资产,比如组件或服务,可用于应用中的 Angular 部分。
  • 降级:让 Angular 中的资产,比如组件或服务,可用于应用中的 AngularJS 部分

依赖互联中最重要的部分之一是把两个主模块联结在一起。这就是 ​downgradeModule()​ 的用武之地。使用它来创建 AngularJS 模块(你可以在 AngularJS 主模块中把这个模块用作依赖项),该模块将引导你的 Angular 主模块,并启动混合式应用中的 Angular 部分。从某种意义上说,它把 NgModule "降级"成了 AngularJS 模块。

有几点需要注意:

  1. 你不必把 Angular 模块直接传给 ​downgradeModule()​。​downgradeModule()​ 所需要的只是一个用来创建模块实例 "配方"(比如工厂函数)。
  2. 除非应用实际用到了,否则不会初始化这个 Angular 模块。

下面是如何使用 ​downgradeModule()​ 来联结两个模块的例子。

// Import `downgradeModule()`.
import { downgradeModule } from '@angular/upgrade/static';

// Use it to downgrade the Angular module to an AngularJS module.
const downgradedModule = downgradeModule(MainAngularModuleFactory);

// Use the downgraded module as a dependency to the main AngularJS module.
angular.module('mainAngularJsModule', [
  downgradedModule
]);

为 Angular 模块指定一个工厂

如前所述,​downgradeModule()​ 需要知道如何实例化 Angular 模块。你可以通过提供可以创建 Angular 模块实例的工厂函数来定义该配方。 ​downgradeModule()​ 接受两种类型的工厂函数:

  1. NgModuleFactory
  2. (extraProviders: StaticProvider[]) => Promise<NgModuleRef>

当传入 ​NgModuleFactory ​时,​downgradeModule()​ 会把它传给 ​platformBrowser ​的 ​bootstrapModuleFactory()​ 来实例化模块。它与预先(AOT)编译模式兼容。 预先编译能让你的应用加载更快。

另外,你还可以传入一个普通函数,它要返回一个解析为 ​NgModuleRef​(比如你的 Angular 模块) 的 Promise。该函数接收一个额外 ​Providers ​的数组,这个数组可以在所返回 ​NgModuleRef ​的 ​Injector ​中可用。 例如,如果你在使用 ​platformBrowser ​或 ​platformBrowserDynamic​,就可以把 ​extraProviders ​数组传给它们:

const bootstrapFn = (extraProviders: StaticProvider[]) => {
  const platformRef = platformBrowserDynamic(extraProviders);
  return platformRef.bootstrapModule(MainAngularModule);
};
// or
const bootstrapFn = (extraProviders: StaticProvider[]) => {
  const platformRef = platformBrowser(extraProviders);
  return platformRef.bootstrapModuleFactory(MainAngularModuleFactory);
};

使用 ​NgModuleFactory ​需要更少的样板代码,并且是一个很好的默认选项,因为它支持 AOT 开箱即用。 使用自定义函数需要稍多的代码,但是给你提供了更大的灵活性。

按需实例化 Angular 模块

downgradeModule()​ 和 ​UpgradeModule ​之间的另一个关键区别,就是后者要求你预先实例化 AngularJS 和 Angular 的模块。 这意味着你必须为实例化应用中的 Angular 而付出代价 —— 即使你以后不会用到任何 Angular 资产。 ​downgradeModule()​ 则不那么激进。它只会在第一次用到时才实例化 Angular 部分,也就是说,当它需要实例化一个降级后的组件时。

你还可以更进一步,甚至不必将应用程序中 Angular 部分的代码下载到用户的浏览器中 —— 直到需要它的那一刻。 当不需要初始渲染或用户尚未访问到混合式应用中的 Angular 部分时,这特别有用。

举一些例子:

  • 你只想在特定的路由上使用 Angular,除非用户访问此路由,否则你不需要它。
  • 你可以将 Angular 用于仅对特定类型的用户可见的特性,比如:登录用户、管理员或 VIP 成员。这样在用户通过了身份验证之前,你都无需加载 Angular。
  • 你可以把 Angular 用于应用中那些在初始渲染时不太重要的特性,并且愿意为了更好地初始加载性能,而忍受加载该特性时的一点延迟。

通过 downgradeModule() 启动

你可能已经猜到了,你不需要修改引导现有 AngularJS 应用的方式。​UpgradeModule ​需要一些额外的步骤,但 ​downgradeModule()​ 能自行引导 Angular 模块,你只要为它提供配方即可。

要开始使用任何 ​upgrade/static​ API,你仍然要像在普通 Angular 应用中一样加载 Angular 框架。要想用 SystemJS 做到这一点,你可以遵循升级的准备工作中的指导,有选择的从快速上手项目的 Github 仓库中复制代码。

你还需要用 ​npm install @angular/upgrade --save​ 安装 ​@angular/upgrade​ 包,并添加一个指向 ​@angular/upgrade/static​ 包的映射:

'@angular/upgrade/static': 'npm:@angular/upgrade/fesm2015/static.mjs',

接下来,创建一个 ​app.module.ts​ 文件,并添加如下 ​NgModule ​类:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

@NgModule({
  imports: [
    BrowserModule
  ]
})
export class MainAngularModule {
  // Empty placeholder method to satisfy the `Compiler`.
  ngDoBootstrap() {}
}

这个最小的 ​NgModule ​导入了 ​BrowserModule​,Angular 每个基于浏览器的应用都会导入该模块。 它还定义了一个空的 ​ngDoBootstrap()​ 方法,来防止 ​Compiler ​返回错误。 在这里它是必要的,因为 ​NgModule ​装饰器上还没有声明 ​bootstrap​。

你不用把 ​bootstrap ​声明加到 ​NgModule ​装饰器上,因为 AngularJS 拥有应用的根组件,并且 ​ngUpgrade ​会负责启动必要的组件。

现在你可以用 ​downgradeModule()​ 把 AngularJS 和 Angular 的模块联结在一起。

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { downgradeModule } from '@angular/upgrade/static';

const bootstrapFn = (extraProviders: StaticProvider[]) => {
  const platformRef = platformBrowserDynamic(extraProviders);
  return platformRef.bootstrapModule(MainAngularModule);
};
const downgradedModule = downgradeModule(bootstrapFn);

angular.module('mainAngularJsModule', [
  downgradedModule
]);

现有的 AngularJS 代码仍然在和以前一样正常工作,但你已经可以开始添加新的 Angular 代码了。

使用组件与可注入对象

downgradeModule()​ 和 ​UpgradeModule ​之间的区别就是这些。 其余的 ​upgrade/static​ API 和概念的工作方式在不同的混合式应用中都完全一样了。 

虽然可以降级可注入对象,但在实例化 Angular 模块之前,无法使用降级后的可注入对象。 安全起见,你需要确保降级后的可注入对象不会用于应用中不受 Angular 控制的任何地方。
比如,在只使用 Angular 组件的已升级组件中可以使用降级后的服务,但是,不能在那些不依赖 Angular 的 AngularJS 组件中使用它,也不能从其它模块中使用降级过的 Angular 组件。

使用混合式应用进行预先编译

你可以像在任何其它 Angular 应用中一样,利用混合式应用的预先(AOT)编译功能。 混合式应用的设置与预先(AOT)编译一章所讲的大致相同,但 ​index.html​ 和 ​main-aot.ts​ 略有差异。

AOT 需要在 AngularJS 的 ​index.html​ 中的 ​<script>​ 标签中加载所有 AngularJS 文件。

你还要将所生成的 ​MainAngularModuleFactory ​传给 ​downgradeModule()​ 函数,而不是自定义引导函数。

import { downgradeModule } from '@angular/upgrade/static';
import { MainAngularModuleNgFactory } from '../aot/app/app.module.ngfactory';

const downgradedModule = downgradeModule(MainAngularModuleNgFactory);

angular.module('mainAngularJsModule', [
  downgradedModule
]);

这就是当你想让混合式应用受益于 AOT 时所要做的一切。

总结

该页面介绍了如何借助 ​upgrade/static​ 包,来按照你自己的节奏逐步升级现有的 AngularJS 应用。并且升级过程中不会方案此应用的进一步开发。

具体来说,本章介绍了如何使用 ​downgradeModule()​ 来代替 ​UpgradeModule​,为混合式应用提供更好的性能和更大的灵活性。

总结,​downgradeModule()​ 中的关键差异性因素是:

  1. 它允许实例化甚至惰性加载 Angular 部分,这能改善初始加载时间。某些情况下,这可能会完全免除启动第二个框架的成本。
  2. 通过避免运行不必要的变更检测,它提高了性能,给开发人员提供了更大的自定义能力。
  3. 它不需要你更改引导 AngularJS 应用的方式。

当你希望混合式应用的 AngularJS 部分和 Angular 部分保持松耦合时,使用 ​downgradeModule()​ 是个很好的选择。 你仍然可以混用并匹配两个框架中的组件和服务。作为回报,​downgradeModule()​ 为你提供了更大的控制权和更好的性能。


Angular 搭建升级环境
Angular 与AngularJS的概念对照
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

Angular 开发指南

Angular 特性预览

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }