|
| 1 | +# Upgrading from AngularJS (in a more flexible way) |
| 2 | + |
| 3 | +<div class="alert is-helpful"> |
| 4 | + |
| 5 | + _Angular_ is the name for the Angular of today and tomorrow.<br /> |
| 6 | + _AngularJS_ is the name for all 1.x versions of Angular. |
| 7 | + |
| 8 | +</div> |
| 9 | + |
| 10 | +This guide describes some of the built-in tools for efficiently migrating AngularJS projects over |
| 11 | +to the Angular platform, one piece at a time. It is very similar to the |
| 12 | +[main upgrade guide](guide/upgrade) with the exception that this one uses the {@link downgradeModule |
| 13 | +downgradeModule()} helper function instead of the {@link UpgradeModule UpgradeModule} class. This |
| 14 | +affects how the application is bootstrapped and how change detection is propagated between the two |
| 15 | +frameworks (more on that later). |
| 16 | + |
| 17 | + |
| 18 | +## Preparation |
| 19 | + |
| 20 | +Before we start discussing how you can use `downgradeModule()` to create hybrid applications, there |
| 21 | +are things that you can do to ease the upgrade process even before you begin upgrading. Although not |
| 22 | +strictly necessary, preparation goes a long way! The steps are the same regardless how you upgrade, |
| 23 | +so go ahead and read the [Preparation](guide/upgrade#preparation) section of the main upgrade guide. |
| 24 | + |
| 25 | + |
| 26 | +## Upgrading with ngUpgrade |
| 27 | + |
| 28 | +With the ngUpgrade library in Angular you can upgrade an existing AngularJS application |
| 29 | +incrementally, by building a hybrid application where you can run both frameworks side-by-side. In |
| 30 | +these hybrid applications you can mix and match AngularJS and Angular components and services and |
| 31 | +have them interoperate seamlessly. That means you don't have to do the upgrade work all at once, |
| 32 | +since there is a natural coexistence between the two frameworks during the transition period. |
| 33 | + |
| 34 | + |
| 35 | +### How ngUpgrade Works |
| 36 | + |
| 37 | +Regardless of whether you choose `downgradeModule()` or `UpgradeModule`, the basic principles of |
| 38 | +upgrading, the mental model behind hybrid applications and how you use the {@link upgrade/static |
| 39 | +upgrade/static} utilities remain the same. You can read about all that in the |
| 40 | +[How ngUpgrade Works](guide/upgrade#how-ngupgrade-works) section of the main upgrade guide. |
| 41 | + |
| 42 | +<div class="alert is-helpful"> |
| 43 | + |
| 44 | + The [Change Detection](guide/upgrade#change-detection) sub-section only applies to applications |
| 45 | + that use `UpgradeModule`. Change detection is handled differently with `downgradeModule()`.<br /> |
| 46 | + We still recommend reading the sub-section in order to better understand the differences and their |
| 47 | + implications. |
| 48 | + |
| 49 | +</div> |
| 50 | + |
| 51 | + |
| 52 | +#### Change Detection with `downgradeModule()` |
| 53 | + |
| 54 | +As mentioned before, one of the key differences between `downgradeModule()` and `UpgradeModule` has |
| 55 | +to do with change detection and how it is propagated between the two frameworks. |
| 56 | + |
| 57 | +With `UpgradeModule`, the two change detection systems are tied together more tightly. Whenever |
| 58 | +something happens in the AngularJS part of the application, change detection is automatically |
| 59 | +triggered on the Angular part and vice versa. This is convenient as it ensures that no important |
| 60 | +change is missed by either framework. Most of the time, though, these extra change detection runs |
| 61 | +are unnecesary. |
| 62 | + |
| 63 | +`downgradeModule()`, on the other side, avoids explicitly triggering change detection, unless it |
| 64 | +knows the other part of the application is interested in the changes. One way to know, for example, |
| 65 | +is when a value is bound to the {@link Input input} of a downgraded component. If the component |
| 66 | +defines an `Input`, chances are it needs to be change-detected when that value changes. Thus, |
| 67 | +`downgradeComponent()` _will_ automatically trigger change detection on that component. |
| 68 | + |
| 69 | +In most cases, though, the changes made locally in a particular component are of no interest to the |
| 70 | +rest of the application. For example, if the user clicks a button that submits a form the component |
| 71 | +will usually handle the result of this action. That being said, there _are_ cases, where you want to |
| 72 | +propagate changes to some other part of the application, that may be controlled by the other |
| 73 | +framework. In such cases, you are responsible for notifying the interested parties, by manually |
| 74 | +triggering change detection. |
| 75 | + |
| 76 | +If you want a particular piece of code to trigger change detection in the AngularJS part of the |
| 77 | +application, you need to wrap it in |
| 78 | +[scope.$apply(...)](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply). Similarly, for |
| 79 | +triggering change detection in Angular you would use {@link NgZone#run ngZone.run(...)}. |
| 80 | + |
| 81 | +In many cases, a few extra change detection runs may not matter much. On larger or |
| 82 | +change-detection-heavy applications, though, they can have a noticeable impact. By giving you more |
| 83 | +fine-grained control over the change detection propagation, `downgradeModule()` allows you to |
| 84 | +achieve better performance for your hybrid applications. |
| 85 | + |
| 86 | + |
| 87 | +### Using `downgradeModule()` |
| 88 | + |
| 89 | +Both AngularJS and Angular have their own concept of modules to help organize an application into |
| 90 | +cohesive blocks of functionality. |
| 91 | + |
| 92 | +Their details are quite different in architecture and implementation. In AngularJS, you create a |
| 93 | +module by specifying its name and dependencies with |
| 94 | +[angular.module()](https://docs.angularjs.org/api/ng/function/angular.module). Then you can add |
| 95 | +assets using its various methods. In Angular, you create a class adorned with an {@link NgModule |
| 96 | +NgModule} decorator that describes assets in metadata. The differences blossom from there. |
| 97 | + |
| 98 | +In a hybrid application you run both frameworks at the same time. This means that you need at least |
| 99 | +one module each from both AngularJS and Angular. |
| 100 | + |
| 101 | +For the most part, you specify the modules in the same way as you would for a regular application. |
| 102 | +Then, you use the `upgrade/static` helpers to let the two frameworks know about assets they can use |
| 103 | +from each other. This is known as "upgrading" and "downgrading" (more on how this is done later). |
| 104 | + |
| 105 | +<div class="alert is-helpful"> |
| 106 | + |
| 107 | + <b>Definitions:</b> |
| 108 | + |
| 109 | + - _Upgrading_: The act of making an AngularJS asset (e.g. component or service) available to the |
| 110 | + Angular part of the application. |
| 111 | + - _Downgrading_: The act of making an Angular asset (e.g. component or service) available to the |
| 112 | + AngularJS part of the application. |
| 113 | + |
| 114 | +</div> |
| 115 | + |
| 116 | +An important part of inter-linking dependencies, is linking the two main modules together. This is |
| 117 | +where `downgradeModule()` comes in. It is used to create an AngularJS module — one that you |
| 118 | +can use as a dependency in your main AngularJS module — that will bootstrap your main Angular |
| 119 | +module and kick off the Angular part of the hybrid application. In a sense, it takes an Angular |
| 120 | +module and "downgrades" it to an AngularJS module. |
| 121 | + |
| 122 | +There are a few things to note, though: |
| 123 | + |
| 124 | +1. You don't pass the Angular module directly to `downgradeModule()`. All `downgradeModule()` needs |
| 125 | + is a "recipe" (e.g. a factory function) for creating an instance for your module. |
| 126 | + |
| 127 | +2. The Angular module is not instantiated until it is actually needed. |
| 128 | + |
| 129 | +We will expand on these two points below. For now, let's see how we can use `downgradeModule()` to |
| 130 | +link the two modules. |
| 131 | + |
| 132 | +```ts |
| 133 | +// Import `downgradeModule()`. |
| 134 | +import { downgradeModule } from '@angular/upgrade/static'; |
| 135 | + |
| 136 | +// Use it to "downgrade" the Angular module to an AngularJS module. |
| 137 | +const downgradedModule = downgradeModule(MainAngularModuleFactory); |
| 138 | + |
| 139 | +// Use the downgraded module as a dependency to the main AngularJS module. |
| 140 | +angular.module('mainAngularJsModule', [ |
| 141 | + downgradedModule |
| 142 | +]); |
| 143 | +``` |
| 144 | + |
| 145 | + |
| 146 | +#### Specifying a factory for the Angular module |
| 147 | + |
| 148 | +As mentioned before, `downgradeModule()` needs to know how to instantiate the Angular module. It |
| 149 | +needs a "recipe". You define that recipe, by providing a factory function that can create an |
| 150 | +instance of the Angular module. `downgradeModule()` accepts two types of factory functions: |
| 151 | + |
| 152 | +1. {@link NgModuleFactory NgModuleFactory} |
| 153 | +2. (extraProviders: {@link StaticProvider StaticProvider}[]) => Promise<{@link NgModuleRef NgModuleRef}> |
| 154 | + |
| 155 | +If you pass an `NgModuleFactory`, it will be used to instantiate the module using |
| 156 | +{@link platformBrowser platformBrowser}'s {@link PlatformRef#bootstrapModuleFactory |
| 157 | +bootstrapModuleFactory()}. This is great, because it is compatible with Ahead-of-Time (AoT) |
| 158 | +compilation. You can read more about AoT compilation and how to create an `NgModuleFactory` in the |
| 159 | +[AoT Compilation](guide/aot-compiler) guide. |
| 160 | + |
| 161 | +Alternatively, you can pass a plain function, which is expected to return a promise resolving to an |
| 162 | +{@link NgModuleRef NgModuleRef} (i.e. an instance of your Angular module). The function is called |
| 163 | +with an array of extra {@link StaticProvider Providers} that are expected to be available on the returned |
| 164 | +`NgModuleRef`'s {@link Injector Injector}. For example, if you are using {@link platformBrowser |
| 165 | +platformBrowser} or {@link platformBrowserDynamic platformBrowserDynamic}, you can pass the |
| 166 | +`extraProviders` array to them: |
| 167 | + |
| 168 | +```ts |
| 169 | +const bootstrapFn = (extraProviders: StaticProvider[]) => { |
| 170 | + const platformRef = platformBrowserDynamic(extraProviders); |
| 171 | + return platformRef.bootstrapModule(MainAngularModule); |
| 172 | +}; |
| 173 | +// or |
| 174 | +const bootstrapFn = (extraProviders: StaticProvider[]) => { |
| 175 | + const platformRef = platformBrowser(extraProviders); |
| 176 | + return platformRef.bootstrapModuleFactory(MainAngularModuleFactory); |
| 177 | +}; |
| 178 | +``` |
| 179 | + |
| 180 | +Using an `NgModuleFactory` requires less boilerplate and is a good default option as it supports |
| 181 | +AoT out-of-the-box. Using a custom function requires slightly more code, but gives you greater |
| 182 | +flexibility. |
| 183 | + |
| 184 | + |
| 185 | +#### Instantiating the Angular module "on-demand" |
| 186 | + |
| 187 | +Another key difference between `downgradeModule()` and `UpgradeModule` is that the latter requires |
| 188 | +you to instantiate both the AngularJS and Angular modules up-front. This means that you have to pay |
| 189 | +the cost of instantiating the Angular part of the application, even if you don't use any Angular |
| 190 | +assets until later. `downgradeModule()` is again less aggressive: It will only instantiate the |
| 191 | +Angular part when it is required for the first time; i.e. as soon as a downgraded component needs to |
| 192 | +be created. |
| 193 | + |
| 194 | +You could go a step further and not even download the code for the Angular part of the application |
| 195 | +to the user's browser, until it is needed. This is especially useful, when you use Angular on parts |
| 196 | +of the hybrid application that are not necessary for the initial rendering or are not often reached |
| 197 | +by the user (or not reached by all types of users). |
| 198 | + |
| 199 | +A few examples: |
| 200 | + |
| 201 | +- You use Angular on specific routes only and you don't need it until/if such a route is visited by |
| 202 | + the user. |
| 203 | +- You use Angular for features that are only visible to specific types of users (e.g. logged-in |
| 204 | + users or administrators or VIP members). You don't need to load Angular until a user is |
| 205 | + authenticated. |
| 206 | +- You use Angular for a feature that is not critical for the initial rendering of the application |
| 207 | + and you can afford a small delay in favor of better initial load performance. |
| 208 | + |
| 209 | + |
| 210 | +### Bootstrapping with `downgradeModule()` |
| 211 | + |
| 212 | +As you may have guessed, you don't need to change anything in the way you bootstrap your existing |
| 213 | +AngularJS application. Unlike `UpgradeModule` — which requires some extra steps — |
| 214 | +`downgradeModule()` is able to take care of bootstrapping the Angular module (as long as you provide |
| 215 | +the recipe). |
| 216 | + |
| 217 | +In order to start using any `upgrade/static` APIs, you still need to load the Angular framework (as |
| 218 | +you would in a normal Angular application). You can see how this can be done with SystemJS by |
| 219 | +following the instructions in the [Setup](guide/setup) guide, selectively copying code from the |
| 220 | +[QuickStart github repository](https://github.com/angular/quickstart). |
| 221 | + |
| 222 | +You also need to install the `@angular/upgrade` package via `npm install @angular/upgrade --save` |
| 223 | +and add a mapping for the `@angular/upgrade/static` package: |
| 224 | + |
| 225 | +<code-example title="system.config.js"> |
| 226 | +'@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js', |
| 227 | +</code-example> |
| 228 | + |
| 229 | +Next, create an `app.module.ts` file and add the following `NgModule` class: |
| 230 | + |
| 231 | +<code-example title="app.module.ts"> |
| 232 | +import { NgModule } from '@angular/core'; |
| 233 | +import { BrowserModule } from '@angular/platform-browser'; |
| 234 | + |
| 235 | +@NgModule({ |
| 236 | + imports: [ |
| 237 | + BrowserModule |
| 238 | + ] |
| 239 | +}) |
| 240 | +export class MainAngularModule { |
| 241 | + // Empty placeholder method to prevent the `Compiler` from complaining. |
| 242 | + ngDoBootstrap() {} |
| 243 | +} |
| 244 | +</code-example> |
| 245 | + |
| 246 | +This bare minimum `NgModule` imports `BrowserModule`, the module every Angular browser-based app |
| 247 | +must have. It also defines an empty `ngDoBootstrap()` method, to prevent the {@link Compiler |
| 248 | +Compiler} from complaining. This is necessary, because the module will not have a `bootstrap` |
| 249 | +declaration on its `NgModule` decorator. |
| 250 | + |
| 251 | +<div class="alert is-important"> |
| 252 | + |
| 253 | + You do not add a `bootstrap` declaration to the `NgModule` decorator, since AngularJS will own the |
| 254 | + root template of the application and ngUpgrade will be bootstrapping the necessary components. |
| 255 | + |
| 256 | +</div> |
| 257 | + |
| 258 | +You can now link the AngularJS and Angular modules together using `downgradeModule()`. |
| 259 | + |
| 260 | +<code-example title="app.module.ts"> |
| 261 | +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; |
| 262 | +import { downgradeModule } from '@angular/upgrade/static'; |
| 263 | + |
| 264 | +const bootstrapFn = (extraProviders: StaticProvider[]) => { |
| 265 | + const platformRef = platformBrowserDynamic(extraProviders); |
| 266 | + return platformRef.bootstrapModule(MainAngularModule); |
| 267 | +}; |
| 268 | +const downgradedModule = downgradeModule(bootstrapFn); |
| 269 | + |
| 270 | +angular.module('mainAngularJsModule', [ |
| 271 | + downgradedModule |
| 272 | +]); |
| 273 | +</code-example> |
| 274 | + |
| 275 | +Congratulations! You are running a hybrid application! The existing AngularJS code works as before |
| 276 | +_and_ you are ready to start adding Angular code. |
| 277 | + |
| 278 | + |
| 279 | +### Using Components and Injectables |
| 280 | + |
| 281 | +The differences between `downgradeModule()` and `UpgradeModule` end here. The rest of the |
| 282 | +`upgrade/static` APIs and concepts work in the exact same way for both types of hybrid applications. |
| 283 | +Head over to the [main upgrade guide](guide/upgrade) to learn about: |
| 284 | + |
| 285 | +- [Using Angular Components from AngularJS Code](guide/upgrade#using-angular-components-from-angularjs-code) |
| 286 | +- [Using AngularJS Component Directives from Angular Code](guide/upgrade#using-angularjs-component-directives-from-angular-code) |
| 287 | +- [Projecting AngularJS Content into Angular Components](guide/upgrade#projecting-angularjs-content-into-angular-components) |
| 288 | +- [Transcluding Angular Content into AngularJS Component Directives](guide/upgrade#transcluding-angular-content-into-angularjs-component-directives) |
| 289 | +- [Making AngularJS Dependencies Injectable to Angular](guide/upgrade#making-angularjs-dependencies-injectable-to-angular) |
| 290 | +- [Making Angular Dependencies Injectable to AngularJS](guide/upgrade#making-angular-dependencies-injectable-to-angularjs) |
| 291 | + |
| 292 | +<div class="alert is-important"> |
| 293 | + |
| 294 | + While it is possible to downgrade injectables, the downgraded injectables will _not_ be available |
| 295 | + until the Angular module is instantiated too. In order to be safe, you need to ensure that the |
| 296 | + downgraded injectables are not used anywhere _outside_ the part of the application that is |
| 297 | + controlled by Angular. |
| 298 | + |
| 299 | + For example, it is _OK_ to use a downgraded service in an upgraded component that is only used |
| 300 | + from Angular components, but it is _not OK_ to use it in an AngularJS component that may be used |
| 301 | + independently of Angular. |
| 302 | + |
| 303 | +</div> |
| 304 | + |
| 305 | + |
| 306 | +## Using Ahead-of-Time compilation with hybrid applications |
| 307 | + |
| 308 | +You can take advantage of Ahead-of-Time (AoT) compilation on hybrid applications just like on any |
| 309 | +other Angular application. The setup for a hybrid application is mostly the same as described in the |
| 310 | +[AoT Compilation](guide/aot-compiler) guide save for differences in `index.html` and `main-aot.ts`. |
| 311 | + |
| 312 | +The `index.html` will likely have script tags loading AngularJS files, so the `index.html` for AoT |
| 313 | +must also load those files. An easy way to copy them is by adding each to the `copy-dist-files.js` |
| 314 | +file. |
| 315 | + |
| 316 | +You will also need to pass the generated `MainAngularModuleFactory` to `downgradeModule()`, instead of |
| 317 | +the custom bootstrap function: |
| 318 | + |
| 319 | +<code-example title="app/main-aot.ts"> |
| 320 | +import { downgradeModule } from '@angular/upgrade/static'; |
| 321 | +import { MainAngularModuleNgFactory } from '../aot/app/app.module.ngfactory'; |
| 322 | + |
| 323 | +const downgradedModule = downgradeModule(MainAngularModuleNgFactory); |
| 324 | + |
| 325 | +angular.module('mainAngularJsModule', [ |
| 326 | + downgradedModule |
| 327 | +]); |
| 328 | +</code-example> |
| 329 | + |
| 330 | +And that is all you need to do to get the full benefit of AoT for Angular applications! |
| 331 | + |
| 332 | + |
| 333 | +## Wrap up |
| 334 | + |
| 335 | +You have learned how to use the {@link upgrade/static upgrade/static} package to incrementally |
| 336 | +upgrade existing AngularJS applications at your own pace and without impeding further development of |
| 337 | +the application for the duration of the upgrade process. |
| 338 | + |
| 339 | +More specifically, you have seen how you can achieve better performance and greater flexibility in |
| 340 | +your hybrid applications, by using {@link downgradeModule downgradeModule()} (instead of |
| 341 | +{@link UpgradeModule UpgradeModule}). |
| 342 | + |
| 343 | +To summarize, the key differentiating factors of `downgradeModule()` are: |
| 344 | + |
| 345 | +1. It allows instantiating (or even loading) the Angular part lazily, which improves the initial |
| 346 | + loading time (and is some cases may waive the cost of running a second framework altogether). |
| 347 | +2. It improves performance by avoiding unnecessary change detection runs, instead putting more |
| 348 | + responsibility on the developer. |
| 349 | +3. It does not require you to change how you bootstrap your AngularJS app. |
| 350 | + |
| 351 | +Based on that, `downgradeModule()` is a good option for hybrid applications that want keep the |
| 352 | +AngularJS and Angular parts less coupled. You can still mix and match components and services from |
| 353 | +both frameworks, but you might need to manually propagate change detection. In return, |
| 354 | +`downgradeModule()` offers more control and better performance characteristics. |
| 355 | + |
| 356 | +There is merit in both approaches, so you should always weight the pros and cons before deciding |
| 357 | +which one better meets the upgrading needs of each project. |
0 commit comments