Skip to content

Commit 70156bc

Browse files
gkalpakjasonaden
authored andcommitted
docs(upgrade): add guide about downgradeModule() (angular#18487) (angular#18487)
PR Close angular#18487 PR Close angular#18487
1 parent 2ac2ab7 commit 70156bc

File tree

4 files changed

+374
-8
lines changed

4 files changed

+374
-8
lines changed

aio/content/guide/upgrade-lite.md

Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
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 &mdash; one that you
118+
can use as a dependency in your main AngularJS module &mdash; 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` &mdash; which requires some extra steps &mdash;
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

Comments
 (0)