Compare commits
49 Commits
4.4.7
...
5.0.0-beta
Author | SHA1 | Date | |
---|---|---|---|
54ea5b6ffd | |||
0af03beaed | |||
d71fa734f5 | |||
6f45519d6f | |||
65c9e13105 | |||
9208f0beea | |||
5344be5182 | |||
5db6f38b73 | |||
d22f8f54db | |||
23146c9201 | |||
a5205c686e | |||
807648251f | |||
5c62e300e1 | |||
256bc8acdd | |||
59c23c7bd7 | |||
e03adb9edd | |||
b399cb26d9 | |||
3b588fe2b0 | |||
95635c18c7 | |||
e20cfe1bbc | |||
eb6fb5f87e | |||
ad3029e786 | |||
2a2fe11e8d | |||
7d0f2cd51e | |||
36faba1aab | |||
92179bcc64 | |||
cdb069ab0e | |||
c453b7bcfa | |||
9d97163c64 | |||
f054c8360b | |||
758848961e | |||
99b666614d | |||
3f331b53b2 | |||
375d598a9f | |||
cd67fced1c | |||
a77cf7ee37 | |||
2150b45954 | |||
9f99f4fae2 | |||
c6ad212a98 | |||
47b3ecd9a3 | |||
8c81c62d46 | |||
7e72317059 | |||
0bb8423df9 | |||
95698d93ad | |||
c649da9f0a | |||
0bf0c35bca | |||
97e6901ded | |||
30e76fcd80 | |||
44b50427d9 |
@ -256,6 +256,8 @@ groups:
|
||||
files:
|
||||
include:
|
||||
- "aio/*"
|
||||
exclude:
|
||||
- "aio/content/*"
|
||||
users:
|
||||
- petebacondarwin #primary
|
||||
- IgorMinar
|
||||
@ -276,6 +278,8 @@ groups:
|
||||
- Foxandxss
|
||||
- stephenfluin
|
||||
- wardbell
|
||||
- petebacondarwin
|
||||
- gkalpak
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
@ -289,5 +293,7 @@ groups:
|
||||
users:
|
||||
- juleskremer #primary
|
||||
- stephenfluin
|
||||
- petebacondarwin
|
||||
- gkalpak
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
50
CHANGELOG.md
50
CHANGELOG.md
@ -1,3 +1,53 @@
|
||||
<a name="5.0.0-beta.0"></a>
|
||||
# [5.0.0-beta.0](https://github.com/angular/angular/compare/4.3.0...5.0.0-beta.0) (2017-07-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** always camelcase style property names that contain auto styles ([d22f8f5](https://github.com/angular/angular/commit/d22f8f5)), closes [#17938](https://github.com/angular/angular/issues/17938)
|
||||
* **animations:** capture cancelled animation styles within grouped animations ([23146c9](https://github.com/angular/angular/commit/23146c9)), closes [#17170](https://github.com/angular/angular/issues/17170)
|
||||
* **animations:** do not crash animations if a nested component fires CD during CD ([5db6f38](https://github.com/angular/angular/commit/5db6f38)), closes [#18193](https://github.com/angular/angular/issues/18193)
|
||||
* **animations:** make sure @.disabled works in non-animation components ([5344be5](https://github.com/angular/angular/commit/5344be5))
|
||||
* **common:** send flushed body as error instead of null ([5c62e30](https://github.com/angular/angular/commit/5c62e30)), closes [#18181](https://github.com/angular/angular/issues/18181)
|
||||
* **compiler:** ensure jit external id arguments names are unique ([95635c1](https://github.com/angular/angular/commit/95635c1))
|
||||
* **compiler-cli:** don't generate empty <target/> when extracting xliff ([65c9e13](https://github.com/angular/angular/commit/65c9e13)), closes [#15754](https://github.com/angular/angular/issues/15754)
|
||||
* **platform-server:** provide XhrFactory for HttpClient ([8076482](https://github.com/angular/angular/commit/8076482))
|
||||
* **router:** canDeactivate guards should run from bottom to top ([e20cfe1](https://github.com/angular/angular/commit/e20cfe1)), closes [#15657](https://github.com/angular/angular/issues/15657)
|
||||
* **router:** should navigate to the same url when config changes ([eb6fb5f](https://github.com/angular/angular/commit/eb6fb5f)), closes [#15535](https://github.com/angular/angular/issues/15535)
|
||||
* **router:** should run resolvers for the same route concurrently ([ad3029e](https://github.com/angular/angular/commit/ad3029e)), closes [#14279](https://github.com/angular/angular/issues/14279)
|
||||
* **router:** terminal route in custom matcher ([b399cb2](https://github.com/angular/angular/commit/b399cb2))
|
||||
* **upgrade:** allow accessing AngularJS injector from downgraded module ([a5205c6](https://github.com/angular/angular/commit/a5205c6))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **animations:** support :increment and :decrement transition aliases ([6f45519](https://github.com/angular/angular/commit/6f45519))
|
||||
* **upgrade:** propagate touched state of NgModelController ([59c23c7](https://github.com/angular/angular/commit/59c23c7))
|
||||
* **upgrade:** support lazy-loading Angular module into AngularJS app ([30e76fc](https://github.com/angular/angular/commit/30e76fc))
|
||||
|
||||
|
||||
|
||||
<a name="4.3.1"></a>
|
||||
## [4.3.1](https://github.com/angular/angular/compare/4.3.0...4.3.1) (2017-07-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** always camelcase style property names that contain auto styles ([383d896](https://github.com/angular/angular/commit/383d896)), closes [#17938](https://github.com/angular/angular/issues/17938)
|
||||
* **animations:** capture cancelled animation styles within grouped animations ([333ffd8](https://github.com/angular/angular/commit/333ffd8)), closes [#17170](https://github.com/angular/angular/issues/17170)
|
||||
* **animations:** do not crash animations if a nested component fires CD during CD ([4c1f32b](https://github.com/angular/angular/commit/4c1f32b)), closes [#18193](https://github.com/angular/angular/issues/18193)
|
||||
* **animations:** make sure @.disabled works in non-animation components ([a5c4bb5](https://github.com/angular/angular/commit/a5c4bb5))
|
||||
* **common:** send flushed body as error instead of null ([17b7bc3](https://github.com/angular/angular/commit/17b7bc3)), closes [#18181](https://github.com/angular/angular/issues/18181)
|
||||
* **compiler:** ensure jit external id arguments names are unique ([4671168](https://github.com/angular/angular/commit/4671168))
|
||||
* **compiler-cli:** don't generate empty <target/> when extracting xliff ([f0476fc](https://github.com/angular/angular/commit/f0476fc)), closes [#15754](https://github.com/angular/angular/issues/15754)
|
||||
* **platform-server:** provide XhrFactory for HttpClient ([4ce29f3](https://github.com/angular/angular/commit/4ce29f3))
|
||||
* **router:** canDeactivate guards should run from bottom to top ([1ac78bf](https://github.com/angular/angular/commit/1ac78bf)), closes [#15657](https://github.com/angular/angular/issues/15657)
|
||||
* **router:** should navigate to the same url when config changes ([4340bea](https://github.com/angular/angular/commit/4340bea)), closes [#15535](https://github.com/angular/angular/issues/15535)
|
||||
* **router:** should run resolvers for the same route concurrently ([ec89f37](https://github.com/angular/angular/commit/ec89f37)), closes [#14279](https://github.com/angular/angular/issues/14279)
|
||||
* **router:** terminal route in custom matcher ([5d275e9](https://github.com/angular/angular/commit/5d275e9))
|
||||
|
||||
|
||||
|
||||
<a name="4.3.0"></a>
|
||||
# [4.3.0](https://github.com/angular/angular/compare/4.3.0-rc.0...4.3.0) (2017-07-14)
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
# Bootstrapping
|
||||
|
||||
An NgModule class describes how the application parts fit together.
|
||||
Every application has at least one NgModule, the _root_ module
|
||||
that you [bootstrap](guide/appmodule#main) to launch the application.
|
||||
Every application has at least one NgModule, the _root_ module
|
||||
that you [bootstrap](#main) to launch the application.
|
||||
You can call it anything you want. The conventional name is `AppModule`.
|
||||
|
||||
The [setup](guide/setup) instructions produce a new project with the following minimal `AppModule`.
|
||||
@ -25,7 +25,7 @@ The `@NgModule` decorator identifies `AppModule` as an `NgModule` class.
|
||||
* **_bootstrap_** — the _root_ component that Angular creates and inserts into the `index.html` host web page.
|
||||
|
||||
The [NgModules](guide/ngmodule) guide dives deeply into the details of NgModules.
|
||||
All you need to know at the moment is a few basics about these three properties.
|
||||
All you need to know at the moment is a few basics about these three properties.
|
||||
|
||||
|
||||
{@a imports}
|
||||
@ -34,7 +34,7 @@ All you need to know at the moment is a few basics about these three properties.
|
||||
### The _imports_ array
|
||||
|
||||
NgModules are a way to consolidate features that belong together into discrete units.
|
||||
Many features of Angular itself are organized as NgModules.
|
||||
Many features of Angular itself are organized as NgModules.
|
||||
HTTP services are in the `HttpModule`. The router is in the `RouterModule`.
|
||||
Eventually you may create a feature module.
|
||||
|
||||
@ -70,7 +70,7 @@ You add `import` statements to almost every application file.
|
||||
They have nothing to do with Angular and Angular knows nothing about them.
|
||||
|
||||
The _module's_ `imports` array appears _exclusively_ in the `@NgModule` metadata object.
|
||||
It tells Angular about specific _other_ NgModules—all of them classes decorated
|
||||
It tells Angular about specific _other_ NgModules—all of them classes decorated
|
||||
with `@NgModule`—that the application needs to function properly.
|
||||
|
||||
</div>
|
||||
@ -110,7 +110,7 @@ Do not put any other kind of class in `declarations`; _not_ `NgModule` classes,
|
||||
|
||||
### The _bootstrap_ array
|
||||
|
||||
You launch the application by [_bootstrapping_](guide/bootstrapping#main) the root `AppModule`.
|
||||
You launch the application by [_bootstrapping_](#main) the root `AppModule`.
|
||||
Among other things, the _bootstrapping_ process creates the component(s) listed in the `bootstrap` array
|
||||
and inserts each one into the browser DOM.
|
||||
|
||||
@ -127,13 +127,6 @@ Which brings us to the _bootstrapping_ process itself.
|
||||
|
||||
{@a main}
|
||||
|
||||
|
||||
<l-main-section>
|
||||
|
||||
</l-main-section>
|
||||
|
||||
|
||||
|
||||
## Bootstrap in _main.ts_
|
||||
|
||||
There are many ways to bootstrap an application.
|
||||
|
@ -27,12 +27,12 @@ Revised samples are more clear and cover all topics discussed.
|
||||
## NEW: Samples re-structured with `src/` folder (2017-02-02)
|
||||
All documentation samples have been realigned with the default folder structure of the Angular CLI.
|
||||
That's a step along the road to basing the sample in the Angular CLI.
|
||||
But it's also good in its own right.
|
||||
But it's also good in its own right.
|
||||
It helps clearly separate app code from setup and configuration files.
|
||||
|
||||
All samples now have a `src/` folder at the project root.
|
||||
The former `app/` folder moves under `src/`.
|
||||
Read about moving your existing project to this structure in
|
||||
The former `app/` folder moves under `src/`.
|
||||
Read about moving your existing project to this structure in
|
||||
<a href="https://github.com/angular/quickstart#updating-to-a-newer-version-of-the-quickstart-repo" target="Migrating samples/quickstart app to the src folder">
|
||||
the QuickStart repo update instructions</a>.
|
||||
|
||||
@ -48,8 +48,8 @@ Notably:
|
||||
The new [**Reactive Forms**](guide/reactive-forms) guide explains how and why to build a "reactive form".
|
||||
"Reactive Forms" are the code-based counterpart to the declarative "Template Driven" forms approach
|
||||
introduced in the [Forms](guide/forms) guide.
|
||||
Check it out before you decide how to add forms to your app.
|
||||
Remember also that you can use both techniques in the same app,
|
||||
Check it out before you decide how to add forms to your app.
|
||||
Remember also that you can use both techniques in the same app,
|
||||
choosing the approach that best fits each scenario.
|
||||
|
||||
## NEW: Deployment guide (2017-01-30)
|
||||
@ -65,25 +65,25 @@ Revised samples are clearer and cover all topics discussed.
|
||||
|
||||
## Miscellaneous (2017-01-05)
|
||||
|
||||
* [Setup](guide/setup) guide:
|
||||
added (optional) instructions on how to remove _non-essential_ files.
|
||||
* [Setup](guide/setup) guide:
|
||||
added (optional) instructions on how to remove _non-essential_ files.
|
||||
* No longer consolidate RxJS operator imports in `rxjs-extensions` file; each file should import what it needs.
|
||||
* All samples prepend template/style URLs with `./` as a best practice.
|
||||
* [Style Guide](guide/styleguide): copy edits and revised rules.
|
||||
|
||||
## Router: more detail (2016-12-21)
|
||||
|
||||
Added more information to the [Router](guide/router) guide
|
||||
Added more information to the [Router](guide/router) guide
|
||||
including sections named outlets, wildcard routes, and preload strategies.
|
||||
|
||||
## HTTP: how to set default request headers (and other request options) (2016-12-14)
|
||||
|
||||
Added section on how to set default request headers (and other request options) to
|
||||
[HTTP](guide/http#override-default-request-options) guide.
|
||||
Added section on how to set default request headers (and other request options) to
|
||||
HTTP guide.
|
||||
|
||||
## Testing: added component test plunkers (2016-12-02)
|
||||
|
||||
Added two plunkers that each test _one simple component_ so you can write a component test plunker of your own: <live-example name="setup" plnkr="quickstart-specs">one</live-example> for the QuickStart seed's `AppComponent` and <live-example name="testing" plnkr="banner-specs">another</live-example> for the Testing guide's `BannerComponent`.
|
||||
Added two plunkers that each test _one simple component_ so you can write a component test plunker of your own: <live-example name="setup" plnkr="quickstart-specs">one</live-example> for the QuickStart seed's `AppComponent` and <live-example name="testing" plnkr="banner-specs">another</live-example> for the Testing guide's `BannerComponent`.
|
||||
Linked to these plunkers in [Testing](guide/testing#live-examples) and [Setup anatomy](guide/setup-systemjs-anatomy) guides.
|
||||
|
||||
## Internationalization: pluralization and _select_ (2016-11-30)
|
||||
@ -112,15 +112,15 @@ Docs and code samples updated and tested with Angular v.2.2.0.
|
||||
|
||||
## UPDATE: NgUpgrade Guide for the AOT friendly _upgrade/static_ module (2016-11-14)
|
||||
|
||||
The updated [NgUpgrade Guide](guide/upgrade) guide covers the
|
||||
new AOT friendly `upgrade/static` module
|
||||
The updated [NgUpgrade Guide](guide/upgrade) guide covers the
|
||||
new AOT friendly `upgrade/static` module
|
||||
released in v.2.2.0, which is the recommended
|
||||
facility for migrating from AngularJS to Angular.
|
||||
The documentation for the version prior to v.2.2.0 has been removed.
|
||||
|
||||
## ES6 described in "TypeScript to JavaScript" (2016-11-14)
|
||||
|
||||
The updated [TypeScript to JavaScript](guide/ts-to-js) guide
|
||||
The updated [TypeScript to JavaScript](guide/ts-to-js) guide
|
||||
now explains how to write apps in ES6/7
|
||||
by translating the common idioms in the TypeScript documentation examples
|
||||
(and elsewhere on the web) to ES6/7 and ES5.
|
||||
@ -156,7 +156,7 @@ in the `in-memory-web-api` repo.
|
||||
The router can lazily _preload_ modules _after_ the app starts and
|
||||
_before_ the user navigates to them for improved perceived performance.
|
||||
|
||||
New `:enter` and `:leave` aliases make animation more natural.
|
||||
New `:enter` and `:leave` aliases make animation more natural.
|
||||
|
||||
## Sync with Angular v.2.1.0 (2016-10-12)
|
||||
|
||||
@ -176,11 +176,11 @@ Docs and code samples updated and tested with Angular v.2.0.2.
|
||||
## "Routing and Navigation" guide with the _Router Module_ (2016-10-5)
|
||||
|
||||
The [Routing and Navigation](guide/router) guide now locates route configuration
|
||||
in a _Routing Module_.
|
||||
in a _Routing Module_.
|
||||
The _Routing Module_ replaces the previous _routing object_ involving the `ModuleWithProviders`.
|
||||
|
||||
All guided samples with routing use the _Routing Module_ and prose content has been updated,
|
||||
most conspicuously in the
|
||||
most conspicuously in the
|
||||
[NgModule](guide/ngmodule) guide and [NgModule FAQ](guide/ngmodule-faq) guide.
|
||||
|
||||
## New "Internationalization" guide (2016-09-30)
|
||||
@ -194,7 +194,7 @@ Many samples use the `angular-in-memory-web-api` to simulate a remote server.
|
||||
This library is also useful to you during early development before you have a server to talk to.
|
||||
|
||||
The package name was changed from "angular2-in-memory-web-api" which is still frozen-in-time on npm.
|
||||
The new "angular-in-memory-web-api" has new features.
|
||||
The new "angular-in-memory-web-api" has new features.
|
||||
<a href="https://github.com/angular/in-memory-web-api/blob/master/README.md">Read about them on github</a>.
|
||||
|
||||
## "Style Guide" with _NgModules_ (2016-09-27)
|
||||
@ -215,5 +215,5 @@ modules with SystemJS as the samples currently do.
|
||||
|
||||
## "Lifecycle Hooks" guide simplified (2016-09-24)
|
||||
|
||||
The [Lifecycle Hooks](guide/lifecycle-hooks) guide is shorter, simpler, and
|
||||
The [Lifecycle Hooks](guide/lifecycle-hooks) guide is shorter, simpler, and
|
||||
draws more attention to the order in which Angular calls the hooks.
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Cheat Sheet
|
||||
<h1 class="no-toc">Cheat Sheet</h1>
|
||||
|
||||
<div id="cheatsheet">
|
||||
<table class="is-full-width is-fixed-layout">
|
||||
@ -23,28 +23,28 @@
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>@<b>NgModule</b>({ declarations: ..., imports: ...,<br> exports: ..., providers: ..., bootstrap: ...})<br>class MyModule {}</code></td>
|
||||
<td><code>@<b>NgModule</b>({ declarations: ..., imports: ...,<br> exports: ..., providers: ..., bootstrap: ...})<br>class MyModule {}</code></td>
|
||||
<td><p>Defines a module that contains components, directives, pipes, and providers.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>declarations:</b> [MyRedComponent, MyBlueComponent, MyDatePipe]</code></td>
|
||||
<td><code><b>declarations:</b> [MyRedComponent, MyBlueComponent, MyDatePipe]</code></td>
|
||||
<td><p>List of components, directives, and pipes that belong to this module.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>imports:</b> [BrowserModule, SomeOtherModule]</code></td>
|
||||
<td><code><b>imports:</b> [BrowserModule, SomeOtherModule]</code></td>
|
||||
<td><p>List of modules to import into this module. Everything from the imported modules
|
||||
is available to <code>declarations</code> of this module.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>exports:</b> [MyRedComponent, MyDatePipe]</code></td>
|
||||
<td><code><b>exports:</b> [MyRedComponent, MyDatePipe]</code></td>
|
||||
<td><p>List of components, directives, and pipes visible to modules that import this module.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>providers:</b> [MyService, { provide: ... }]</code></td>
|
||||
<td><code><b>providers:</b> [MyService, { provide: ... }]</code></td>
|
||||
<td><p>List of dependency injection providers visible both to the contents of this module and to importers of this module.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>bootstrap:</b> [MyAppComponent]</code></td>
|
||||
<td><code><b>bootstrap:</b> [MyAppComponent]</code></td>
|
||||
<td><p>List of components to bootstrap when this module is bootstrapped.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@ -56,61 +56,61 @@ is available to <code>declarations</code> of this module.</p>
|
||||
<th></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><input <b>[value]</b>="firstName"></code></td>
|
||||
<td><code><input <b>[value]</b>="firstName"></code></td>
|
||||
<td><p>Binds property <code>value</code> to the result of expression <code>firstName</code>.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><div <b>[attr.role]</b>="myAriaRole"></code></td>
|
||||
<td><code><div <b>[attr.role]</b>="myAriaRole"></code></td>
|
||||
<td><p>Binds attribute <code>role</code> to the result of expression <code>myAriaRole</code>.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><div <b>[class.extra-sparkle]</b>="isDelightful"></code></td>
|
||||
<td><code><div <b>[class.extra-sparkle]</b>="isDelightful"></code></td>
|
||||
<td><p>Binds the presence of the CSS class <code>extra-sparkle</code> on the element to the truthiness of the expression <code>isDelightful</code>.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><div <b>[style.width.px]</b>="mySize"></code></td>
|
||||
<td><code><div <b>[style.width.px]</b>="mySize"></code></td>
|
||||
<td><p>Binds style property <code>width</code> to the result of expression <code>mySize</code> in pixels. Units are optional.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><button <b>(click)</b>="readRainbow($event)"></code></td>
|
||||
<td><code><button <b>(click)</b>="readRainbow($event)"></code></td>
|
||||
<td><p>Calls method <code>readRainbow</code> when a click event is triggered on this button element (or its children) and passes in the event object.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><div title="Hello <b>{{ponyName}}</b>"></code></td>
|
||||
<td><code><div title="Hello <b>{{ponyName}}</b>"></code></td>
|
||||
<td><p>Binds a property to an interpolated string, for example, "Hello Seabiscuit". Equivalent to:
|
||||
<code><div [title]="'Hello ' + ponyName"></code></p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><p>Hello <b>{{ponyName}}</b></p></code></td>
|
||||
<td><code><p>Hello <b>{{ponyName}}</b></p></code></td>
|
||||
<td><p>Binds text content to an interpolated string, for example, "Hello Seabiscuit".</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><my-cmp <b>[(title)]</b>="name"></code></td>
|
||||
<td><code><my-cmp <b>[(title)]</b>="name"></code></td>
|
||||
<td><p>Sets up two-way data binding. Equivalent to: <code><my-cmp [title]="name" (titleChange)="name=$event"></code></p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><video <b>#movieplayer</b> ...><br> <button <b>(click)</b>="movieplayer.play()"><br></video></code></td>
|
||||
<td><code><video <b>#movieplayer</b> ...><br> <button <b>(click)</b>="movieplayer.play()"><br></video></code></td>
|
||||
<td><p>Creates a local variable <code>movieplayer</code> that provides access to the <code>video</code> element instance in data-binding and event-binding expressions in the current template.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><p <b>*myUnless</b>="myExpression">...</p></code></td>
|
||||
<td><code><p <b>*myUnless</b>="myExpression">...</p></code></td>
|
||||
<td><p>The <code>*</code> symbol turns the current element into an embedded template. Equivalent to:
|
||||
<code><ng-template [myUnless]="myExpression"><p>...</p></ng-template></code></p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><p>Card No.: <b>{{cardNumber | myCardNumberFormatter}}</b></p></code></td>
|
||||
<td><code><p>Card No.: <b>{{cardNumber | myCardNumberFormatter}}</b></p></code></td>
|
||||
<td><p>Transforms the current value of expression <code>cardNumber</code> via the pipe called <code>myCardNumberFormatter</code>.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><p>Employer: <b>{{employer?.companyName}}</b></p></code></td>
|
||||
<td><code><p>Employer: <b>{{employer?.companyName}}</b></p></code></td>
|
||||
<td><p>The safe navigation operator (<code>?</code>) means that the <code>employer</code> field is optional and if <code>undefined</code>, the rest of the expression should be ignored.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><<b>svg:</b>rect x="0" y="0" width="100" height="100"/></code></td>
|
||||
<td><code><<b>svg:</b>rect x="0" y="0" width="100" height="100"/></code></td>
|
||||
<td><p>An SVG snippet template needs an <code>svg:</code> prefix on its root element to disambiguate the SVG element from an HTML component.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><<b>svg</b>><br> <rect x="0" y="0" width="100" height="100"/><br></<b>svg</b>></code></td>
|
||||
<td><code><<b>svg</b>><br> <rect x="0" y="0" width="100" height="100"/><br></<b>svg</b>></code></td>
|
||||
<td><p>An <code><svg></code> root element is detected as an SVG element automatically, without the prefix.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@ -124,19 +124,19 @@ is available to <code>declarations</code> of this module.</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><section <b>*ngIf</b>="showSection"></code></td>
|
||||
<td><code><section <b>*ngIf</b>="showSection"></code></td>
|
||||
<td><p>Removes or recreates a portion of the DOM tree based on the <code>showSection</code> expression.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><li <b>*ngFor</b>="let item of list"></code></td>
|
||||
<td><code><li <b>*ngFor</b>="let item of list"></code></td>
|
||||
<td><p>Turns the li element and its contents into a template, and uses that to instantiate a view for each item in list.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><div <b>[ngSwitch]</b>="conditionExpression"><br> <ng-template <b>[<b>ngSwitchCase</b>]</b>="case1Exp">...</ng-template><br> <ng-template <b>ngSwitchCase</b>="case2LiteralString">...</ng-template><br> <ng-template <b>ngSwitchDefault</b>>...</ng-template><br></div></code></td>
|
||||
<td><code><div <b>[ngSwitch]</b>="conditionExpression"><br> <ng-template <b>[<b>ngSwitchCase</b>]</b>="case1Exp">...</ng-template><br> <ng-template <b>ngSwitchCase</b>="case2LiteralString">...</ng-template><br> <ng-template <b>ngSwitchDefault</b>>...</ng-template><br></div></code></td>
|
||||
<td><p>Conditionally swaps the contents of the div by selecting one of the embedded templates based on the current value of <code>conditionExpression</code>.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><div <b>[ngClass]</b>="{'active': isActive, 'disabled': isDisabled}"></code></td>
|
||||
<td><code><div <b>[ngClass]</b>="{'active': isActive, 'disabled': isDisabled}"></code></td>
|
||||
<td><p>Binds the presence of CSS classes on the element to the truthiness of the associated map values. The right-hand expression should return {class-name: true/false} map.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@ -150,7 +150,7 @@ is available to <code>declarations</code> of this module.</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><input <b>[(ngModel)]</b>="userName"></code></td>
|
||||
<td><code><input <b>[(ngModel)]</b>="userName"></code></td>
|
||||
<td><p>Provides two-way data-binding, parsing, and validation for form controls.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@ -164,19 +164,19 @@ is available to <code>declarations</code> of this module.</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><b>@Component({...})</b><br>class MyComponent() {}</code></td>
|
||||
<td><code><b>@Component({...})</b><br>class MyComponent() {}</code></td>
|
||||
<td><p>Declares that a class is a component and provides metadata about the component.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@Directive({...})</b><br>class MyDirective() {}</code></td>
|
||||
<td><code><b>@Directive({...})</b><br>class MyDirective() {}</code></td>
|
||||
<td><p>Declares that a class is a directive and provides metadata about the directive.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@Pipe({...})</b><br>class MyPipe() {}</code></td>
|
||||
<td><code><b>@Pipe({...})</b><br>class MyPipe() {}</code></td>
|
||||
<td><p>Declares that a class is a pipe and provides metadata about the pipe.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@Injectable()</b><br>class MyService() {}</code></td>
|
||||
<td><code><b>@Injectable()</b><br>class MyService() {}</code></td>
|
||||
<td><p>Declares that a class has dependencies that should be injected into the constructor when the dependency injector is creating an instance of this class.
|
||||
</p>
|
||||
</td>
|
||||
@ -191,13 +191,13 @@ is available to <code>declarations</code> of this module.</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><b>selector:</b> '.cool-button:not(a)'</code></td>
|
||||
<td><code><b>selector:</b> '.cool-button:not(a)'</code></td>
|
||||
<td><p>Specifies a CSS selector that identifies this directive within a template. Supported selectors include <code>element</code>,
|
||||
<code>[attribute]</code>, <code>.class</code>, and <code>:not()</code>.</p>
|
||||
<p>Does not support parent-child relationship selectors.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>providers:</b> [MyService, { provide: ... }]</code></td>
|
||||
<td><code><b>providers:</b> [MyService, { provide: ... }]</code></td>
|
||||
<td><p>List of dependency injection providers for this directive and its children.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@ -212,19 +212,19 @@ so the <code>@Directive</code> configuration applies to components as well</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><b>moduleId:</b> module.id</code></td>
|
||||
<td><code><b>moduleId:</b> module.id</code></td>
|
||||
<td><p>If set, the <code>templateUrl</code> and <code>styleUrl</code> are resolved relative to the component.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>viewProviders:</b> [MyService, { provide: ... }]</code></td>
|
||||
<td><code><b>viewProviders:</b> [MyService, { provide: ... }]</code></td>
|
||||
<td><p>List of dependency injection providers scoped to this component's view.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>template:</b> 'Hello {{name}}'<br><b>templateUrl:</b> 'my-component.html'</code></td>
|
||||
<td><code><b>template:</b> 'Hello {{name}}'<br><b>templateUrl:</b> 'my-component.html'</code></td>
|
||||
<td><p>Inline template or external template URL of the component's view.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>styles:</b> ['.primary {color: red}']<br><b>styleUrls:</b> ['my-component.css']</code></td>
|
||||
<td><code><b>styles:</b> ['.primary {color: red}']<br><b>styleUrls:</b> ['my-component.css']</code></td>
|
||||
<td><p>List of inline CSS styles or external stylesheet URLs for styling the component’s view.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@ -238,36 +238,36 @@ so the <code>@Directive</code> configuration applies to components as well</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><b>@Input()</b> myProperty;</code></td>
|
||||
<td><code><b>@Input()</b> myProperty;</code></td>
|
||||
<td><p>Declares an input property that you can update via property binding (example:
|
||||
<code><my-cmp [myProperty]="someExpression"></code>).</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@Output()</b> myEvent = new EventEmitter();</code></td>
|
||||
<td><code><b>@Output()</b> myEvent = new EventEmitter();</code></td>
|
||||
<td><p>Declares an output property that fires events that you can subscribe to with an event binding (example: <code><my-cmp (myEvent)="doSomething()"></code>).</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@HostBinding('class.valid')</b> isValid;</code></td>
|
||||
<td><code><b>@HostBinding('class.valid')</b> isValid;</code></td>
|
||||
<td><p>Binds a host element property (here, the CSS class <code>valid</code>) to a directive/component property (<code>isValid</code>).</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@HostListener('click', ['$event'])</b> onClick(e) {...}</code></td>
|
||||
<td><code><b>@HostListener('click', ['$event'])</b> onClick(e) {...}</code></td>
|
||||
<td><p>Subscribes to a host element event (<code>click</code>) with a directive/component method (<code>onClick</code>), optionally passing an argument (<code>$event</code>).</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@ContentChild(myPredicate)</b> myChildComponent;</code></td>
|
||||
<td><code><b>@ContentChild(myPredicate)</b> myChildComponent;</code></td>
|
||||
<td><p>Binds the first result of the component content query (<code>myPredicate</code>) to a property (<code>myChildComponent</code>) of the class.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@ContentChildren(myPredicate)</b> myChildComponents;</code></td>
|
||||
<td><code><b>@ContentChildren(myPredicate)</b> myChildComponents;</code></td>
|
||||
<td><p>Binds the results of the component content query (<code>myPredicate</code>) to a property (<code>myChildComponents</code>) of the class.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@ViewChild(myPredicate)</b> myChildComponent;</code></td>
|
||||
<td><code><b>@ViewChild(myPredicate)</b> myChildComponent;</code></td>
|
||||
<td><p>Binds the first result of the component view query (<code>myPredicate</code>) to a property (<code>myChildComponent</code>) of the class. Not available for directives.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>@ViewChildren(myPredicate)</b> myChildComponents;</code></td>
|
||||
<td><code><b>@ViewChildren(myPredicate)</b> myChildComponents;</code></td>
|
||||
<td><p>Binds the results of the component view query (<code>myPredicate</code>) to a property (<code>myChildComponents</code>) of the class. Not available for directives.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@ -281,39 +281,39 @@ so the <code>@Directive</code> configuration applies to components as well</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code><b>constructor(myService: MyService, ...)</b> { ... }</code></td>
|
||||
<td><code><b>constructor(myService: MyService, ...)</b> { ... }</code></td>
|
||||
<td><p>Called before any other lifecycle hook. Use it to inject dependencies, but avoid any serious work here.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngOnChanges(changeRecord)</b> { ... }</code></td>
|
||||
<td><code><b>ngOnChanges(changeRecord)</b> { ... }</code></td>
|
||||
<td><p>Called after every change to input properties and before processing content or child views.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngOnInit()</b> { ... }</code></td>
|
||||
<td><code><b>ngOnInit()</b> { ... }</code></td>
|
||||
<td><p>Called after the constructor, initializing input properties, and the first call to <code>ngOnChanges</code>.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngDoCheck()</b> { ... }</code></td>
|
||||
<td><code><b>ngDoCheck()</b> { ... }</code></td>
|
||||
<td><p>Called every time that the input properties of a component or a directive are checked. Use it to extend change detection by performing a custom check.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngAfterContentInit()</b> { ... }</code></td>
|
||||
<td><code><b>ngAfterContentInit()</b> { ... }</code></td>
|
||||
<td><p>Called after <code>ngOnInit</code> when the component's or directive's content has been initialized.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngAfterContentChecked()</b> { ... }</code></td>
|
||||
<td><code><b>ngAfterContentChecked()</b> { ... }</code></td>
|
||||
<td><p>Called after every check of the component's or directive's content.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngAfterViewInit()</b> { ... }</code></td>
|
||||
<td><code><b>ngAfterViewInit()</b> { ... }</code></td>
|
||||
<td><p>Called after <code>ngAfterContentInit</code> when the component's view has been initialized. Applies to components only.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngAfterViewChecked()</b> { ... }</code></td>
|
||||
<td><code><b>ngAfterViewChecked()</b> { ... }</code></td>
|
||||
<td><p>Called after every check of the component's view. Applies to components only.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><b>ngOnDestroy()</b> { ... }</code></td>
|
||||
<td><code><b>ngOnDestroy()</b> { ... }</code></td>
|
||||
<td><p>Called once, before the instance is destroyed.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@ -325,15 +325,15 @@ so the <code>@Directive</code> configuration applies to components as well</p>
|
||||
<th></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>{ <b>provide</b>: MyService, <b>useClass</b>: MyMockService }</code></td>
|
||||
<td><code>{ <b>provide</b>: MyService, <b>useClass</b>: MyMockService }</code></td>
|
||||
<td><p>Sets or overrides the provider for <code>MyService</code> to the <code>MyMockService</code> class.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>{ <b>provide</b>: MyService, <b>useFactory</b>: myFactory }</code></td>
|
||||
<td><code>{ <b>provide</b>: MyService, <b>useFactory</b>: myFactory }</code></td>
|
||||
<td><p>Sets or overrides the provider for <code>MyService</code> to the <code>myFactory</code> factory function.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>{ <b>provide</b>: MyValue, <b>useValue</b>: 41 }</code></td>
|
||||
<td><code>{ <b>provide</b>: MyValue, <b>useValue</b>: 41 }</code></td>
|
||||
<td><p>Sets or overrides the provider for <code>MyValue</code> to the value <code>41</code>.</p>
|
||||
</td>
|
||||
</tr>
|
||||
@ -347,39 +347,39 @@ so the <code>@Directive</code> configuration applies to components as well</p>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>const routes: <b>Routes</b> = [<br> { path: '', component: HomeComponent },<br> { path: 'path/:routeParam', component: MyComponent },<br> { path: 'staticPath', component: ... },<br> { path: '**', component: ... },<br> { path: 'oldPath', redirectTo: '/staticPath' },<br> { path: ..., component: ..., data: { message: 'Custom' } }<br>]);<br><br>const routing = RouterModule.forRoot(routes);</code></td>
|
||||
<td><code>const routes: <b>Routes</b> = [<br> { path: '', component: HomeComponent },<br> { path: 'path/:routeParam', component: MyComponent },<br> { path: 'staticPath', component: ... },<br> { path: '**', component: ... },<br> { path: 'oldPath', redirectTo: '/staticPath' },<br> { path: ..., component: ..., data: { message: 'Custom' } }<br>]);<br><br>const routing = RouterModule.forRoot(routes);</code></td>
|
||||
<td><p>Configures routes for the application. Supports static, parameterized, redirect, and wildcard routes. Also supports custom route data and resolve.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><br><<b>router-outlet</b>></<b>router-outlet</b>><br><<b>router-outlet</b> name="aux"></<b>router-outlet</b>><br></code></td>
|
||||
<td><code><br><<b>router-outlet</b>></<b>router-outlet</b>><br><<b>router-outlet</b> name="aux"></<b>router-outlet</b>><br></code></td>
|
||||
<td><p>Marks the location to load the component of the active route.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><br><a routerLink="/path"><br><a <b>[routerLink]</b>="[ '/path', routeParam ]"><br><a <b>[routerLink]</b>="[ '/path', { matrixParam: 'value' } ]"><br><a <b>[routerLink]</b>="[ '/path' ]" [queryParams]="{ page: 1 }"><br><a <b>[routerLink]</b>="[ '/path' ]" fragment="anchor"><br></code></td>
|
||||
<td><code><br><a routerLink="/path"><br><a <b>[routerLink]</b>="[ '/path', routeParam ]"><br><a <b>[routerLink]</b>="[ '/path', { matrixParam: 'value' } ]"><br><a <b>[routerLink]</b>="[ '/path' ]" [queryParams]="{ page: 1 }"><br><a <b>[routerLink]</b>="[ '/path' ]" fragment="anchor"><br></code></td>
|
||||
<td><p>Creates a link to a different view based on a route instruction consisting of a route path, required and optional parameters, query parameters, and a fragment. To navigate to a root route, use the <code>/</code> prefix; for a child route, use the <code>./</code>prefix; for a sibling or parent, use the <code>../</code> prefix.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code><a [routerLink]="[ '/path' ]" routerLinkActive="active"></code></td>
|
||||
<td><code><a [routerLink]="[ '/path' ]" routerLinkActive="active"></code></td>
|
||||
<td><p>The provided classes are added to the element when the <code>routerLink</code> becomes the current active route.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>class <b>CanActivate</b>Guard implements <b>CanActivate</b> {<br> canActivate(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canActivate: [<b>CanActivate</b>Guard] }</code></td>
|
||||
<td><code>class <b>CanActivate</b>Guard implements <b>CanActivate</b> {<br> canActivate(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canActivate: [<b>CanActivate</b>Guard] }</code></td>
|
||||
<td><p>An interface for defining a class that the router should call first to determine if it should activate this component. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>class <b>CanDeactivate</b>Guard implements <b>CanDeactivate</b><T> {<br> canDeactivate(<br> component: T,<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canDeactivate: [<b>CanDeactivate</b>Guard] }</code></td>
|
||||
<td><code>class <b>CanDeactivate</b>Guard implements <b>CanDeactivate</b><T> {<br> canDeactivate(<br> component: T,<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canDeactivate: [<b>CanDeactivate</b>Guard] }</code></td>
|
||||
<td><p>An interface for defining a class that the router should call first to determine if it should deactivate this component after a navigation. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>class <b>CanActivateChild</b>Guard implements <b>CanActivateChild</b> {<br> canActivateChild(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canActivateChild: [CanActivateGuard],<br> children: ... }</code></td>
|
||||
<td><code>class <b>CanActivateChild</b>Guard implements <b>CanActivateChild</b> {<br> canActivateChild(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canActivateChild: [CanActivateGuard],<br> children: ... }</code></td>
|
||||
<td><p>An interface for defining a class that the router should call first to determine if it should activate the child route. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>class <b>Resolve</b>Guard implements <b>Resolve</b><T> {<br> resolve(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<any>|Promise<any>|any { ... }<br>}<br><br>{ path: ..., resolve: [<b>Resolve</b>Guard] }</code></td>
|
||||
<td><code>class <b>Resolve</b>Guard implements <b>Resolve</b><T> {<br> resolve(<br> route: ActivatedRouteSnapshot,<br> state: RouterStateSnapshot<br> ): Observable<any>|Promise<any>|any { ... }<br>}<br><br>{ path: ..., resolve: [<b>Resolve</b>Guard] }</code></td>
|
||||
<td><p>An interface for defining a class that the router should call first to resolve route data before rendering the route. Should return a value or an Observable/Promise that resolves to a value.</p>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><code>class <b>CanLoad</b>Guard implements <b>CanLoad</b> {<br> canLoad(<br> route: Route<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canLoad: [<b>CanLoad</b>Guard], loadChildren: ... }</code></td>
|
||||
<td><code>class <b>CanLoad</b>Guard implements <b>CanLoad</b> {<br> canLoad(<br> route: Route<br> ): Observable<boolean>|Promise<boolean>|boolean { ... }<br>}<br><br>{ path: ..., canLoad: [<b>CanLoad</b>Guard], loadChildren: ... }</code></td>
|
||||
<td><p>An interface for defining a class that the router should call first to check if the lazy loaded module should be loaded. Should return a boolean or an Observable/Promise that resolves to a boolean.</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -50,7 +50,10 @@ The `get()` method on `HttpClient` makes accessing this data straightforward.
|
||||
|
||||
```javascript
|
||||
@Component(...)
|
||||
export class MyComponent implements NgOnInit {
|
||||
export class MyComponent implements OnInit {
|
||||
|
||||
results: string[];
|
||||
|
||||
// Inject HttpClient into your component or service.
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
@ -268,12 +271,12 @@ has a single `intercept()` method. Here is a simple interceptor which does nothi
|
||||
|
||||
```javascript
|
||||
import {Injectable} from '@angular/core';
|
||||
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest) from '@angular/common/http';
|
||||
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/common/http';
|
||||
|
||||
@Injectable()
|
||||
export class NoopInterceptor implements HttpInterceptor {
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
return next.handle(Req);
|
||||
return next.handle(req);
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -349,7 +352,7 @@ A common use of interceptors is to set default headers on outgoing responses. Fo
|
||||
|
||||
```javascript
|
||||
import {Injectable} from '@angular/core';
|
||||
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest) from '@angular/common/http';
|
||||
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/common/http';
|
||||
|
||||
@Injectable()
|
||||
export class AuthInterceptor implements HttpInterceptor {
|
||||
@ -357,7 +360,7 @@ export class AuthInterceptor implements HttpInterceptor {
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
// Get the auth header from the service.
|
||||
const authHeader: this.auth.getAuthorizationHeader();
|
||||
const authHeader = this.auth.getAuthorizationHeader();
|
||||
// Clone the request to add the new header.
|
||||
const authReq = req.clone({headers: req.headers.set('Authorization', authHeader)});
|
||||
// Pass on the cloned request instead of the original request.
|
||||
@ -389,12 +392,12 @@ export class TimingInterceptor implements HttpInterceptor {
|
||||
constructor(private auth: AuthService) {}
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
const elapsed = Date.now();
|
||||
const started = Date.now();
|
||||
return next
|
||||
.handle(req)
|
||||
.do(event => {
|
||||
if (event instanceof HttpResponse) {
|
||||
const time = Date.now() - started;
|
||||
const elapsed = Date.now() - started;
|
||||
console.log(`Request for ${req.urlWithParams} took ${elapsed} ms.`);
|
||||
}
|
||||
});
|
||||
@ -598,7 +601,7 @@ it('expects a GET request', inject([HttpClient, HttpTestingController], (http: H
|
||||
|
||||
// At this point, the request is pending, and no response has been
|
||||
// sent. The next step is to expect that the request happened.
|
||||
const req = httpMock.expectOne('/test');
|
||||
const req = httpMock.expectOne('/data');
|
||||
|
||||
// If no request with that URL was made, or if multiple requests match,
|
||||
// expectOne() would throw. However this test makes only one request to
|
||||
@ -611,7 +614,7 @@ it('expects a GET request', inject([HttpClient, HttpTestingController], (http: H
|
||||
req.flush({name: 'Test Data'});
|
||||
|
||||
// Finally, assert that there are no outstanding requests.
|
||||
mockHttp.verify();
|
||||
httpMock.verify();
|
||||
}));
|
||||
```
|
||||
|
||||
@ -619,7 +622,7 @@ The last step, verifying that no requests remain outstanding, is common enough f
|
||||
|
||||
```javascript
|
||||
afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
|
||||
mockHttp.verify();
|
||||
httpMock.verify();
|
||||
}));
|
||||
```
|
||||
|
||||
@ -628,7 +631,7 @@ afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => {
|
||||
If matching by URL isn't sufficient, it's possible to implement your own matching function. For example, you could look for an outgoing request that has an Authorization header:
|
||||
|
||||
```javascript
|
||||
const req = mockHttp.expectOne((req) => req.headers.has('Authorization'));
|
||||
const req = httpMock.expectOne((req) => req.headers.has('Authorization'));
|
||||
```
|
||||
|
||||
Just as with the `expectOne()` by URL in the test above, if 0 or 2+ requests match this expectation, it will throw.
|
||||
@ -639,7 +642,7 @@ If you need to respond to duplicate requests in your test, use the `match()` API
|
||||
|
||||
```javascript
|
||||
// Expect that 5 pings have been made and flush them.
|
||||
const reqs = mockHttp.match('/ping');
|
||||
const reqs = httpMock.match('/ping');
|
||||
expect(reqs.length).toBe(5);
|
||||
reqs.forEach(req => req.flush());
|
||||
```
|
||||
|
176
aio/content/guide/language-service.md
Normal file
176
aio/content/guide/language-service.md
Normal file
@ -0,0 +1,176 @@
|
||||
# Angular Language Service
|
||||
|
||||
The Angular Language Service is a way to get completions, errors,
|
||||
hints, and navigation inside your Angular templates whether they
|
||||
are external in an HTML file or embedded in annotations/decorators
|
||||
in a string. The Angular Language Service autodetects that you are
|
||||
opening an Angular file, reads your `tsconfig.json` file, finds all the
|
||||
templates you have in your application, and then provides language
|
||||
services for any templates that you open.
|
||||
|
||||
|
||||
## Autocompletion
|
||||
|
||||
Autocompletion can speed up your development time by providing you with
|
||||
contextual possibilities and hints as you type. This example shows
|
||||
autocomplete in an interpolation. As you type it out,
|
||||
you can hit tab to complete.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/language-service/language-completion.gif" alt="autocompletion">
|
||||
</figure>
|
||||
|
||||
There are also completions within
|
||||
elements. Any elements you have as a component selector will
|
||||
show up in the completion list.
|
||||
|
||||
## Error checking
|
||||
|
||||
The Angular Language Service can also forewarn you of mistakes in your code.
|
||||
In this example, Angular doesn't know what `orders` is or where it comes from.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/language-service/language-error.gif" alt="error checking">
|
||||
</figure>
|
||||
|
||||
## Navigation
|
||||
|
||||
Navigation allows you to hover to
|
||||
see where a component, directive, module, etc. is from and then
|
||||
click and press F12 to go directly to its definition.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/language-service/language-navigation.gif" alt="navigation">
|
||||
</figure>
|
||||
|
||||
|
||||
## Angular Language Service in your editor
|
||||
|
||||
Angular Language Service is currently available for [Visual Studio Code](https://code.visualstudio.com/) and
|
||||
[WebStorm](https://www.jetbrains.com/webstorm).
|
||||
|
||||
### Visual Studio Code
|
||||
|
||||
In Visual Studio Code, install Angular Language Service from the store,
|
||||
which is accessible from the bottom icon on the left menu pane.
|
||||
You can also use the VS Quick Open (⌘+P) to search for the extension. When you've opened it,
|
||||
enter the following command:
|
||||
|
||||
```sh
|
||||
ext install ng-template
|
||||
```
|
||||
|
||||
Then click the install button to install the Angular Language Service.
|
||||
|
||||
|
||||
### WebStorm
|
||||
|
||||
In webstorm, you have to install the language service as a dev dependency.
|
||||
When Angular sees this dev dependency, it provides the
|
||||
language service inside of WebStorm. Webstorm then gives you
|
||||
colorization inside the template and autocomplete in addition to the Angular Language Service.
|
||||
|
||||
Here's the dev dependency
|
||||
you need to have in `package.json`:
|
||||
|
||||
```json
|
||||
|
||||
devDependencies {
|
||||
"@angular/language-service": "^4.0.0"
|
||||
}
|
||||
```
|
||||
|
||||
Then in the terminal window at the root of your project,
|
||||
install the `devDependencies` with `npm` or `yarn`:
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
*OR*
|
||||
|
||||
```sh
|
||||
yarn
|
||||
```
|
||||
|
||||
*OR*
|
||||
|
||||
```sh
|
||||
yarn install
|
||||
```
|
||||
|
||||
|
||||
### Sublime Text
|
||||
|
||||
In [Sublime Text](https://www.sublimetext.com/), you first need an extension to allow Typescript.
|
||||
Install the latest version of typescript in a local `node_modules` directory:
|
||||
|
||||
```sh
|
||||
npm install --save-dev typescript
|
||||
```
|
||||
|
||||
Then install the Angular Language Service in the same location:
|
||||
```sh
|
||||
npm install --save-dev @angular/language-service
|
||||
```
|
||||
|
||||
Starting with TypeScript 2.3, TypeScript has a language service plugin model that the language service can use.
|
||||
|
||||
Next, in your user preferences (`Cmd+,` or `Ctrl+,`), add:
|
||||
|
||||
```json
|
||||
"typescript-tsdk": "<path to your folder>/node_modules/typescript/lib"
|
||||
```
|
||||
|
||||
|
||||
## Installing in your project
|
||||
|
||||
You can also install Angular Language Service in your project with the
|
||||
following `npm` command:
|
||||
|
||||
```sh
|
||||
npm install --save-dev @angular/language-service
|
||||
```
|
||||
Additionally, add the following to the `"compilerOptions"` section of
|
||||
your project's `tsconfig.json`.
|
||||
|
||||
```json
|
||||
"plugins": [
|
||||
{"name": "@angular/language-service"}
|
||||
]
|
||||
```
|
||||
Note that this only provides diagnostics and completions in `.ts`
|
||||
files. You need a custom sublime plugin (or modifications to the current plugin)
|
||||
for completions in HTML files.
|
||||
|
||||
|
||||
## How the Language Service works
|
||||
|
||||
When you use an editor with a language service, there's an
|
||||
editor process which starts a separate language process/service
|
||||
to which it speaks through an [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call).
|
||||
Any time you type inside of the editor, it sends information to the other process to
|
||||
track the state of your project. When you trigger a completion list within a template, the editor process first parses the template into an HTML AST, or [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree). Then the Angular compiler interprets
|
||||
what module the template is part of, the scope you're in, and the component selector. Then it figures out where in the template AST your cursor is. When it determines the
|
||||
context, it can then determine what the children can be.
|
||||
|
||||
It's a little more involved if you are in an interpolation. If you have an interpolation of `{{data.---}}` inside a `div` and need the completion list after `data.---`, the compiler can't use the HTML AST to find the answer. The HTML AST can only tell the compiler that there is some text with the characters "`{{data.---}}`". That's when the template parser produces an expression AST, which resides within the template AST. The Angular Language Services then looks at `data.---` within its context and asks the TypeScript Language Service what the members of data are. TypeScript then returns the list of possibilities.
|
||||
|
||||
|
||||
For more in-depth information, see the
|
||||
[Angular Language Service API](https://github.com/angular/angular/blob/master/packages/language-service/src/types.ts)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<hr>
|
||||
|
||||
## More on Information
|
||||
|
||||
For more information, see [Chuck Jazdzewski's presentation](https://www.youtube.com/watch?v=ez3R0Gi4z5A&t=368s) on the Angular Language
|
||||
Service from [ng-conf](https://www.ng-conf.org/) 2017.
|
||||
|
||||
|
@ -397,8 +397,7 @@ created under test or before you decide to display it.
|
||||
Constructors should do no more than set the initial local variables to simple values.
|
||||
|
||||
An `ngOnInit()` is a good place for a component to fetch its initial data. The
|
||||
[Tour of Heroes Tutorial](tutorial/toh-pt4#oninit) and [HTTP Client](guide/http#oninit)
|
||||
guides show how.
|
||||
[Tour of Heroes Tutorial](tutorial/toh-pt4#oninit) guide shows how.
|
||||
|
||||
|
||||
Remember also that a directive's data-bound input properties are not set until _after construction_.
|
||||
|
@ -163,7 +163,6 @@ without waiting for Angular updates.
|
||||
***angular-in-memory-web-api***: An Angular-supported library that simulates a remote server's web api
|
||||
without requiring an actual server or real HTTP calls.
|
||||
Good for demos, samples, and early stage development (before you even have a server).
|
||||
Read about it in the [HTTP Client](guide/http#in-mem-web-api) page.
|
||||
|
||||
***bootstrap***: [Bootstrap](http://getbootstrap.com/) is a popular HTML and CSS framework for designing responsive web apps.
|
||||
Some of the samples improve their appearance with *bootstrap*.
|
||||
|
@ -255,11 +255,11 @@ During each navigation, the `Router` emits navigation events through the `Router
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<code>RouteConfigLoadStart</code>
|
||||
<code>RouteConfigLoadEnd</code>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
An [event](api/router/RouteConfigLoadStart) triggered after a route has been lazy loaded.
|
||||
An [event](api/router/RouteConfigLoadEnd) triggered after a route has been lazy loaded.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -178,7 +178,7 @@ For a discussion of the unit testing setup files, [see below](guide/testing#setu
|
||||
{@a isolated-v-testing-utilities}
|
||||
|
||||
|
||||
### Isolated unit tests vs. the Angular testing utilites
|
||||
### Isolated unit tests vs. the Angular testing utilities
|
||||
|
||||
[Isolated unit tests](guide/testing#isolated-unit-tests "Unit testing without the Angular testing utilities")
|
||||
examine an instance of a class all by itself without any dependence on Angular or any injected values.
|
||||
|
@ -247,12 +247,10 @@ next to the original _ES5_ version for comparison:
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
{@a name-constructor}
|
||||
|
||||
<div class="callout is-helpful">
|
||||
|
||||
{@a name-constructor}
|
||||
|
||||
### Name the constructor
|
||||
|
||||
A **named** constructor displays clearly in the console log
|
||||
if the component throws a runtime error.
|
||||
An **unnamed** constructor displays as an anonymous function, for example, `class0`,
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 8.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 132 KiB |
BIN
aio/content/images/guide/language-service/language-error.gif
Normal file
BIN
aio/content/images/guide/language-service/language-error.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 274 KiB |
Binary file not shown.
After Width: | Height: | Size: 857 KiB |
@ -8,7 +8,7 @@
|
||||
<div class="feature-section">
|
||||
<div class="feature-header">
|
||||
<div class="text-headline">Cross Platform</div>
|
||||
<img src="../assets/images/icons/feature-icon.svg" height="70px">
|
||||
<img src="assets/images/icons/feature-icon.svg" height="70px">
|
||||
</div>
|
||||
<div class="feature-row">
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
<div class="feature-section">
|
||||
<div class="feature-header">
|
||||
<div class="text-headline">Speed and Performance</div>
|
||||
<img src="../assets/images/icons/feature-icon.svg" height="70px">
|
||||
<img src="assets/images/icons/feature-icon.svg" height="70px">
|
||||
</div>
|
||||
<div class="feature-row">
|
||||
|
||||
@ -59,7 +59,7 @@
|
||||
<div class="feature-section">
|
||||
<div class="feature-header">
|
||||
<div class="text-headline">Productivity</div>
|
||||
<img src="../assets/images/icons/feature-icon.svg" height="70px">
|
||||
<img src="assets/images/icons/feature-icon.svg" height="70px">
|
||||
</div>
|
||||
<div class="feature-row">
|
||||
|
||||
@ -84,7 +84,7 @@
|
||||
<div class="feature-section">
|
||||
<div class="feature-header">
|
||||
<div class="text-headline">Full Development Story</div>
|
||||
<img src="../assets/images/icons/feature-icon.svg" height="70px">
|
||||
<img src="assets/images/icons/feature-icon.svg" height="70px">
|
||||
</div>
|
||||
<div class="feature-row">
|
||||
|
||||
|
@ -110,7 +110,7 @@
|
||||
|
||||
<a href="guide/quickstart">
|
||||
<div class="card">
|
||||
<img src="../assets/images/icons/code-icon.svg" height="70px">
|
||||
<img src="assets/images/icons/code-icon.svg" height="70px">
|
||||
<div class="card-text-container">
|
||||
<div class="text-headline">Get Started</div>
|
||||
<p>Start building your Angular application.</p>
|
||||
|
@ -1,116 +0,0 @@
|
||||
<header class="hero background-sky">
|
||||
<h1 class="hero-title no-toc">News</h1>
|
||||
<div class="clear"></div>
|
||||
</header>
|
||||
<artice>
|
||||
<div class="grid-fluid l-space-bottom-2">
|
||||
<div class="c12 text-center"><h3 class="text-headline text-uppercase"> Core Team</h3></div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
<div class="grid-fluid">
|
||||
<div class="c6">
|
||||
<div class="article-card">
|
||||
<div class="date">Oct 12, 2016</div>
|
||||
<div class="title"><a target="_blank"
|
||||
href="http://angularjs.blogspot.com/2016/10/angular-210-now-available.html">Angular
|
||||
2.1.0 Now Available</a></div>
|
||||
<p>Angular version 2.1.0 - incremental-metamorphosis - is a minor release following our
|
||||
announced adoption of Semantic Versioning...</p>
|
||||
<div class="author"><img src="generated/images/bios/stephenfluin.jpg">
|
||||
<div class="posted">Posted by <b>Stephen Fluin</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c6">
|
||||
<div class="article-card">
|
||||
<div class="date">Oct 7, 2016</div>
|
||||
<div class="title"><a target="_blank"
|
||||
href="http://angularjs.blogspot.com/2016/10/versioning-and-releasing-angular.html">Versioning
|
||||
and Releasing Angular</a></div>
|
||||
<p>In order for the ecosystem around Angular to thrive, developers need stability from the
|
||||
Angular framework so that reusable components and libraries, tools and learned practices
|
||||
don’t go obsolete unexpectedly...</p>
|
||||
<div class="author"><img src="generated/images/bios/igor-minar.jpg">
|
||||
<div class="posted">Posted by <b>Igor Minar</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-fluid l-space-bottom-2 l-space-top-4">
|
||||
<div class="c12 text-center"><h3 class="text-headline text-uppercase"> Developer Community</h3>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
<div class="grid-fluid">
|
||||
<div class="c6">
|
||||
<div class="article-card">
|
||||
<div class="date">Oct 30, 2016</div>
|
||||
<div class="title"><a target="_blank"
|
||||
href="https://www.thepolyglotdeveloper.com/2016/10/use-pre-populated-sqlite-database-nativescript-angular-2/">Use
|
||||
A Pre-Populated SQLite Database With NativeScript And Angular 2</a></div>
|
||||
<p>I figured it would be a good idea to demonstrate how to ship a NativeScript Angular 2
|
||||
application with a pre-filled SQLite database rather than populating it on-the-fly....</p>
|
||||
<div class="author"><img src="generated/images/bios/shield-bio-placeholder.png">
|
||||
<div class="posted">Posted by <b>Nic Raboy</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c6">
|
||||
<div class="article-card">
|
||||
<div class="date">Oct 13, 2016</div>
|
||||
<div class="title"><a target="_blank"
|
||||
href="http://blog.thoughtram.io/angular/2016/10/13/two-way-data-binding-in-angular-2.html">Two-way
|
||||
Data Binding in Angular 2</a></div>
|
||||
<p>If there was one feature in Angular that made us go “Wow”, then it was probably its
|
||||
two-way data binding system. Changes in the application state have been automagically
|
||||
reflected into the view...</p>
|
||||
<div class="author"><img src="generated/images/bios/angular-gde-bio-placeholder.png">
|
||||
<div class="posted">Posted by <b>Pascal Precht</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-fluid">
|
||||
<div class="c6">
|
||||
<div class="article-card">
|
||||
<div class="date">Oct 10, 2016</div>
|
||||
<div class="title"><a target="_blank"
|
||||
href="http://www.creativebloq.com/how-to/build-a-material-design-app-with-angular-2">Build
|
||||
a Material Design app with Angular 2</a></div>
|
||||
<p>This walkthrough reveals how to create a DialogComponent and to-do app with Angular
|
||||
Material and the Angular CLI...</p>
|
||||
<div class="author"><img src="generated/images/bios/shield-bio-placeholder.png">
|
||||
<div class="posted">Posted by <b>Daniel Zen</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="c6">
|
||||
<div class="article-card">
|
||||
<div class="date">Sept 30, 2016</div>
|
||||
<div class="title"><a target="_blank"
|
||||
href="http://www.simb.co/angular-cli-using-docker/?platform=hootsuite">Using
|
||||
Angular CLI to create Angular 2 applications in Docker</a></div>
|
||||
<p>Angular CLI is a great tool for developing Angular 2 applications. I thought it would be
|
||||
fun to do a quick demo...</p>
|
||||
<div class="author"><img src="generated/images/bios/shield-bio-placeholder.png">
|
||||
<div class="posted">Posted by <b>Simeon Bateman</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-fluid l-space-bottom-2 l-space-top-4">
|
||||
<div class="c12 text-center"><h3 class="text-headline text-uppercase">Twitter</h3></div>
|
||||
<div class="clear"></div>
|
||||
<div class="grid-fluid">
|
||||
<div class="c3"><p></p></div>
|
||||
<div class="c6">
|
||||
<div class="article-card">
|
||||
<div class="title"><a href="http://twitter.com/angularjs" data-show-count="false"
|
||||
class="twitter-follow-button">Follow @angularjs</a></div>
|
||||
<p><a class="twitter-timeline" data-chrome="nofooter noborders noheader"
|
||||
href="http://twitter.com/angularjs" data-widget-id="700150278465523713"></a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
@ -282,6 +282,11 @@
|
||||
"title": "Internationalization (i18n)",
|
||||
"tooltip": "Translate the app's template text into multiple languages."
|
||||
},
|
||||
{
|
||||
"url": "guide/language-service",
|
||||
"title": "Language Service",
|
||||
"tooltip": "Use Angular Language Service to speed up dev time."
|
||||
},
|
||||
{
|
||||
"url": "guide/security",
|
||||
"title": "Security",
|
||||
|
@ -83,11 +83,7 @@ Added hero "Zero" to confirm that the data service can handle a hero with `id==0
|
||||
Don't worry about the details of this backend substitution; you can
|
||||
skip it when you have a real web API server.
|
||||
|
||||
Read more about the in-memory web API in the
|
||||
[Appendix: Tour of Heroes in-memory web api](guide/http#in-mem-web-api)
|
||||
section of the [HTTP Client](guide/http#in-mem-web-api) page.
|
||||
|
||||
</div>
|
||||
div>
|
||||
|
||||
## Heroes and HTTP
|
||||
|
||||
|
@ -81,7 +81,7 @@
|
||||
"concurrently": "^3.4.0",
|
||||
"cross-spawn": "^5.1.0",
|
||||
"dgeni": "^0.4.7",
|
||||
"dgeni-packages": "^0.20.0-rc.5",
|
||||
"dgeni-packages": "^0.20.0-rc.6",
|
||||
"entities": "^1.1.1",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-plugin-jasmine": "^2.2.0",
|
||||
|
@ -45,15 +45,20 @@ timestamp=$(date +%s)
|
||||
payloadData="$payloadData\"timestamp\": $timestamp, "
|
||||
|
||||
# Add change source: application, dependencies, or 'application+dependencies'
|
||||
applicationChanges=$(git diff --name-only $TRAVIS_COMMIT_RANGE $parentDir | grep -v ${parentDir}/content | grep -v ${parentDir}/yarn.lock | wc -l)
|
||||
dependencyChanges=$(git diff --name-only $TRAVIS_COMMIT_RANGE ${parentDir}/yarn.lock | wc -l)
|
||||
yarnChanged=false
|
||||
allChangedFiles=$(git diff --name-only $TRAVIS_COMMIT_RANGE $parentDir | wc -l)
|
||||
allChangedFileNames=$(git diff --name-only $TRAVIS_COMMIT_RANGE $parentDir)
|
||||
|
||||
if [[ $dependencyChanges -eq 1 ]] && [[ $applicationChanges -eq 0 ]]; then
|
||||
if [[ $allChangedFileNames == *"yarn.lock"* ]]; then
|
||||
yarnChanged=true
|
||||
fi
|
||||
|
||||
if [[ $allChangedFiles -eq 1 ]] && [[ "$yarnChanged" = true ]]; then
|
||||
# only yarn.lock changed
|
||||
change='dependencies'
|
||||
elif [[ $dependencyChanges -eq 1 ]] && [[ $applicationChanges -gt 0 ]]; then
|
||||
elif [[ $allChangedFiles -gt 1 ]] && [[ "$yarnChanged" = true ]]; then
|
||||
change='application+dependencies'
|
||||
elif [[ $applicationChanges -gt 0 ]]; then
|
||||
elif [[ $allChangedFiles -gt 0 ]]; then
|
||||
change='application'
|
||||
else
|
||||
# Nothing changed in aio/
|
||||
|
@ -48,7 +48,7 @@ export class AppComponent implements OnInit {
|
||||
* the styling of individual pages.
|
||||
* You will get three classes:
|
||||
*
|
||||
* * `page-...`: computed from the current document id (e.g. news, guide-security, tutorial-toh-pt2)
|
||||
* * `page-...`: computed from the current document id (e.g. events, guide-security, tutorial-toh-pt2)
|
||||
* * `folder-...`: computed from the top level folder for an id (e.g. guide, tutorial, etc)
|
||||
* * `view-...`: computef from the navigation view (e.g. SideNav, TopBar, etc)
|
||||
*/
|
||||
|
@ -65,6 +65,13 @@ describe('CodeExampleComponent', () => {
|
||||
expect(actual).toBe('Great Example');
|
||||
});
|
||||
|
||||
it('should remove the `title` attribute after initialisation', () => {
|
||||
TestBed.overrideComponent(HostComponent, {
|
||||
set: {template: '<code-example title="Great Example"></code-example>'}});
|
||||
createComponent(oneLineCode);
|
||||
expect(codeExampleDe.nativeElement.getAttribute('title')).toEqual(null);
|
||||
});
|
||||
|
||||
it('should pass hideCopy to CodeComonent', () => {
|
||||
TestBed.overrideComponent(HostComponent, {
|
||||
set: {template: '<code-example hideCopy="true"></code-example>'}});
|
||||
|
@ -45,6 +45,8 @@ export class CodeExampleComponent implements OnInit {
|
||||
this.path = element.getAttribute('path') || '';
|
||||
this.region = element.getAttribute('region') || '';
|
||||
this.title = element.getAttribute('title') || '';
|
||||
// Now remove the title attribute to prevent unwanted tooltip popups when hovering over the code.
|
||||
element.removeAttribute('title');
|
||||
|
||||
this.isAvoid = this.path.indexOf('.avoid.') !== -1;
|
||||
this.hideCopy = this.isAvoid || getBoolFromAttribute(element, ['hidecopy', 'hide-copy']);
|
||||
|
@ -17,6 +17,7 @@ aio-shell.page-docs {
|
||||
|
||||
.sidenav-content {
|
||||
min-height: 450px;
|
||||
padding: 80px 1rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,7 +106,11 @@ aio-code pre {
|
||||
}
|
||||
}
|
||||
|
||||
.code-tab-group div.mat-tab-body-content {
|
||||
.code-tab-group .mat-tab-label {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.code-tab-group .mat-tab-body-content {
|
||||
height: auto;
|
||||
transform: none;
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ table {
|
||||
background-color: $lightgray;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
th {
|
||||
@ -80,6 +80,35 @@ table {
|
||||
}
|
||||
}
|
||||
|
||||
#cheatsheet table tbody td {
|
||||
overflow: auto;
|
||||
}
|
||||
#cheatsheet {
|
||||
|
||||
table tbody td {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 990px) {
|
||||
|
||||
/* Force table to not be like tables anymore */
|
||||
table, thead, tbody, tfoot, tr, th, td {
|
||||
display: block;
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
|
||||
code {
|
||||
padding: 0;
|
||||
background-color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
th, td {
|
||||
&:not(:last-child) {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,14 +20,16 @@ module.exports = function addImageDimensions(getImageDimensions) {
|
||||
const src = props.src;
|
||||
if (!src) {
|
||||
file.message('Missing src in image tag `' + source(node, file) + '`');
|
||||
} else if (props.width === undefined && props.height === undefined) {
|
||||
} else {
|
||||
try {
|
||||
const dimensions = getImageDimensions(addImageDimensionsImpl.basePath, src);
|
||||
props.width = '' + dimensions.width;
|
||||
props.height = '' + dimensions.height;
|
||||
if (props.width === undefined && props.height === undefined) {
|
||||
props.width = '' + dimensions.width;
|
||||
props.height = '' + dimensions.height;
|
||||
}
|
||||
} catch(e) {
|
||||
if (e.code === 'ENOENT') {
|
||||
file.message('Unable to load src in image tag `' + source(node, file) + '`');
|
||||
file.fail('Unable to load src in image tag `' + source(node, file) + '`');
|
||||
} else {
|
||||
file.fail(e.message);
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ describe('addImageDimensions post-processor', () => {
|
||||
expect(log.warn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should log a warning for images whose source cannot be loaded', () => {
|
||||
it('should fail for images whose source cannot be loaded', () => {
|
||||
getImageDimensionsSpy.and.callFake(() => {
|
||||
const error = new Error('no such file or directory');
|
||||
error.code = 'ENOENT';
|
||||
@ -68,13 +68,8 @@ describe('addImageDimensions post-processor', () => {
|
||||
docType: 'a',
|
||||
renderedContent: '<img src="missing">'
|
||||
}];
|
||||
processor.$process(docs);
|
||||
expect(() => processor.$process(docs)).toThrowError('Unable to load src in image tag `<img src="missing">` - doc (a) ');
|
||||
expect(getImageDimensionsSpy).toHaveBeenCalled();
|
||||
expect(docs).toEqual([jasmine.objectContaining({
|
||||
docType: 'a',
|
||||
renderedContent: '<img src="missing">'
|
||||
})]);
|
||||
expect(log.warn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should ignore images with width or height attributes', () => {
|
||||
@ -87,7 +82,6 @@ describe('addImageDimensions post-processor', () => {
|
||||
`
|
||||
}];
|
||||
processor.$process(docs);
|
||||
expect(getImageDimensionsSpy).not.toHaveBeenCalled();
|
||||
expect(docs).toEqual([jasmine.objectContaining({
|
||||
docType: 'a',
|
||||
renderedContent: `
|
||||
|
@ -47,4 +47,5 @@ module.exports = new Package('angular.io', [gitPackage, apiPackage, contentPacka
|
||||
}
|
||||
});
|
||||
checkAnchorLinksProcessor.pathVariants = ['', '/', '.html', '/index.html', '#top-of-page'];
|
||||
checkAnchorLinksProcessor.errorOnUnmatchedLinks = true;
|
||||
});
|
||||
|
@ -31,8 +31,14 @@ describe('example inline-tag-def', function() {
|
||||
};
|
||||
});
|
||||
|
||||
it('should return a <code-example> tag', () => {
|
||||
expect(handler({}, 'example', 'some/uri')).toEqual('<code-example>\n\n</code-example>');
|
||||
it('should throw an error if there is no matching example', () => {
|
||||
expect(function() {
|
||||
handler({}, 'example', 'missing/uri');
|
||||
}).toThrowError();
|
||||
|
||||
expect(function() {
|
||||
handler({}, 'example', 'test/url missing-region');
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
it('should contain the whole contents from the example file if no region is specified', () => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
module.exports = function getExampleRegion(exampleMap, createDocMessage, log, collectExamples) {
|
||||
module.exports = function getExampleRegion(exampleMap, createDocMessage, collectExamples) {
|
||||
return function getExampleRegionImpl(doc, relativePath, regionName) {
|
||||
const EXAMPLES_FOLDERS = collectExamples.exampleFolders;
|
||||
|
||||
@ -14,16 +14,16 @@ module.exports = function getExampleRegion(exampleMap, createDocMessage, log, co
|
||||
|
||||
// If still no file then we error
|
||||
if (!exampleFile) {
|
||||
log.error(createDocMessage('Missing example file... relativePath: "' + relativePath + '".', doc));
|
||||
log.error('Example files can be found in: ' + EXAMPLES_FOLDERS.join(', '));
|
||||
return '';
|
||||
const message = createDocMessage('Missing example file... relativePath: "' + relativePath + '".', doc) + '\n' +
|
||||
'Example files can be found in: ' + EXAMPLES_FOLDERS.join(', ');
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
var sourceCodeDoc = exampleFile.regions[regionName || ''];
|
||||
if (!sourceCodeDoc) {
|
||||
log.error(createDocMessage('Missing example region... relativePath: "' + relativePath + '", region: "' + regionName + '".', doc));
|
||||
log.error('Regions available are:', Object.keys[exampleFile.regions]);
|
||||
return '';
|
||||
const message = createDocMessage('Missing example region... relativePath: "' + relativePath + '", region: "' + regionName + '".', doc) + '\n' +
|
||||
'Regions available are:' + Object.keys[exampleFile.regions];
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return sourceCodeDoc.renderedContent;
|
||||
|
@ -26,4 +26,13 @@ describe('getExampleRegion', () => {
|
||||
it('should contain the region contents from the example file if a region is specified', () => {
|
||||
expect(getExampleRegion({}, 'test/url', 'region-1')).toEqual('region 1 contents');
|
||||
});
|
||||
|
||||
it('should throw an error if an example doesn\'t exist', function() {
|
||||
expect(function() {
|
||||
getExampleRegion({}, 'missing/file', 'region-1');
|
||||
}).toThrowError();
|
||||
expect(function() {
|
||||
getExampleRegion({}, 'test/url', 'missing-region');
|
||||
}).toThrowError();
|
||||
});
|
||||
});
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
{% block overview %}
|
||||
<code-example language="ts" hideCopy="true" class="no-box api-heading">
|
||||
function {$ doc.name $}{$ params.paramList(doc.parameters) $}
|
||||
function {$ doc.name $}{$ doc.typeParameters | escape $}{$ params.paramList(doc.parameters) $}
|
||||
{%- if doc.type %}: {$ doc.type | escape $}{% endif %};
|
||||
</code-example>
|
||||
{% endblock %}
|
||||
@ -13,7 +13,7 @@ function {$ doc.name $}{$ params.paramList(doc.parameters) $}
|
||||
{% if doc.overloads.length %}
|
||||
<h2>Overloads</h2>{% for overload in doc.overloads %}
|
||||
<code-example language="ts" hideCopy="true" class="no-box api-heading">
|
||||
function {$ overload.name $}{$ params.paramList(overload.parameters) $}
|
||||
function {$ overload.name $}{$ doc.typeParameters | escape $}{$ params.paramList(overload.parameters) $}
|
||||
{%- if overload.type %}: {$ overload.type | escape $}{% endif %};
|
||||
</code-example>
|
||||
<section class="description">
|
||||
|
@ -11,7 +11,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Module</th>
|
||||
<td><code>import { {$ doc.name $} } from <a href="{$ doc.moduleDoc.path $}">@angular/{$ doc.moduleDoc.id $}</a>;</code></td>
|
||||
<td><code>import { {$ doc.name $} } from <a href="{$ doc.moduleDoc.path $}">'@angular/{$ doc.moduleDoc.id $}'</a>;</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Source</th>
|
||||
|
@ -1904,9 +1904,9 @@ devtools-timeline-model@1.1.6:
|
||||
chrome-devtools-frontend "1.0.401423"
|
||||
resolve "1.1.7"
|
||||
|
||||
dgeni-packages@^0.20.0-rc.5:
|
||||
version "0.20.0-rc.5"
|
||||
resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.20.0-rc.5.tgz#09dd8134a3d79595578d6c0192ec0d82c78354be"
|
||||
dgeni-packages@^0.20.0-rc.6:
|
||||
version "0.20.0-rc.6"
|
||||
resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.20.0-rc.6.tgz#d615e0631305dcf091386c802d0e424ef86206d2"
|
||||
dependencies:
|
||||
canonical-path "0.0.2"
|
||||
catharsis "^0.8.1"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "4.3.0",
|
||||
"version": "5.0.0-beta.0",
|
||||
"private": true,
|
||||
"branchPattern": "2.0.*",
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
|
@ -24,8 +24,14 @@ export function parseTransitionExpr(
|
||||
function parseInnerTransitionStr(
|
||||
eventStr: string, expressions: TransitionMatcherFn[], errors: string[]) {
|
||||
if (eventStr[0] == ':') {
|
||||
eventStr = parseAnimationAlias(eventStr, errors);
|
||||
const result = parseAnimationAlias(eventStr, errors);
|
||||
if (typeof result == 'function') {
|
||||
expressions.push(result);
|
||||
return;
|
||||
}
|
||||
eventStr = result as string;
|
||||
}
|
||||
|
||||
const match = eventStr.match(/^(\*|[-\w]+)\s*(<?[=-]>)\s*(\*|[-\w]+)$/);
|
||||
if (match == null || match.length < 4) {
|
||||
errors.push(`The provided transition expression "${eventStr}" is not supported`);
|
||||
@ -43,12 +49,16 @@ function parseInnerTransitionStr(
|
||||
}
|
||||
}
|
||||
|
||||
function parseAnimationAlias(alias: string, errors: string[]): string {
|
||||
function parseAnimationAlias(alias: string, errors: string[]): string|TransitionMatcherFn {
|
||||
switch (alias) {
|
||||
case ':enter':
|
||||
return 'void => *';
|
||||
case ':leave':
|
||||
return '* => void';
|
||||
case ':increment':
|
||||
return (fromState: any, toState: any): boolean => parseFloat(toState) > parseFloat(fromState);
|
||||
case ':decrement':
|
||||
return (fromState: any, toState: any): boolean => parseFloat(toState) < parseFloat(fromState);
|
||||
default:
|
||||
errors.push(`The transition alias value "${alias}" is not supported`);
|
||||
return '* => *';
|
||||
|
@ -67,20 +67,17 @@ export class AnimationEngine {
|
||||
this._transitionEngine.removeNode(namespaceId, element, context);
|
||||
}
|
||||
|
||||
process(namespaceId: string, element: any, property: string, value: any): boolean {
|
||||
switch (property.charAt(0)) {
|
||||
case '.':
|
||||
if (property == '.disabled') {
|
||||
this._transitionEngine.markElementAsDisabled(element, !!value);
|
||||
}
|
||||
return false;
|
||||
case '@':
|
||||
const [id, action] = parseTimelineCommand(property);
|
||||
const args = value as any[];
|
||||
this._timelineEngine.command(id, element, action, args);
|
||||
return false;
|
||||
default:
|
||||
return this._transitionEngine.trigger(namespaceId, element, property, value);
|
||||
disableAnimations(element: any, disable: boolean) {
|
||||
this._transitionEngine.markElementAsDisabled(element, disable);
|
||||
}
|
||||
|
||||
process(namespaceId: string, element: any, property: string, value: any) {
|
||||
if (property.charAt(0) == '@') {
|
||||
const [id, action] = parseTimelineCommand(property);
|
||||
const args = value as any[];
|
||||
this._timelineEngine.command(id, element, action, args);
|
||||
} else {
|
||||
this._transitionEngine.trigger(namespaceId, element, property, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,13 +36,22 @@ export function normalizeKeyframes(
|
||||
Object.keys(kf).forEach(prop => {
|
||||
let normalizedProp = prop;
|
||||
let normalizedValue = kf[prop];
|
||||
if (normalizedValue == PRE_STYLE) {
|
||||
normalizedValue = preStyles[prop];
|
||||
} else if (normalizedValue == AUTO_STYLE) {
|
||||
normalizedValue = postStyles[prop];
|
||||
} else if (prop != 'offset') {
|
||||
normalizedProp = normalizer.normalizePropertyName(prop, errors);
|
||||
normalizedValue = normalizer.normalizeStyleValue(prop, normalizedProp, kf[prop], errors);
|
||||
if (prop !== 'offset') {
|
||||
normalizedProp = normalizer.normalizePropertyName(normalizedProp, errors);
|
||||
switch (normalizedValue) {
|
||||
case PRE_STYLE:
|
||||
normalizedValue = preStyles[prop];
|
||||
break;
|
||||
|
||||
case AUTO_STYLE:
|
||||
normalizedValue = postStyles[prop];
|
||||
break;
|
||||
|
||||
default:
|
||||
normalizedValue =
|
||||
normalizer.normalizeStyleValue(prop, normalizedProp, normalizedValue, errors);
|
||||
break;
|
||||
}
|
||||
}
|
||||
normalizedKeyframe[normalizedProp] = normalizedValue;
|
||||
});
|
||||
|
@ -5,7 +5,7 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {AUTO_STYLE, AnimationOptions, AnimationPlayer, NoopAnimationPlayer, ɵPRE_STYLE as PRE_STYLE, ɵStyleData} from '@angular/animations';
|
||||
import {AUTO_STYLE, AnimationOptions, AnimationPlayer, NoopAnimationPlayer, ɵAnimationGroupPlayer as AnimationGroupPlayer, ɵPRE_STYLE as PRE_STYLE, ɵStyleData} from '@angular/animations';
|
||||
|
||||
import {AnimationTimelineInstruction} from '../dsl/animation_timeline_instruction';
|
||||
import {AnimationTransitionFactory} from '../dsl/animation_transition_factory';
|
||||
@ -1168,8 +1168,8 @@ export class TransitionAnimationEngine {
|
||||
if (details && details.removedBeforeQueried) return new NoopAnimationPlayer();
|
||||
|
||||
const isQueriedElement = element !== rootElement;
|
||||
const previousPlayers =
|
||||
(allPreviousPlayersMap.get(element) || EMPTY_PLAYER_ARRAY).map(p => p.getRealPlayer());
|
||||
const previousPlayers = flattenGroupPlayers(
|
||||
(allPreviousPlayersMap.get(element) || EMPTY_PLAYER_ARRAY).map(p => p.getRealPlayer()));
|
||||
|
||||
const preStyles = preStylesMap.get(element);
|
||||
const postStyles = postStylesMap.get(element);
|
||||
@ -1464,3 +1464,20 @@ function removeNodesAfterAnimationDone(
|
||||
engine: TransitionAnimationEngine, element: any, players: AnimationPlayer[]) {
|
||||
optimizeGroupPlayer(players).onDone(() => engine.processLeaveNode(element));
|
||||
}
|
||||
|
||||
function flattenGroupPlayers(players: AnimationPlayer[]): AnimationPlayer[] {
|
||||
const finalPlayers: AnimationPlayer[] = [];
|
||||
_flattenGroupPlayersRecur(players, finalPlayers);
|
||||
return finalPlayers;
|
||||
}
|
||||
|
||||
function _flattenGroupPlayersRecur(players: AnimationPlayer[], finalPlayers: AnimationPlayer[]) {
|
||||
for (let i = 0; i < players.length; i++) {
|
||||
const player = players[i];
|
||||
if (player instanceof AnimationGroupPlayer) {
|
||||
_flattenGroupPlayersRecur(player.players, finalPlayers);
|
||||
} else {
|
||||
finalPlayers.push(player as AnimationPlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -707,7 +707,7 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe
|
||||
* ])
|
||||
* ```
|
||||
*
|
||||
* ### Transition Aliases (`:enter` and `:leave`)
|
||||
* ### Using :enter and :leave
|
||||
*
|
||||
* Given that enter (insertion) and leave (removal) animations are so common, the `transition`
|
||||
* function accepts both `:enter` and `:leave` values which are aliases for the `void => *` and `*
|
||||
@ -717,12 +717,88 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe
|
||||
* transition(":enter", [
|
||||
* style({ opacity: 0 }),
|
||||
* animate(500, style({ opacity: 1 }))
|
||||
* ])
|
||||
* ]),
|
||||
* transition(":leave", [
|
||||
* animate(500, style({ opacity: 0 }))
|
||||
* ])
|
||||
* ```
|
||||
*
|
||||
* ### Using :increment and :decrement
|
||||
* In addition to the :enter and :leave transition aliases, the :increment and :decrement aliases
|
||||
* can be used to kick off a transition when a numeric value has increased or decreased in value.
|
||||
*
|
||||
* ```
|
||||
* import {group, animate, query, transition, style, trigger} from '@angular/animations';
|
||||
* import {Component} from '@angular/core';
|
||||
*
|
||||
* @Component({
|
||||
* selector: 'banner-carousel-component',
|
||||
* styles: [`
|
||||
* .banner-container {
|
||||
* position:relative;
|
||||
* height:500px;
|
||||
* overflow:hidden;
|
||||
* }
|
||||
* .banner-container > .banner {
|
||||
* position:absolute;
|
||||
* left:0;
|
||||
* top:0;
|
||||
* font-size:200px;
|
||||
* line-height:500px;
|
||||
* font-weight:bold;
|
||||
* text-align:center;
|
||||
* width:100%;
|
||||
* }
|
||||
* `],
|
||||
* template: `
|
||||
* <button (click)="previous()">Previous</button>
|
||||
* <button (click)="next()">Next</button>
|
||||
* <hr>
|
||||
* <div [@bannerAnimation]="selectedIndex" class="banner-container">
|
||||
* <div class="banner"> {{ banner }} </div>
|
||||
* </div>
|
||||
* `
|
||||
* animations: [
|
||||
* trigger('bannerAnimation', [
|
||||
* transition(":increment", group([
|
||||
* query(':enter', [
|
||||
* style({ left: '100%' }),
|
||||
* animate('0.5s ease-out', style('*'))
|
||||
* ]),
|
||||
* query(':leave', [
|
||||
* animate('0.5s ease-out', style({ left: '-100%' }))
|
||||
* ])
|
||||
* ])),
|
||||
* transition(":decrement", group([
|
||||
* query(':enter', [
|
||||
* style({ left: '-100%' }),
|
||||
* animate('0.5s ease-out', style('*'))
|
||||
* ]),
|
||||
* query(':leave', [
|
||||
* animate('0.5s ease-out', style({ left: '100%' }))
|
||||
* ])
|
||||
* ])),
|
||||
* ])
|
||||
* ]
|
||||
* })
|
||||
* class BannerCarouselComponent {
|
||||
* allBanners: string[] = ['1', '2', '3', '4'];
|
||||
* selectedIndex: number = 0;
|
||||
*
|
||||
* get banners() {
|
||||
* return [this.allBanners[this.selectedIndex]];
|
||||
* }
|
||||
*
|
||||
* previous() {
|
||||
* this.selectedIndex = Math.max(this.selectedIndex - 1, 0);
|
||||
* }
|
||||
*
|
||||
* next() {
|
||||
* this.selectedIndex = Math.min(this.selectedIndex + 1, this.allBanners.length - 1);
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* {@example core/animation/ts/dsl/animation_example.ts region='Component'}
|
||||
*
|
||||
* @experimental Animation support is experimental.
|
||||
|
@ -132,4 +132,12 @@ export class AnimationGroupPlayer implements AnimationPlayer {
|
||||
}
|
||||
|
||||
get players(): AnimationPlayer[] { return this._players; }
|
||||
|
||||
beforeDestroy(): void {
|
||||
this.players.forEach(player => {
|
||||
if (player.beforeDestroy) {
|
||||
player.beforeDestroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import 'rxjs/add/operator/toPromise';
|
||||
import {ddescribe, describe, iit, it} from '@angular/core/testing/src/testing_internal';
|
||||
|
||||
import {HttpClient} from '../src/client';
|
||||
import {HttpEventType, HttpResponse} from '../src/response';
|
||||
import {HttpErrorResponse, HttpEventType, HttpResponse} from '../src/response';
|
||||
import {HttpClientTestingBackend} from '../testing/src/backend';
|
||||
|
||||
export function main() {
|
||||
@ -123,5 +123,15 @@ export function main() {
|
||||
.flush('hello world');
|
||||
});
|
||||
});
|
||||
describe('makes a request for an error response', () => {
|
||||
it('with a JSON body', (done: DoneFn) => {
|
||||
client.get('/test').subscribe(() => {}, (res: HttpErrorResponse) => {
|
||||
expect(res.error.data).toEqual('hello world');
|
||||
done();
|
||||
});
|
||||
backend.expectOne('/test').flush(
|
||||
{'data': 'hello world'}, {status: 500, statusText: 'Server error'});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -61,12 +61,11 @@ export class TestRequest {
|
||||
if (statusText === undefined) {
|
||||
throw new Error('statusText is required when setting a custom status.');
|
||||
}
|
||||
const res = {body, headers, status, statusText, url};
|
||||
if (status >= 200 && status < 300) {
|
||||
this.observer.next(new HttpResponse<any>(res));
|
||||
this.observer.next(new HttpResponse<any>({body, headers, status, statusText, url}));
|
||||
this.observer.complete();
|
||||
} else {
|
||||
this.observer.error(new HttpErrorResponse(res));
|
||||
this.observer.error(new HttpErrorResponse({error: body, headers, status, statusText, url}));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,6 @@ const EXPECTED_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<body>
|
||||
<trans-unit id="76e1eccb1b772fa9f294ef9c146ea6d0efa8a2d4" datatype="html">
|
||||
<source>translate me</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/basic.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
@ -57,7 +56,6 @@ const EXPECTED_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
</trans-unit>
|
||||
<trans-unit id="65cc4ab3b4c438e07c89be2b677d08369fb62da2" datatype="html">
|
||||
<source>Welcome</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/basic.ts</context>
|
||||
<context context-type="linenumber">5</context>
|
||||
@ -70,7 +68,6 @@ const EXPECTED_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<trans-unit id="b0a17f08a4bd742b2acf39780c257c2f519d33ed" datatype="html">
|
||||
<source>other-3rdP-component
|
||||
multi-lines</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">node_modules/third_party/other_comp.d.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
|
@ -9,7 +9,7 @@
|
||||
"ng-xi18n": "./src/extract_i18n.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/tsc-wrapped": "4.3.0",
|
||||
"@angular/tsc-wrapped": "5.0.0-beta.0",
|
||||
"reflect-metadata": "^0.1.2",
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
|
@ -53,7 +53,7 @@ export class Xliff extends Serializer {
|
||||
const transUnit = new xml.Tag(_UNIT_TAG, {id: message.id, datatype: 'html'});
|
||||
transUnit.children.push(
|
||||
new xml.CR(8), new xml.Tag(_SOURCE_TAG, {}, visitor.serialize(message.nodes)),
|
||||
new xml.CR(8), new xml.Tag(_TARGET_TAG), ...contextTags);
|
||||
...contextTags);
|
||||
|
||||
if (message.description) {
|
||||
transUnit.children.push(
|
||||
|
@ -43,7 +43,7 @@ export function jitStatements(sourceUrl: string, statements: o.Statement[]): {[k
|
||||
return evalExpression(sourceUrl, ctx, converter.getArgs());
|
||||
}
|
||||
|
||||
class JitEmitterVisitor extends AbstractJsEmitterVisitor {
|
||||
export class JitEmitterVisitor extends AbstractJsEmitterVisitor {
|
||||
private _evalArgNames: string[] = [];
|
||||
private _evalArgValues: any[] = [];
|
||||
private _evalExportedVars: string[] = [];
|
||||
@ -69,7 +69,7 @@ class JitEmitterVisitor extends AbstractJsEmitterVisitor {
|
||||
id = this._evalArgValues.length;
|
||||
this._evalArgValues.push(value);
|
||||
const name = identifierName({reference: ast.value.runtime}) || 'val';
|
||||
this._evalArgNames.push(`jit_${name}${id}`);
|
||||
this._evalArgNames.push(`jit_${name}_${id}`);
|
||||
}
|
||||
ctx.print(ast, this._evalArgNames[id]);
|
||||
return null;
|
||||
|
@ -162,7 +162,6 @@ const XLIFF_TOMERGE = `
|
||||
const XLIFF_EXTRACTED = `
|
||||
<trans-unit id="3cb04208df1c2f62553ed48e75939cf7107f9dad" datatype="html">
|
||||
<source>i18n attribute on tags</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">3</context>
|
||||
@ -170,7 +169,6 @@ const XLIFF_EXTRACTED = `
|
||||
</trans-unit>
|
||||
<trans-unit id="52895b1221effb3f3585b689f049d2784d714952" datatype="html">
|
||||
<source>nested</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">5</context>
|
||||
@ -178,7 +176,6 @@ const XLIFF_EXTRACTED = `
|
||||
</trans-unit>
|
||||
<trans-unit id="88d5f22050a9df477ee5646153558b3a4862d47e" datatype="html">
|
||||
<source>nested</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">7</context>
|
||||
@ -187,7 +184,6 @@ const XLIFF_EXTRACTED = `
|
||||
</trans-unit>
|
||||
<trans-unit id="34fec9cc62e28e8aa6ffb306fa8569ef0a8087fe" datatype="html">
|
||||
<source><x id="START_ITALIC_TEXT" ctype="x-i"/>with placeholders<x id="CLOSE_ITALIC_TEXT" ctype="x-i"/></source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">9</context>
|
||||
@ -199,7 +195,6 @@ const XLIFF_EXTRACTED = `
|
||||
</trans-unit>
|
||||
<trans-unit id="1fe4616cce80a57c7707bac1c97054aa8e244a67" datatype="html">
|
||||
<source>on not translatable node</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
@ -207,7 +202,6 @@ const XLIFF_EXTRACTED = `
|
||||
</trans-unit>
|
||||
<trans-unit id="67162b5af5f15fd0eb6480c88688dafdf952b93a" datatype="html">
|
||||
<source>on translatable node</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">14</context>
|
||||
@ -215,7 +209,6 @@ const XLIFF_EXTRACTED = `
|
||||
</trans-unit>
|
||||
<trans-unit id="dc5536bb9e0e07291c185a0d306601a2ecd4813f" datatype="html">
|
||||
<source>{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<x id="START_BOLD_TEXT" ctype="x-b"/>many<x id="CLOSE_BOLD_TEXT" ctype="x-b"/>} }</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">19</context>
|
||||
@ -229,7 +222,6 @@ const XLIFF_EXTRACTED = `
|
||||
<source>
|
||||
<x id="ICU"/>
|
||||
</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">21</context>
|
||||
@ -237,7 +229,6 @@ const XLIFF_EXTRACTED = `
|
||||
</trans-unit>
|
||||
<trans-unit id="c0ca5e58fe954d528bbfa516007a5a11690a7e99" datatype="html">
|
||||
<source>{VAR_SELECT, select, m {male} f {female} }</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">22</context>
|
||||
@ -247,7 +238,6 @@ const XLIFF_EXTRACTED = `
|
||||
<source>
|
||||
<x id="ICU"/>
|
||||
</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">24</context>
|
||||
@ -255,7 +245,6 @@ const XLIFF_EXTRACTED = `
|
||||
</trans-unit>
|
||||
<trans-unit id="a25cf2e21a299f30be1392e731163825233edc61" datatype="html">
|
||||
<source>{VAR_SELECT, select, m {male} f {female} }</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">25</context>
|
||||
@ -263,7 +252,6 @@ const XLIFF_EXTRACTED = `
|
||||
</trans-unit>
|
||||
<trans-unit id="d9879678f727b244bc7c7e20f22b63d98cb14890" datatype="html">
|
||||
<source><x id="INTERPOLATION"/></source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">28</context>
|
||||
@ -271,7 +259,6 @@ const XLIFF_EXTRACTED = `
|
||||
</trans-unit>
|
||||
<trans-unit id="50dac33dc6fc0578884baac79d875785ed77c928" datatype="html">
|
||||
<source>sex = <x id="INTERPOLATION"/></source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">29</context>
|
||||
@ -279,7 +266,6 @@ const XLIFF_EXTRACTED = `
|
||||
</trans-unit>
|
||||
<trans-unit id="a46f833b1fe6ca49e8b97c18f4b7ea0b930c9383" datatype="html">
|
||||
<source><x id="CUSTOM_NAME"/></source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">30</context>
|
||||
@ -287,7 +273,6 @@ const XLIFF_EXTRACTED = `
|
||||
</trans-unit>
|
||||
<trans-unit id="2ec983b4893bcd5b24af33bebe3ecba63868453c" datatype="html">
|
||||
<source>in a translatable section</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">35</context>
|
||||
@ -303,7 +288,6 @@ const XLIFF_EXTRACTED = `
|
||||
<x id="START_TAG_DIV" ctype="x-div"/><x id="CLOSE_TAG_DIV" ctype="x-div"/>
|
||||
<x id="START_TAG_DIV_1" ctype="x-div"/><x id="ICU"/><x id="CLOSE_TAG_DIV" ctype="x-div"/>
|
||||
</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">33</context>
|
||||
@ -311,7 +295,6 @@ const XLIFF_EXTRACTED = `
|
||||
</trans-unit>
|
||||
<trans-unit id="93a30c67d4e6c9b37aecfe2ac0f2b5d366d7b520" datatype="html">
|
||||
<source>it <x id="START_BOLD_TEXT" ctype="x-b"/>should<x id="CLOSE_BOLD_TEXT" ctype="x-b"/> work</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">39</context>
|
||||
@ -319,7 +302,6 @@ const XLIFF_EXTRACTED = `
|
||||
</trans-unit>
|
||||
<trans-unit id="i18n16" datatype="html">
|
||||
<source>with an explicit ID</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">41</context>
|
||||
@ -327,7 +309,6 @@ const XLIFF_EXTRACTED = `
|
||||
</trans-unit>
|
||||
<trans-unit id="i18n17" datatype="html">
|
||||
<source>{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<x id="START_BOLD_TEXT" ctype="x-b"/>many<x id="CLOSE_BOLD_TEXT" ctype="x-b"/>} }</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">42</context>
|
||||
@ -335,7 +316,6 @@ const XLIFF_EXTRACTED = `
|
||||
</trans-unit>
|
||||
<trans-unit id="2370d995bdcc1e7496baa32df20654aff65c2d10" datatype="html">
|
||||
<source>{VAR_PLURAL, plural, =0 {Found no results} =1 {Found one result} other {Found <x id="INTERPOLATION"/> results} }</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">45</context>
|
||||
@ -344,7 +324,6 @@ const XLIFF_EXTRACTED = `
|
||||
</trans-unit>
|
||||
<trans-unit id="296ab5eab8d370822488c152586db3a5875ee1a2" datatype="html">
|
||||
<source>foo<x id="START_LINK" ctype="x-a"/>bar<x id="CLOSE_LINK" ctype="x-a"/></source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">53</context>
|
||||
@ -352,7 +331,6 @@ const XLIFF_EXTRACTED = `
|
||||
</trans-unit>
|
||||
<trans-unit id="2e013b311caa0916478941a985887e091d8288b6" datatype="html">
|
||||
<source><x id="MAP NAME"/></source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">55</context>
|
||||
|
@ -35,7 +35,6 @@ const WRITE_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<body>
|
||||
<trans-unit id="983775b9a51ce14b036be72d4cfd65d68d64e231" datatype="html">
|
||||
<source>translatable attribute</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">2</context>
|
||||
@ -43,7 +42,6 @@ const WRITE_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
</trans-unit>
|
||||
<trans-unit id="ec1d033f2436133c14ab038286c4f5df4697484a" datatype="html">
|
||||
<source>translatable element <x id="START_BOLD_TEXT" ctype="x-b"/>with placeholders<x id="CLOSE_BOLD_TEXT" ctype="x-b"/> <x id="INTERPOLATION"/></source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">3</context>
|
||||
@ -51,7 +49,6 @@ const WRITE_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
</trans-unit>
|
||||
<trans-unit id="e2ccf3d131b15f54aa1fcf1314b1ca77c14bfcc2" datatype="html">
|
||||
<source>{VAR_PLURAL, plural, =0 {<x id="START_PARAGRAPH" ctype="x-p"/>test<x id="CLOSE_PARAGRAPH" ctype="x-p"/>} }</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">4</context>
|
||||
@ -59,7 +56,6 @@ const WRITE_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
</trans-unit>
|
||||
<trans-unit id="db3e0a6a5a96481f60aec61d98c3eecddef5ac23" datatype="html">
|
||||
<source>foo</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">5</context>
|
||||
@ -73,7 +69,6 @@ const WRITE_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
</trans-unit>
|
||||
<trans-unit id="i" datatype="html">
|
||||
<source>foo</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">7</context>
|
||||
@ -83,7 +78,6 @@ const WRITE_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
</trans-unit>
|
||||
<trans-unit id="bar" datatype="html">
|
||||
<source>foo</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">8</context>
|
||||
@ -91,7 +85,6 @@ const WRITE_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
</trans-unit>
|
||||
<trans-unit id="d7fa2d59aaedcaa5309f13028c59af8c85b8c49d" datatype="html">
|
||||
<source><x id="LINE_BREAK" ctype="lb"/><x id="TAG_IMG" ctype="image"/><x id="START_TAG_DIV" ctype="x-div"/><x id="CLOSE_TAG_DIV" ctype="x-div"/></source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">9</context>
|
||||
@ -100,7 +93,6 @@ const WRITE_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
</trans-unit>
|
||||
<trans-unit id="baz" datatype="html">
|
||||
<source>{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<x id="START_PARAGRAPH" ctype="x-p"/>deeply nested<x id="CLOSE_PARAGRAPH" ctype="x-p"/>} } } }</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">10</context>
|
||||
@ -108,7 +100,6 @@ const WRITE_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
</trans-unit>
|
||||
<trans-unit id="0e16a673a5a7a135c9f7b957ec2c5c6f6ee6e2c4" datatype="html">
|
||||
<source>{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<x id="START_PARAGRAPH" ctype="x-p"/>deeply nested<x id="CLOSE_PARAGRAPH" ctype="x-p"/>} } } }</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">11</context>
|
||||
@ -117,7 +108,6 @@ const WRITE_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<trans-unit id="fcfa109b0e152d4c217dbc02530be0bcb8123ad1" datatype="html">
|
||||
<source>multi
|
||||
lines</source>
|
||||
<target/>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">file.ts</context>
|
||||
<context context-type="linenumber">12</context>
|
||||
|
35
packages/compiler/test/output/output_jit_spec.ts
Normal file
35
packages/compiler/test/output/output_jit_spec.ts
Normal file
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {EmitterVisitorContext} from '@angular/compiler/src/output/abstract_emitter';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
import {JitEmitterVisitor} from '@angular/compiler/src/output/output_jit';
|
||||
|
||||
const anotherModuleUrl = 'somePackage/someOtherPath';
|
||||
|
||||
export function main() {
|
||||
describe('Output JIT', () => {
|
||||
describe('regression', () => {
|
||||
it('should generate unique argument names', () => {
|
||||
const externalIds = new Array(10).fill(1).map(
|
||||
(_, index) =>
|
||||
new o.ExternalReference(anotherModuleUrl, `id_${index}_`, {name: `id_${index}_`}));
|
||||
const externalIds1 = new Array(10).fill(1).map(
|
||||
(_, index) => new o.ExternalReference(
|
||||
anotherModuleUrl, `id_${index}_1`, {name: `id_${index}_1`}));
|
||||
const ctx = EmitterVisitorContext.createRoot();
|
||||
const converter = new JitEmitterVisitor();
|
||||
converter.visitAllStatements(
|
||||
[o.literalArr([...externalIds1, ...externalIds].map(id => o.importExpr(id))).toStmt()],
|
||||
ctx);
|
||||
const args = converter.getArgs();
|
||||
expect(Object.keys(args).length).toBe(20);
|
||||
});
|
||||
});
|
||||
})
|
||||
}
|
@ -5,10 +5,10 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {AUTO_STYLE, AnimationEvent, AnimationOptions, animate, animateChild, group, keyframes, query, state, style, transition, trigger} from '@angular/animations';
|
||||
import {AUTO_STYLE, AnimationEvent, AnimationOptions, animate, animateChild, group, keyframes, query, state, style, transition, trigger, ɵPRE_STYLE as PRE_STYLE} from '@angular/animations';
|
||||
import {AnimationDriver, ɵAnimationEngine, ɵNoopAnimationDriver} from '@angular/animations/browser';
|
||||
import {MockAnimationDriver, MockAnimationPlayer} from '@angular/animations/browser/testing';
|
||||
import {Component, HostBinding, HostListener, RendererFactory2, ViewChild} from '@angular/core';
|
||||
import {ChangeDetectorRef, Component, HostBinding, HostListener, RendererFactory2, ViewChild} from '@angular/core';
|
||||
import {ɵDomRendererFactory2} from '@angular/platform-browser';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
@ -799,6 +799,75 @@ export function main() {
|
||||
expect(p3.previousStyles).toEqual({});
|
||||
});
|
||||
|
||||
it('should provide the styling of previous players that are grouped', () => {
|
||||
@Component({
|
||||
selector: 'ani-cmp',
|
||||
template: `
|
||||
<div [@myAnimation]="exp"></div>
|
||||
`,
|
||||
animations: [trigger(
|
||||
'myAnimation',
|
||||
[
|
||||
transition(
|
||||
'1 => 2',
|
||||
[
|
||||
group([
|
||||
animate(500, style({'width': '100px'})),
|
||||
animate(500, style({'height': '100px'})),
|
||||
]),
|
||||
animate(500, keyframes([
|
||||
style({'opacity': '0'}),
|
||||
style({'opacity': '1'})
|
||||
]))
|
||||
]),
|
||||
transition(
|
||||
'2 => 3',
|
||||
[
|
||||
style({'opacity': '0'}),
|
||||
animate(500, style({'opacity': '1'})),
|
||||
]),
|
||||
])],
|
||||
})
|
||||
class Cmp {
|
||||
exp: any = false;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
|
||||
cmp.exp = '1';
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
expect(getLog().length).toEqual(0);
|
||||
resetLog();
|
||||
|
||||
cmp.exp = '2';
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
expect(getLog().length).toEqual(3);
|
||||
resetLog();
|
||||
|
||||
cmp.exp = '3';
|
||||
fixture.detectChanges();
|
||||
engine.flush();
|
||||
|
||||
const players = getLog();
|
||||
expect(players.length).toEqual(1);
|
||||
const player = players[0] as MockAnimationPlayer;
|
||||
const pp = player.previousPlayers as MockAnimationPlayer[];
|
||||
|
||||
expect(pp.length).toEqual(3);
|
||||
expect(pp[0].currentSnapshot).toEqual({width: AUTO_STYLE});
|
||||
expect(pp[1].currentSnapshot).toEqual({height: AUTO_STYLE});
|
||||
expect(pp[2].currentSnapshot).toEqual({opacity: AUTO_STYLE});
|
||||
});
|
||||
|
||||
it('should properly balance styles between states even if there are no destination state styles',
|
||||
() => {
|
||||
@Component({
|
||||
@ -1413,6 +1482,157 @@ export function main() {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not flush animations twice when an inner component runs change detection', () => {
|
||||
@Component({
|
||||
selector: 'outer-cmp',
|
||||
template: `
|
||||
<div *ngIf="exp" @outer></div>
|
||||
<inner-cmp #inner></inner-cmp>
|
||||
`,
|
||||
animations: [trigger(
|
||||
'outer',
|
||||
[transition(':enter', [style({opacity: 0}), animate('1s', style({opacity: 1}))])])]
|
||||
})
|
||||
class OuterCmp {
|
||||
@ViewChild('inner') public inner: any;
|
||||
public exp: any = null;
|
||||
|
||||
update() { this.exp = 'go'; }
|
||||
|
||||
ngDoCheck() {
|
||||
if (this.exp == 'go') {
|
||||
this.inner.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'inner-cmp',
|
||||
template: `
|
||||
<div *ngIf="exp" @inner></div>
|
||||
`,
|
||||
animations: [trigger('inner', [transition(
|
||||
':enter',
|
||||
[
|
||||
style({opacity: 0}),
|
||||
animate('1s', style({opacity: 1})),
|
||||
])])]
|
||||
})
|
||||
class InnerCmp {
|
||||
public exp: any;
|
||||
constructor(private _ref: ChangeDetectorRef) {}
|
||||
update() {
|
||||
this.exp = 'go';
|
||||
this._ref.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [OuterCmp, InnerCmp]});
|
||||
|
||||
const engine = TestBed.get(ɵAnimationEngine);
|
||||
const fixture = TestBed.createComponent(OuterCmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
expect(getLog()).toEqual([]);
|
||||
|
||||
cmp.update();
|
||||
fixture.detectChanges();
|
||||
|
||||
const players = getLog();
|
||||
expect(players.length).toEqual(2);
|
||||
});
|
||||
|
||||
describe('transition aliases', () => {
|
||||
describe(':increment', () => {
|
||||
it('should detect when a value has incremented', () => {
|
||||
@Component({
|
||||
selector: 'if-cmp',
|
||||
template: `
|
||||
<div [@myAnimation]="exp"></div>
|
||||
`,
|
||||
animations: [
|
||||
trigger(
|
||||
'myAnimation',
|
||||
[
|
||||
transition(
|
||||
':increment',
|
||||
[
|
||||
animate(1234, style({background: 'red'})),
|
||||
]),
|
||||
]),
|
||||
]
|
||||
})
|
||||
class Cmp {
|
||||
exp: number = 0;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
let players = getLog();
|
||||
expect(players.length).toEqual(0);
|
||||
|
||||
cmp.exp++;
|
||||
fixture.detectChanges();
|
||||
players = getLog();
|
||||
expect(players.length).toEqual(1);
|
||||
expect(players[0].duration).toEqual(1234);
|
||||
resetLog();
|
||||
|
||||
cmp.exp = 5;
|
||||
fixture.detectChanges();
|
||||
players = getLog();
|
||||
expect(players.length).toEqual(1);
|
||||
expect(players[0].duration).toEqual(1234);
|
||||
});
|
||||
});
|
||||
|
||||
describe(':decrement', () => {
|
||||
it('should detect when a value has decremented', () => {
|
||||
@Component({
|
||||
selector: 'if-cmp',
|
||||
template: `
|
||||
<div [@myAnimation]="exp"></div>
|
||||
`,
|
||||
animations: [
|
||||
trigger(
|
||||
'myAnimation',
|
||||
[
|
||||
transition(
|
||||
':decrement',
|
||||
[
|
||||
animate(1234, style({background: 'red'})),
|
||||
]),
|
||||
]),
|
||||
]
|
||||
})
|
||||
class Cmp {
|
||||
exp: number = 5;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
let players = getLog();
|
||||
expect(players.length).toEqual(0);
|
||||
|
||||
cmp.exp--;
|
||||
fixture.detectChanges();
|
||||
players = getLog();
|
||||
expect(players.length).toEqual(1);
|
||||
expect(players[0].duration).toEqual(1234);
|
||||
resetLog();
|
||||
|
||||
cmp.exp = 0;
|
||||
fixture.detectChanges();
|
||||
players = getLog();
|
||||
expect(players.length).toEqual(1);
|
||||
expect(players[0].duration).toEqual(1234);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('animation listeners', () => {
|
||||
@ -1815,12 +2035,12 @@ export function main() {
|
||||
selector: 'my-cmp',
|
||||
template: `
|
||||
<div class="parent" [@parent]="exp" (@parent.done)="cb('all','done', $event)">
|
||||
<div *ngFor="let item of items"
|
||||
<div *ngFor="let item of items"
|
||||
class="item item-{{ item }}"
|
||||
@child
|
||||
(@child.start)="cb('c-' + item, 'start', $event)"
|
||||
(@child.done)="cb('c-' + item, 'done', $event)">
|
||||
{{ item }}
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
@ -2153,9 +2373,141 @@ export function main() {
|
||||
expect(cmp.startEvent.totalTime).toEqual(9876);
|
||||
// the done event isn't fired because it's an actual animation
|
||||
}));
|
||||
|
||||
it('should work when there are no animations on the component handling the disable/enable flag',
|
||||
() => {
|
||||
@Component({
|
||||
selector: 'parent-cmp',
|
||||
template: `
|
||||
<div [@.disabled]="disableExp">
|
||||
<child-cmp #child></child-cmp>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
class ParentCmp {
|
||||
@ViewChild('child') public child: ChildCmp|null = null;
|
||||
disableExp = false;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'child-cmp',
|
||||
template: `
|
||||
<div [@myAnimation]="exp"></div>
|
||||
`,
|
||||
animations: [trigger(
|
||||
'myAnimation',
|
||||
[transition(
|
||||
'* => go, * => goAgain',
|
||||
[style({opacity: 0}), animate('1s', style({opacity: 1}))])])]
|
||||
})
|
||||
class ChildCmp {
|
||||
public exp = '';
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [ParentCmp, ChildCmp]});
|
||||
|
||||
const fixture = TestBed.createComponent(ParentCmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.disableExp = true;
|
||||
fixture.detectChanges();
|
||||
resetLog();
|
||||
|
||||
const child = cmp.child !;
|
||||
child.exp = 'go';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getLog().length).toEqual(0);
|
||||
resetLog();
|
||||
|
||||
cmp.disableExp = false;
|
||||
child.exp = 'goAgain';
|
||||
fixture.detectChanges();
|
||||
expect(getLog().length).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('animation normalization', () => {
|
||||
it('should convert hyphenated properties to camelcase by default', () => {
|
||||
@Component({
|
||||
selector: 'cmp',
|
||||
template: `
|
||||
<div [@myAnimation]="exp"></div>
|
||||
`,
|
||||
animations: [
|
||||
trigger(
|
||||
'myAnimation',
|
||||
[
|
||||
transition(
|
||||
'* => go',
|
||||
[
|
||||
style({'background-color': 'red', height: '100px', fontSize: '100px'}),
|
||||
animate(
|
||||
'1s',
|
||||
style(
|
||||
{'background-color': 'blue', height: '200px', fontSize: '200px'})),
|
||||
]),
|
||||
]),
|
||||
]
|
||||
})
|
||||
class Cmp {
|
||||
exp: any = false;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.exp = 'go';
|
||||
fixture.detectChanges();
|
||||
|
||||
const players = getLog();
|
||||
expect(players.length).toEqual(1);
|
||||
expect(players[0].keyframes).toEqual([
|
||||
{backgroundColor: 'red', height: '100px', fontSize: '100px', offset: 0},
|
||||
{backgroundColor: 'blue', height: '200px', fontSize: '200px', offset: 1},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should convert hyphenated properties to camelcase by default that are auto/pre style properties',
|
||||
() => {
|
||||
@Component({
|
||||
selector: 'cmp',
|
||||
template: `
|
||||
<div [@myAnimation]="exp"></div>
|
||||
`,
|
||||
animations: [
|
||||
trigger(
|
||||
'myAnimation',
|
||||
[
|
||||
transition(
|
||||
'* => go',
|
||||
[
|
||||
style({'background-color': AUTO_STYLE, 'font-size': '100px'}),
|
||||
animate(
|
||||
'1s', style({'background-color': 'blue', 'font-size': PRE_STYLE})),
|
||||
]),
|
||||
]),
|
||||
]
|
||||
})
|
||||
class Cmp {
|
||||
exp: any = false;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
cmp.exp = 'go';
|
||||
fixture.detectChanges();
|
||||
|
||||
const players = getLog();
|
||||
expect(players.length).toEqual(1);
|
||||
expect(players[0].keyframes).toEqual([
|
||||
{backgroundColor: AUTO_STYLE, fontSize: '100px', offset: 0},
|
||||
{backgroundColor: 'blue', fontSize: PRE_STYLE, offset: 1},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw neither state() or transition() are used inside of trigger()', () => {
|
||||
@Component({
|
||||
selector: 'if-cmp',
|
||||
|
@ -9,12 +9,16 @@ import {AnimationTriggerMetadata} from '@angular/animations';
|
||||
import {ɵAnimationEngine as AnimationEngine} from '@angular/animations/browser';
|
||||
import {Injectable, NgZone, Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '@angular/core';
|
||||
|
||||
const ANIMATION_PREFIX = '@';
|
||||
const DISABLE_ANIMATIONS_FLAG = '@.disabled';
|
||||
|
||||
@Injectable()
|
||||
export class AnimationRendererFactory implements RendererFactory2 {
|
||||
private _currentId: number = 0;
|
||||
private _microtaskId: number = 1;
|
||||
private _animationCallbacksBuffer: [(e: any) => any, any][] = [];
|
||||
private _rendererCache = new Map<Renderer2, BaseAnimationRenderer>();
|
||||
private _cdRecurDepth = 0;
|
||||
|
||||
constructor(
|
||||
private delegate: RendererFactory2, private engine: AnimationEngine, private _zone: NgZone) {
|
||||
@ -58,6 +62,7 @@ export class AnimationRendererFactory implements RendererFactory2 {
|
||||
}
|
||||
|
||||
begin() {
|
||||
this._cdRecurDepth++;
|
||||
if (this.delegate.begin) {
|
||||
this.delegate.begin();
|
||||
}
|
||||
@ -90,10 +95,16 @@ export class AnimationRendererFactory implements RendererFactory2 {
|
||||
}
|
||||
|
||||
end() {
|
||||
this._zone.runOutsideAngular(() => {
|
||||
this._scheduleCountTask();
|
||||
this.engine.flush(this._microtaskId);
|
||||
});
|
||||
this._cdRecurDepth--;
|
||||
|
||||
// this is to prevent animations from running twice when an inner
|
||||
// component does CD when a parent component insted has inserted it
|
||||
if (this._cdRecurDepth == 0) {
|
||||
this._zone.runOutsideAngular(() => {
|
||||
this._scheduleCountTask();
|
||||
this.engine.flush(this._microtaskId);
|
||||
});
|
||||
}
|
||||
if (this.delegate.end) {
|
||||
this.delegate.end();
|
||||
}
|
||||
@ -166,7 +177,11 @@ export class BaseAnimationRenderer implements Renderer2 {
|
||||
}
|
||||
|
||||
setProperty(el: any, name: string, value: any): void {
|
||||
this.delegate.setProperty(el, name, value);
|
||||
if (name.charAt(0) == ANIMATION_PREFIX && name == DISABLE_ANIMATIONS_FLAG) {
|
||||
this.disableAnimations(el, !!value);
|
||||
} else {
|
||||
this.delegate.setProperty(el, name, value);
|
||||
}
|
||||
}
|
||||
|
||||
setValue(node: any, value: string): void { this.delegate.setValue(node, value); }
|
||||
@ -174,6 +189,10 @@ export class BaseAnimationRenderer implements Renderer2 {
|
||||
listen(target: any, eventName: string, callback: (event: any) => boolean | void): () => void {
|
||||
return this.delegate.listen(target, eventName, callback);
|
||||
}
|
||||
|
||||
protected disableAnimations(element: any, value: boolean) {
|
||||
this.engine.disableAnimations(element, value);
|
||||
}
|
||||
}
|
||||
|
||||
export class AnimationRenderer extends BaseAnimationRenderer implements Renderer2 {
|
||||
@ -185,9 +204,12 @@ export class AnimationRenderer extends BaseAnimationRenderer implements Renderer
|
||||
}
|
||||
|
||||
setProperty(el: any, name: string, value: any): void {
|
||||
if (name.charAt(0) == '@') {
|
||||
name = name.substr(1);
|
||||
this.engine.process(this.namespaceId, el, name, value);
|
||||
if (name.charAt(0) == ANIMATION_PREFIX) {
|
||||
if (name.charAt(1) == '.' && name == DISABLE_ANIMATIONS_FLAG) {
|
||||
this.disableAnimations(el, !!value);
|
||||
} else {
|
||||
this.engine.process(this.namespaceId, el, name.substr(1), value);
|
||||
}
|
||||
} else {
|
||||
this.delegate.setProperty(el, name, value);
|
||||
}
|
||||
@ -195,11 +217,13 @@ export class AnimationRenderer extends BaseAnimationRenderer implements Renderer
|
||||
|
||||
listen(target: 'window'|'document'|'body'|any, eventName: string, callback: (event: any) => any):
|
||||
() => void {
|
||||
if (eventName.charAt(0) == '@') {
|
||||
if (eventName.charAt(0) == ANIMATION_PREFIX) {
|
||||
const element = resolveElementFromTarget(target);
|
||||
let name = eventName.substr(1);
|
||||
let phase = '';
|
||||
if (name.charAt(0) != '@') { // transition-specific
|
||||
// @listener.phase is for trigger animation callbacks
|
||||
// @@listener is for animation builder callbacks
|
||||
if (name.charAt(0) != ANIMATION_PREFIX) {
|
||||
[name, phase] = parseTriggerCallbackName(name);
|
||||
}
|
||||
return this.engine.listen(this.namespaceId, element, name, phase, event => {
|
||||
|
@ -11,7 +11,7 @@ const xhr2: any = require('xhr2');
|
||||
import {Injectable, Optional, Provider} from '@angular/core';
|
||||
import {BrowserXhr, Connection, ConnectionBackend, Http, ReadyState, Request, RequestOptions, Response, XHRBackend, XSRFStrategy} from '@angular/http';
|
||||
|
||||
import {HttpClient, HttpRequest, HttpHandler, HttpInterceptor, HttpResponse, HTTP_INTERCEPTORS, HttpBackend, XhrFactory, ɵinterceptingHandler as interceptingHandler} from '@angular/common/http';
|
||||
import {HttpClient, HttpEvent, HttpRequest, HttpHandler, HttpInterceptor, HttpResponse, HTTP_INTERCEPTORS, HttpBackend, XhrFactory, ɵinterceptingHandler as interceptingHandler} from '@angular/common/http';
|
||||
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {Observer} from 'rxjs/Observer';
|
||||
@ -143,12 +143,12 @@ export class ZoneMacroTaskBackend implements ConnectionBackend {
|
||||
}
|
||||
|
||||
export class ZoneClientBackend extends
|
||||
ZoneMacroTaskWrapper<HttpRequest<any>, HttpResponse<any>> implements HttpBackend {
|
||||
ZoneMacroTaskWrapper<HttpRequest<any>, HttpEvent<any>> implements HttpBackend {
|
||||
constructor(private backend: HttpBackend) { super(); }
|
||||
|
||||
handle(request: HttpRequest<any>): Observable<HttpResponse<any>> { return this.wrap(request); }
|
||||
handle(request: HttpRequest<any>): Observable<HttpEvent<any>> { return this.wrap(request); }
|
||||
|
||||
protected delegate(request: HttpRequest<any>): Observable<HttpResponse<any>> {
|
||||
protected delegate(request: HttpRequest<any>): Observable<HttpEvent<any>> {
|
||||
return this.backend.handle(request);
|
||||
}
|
||||
}
|
||||
@ -167,7 +167,7 @@ export function zoneWrappedInterceptingHandler(
|
||||
export const SERVER_HTTP_PROVIDERS: Provider[] = [
|
||||
{provide: Http, useFactory: httpFactory, deps: [XHRBackend, RequestOptions]},
|
||||
{provide: BrowserXhr, useClass: ServerXhr}, {provide: XSRFStrategy, useClass: ServerXsrfStrategy},
|
||||
{
|
||||
{provide: XhrFactory, useClass: ServerXhr}, {
|
||||
provide: HttpHandler,
|
||||
useFactory: zoneWrappedInterceptingHandler,
|
||||
deps: [HttpBackend, [new Optional(), HTTP_INTERCEPTORS]]
|
||||
|
@ -18,9 +18,26 @@ import {INITIAL_CONFIG} from './tokens';
|
||||
|
||||
const parse5 = require('parse5');
|
||||
|
||||
/**
|
||||
* Options used to configure the server Platform instance that is created in {@link renderModule}
|
||||
* and {@link renderModuleFactory}.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export interface PlatformOptions {
|
||||
/**
|
||||
* The full document HTML of the page to render as a string.
|
||||
*/
|
||||
document?: string;
|
||||
|
||||
/**
|
||||
* The URL for the current render request.
|
||||
*/
|
||||
url?: string;
|
||||
|
||||
/**
|
||||
* Platform level providers for the current render request.
|
||||
*/
|
||||
extraProviders?: Provider[];
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,8 @@
|
||||
|
||||
import {animate, style, transition, trigger} from '@angular/animations';
|
||||
import {APP_BASE_HREF, PlatformLocation, isPlatformServer} from '@angular/common';
|
||||
import {HttpClient, HttpClientModule} from '@angular/common/http';
|
||||
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
|
||||
import {ApplicationRef, CompilerFactory, Component, HostListener, Input, NgModule, NgModuleRef, NgZone, PLATFORM_ID, PlatformRef, ViewEncapsulation, destroyPlatform, getPlatform} from '@angular/core';
|
||||
import {TestBed, async, inject} from '@angular/core/testing';
|
||||
import {Http, HttpModule, Response, ResponseOptions, XHRBackend} from '@angular/http';
|
||||
@ -145,6 +147,14 @@ export class HttpBeforeExampleModule {
|
||||
export class HttpAfterExampleModule {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
bootstrap: [MyServerApp],
|
||||
declarations: [MyServerApp],
|
||||
imports: [ServerModule, HttpClientModule, HttpClientTestingModule],
|
||||
})
|
||||
export class HttpClientExmapleModule {
|
||||
}
|
||||
|
||||
@Component({selector: 'app', template: `<img [src]="'link'">`})
|
||||
class ImageApp {
|
||||
}
|
||||
@ -534,5 +544,45 @@ export function main() {
|
||||
});
|
||||
}));
|
||||
});
|
||||
describe('HttpClient', () => {
|
||||
it('can inject HttpClient', async(() => {
|
||||
const platform = platformDynamicServer(
|
||||
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
||||
platform.bootstrapModule(HttpClientExmapleModule).then(ref => {
|
||||
expect(ref.injector.get(HttpClient) instanceof HttpClient).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
it('can make HttpClient requests', async(() => {
|
||||
const platform = platformDynamicServer(
|
||||
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
||||
platform.bootstrapModule(HttpClientExmapleModule).then(ref => {
|
||||
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
||||
const http = ref.injector.get(HttpClient);
|
||||
ref.injector.get(NgZone).run(() => {
|
||||
http.get('http://localhost/testing').subscribe(body => {
|
||||
NgZone.assertInAngularZone();
|
||||
expect(body).toEqual('success!');
|
||||
});
|
||||
mock.expectOne('http://localhost/testing').flush('success!');
|
||||
});
|
||||
});
|
||||
}));
|
||||
it('requests are macrotasks', async(() => {
|
||||
const platform = platformDynamicServer(
|
||||
[{provide: INITIAL_CONFIG, useValue: {document: '<app></app>'}}]);
|
||||
platform.bootstrapModule(HttpClientExmapleModule).then(ref => {
|
||||
const mock = ref.injector.get(HttpTestingController) as HttpTestingController;
|
||||
const http = ref.injector.get(HttpClient);
|
||||
ref.injector.get(NgZone).run(() => {
|
||||
http.get('http://localhost/testing').subscribe(body => {
|
||||
expect(body).toEqual('success!');
|
||||
});
|
||||
expect(ref.injector.get(NgZone).hasPendingMacrotasks).toBeTruthy();
|
||||
mock.expectOne('http://localhost/testing').flush('success!');
|
||||
expect(ref.injector.get(NgZone).hasPendingMacrotasks).toBeFalsy();
|
||||
});
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -181,7 +181,9 @@ function match(segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment
|
||||
|
||||
const posParams: {[n: string]: string} = {};
|
||||
forEach(res.posParams !, (v: UrlSegment, k: string) => { posParams[k] = v.path; });
|
||||
const parameters = {...posParams, ...res.consumed[res.consumed.length - 1].parameters};
|
||||
const parameters = res.consumed.length > 0 ?
|
||||
{...posParams, ...res.consumed[res.consumed.length - 1].parameters} :
|
||||
posParams;
|
||||
|
||||
return {consumedSegments: res.consumed, lastChild: res.consumed.length, parameters};
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import {of } from 'rxjs/observable/of';
|
||||
import {concatMap} from 'rxjs/operator/concatMap';
|
||||
import {every} from 'rxjs/operator/every';
|
||||
import {first} from 'rxjs/operator/first';
|
||||
import {last} from 'rxjs/operator/last';
|
||||
import {map} from 'rxjs/operator/map';
|
||||
import {mergeMap} from 'rxjs/operator/mergeMap';
|
||||
import {reduce} from 'rxjs/operator/reduce';
|
||||
@ -312,7 +313,7 @@ export class Router {
|
||||
get events(): Observable<Event> { return this.routerEvents; }
|
||||
|
||||
/** @internal */
|
||||
triggerEvent(e: Event) { this.routerEvents.next(e); }
|
||||
triggerEvent(e: Event): void { this.routerEvents.next(e); }
|
||||
|
||||
/**
|
||||
* Resets the configuration used for navigation and generating links.
|
||||
@ -331,10 +332,11 @@ export class Router {
|
||||
resetConfig(config: Routes): void {
|
||||
validateConfig(config);
|
||||
this.config = config;
|
||||
this.navigated = false;
|
||||
}
|
||||
|
||||
/** @docsNotRequired */
|
||||
ngOnDestroy() { this.dispose(); }
|
||||
ngOnDestroy(): void { this.dispose(); }
|
||||
|
||||
/** Disposes of the router */
|
||||
dispose(): void {
|
||||
@ -837,11 +839,10 @@ export class PreActivation {
|
||||
|
||||
// reusing the node
|
||||
if (curr && future._routeConfig === curr._routeConfig) {
|
||||
if (this.shouldRunGuardsAndResolvers(
|
||||
curr, future, future._routeConfig !.runGuardsAndResolvers)) {
|
||||
const shouldRunGuardsAndResolvers = this.shouldRunGuardsAndResolvers(
|
||||
curr, future, future._routeConfig !.runGuardsAndResolvers);
|
||||
if (shouldRunGuardsAndResolvers) {
|
||||
this.canActivateChecks.push(new CanActivate(futurePath));
|
||||
const outlet = context !.outlet !;
|
||||
this.canDeactivateChecks.push(new CanDeactivate(outlet.component, curr));
|
||||
} else {
|
||||
// we need to set the data
|
||||
future.data = curr.data;
|
||||
@ -857,6 +858,11 @@ export class PreActivation {
|
||||
} else {
|
||||
this.traverseChildRoutes(futureNode, currNode, parentContexts, futurePath);
|
||||
}
|
||||
|
||||
if (shouldRunGuardsAndResolvers) {
|
||||
const outlet = context !.outlet !;
|
||||
this.canDeactivateChecks.push(new CanDeactivate(outlet.component, curr));
|
||||
}
|
||||
} else {
|
||||
if (curr) {
|
||||
this.deactivateRouteAndItsChildren(currNode, context);
|
||||
@ -1004,11 +1010,29 @@ export class PreActivation {
|
||||
}
|
||||
|
||||
private resolveNode(resolve: ResolveData, future: ActivatedRouteSnapshot): Observable<any> {
|
||||
return waitForMap(resolve, (k, v) => {
|
||||
const resolver = this.getToken(v, future);
|
||||
return resolver.resolve ? wrapIntoObservable(resolver.resolve(future, this.future)) :
|
||||
wrapIntoObservable(resolver(future, this.future));
|
||||
const keys = Object.keys(resolve);
|
||||
if (keys.length === 0) {
|
||||
return of ({});
|
||||
}
|
||||
if (keys.length === 1) {
|
||||
const key = keys[0];
|
||||
return map.call(
|
||||
this.getResolver(resolve[key], future), (value: any) => { return {[key]: value}; });
|
||||
}
|
||||
const data: {[k: string]: any} = {};
|
||||
const runningResolvers$ = mergeMap.call(from(keys), (key: string) => {
|
||||
return map.call(this.getResolver(resolve[key], future), (value: any) => {
|
||||
data[key] = value;
|
||||
return value;
|
||||
});
|
||||
});
|
||||
return map.call(last.call(runningResolvers$), () => data);
|
||||
}
|
||||
|
||||
private getResolver(injectionToken: any, future: ActivatedRouteSnapshot): Observable<any> {
|
||||
const resolver = this.getToken(injectionToken, future);
|
||||
return resolver.resolve ? wrapIntoObservable(resolver.resolve(future, this.future)) :
|
||||
wrapIntoObservable(resolver(future, this.future));
|
||||
}
|
||||
|
||||
private getToken(token: any, snapshot: ActivatedRouteSnapshot): any {
|
||||
|
@ -13,6 +13,8 @@ import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
import {ActivatedRoute, ActivatedRouteSnapshot, CanActivate, CanDeactivate, DetachedRouteHandle, Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, PRIMARY_OUTLET, ParamMap, Params, PreloadAllModules, PreloadingStrategy, Resolve, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouteReuseStrategy, Router, RouterModule, RouterPreloader, RouterStateSnapshot, RoutesRecognized, RunGuardsAndResolvers, UrlHandlingStrategy, UrlSegmentGroup, UrlTree} from '@angular/router';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {Observer} from 'rxjs/Observer';
|
||||
import {of } from 'rxjs/observable/of';
|
||||
import {map} from 'rxjs/operator/map';
|
||||
|
||||
import {forEach} from '../src/utils/collection';
|
||||
@ -396,6 +398,25 @@ describe('Integration', () => {
|
||||
expect(location.path()).toEqual('/team/22/user/victor');
|
||||
})));
|
||||
|
||||
it('should navigate to the same url when config changes',
|
||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
|
||||
router.resetConfig([{path: 'a', component: SimpleCmp}]);
|
||||
|
||||
router.navigate(['/a']);
|
||||
advance(fixture);
|
||||
expect(location.path()).toEqual('/a');
|
||||
expect(fixture.nativeElement).toHaveText('simple');
|
||||
|
||||
router.resetConfig([{path: 'a', component: RouteCmp}]);
|
||||
|
||||
router.navigate(['/a']);
|
||||
advance(fixture);
|
||||
expect(location.path()).toEqual('/a');
|
||||
expect(fixture.nativeElement).toHaveText('route');
|
||||
})));
|
||||
|
||||
it('should navigate when locations changes',
|
||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
@ -913,13 +934,12 @@ describe('Integration', () => {
|
||||
{provide: 'resolveFour', useValue: (a: any, b: any) => 4},
|
||||
{provide: 'resolveSix', useClass: ResolveSix},
|
||||
{provide: 'resolveError', useValue: (a: any, b: any) => Promise.reject('error')},
|
||||
{provide: 'numberOfUrlSegments', useValue: (a: any, b: any) => a.url.length}
|
||||
{provide: 'numberOfUrlSegments', useValue: (a: any, b: any) => a.url.length},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it('should provide resolved data',
|
||||
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
|
||||
it('should provide resolved data', fakeAsync(inject([Router], (router: Router) => {
|
||||
const fixture = createRoot(router, RootCmpWithTwoOutlets);
|
||||
|
||||
router.resetConfig([{
|
||||
@ -1025,6 +1045,57 @@ describe('Integration', () => {
|
||||
|
||||
expect(cmp.route.snapshot.data).toEqual({numberOfUrlSegments: 3});
|
||||
})));
|
||||
|
||||
describe('should run resolvers for the same route concurrently', () => {
|
||||
let log: string[];
|
||||
let observer: Observer<any>;
|
||||
|
||||
beforeEach(() => {
|
||||
log = [];
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{
|
||||
provide: 'resolver1',
|
||||
useValue: () => {
|
||||
const obs$ = new Observable((obs: Observer<any>) => {
|
||||
observer = obs;
|
||||
return () => {};
|
||||
});
|
||||
return map.call(obs$, () => log.push('resolver1'));
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: 'resolver2',
|
||||
useValue: () => {
|
||||
return map.call(of (null), () => {
|
||||
log.push('resolver2');
|
||||
observer.next(null);
|
||||
observer.complete()
|
||||
});
|
||||
}
|
||||
},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it('works', fakeAsync(inject([Router], (router: Router) => {
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
|
||||
router.resetConfig([{
|
||||
path: 'a',
|
||||
resolve: {
|
||||
one: 'resolver1',
|
||||
two: 'resolver2',
|
||||
},
|
||||
component: SimpleCmp
|
||||
}]);
|
||||
|
||||
router.navigateByUrl('/a');
|
||||
advance(fixture);
|
||||
|
||||
expect(log).toEqual(['resolver2', 'resolver1']);
|
||||
})));
|
||||
});
|
||||
});
|
||||
|
||||
describe('router links', () => {
|
||||
@ -2364,6 +2435,11 @@ describe('Integration', () => {
|
||||
provide: 'canDeactivate_team',
|
||||
useFactory: (logger: Logger) => () => (logger.add('canDeactivate_team'), true),
|
||||
deps: [Logger]
|
||||
},
|
||||
{
|
||||
provide: 'canDeactivate_simple',
|
||||
useFactory: (logger: Logger) => () => (logger.add('canDeactivate_simple'), true),
|
||||
deps: [Logger]
|
||||
}
|
||||
]
|
||||
});
|
||||
@ -2397,6 +2473,31 @@ describe('Integration', () => {
|
||||
'canDeactivate_team', 'canActivateChild_parent', 'canActivate_team'
|
||||
]);
|
||||
})));
|
||||
|
||||
it('should call deactivate guards from bottom to top',
|
||||
fakeAsync(inject(
|
||||
[Router, Location, Logger], (router: Router, location: Location, logger: Logger) => {
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
|
||||
router.resetConfig([{
|
||||
path: '',
|
||||
children: [{
|
||||
path: 'team/:id',
|
||||
canDeactivate: ['canDeactivate_team'],
|
||||
children:
|
||||
[{path: '', component: SimpleCmp, canDeactivate: ['canDeactivate_simple']}],
|
||||
component: TeamCmp
|
||||
}]
|
||||
}]);
|
||||
|
||||
router.navigateByUrl('/team/22');
|
||||
advance(fixture);
|
||||
|
||||
router.navigateByUrl('/team/33');
|
||||
advance(fixture);
|
||||
|
||||
expect(logger.logs).toEqual(['canDeactivate_simple', 'canDeactivate_team']);
|
||||
})));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -658,6 +658,26 @@ describe('recognize', () => {
|
||||
checkActivatedRoute(a.firstChild !, 'b', {}, ComponentB);
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with terminal route', () => {
|
||||
const matcher = (s: any, g: any, r: any) => s.length === 0 ? ({consumed: s}) : null;
|
||||
|
||||
checkRecognize([{matcher, component: ComponentA}] as any, '', (s: RouterStateSnapshot) => {
|
||||
const a = s.root.firstChild !;
|
||||
checkActivatedRoute(a, '', {}, ComponentA);
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with child terminal route', () => {
|
||||
const matcher = (s: any, g: any, r: any) => s.length === 0 ? ({consumed: s}) : null;
|
||||
|
||||
checkRecognize(
|
||||
[{path: 'a', component: ComponentA, children: [{matcher, component: ComponentB}]}] as any,
|
||||
'a', (s: RouterStateSnapshot) => {
|
||||
const a = s.root.firstChild !;
|
||||
checkActivatedRoute(a, 'a', {}, ComponentA);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('query parameters', () => {
|
||||
|
@ -50,7 +50,7 @@ export interface IRootScopeService {
|
||||
$destroy(): any;
|
||||
$apply(exp?: Ng1Expression): any;
|
||||
$digest(): any;
|
||||
$evalAsync(): any;
|
||||
$evalAsync(exp: Ng1Expression, locals?: any): void;
|
||||
$on(event: string, fn?: (event?: any, ...args: any[]) => void): Function;
|
||||
$$childTail: IScope;
|
||||
$$childHead: IScope;
|
||||
@ -101,7 +101,10 @@ export interface IComponent {
|
||||
templateUrl?: string|Function;
|
||||
transclude?: DirectiveTranscludeProperty;
|
||||
}
|
||||
export interface IAttributes { $observe(attr: string, fn: (v: string) => void): void; }
|
||||
export interface IAttributes {
|
||||
$observe(attr: string, fn: (v: string) => void): void;
|
||||
[key: string]: any;
|
||||
}
|
||||
export interface ITranscludeFunction {
|
||||
// If the scope is provided, then the cloneAttachFn must be as well.
|
||||
(scope: IScope, cloneAttachFn: ICloneAttachFunction): IAugmentedJQuery;
|
||||
@ -135,7 +138,10 @@ export interface IProvideService {
|
||||
decorator(token: Ng1Token, factory: IInjectable): void;
|
||||
}
|
||||
export interface IParseService { (expression: string): ICompiledExpression; }
|
||||
export interface ICompiledExpression { assign(context: any, value: any): any; }
|
||||
export interface ICompiledExpression {
|
||||
(context: any, locals: any): any;
|
||||
assign?: (context: any, value: any) => any;
|
||||
}
|
||||
export interface IHttpBackendService {
|
||||
(method: string, url: string, post?: any, callback?: Function, headers?: any, timeout?: number,
|
||||
withCredentials?: boolean): void;
|
||||
@ -211,8 +217,8 @@ function noNg() {
|
||||
|
||||
|
||||
let angular: {
|
||||
bootstrap: (e: Element, modules: (string | IInjectable)[], config: IAngularBootstrapConfig) =>
|
||||
void,
|
||||
bootstrap: (e: Element, modules: (string | IInjectable)[], config?: IAngularBootstrapConfig) =>
|
||||
IInjectorService,
|
||||
module: (prefix: string, dependencies?: string[]) => IModule,
|
||||
element: (e: Element | string) => IAugmentedJQuery,
|
||||
version: {major: number},
|
||||
@ -256,16 +262,16 @@ export function getAngularLib(): any {
|
||||
}
|
||||
|
||||
export const bootstrap =
|
||||
(e: Element, modules: (string | IInjectable)[], config: IAngularBootstrapConfig): void =>
|
||||
(e: Element, modules: (string | IInjectable)[], config?: IAngularBootstrapConfig) =>
|
||||
angular.bootstrap(e, modules, config);
|
||||
|
||||
export const module = (prefix: string, dependencies?: string[]): IModule =>
|
||||
export const module = (prefix: string, dependencies?: string[]) =>
|
||||
angular.module(prefix, dependencies);
|
||||
|
||||
export const element = (e: Element | string): IAugmentedJQuery => angular.element(e);
|
||||
export const element = (e: Element | string) => angular.element(e);
|
||||
|
||||
export const resumeBootstrap = (): void => angular.resumeBootstrap();
|
||||
export const resumeBootstrap = () => angular.resumeBootstrap();
|
||||
|
||||
export const getTestability = (e: Element): ITestabilityService => angular.getTestability(e);
|
||||
export const getTestability = (e: Element) => angular.getTestability(e);
|
||||
|
||||
export const version = angular.version;
|
||||
|
@ -24,6 +24,7 @@ export const $$TESTABILITY = '$$testability';
|
||||
export const COMPILER_KEY = '$$angularCompiler';
|
||||
export const GROUP_PROJECTABLE_NODES_KEY = '$$angularGroupProjectableNodes';
|
||||
export const INJECTOR_KEY = '$$angularInjector';
|
||||
export const LAZY_MODULE_REF = '$$angularLazyModuleRef';
|
||||
export const NG_ZONE_KEY = '$$angularNgZone';
|
||||
|
||||
export const REQUIRE_INJECTOR = '?^^' + INJECTOR_KEY;
|
||||
|
@ -9,9 +9,14 @@
|
||||
import {ComponentFactory, ComponentFactoryResolver, Injector, Type} from '@angular/core';
|
||||
|
||||
import * as angular from './angular1';
|
||||
import {$COMPILE, $INJECTOR, $PARSE, INJECTOR_KEY, REQUIRE_INJECTOR, REQUIRE_NG_MODEL} from './constants';
|
||||
import {$COMPILE, $INJECTOR, $PARSE, INJECTOR_KEY, LAZY_MODULE_REF, REQUIRE_INJECTOR, REQUIRE_NG_MODEL} from './constants';
|
||||
import {DowngradeComponentAdapter} from './downgrade_component_adapter';
|
||||
import {controllerKey, getComponentName} from './util';
|
||||
import {LazyModuleRef, controllerKey, getComponentName, isFunction} from './util';
|
||||
|
||||
|
||||
interface Thenable<T> {
|
||||
then(callback: (value: T) => any): any;
|
||||
}
|
||||
|
||||
let downgradeCount = 0;
|
||||
|
||||
@ -50,6 +55,8 @@ let downgradeCount = 0;
|
||||
*/
|
||||
export function downgradeComponent(info: {
|
||||
component: Type<any>;
|
||||
/** @experimental */
|
||||
propagateDigest?: boolean;
|
||||
/** @deprecated since v4. This parameter is no longer used */
|
||||
inputs?: string[];
|
||||
/** @deprecated since v4. This parameter is no longer used */
|
||||
@ -76,9 +83,14 @@ export function downgradeComponent(info: {
|
||||
// triggered by `UpgradeNg1ComponentAdapterBuilder`, before the Angular templates have
|
||||
// been compiled.
|
||||
|
||||
const parentInjector: Injector|ParentInjectorPromise =
|
||||
required[0] || $injector.get(INJECTOR_KEY);
|
||||
const ngModel: angular.INgModelController = required[1];
|
||||
let parentInjector: Injector|Thenable<Injector>|undefined = required[0];
|
||||
let ranAsync = false;
|
||||
|
||||
if (!parentInjector) {
|
||||
const lazyModuleRef = $injector.get(LAZY_MODULE_REF) as LazyModuleRef;
|
||||
parentInjector = lazyModuleRef.injector || lazyModuleRef.promise;
|
||||
}
|
||||
|
||||
const downgradeFn = (injector: Injector) => {
|
||||
const componentFactoryResolver: ComponentFactoryResolver =
|
||||
@ -98,18 +110,26 @@ export function downgradeComponent(info: {
|
||||
|
||||
const projectableNodes = facade.compileContents();
|
||||
facade.createComponent(projectableNodes);
|
||||
facade.setupInputs();
|
||||
facade.setupInputs(info.propagateDigest);
|
||||
facade.setupOutputs();
|
||||
facade.registerCleanup();
|
||||
|
||||
injectorPromise.resolve(facade.getInjector());
|
||||
|
||||
if (ranAsync) {
|
||||
// If this is run async, it is possible that it is not run inside a
|
||||
// digest and initial input values will not be detected.
|
||||
scope.$evalAsync(() => {});
|
||||
}
|
||||
};
|
||||
|
||||
if (parentInjector instanceof ParentInjectorPromise) {
|
||||
if (isThenable<Injector>(parentInjector)) {
|
||||
parentInjector.then(downgradeFn);
|
||||
} else {
|
||||
downgradeFn(parentInjector);
|
||||
}
|
||||
|
||||
ranAsync = true;
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -155,3 +175,7 @@ class ParentInjectorPromise {
|
||||
this.callbacks.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function isThenable<T>(obj: object): obj is Thenable<T> {
|
||||
return isFunction((obj as any).then);
|
||||
}
|
||||
|
@ -18,8 +18,9 @@ const INITIAL_VALUE = {
|
||||
};
|
||||
|
||||
export class DowngradeComponentAdapter {
|
||||
private implementsOnChanges = false;
|
||||
private inputChangeCount: number = 0;
|
||||
private inputChanges: SimpleChanges|null = null;
|
||||
private inputChanges: SimpleChanges = {};
|
||||
private componentScope: angular.IScope;
|
||||
private componentRef: ComponentRef<any>|null = null;
|
||||
private component: any = null;
|
||||
@ -64,12 +65,12 @@ export class DowngradeComponentAdapter {
|
||||
hookupNgModel(this.ngModel, this.component);
|
||||
}
|
||||
|
||||
setupInputs(): void {
|
||||
setupInputs(propagateDigest = true): void {
|
||||
const attrs = this.attrs;
|
||||
const inputs = this.componentFactory.inputs || [];
|
||||
for (let i = 0; i < inputs.length; i++) {
|
||||
const input = new PropertyBinding(inputs[i].propName, inputs[i].templateName);
|
||||
let expr: any /** TODO #9100 */ = null;
|
||||
let expr: string|null = null;
|
||||
|
||||
if (attrs.hasOwnProperty(input.attr)) {
|
||||
const observeFn = (prop => {
|
||||
@ -91,20 +92,20 @@ export class DowngradeComponentAdapter {
|
||||
// Use `$watch()` (in addition to `$observe()`) in order to initialize the input in time
|
||||
// for `ngOnChanges()`. This is necessary if we are already in a `$digest`, which means that
|
||||
// `ngOnChanges()` (which is called by a watcher) will run before the `$observe()` callback.
|
||||
let unwatch: any = this.componentScope.$watch(() => {
|
||||
unwatch('');
|
||||
let unwatch: Function|null = this.componentScope.$watch(() => {
|
||||
unwatch !();
|
||||
unwatch = null;
|
||||
observeFn((attrs as any)[input.attr]);
|
||||
observeFn(attrs[input.attr]);
|
||||
});
|
||||
|
||||
} else if (attrs.hasOwnProperty(input.bindAttr)) {
|
||||
expr = (attrs as any /** TODO #9100 */)[input.bindAttr];
|
||||
expr = attrs[input.bindAttr];
|
||||
} else if (attrs.hasOwnProperty(input.bracketAttr)) {
|
||||
expr = (attrs as any /** TODO #9100 */)[input.bracketAttr];
|
||||
expr = attrs[input.bracketAttr];
|
||||
} else if (attrs.hasOwnProperty(input.bindonAttr)) {
|
||||
expr = (attrs as any /** TODO #9100 */)[input.bindonAttr];
|
||||
expr = attrs[input.bindonAttr];
|
||||
} else if (attrs.hasOwnProperty(input.bracketParenAttr)) {
|
||||
expr = (attrs as any /** TODO #9100 */)[input.bracketParenAttr];
|
||||
expr = attrs[input.bracketParenAttr];
|
||||
}
|
||||
if (expr != null) {
|
||||
const watchFn =
|
||||
@ -114,17 +115,29 @@ export class DowngradeComponentAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke `ngOnChanges()` and Change Detection (when necessary)
|
||||
const detectChanges = () => this.changeDetector && this.changeDetector.detectChanges();
|
||||
const prototype = this.componentFactory.componentType.prototype;
|
||||
if (prototype && (<OnChanges>prototype).ngOnChanges) {
|
||||
// Detect: OnChanges interface
|
||||
this.inputChanges = {};
|
||||
this.componentScope.$watch(() => this.inputChangeCount, () => {
|
||||
this.implementsOnChanges = !!(prototype && (<OnChanges>prototype).ngOnChanges);
|
||||
|
||||
this.componentScope.$watch(() => this.inputChangeCount, () => {
|
||||
// Invoke `ngOnChanges()`
|
||||
if (this.implementsOnChanges) {
|
||||
const inputChanges = this.inputChanges;
|
||||
this.inputChanges = {};
|
||||
(<OnChanges>this.component).ngOnChanges(inputChanges !);
|
||||
});
|
||||
}
|
||||
|
||||
// If opted out of propagating digests, invoke change detection when inputs change
|
||||
if (!propagateDigest) {
|
||||
detectChanges();
|
||||
}
|
||||
});
|
||||
|
||||
// If not opted out of propagating digests, invoke change detection on every digest
|
||||
if (propagateDigest) {
|
||||
this.componentScope.$watch(detectChanges);
|
||||
}
|
||||
this.componentScope.$watch(() => this.changeDetector && this.changeDetector.detectChanges());
|
||||
}
|
||||
|
||||
setupOutputs() {
|
||||
@ -132,24 +145,22 @@ export class DowngradeComponentAdapter {
|
||||
const outputs = this.componentFactory.outputs || [];
|
||||
for (let j = 0; j < outputs.length; j++) {
|
||||
const output = new PropertyBinding(outputs[j].propName, outputs[j].templateName);
|
||||
let expr: any /** TODO #9100 */ = null;
|
||||
let expr: string|null = null;
|
||||
let assignExpr = false;
|
||||
|
||||
const bindonAttr =
|
||||
output.bindonAttr ? output.bindonAttr.substring(0, output.bindonAttr.length - 6) : null;
|
||||
const bracketParenAttr = output.bracketParenAttr ?
|
||||
`[(${output.bracketParenAttr.substring(2, output.bracketParenAttr.length - 8)})]` :
|
||||
null;
|
||||
const bindonAttr = output.bindonAttr.substring(0, output.bindonAttr.length - 6);
|
||||
const bracketParenAttr =
|
||||
`[(${output.bracketParenAttr.substring(2, output.bracketParenAttr.length - 8)})]`;
|
||||
|
||||
if (attrs.hasOwnProperty(output.onAttr)) {
|
||||
expr = (attrs as any /** TODO #9100 */)[output.onAttr];
|
||||
expr = attrs[output.onAttr];
|
||||
} else if (attrs.hasOwnProperty(output.parenAttr)) {
|
||||
expr = (attrs as any /** TODO #9100 */)[output.parenAttr];
|
||||
} else if (attrs.hasOwnProperty(bindonAttr !)) {
|
||||
expr = (attrs as any /** TODO #9100 */)[bindonAttr !];
|
||||
expr = attrs[output.parenAttr];
|
||||
} else if (attrs.hasOwnProperty(bindonAttr)) {
|
||||
expr = attrs[bindonAttr];
|
||||
assignExpr = true;
|
||||
} else if (attrs.hasOwnProperty(bracketParenAttr !)) {
|
||||
expr = (attrs as any /** TODO #9100 */)[bracketParenAttr !];
|
||||
} else if (attrs.hasOwnProperty(bracketParenAttr)) {
|
||||
expr = attrs[bracketParenAttr];
|
||||
assignExpr = true;
|
||||
}
|
||||
|
||||
@ -162,10 +173,8 @@ export class DowngradeComponentAdapter {
|
||||
const emitter = this.component[output.prop] as EventEmitter<any>;
|
||||
if (emitter) {
|
||||
emitter.subscribe({
|
||||
next: assignExpr ?
|
||||
((setter: any) => (v: any /** TODO #9100 */) => setter(this.scope, v))(setter) :
|
||||
((getter: any) => (v: any /** TODO #9100 */) =>
|
||||
getter(this.scope, {'$event': v}))(getter)
|
||||
next: assignExpr ? (v: any) => setter !(this.scope, v) :
|
||||
(v: any) => getter(this.scope, {'$event': v})
|
||||
});
|
||||
} else {
|
||||
throw new Error(
|
||||
@ -185,11 +194,11 @@ export class DowngradeComponentAdapter {
|
||||
getInjector(): Injector { return this.componentRef ! && this.componentRef !.injector; }
|
||||
|
||||
private updateInput(prop: string, prevValue: any, currValue: any) {
|
||||
if (this.inputChanges) {
|
||||
this.inputChangeCount++;
|
||||
if (this.implementsOnChanges) {
|
||||
this.inputChanges[prop] = new SimpleChange(prevValue, currValue, prevValue === currValue);
|
||||
}
|
||||
|
||||
this.inputChangeCount++;
|
||||
this.component[prop] = currValue;
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Type} from '@angular/core';
|
||||
import {Injector, Type} from '@angular/core';
|
||||
import * as angular from './angular1';
|
||||
|
||||
const DIRECTIVE_PREFIX_REGEXP = /^(?:x|data)[:\-_]/i;
|
||||
@ -67,6 +67,11 @@ export class Deferred<R> {
|
||||
}
|
||||
}
|
||||
|
||||
export interface LazyModuleRef {
|
||||
injector?: Injector;
|
||||
promise: Promise<Injector>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the passed-in component implements the subset of the
|
||||
* `ControlValueAccessor` interface needed for AngularJS `ng-model`
|
||||
@ -85,6 +90,9 @@ export function hookupNgModel(ngModel: angular.INgModelController, component: an
|
||||
if (ngModel && supportsNgModel(component)) {
|
||||
ngModel.$render = () => { component.writeValue(ngModel.$viewValue); };
|
||||
component.registerOnChange(ngModel.$setViewValue.bind(ngModel));
|
||||
if (typeof component.registerOnTouched === 'function') {
|
||||
component.registerOnTouched(ngModel.$setTouched.bind(ngModel));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ import {Compiler, CompilerOptions, Directive, Injector, NgModule, NgModuleRef, N
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
|
||||
import * as angular from '../common/angular1';
|
||||
import {$$TESTABILITY, $COMPILE, $INJECTOR, $ROOT_SCOPE, COMPILER_KEY, INJECTOR_KEY, NG_ZONE_KEY} from '../common/constants';
|
||||
import {$$TESTABILITY, $COMPILE, $INJECTOR, $ROOT_SCOPE, COMPILER_KEY, INJECTOR_KEY, LAZY_MODULE_REF, NG_ZONE_KEY} from '../common/constants';
|
||||
import {downgradeComponent} from '../common/downgrade_component';
|
||||
import {downgradeInjectable} from '../common/downgrade_injectable';
|
||||
import {Deferred, controllerKey, onError} from '../common/util';
|
||||
@ -495,6 +495,12 @@ export class UpgradeAdapter {
|
||||
this.ngZone = new NgZone({enableLongStackTrace: Zone.hasOwnProperty('longStackTraceZoneSpec')});
|
||||
this.ng2BootstrapDeferred = new Deferred();
|
||||
ng1Module.factory(INJECTOR_KEY, () => this.moduleRef !.injector.get(Injector))
|
||||
.factory(
|
||||
LAZY_MODULE_REF,
|
||||
[
|
||||
INJECTOR_KEY,
|
||||
(injector: Injector) => ({injector, promise: Promise.resolve(injector)})
|
||||
])
|
||||
.constant(NG_ZONE_KEY, this.ngZone)
|
||||
.factory(COMPILER_KEY, () => this.moduleRef !.injector.get(Compiler))
|
||||
.config([
|
||||
|
59
packages/upgrade/src/static/downgrade_module.ts
Normal file
59
packages/upgrade/src/static/downgrade_module.ts
Normal file
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Injector, NgModuleFactory, NgModuleRef, Provider} from '@angular/core';
|
||||
import {platformBrowser} from '@angular/platform-browser';
|
||||
|
||||
import * as angular from '../common/angular1';
|
||||
import {$INJECTOR, INJECTOR_KEY, LAZY_MODULE_REF, UPGRADE_MODULE_NAME} from '../common/constants';
|
||||
import {LazyModuleRef, isFunction} from '../common/util';
|
||||
|
||||
import {angular1Providers, setTempInjectorRef} from './angular1_providers';
|
||||
import {NgAdapterInjector} from './util';
|
||||
|
||||
|
||||
/** @experimental */
|
||||
export function downgradeModule<T>(
|
||||
moduleFactoryOrBootstrapFn: NgModuleFactory<T>|
|
||||
((extraProviders: Provider[]) => Promise<NgModuleRef<T>>)): string {
|
||||
const LAZY_MODULE_NAME = UPGRADE_MODULE_NAME + '.lazy';
|
||||
const bootstrapFn = isFunction(moduleFactoryOrBootstrapFn) ?
|
||||
moduleFactoryOrBootstrapFn :
|
||||
(extraProviders: Provider[]) =>
|
||||
platformBrowser(extraProviders).bootstrapModuleFactory(moduleFactoryOrBootstrapFn);
|
||||
|
||||
let injector: Injector;
|
||||
|
||||
// Create an ng1 module to bootstrap.
|
||||
angular.module(LAZY_MODULE_NAME, [])
|
||||
.factory(
|
||||
INJECTOR_KEY,
|
||||
() => {
|
||||
if (!injector) {
|
||||
throw new Error('The Angular module has not been bootstrapped yet.');
|
||||
}
|
||||
return injector;
|
||||
})
|
||||
.factory(LAZY_MODULE_REF, [
|
||||
$INJECTOR,
|
||||
($injector: angular.IInjectorService) => {
|
||||
setTempInjectorRef($injector);
|
||||
const result: LazyModuleRef = {
|
||||
promise: bootstrapFn(angular1Providers).then(ref => {
|
||||
injector = result.injector = new NgAdapterInjector(ref.injector);
|
||||
injector.get($INJECTOR);
|
||||
|
||||
return injector;
|
||||
})
|
||||
};
|
||||
return result;
|
||||
}
|
||||
]);
|
||||
|
||||
return LAZY_MODULE_NAME;
|
||||
}
|
@ -6,13 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Injector, NgModule, NgZone, Testability, ɵNOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from '@angular/core';
|
||||
import {Injector, NgModule, NgZone, Testability} from '@angular/core';
|
||||
|
||||
import * as angular from '../common/angular1';
|
||||
import {$$TESTABILITY, $DELEGATE, $INJECTOR, $INTERVAL, $PROVIDE, INJECTOR_KEY, UPGRADE_MODULE_NAME} from '../common/constants';
|
||||
import {$$TESTABILITY, $DELEGATE, $INJECTOR, $INTERVAL, $PROVIDE, INJECTOR_KEY, LAZY_MODULE_REF, UPGRADE_MODULE_NAME} from '../common/constants';
|
||||
import {controllerKey} from '../common/util';
|
||||
|
||||
import {angular1Providers, setTempInjectorRef} from './angular1_providers';
|
||||
import {NgAdapterInjector} from './util';
|
||||
|
||||
|
||||
/**
|
||||
@ -163,6 +164,13 @@ export class UpgradeModule {
|
||||
|
||||
.value(INJECTOR_KEY, this.injector)
|
||||
|
||||
.factory(
|
||||
LAZY_MODULE_REF,
|
||||
[
|
||||
INJECTOR_KEY,
|
||||
(injector: Injector) => ({injector, promise: Promise.resolve(injector)})
|
||||
])
|
||||
|
||||
.config([
|
||||
$PROVIDE, $INJECTOR,
|
||||
($provide: angular.IProvideService, $injector: angular.IInjectorService) => {
|
||||
@ -247,7 +255,7 @@ export class UpgradeModule {
|
||||
const upgradeModule = angular.module(UPGRADE_MODULE_NAME, [INIT_MODULE_NAME].concat(modules));
|
||||
|
||||
// Make sure resumeBootstrap() only exists if the current bootstrap is deferred
|
||||
const windowAngular = (window as any /** TODO #???? */)['angular'];
|
||||
const windowAngular = (window as any)['angular'];
|
||||
windowAngular.resumeBootstrap = undefined;
|
||||
|
||||
// Bootstrap the AngularJS application inside our zone
|
||||
@ -265,19 +273,3 @@ export class UpgradeModule {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NgAdapterInjector implements Injector {
|
||||
constructor(private modInjector: Injector) {}
|
||||
|
||||
// When Angular locate a service in the component injector tree, the not found value is set to
|
||||
// `NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR`. In such a case we should not walk up to the module
|
||||
// injector.
|
||||
// AngularJS only supports a single tree and should always check the module injector.
|
||||
get(token: any, notFoundValue?: any): any {
|
||||
if (notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) {
|
||||
return notFoundValue;
|
||||
}
|
||||
|
||||
return this.modInjector.get(token, notFoundValue);
|
||||
}
|
||||
}
|
26
packages/upgrade/src/static/util.ts
Normal file
26
packages/upgrade/src/static/util.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Injector, ɵNOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR as NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR} from '@angular/core';
|
||||
|
||||
|
||||
export class NgAdapterInjector implements Injector {
|
||||
constructor(private modInjector: Injector) {}
|
||||
|
||||
// When Angular locate a service in the component injector tree, the not found value is set to
|
||||
// `NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR`. In such a case we should not walk up to the module
|
||||
// injector.
|
||||
// AngularJS only supports a single tree and should always check the module injector.
|
||||
get(token: any, notFoundValue?: any): any {
|
||||
if (notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) {
|
||||
return notFoundValue;
|
||||
}
|
||||
|
||||
return this.modInjector.get(token, notFoundValue);
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ export {getAngularLib, setAngularLib} from './src/common/angular1';
|
||||
export {downgradeComponent} from './src/common/downgrade_component';
|
||||
export {downgradeInjectable} from './src/common/downgrade_injectable';
|
||||
export {VERSION} from './src/common/version';
|
||||
export {downgradeModule} from './src/static/downgrade_module';
|
||||
export {UpgradeComponent} from './src/static/upgrade_component';
|
||||
export {UpgradeModule} from './src/static/upgrade_module';
|
||||
|
||||
|
@ -478,9 +478,12 @@ export function main() {
|
||||
class Ng2 {
|
||||
private _value: any = '';
|
||||
private _onChangeCallback: (_: any) => void = () => {};
|
||||
private _onTouchedCallback: () => void = () => {};
|
||||
constructor() { ng2Instance = this; }
|
||||
writeValue(value: any) { this._value = value; }
|
||||
registerOnChange(fn: any) { this._onChangeCallback = fn; }
|
||||
registerOnTouched(fn: any) { this._onTouchedCallback = fn; }
|
||||
doTouch() { this._onTouchedCallback(); }
|
||||
doChange(newValue: string) {
|
||||
this._value = newValue;
|
||||
this._onChangeCallback(newValue);
|
||||
@ -509,6 +512,13 @@ export function main() {
|
||||
expect($rootScope.modelA).toBe('C');
|
||||
expect(multiTrim(document.body.textContent)).toEqual('C | C');
|
||||
|
||||
const downgradedElement = <Element>document.body.querySelector('ng2');
|
||||
expect(downgradedElement.classList.contains('ng-touched')).toBe(false);
|
||||
|
||||
ng2Instance.doTouch();
|
||||
$rootScope.$apply();
|
||||
expect(downgradedElement.classList.contains('ng-touched')).toBe(true);
|
||||
|
||||
ref.dispose();
|
||||
});
|
||||
}));
|
||||
|
@ -21,8 +21,8 @@ export function main() {
|
||||
afterEach(() => destroyPlatform());
|
||||
|
||||
it('should interleave scope and component expressions', async(() => {
|
||||
const log: any[] /** TODO #9100 */ = [];
|
||||
const l = (value: any /** TODO #9100 */) => {
|
||||
const log: string[] = [];
|
||||
const l = (value: string) => {
|
||||
log.push(value);
|
||||
return value + ';';
|
||||
};
|
||||
@ -46,8 +46,7 @@ export function main() {
|
||||
template: `{{l('2A')}}<ng1a></ng1a>{{l('2B')}}<ng1b></ng1b>{{l('2C')}}`
|
||||
})
|
||||
class Ng2Component {
|
||||
l: (value: any) => string;
|
||||
constructor() { this.l = l; }
|
||||
l = l;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
@ -63,7 +62,7 @@ export function main() {
|
||||
.directive('ng1a', () => ({template: '{{ l(\'ng1a\') }}'}))
|
||||
.directive('ng1b', () => ({template: '{{ l(\'ng1b\') }}'}))
|
||||
.directive('ng2', downgradeComponent({component: Ng2Component}))
|
||||
.run(($rootScope: any /** TODO #9100 */) => {
|
||||
.run(($rootScope: angular.IRootScopeService) => {
|
||||
$rootScope.l = l;
|
||||
$rootScope.reset = () => log.length = 0;
|
||||
});
|
||||
@ -72,7 +71,6 @@ export function main() {
|
||||
html('<div>{{reset(); l(\'1A\');}}<ng2>{{l(\'1B\')}}</ng2>{{l(\'1C\')}}</div>');
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => {
|
||||
expect(document.body.textContent).toEqual('1A;2A;ng1a;2B;ng1b;2C;1C;');
|
||||
// https://github.com/angular/angular.js/issues/12983
|
||||
expect(log).toEqual(['1A', '1C', '2A', '2B', '2C', 'ng1a', 'ng1b']);
|
||||
});
|
||||
}));
|
||||
@ -88,7 +86,7 @@ export function main() {
|
||||
|
||||
@Component({
|
||||
selector: 'my-child',
|
||||
template: '<div>{{valueFromPromise}}',
|
||||
template: '<div>{{ valueFromPromise }}</div>',
|
||||
})
|
||||
class ChildComponent {
|
||||
valueFromPromise: number;
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Compiler, Component, ComponentFactoryResolver, EventEmitter, Injector, Input, NgModule, NgModuleRef, OnChanges, OnDestroy, SimpleChanges, destroyPlatform} from '@angular/core';
|
||||
import {ChangeDetectorRef, Compiler, Component, ComponentFactoryResolver, EventEmitter, Injector, Input, NgModule, NgModuleRef, OnChanges, OnDestroy, SimpleChanges, destroyPlatform} from '@angular/core';
|
||||
import {async} from '@angular/core/testing';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
@ -148,6 +148,132 @@ export function main() {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should run change-detection on every digest (by default)', async(() => {
|
||||
let ng2Component: Ng2Component;
|
||||
|
||||
@Component({selector: 'ng2', template: '{{ value1 }} | {{ value2 }}'})
|
||||
class Ng2Component {
|
||||
@Input() value1 = -1;
|
||||
@Input() value2 = -1;
|
||||
|
||||
constructor() { ng2Component = this; }
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [BrowserModule, UpgradeModule],
|
||||
declarations: [Ng2Component],
|
||||
entryComponents: [Ng2Component]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const ng1Module = angular.module('ng1', [])
|
||||
.directive('ng2', downgradeComponent({component: Ng2Component}))
|
||||
.run(($rootScope: angular.IRootScopeService) => {
|
||||
$rootScope.value1 = 0;
|
||||
$rootScope.value2 = 0;
|
||||
});
|
||||
|
||||
const element = html('<ng2 [value1]="value1" value2="{{ value2 }}"></ng2>');
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
|
||||
const $rootScope = upgrade.$injector.get('$rootScope') as angular.IRootScopeService;
|
||||
|
||||
expect(element.textContent).toBe('0 | 0');
|
||||
|
||||
// Digest should invoke CD
|
||||
$rootScope.$digest();
|
||||
$rootScope.$digest();
|
||||
expect(element.textContent).toBe('0 | 0');
|
||||
|
||||
// Internal changes should be detected on digest
|
||||
ng2Component.value1 = 1;
|
||||
ng2Component.value2 = 2;
|
||||
$rootScope.$digest();
|
||||
expect(element.textContent).toBe('1 | 2');
|
||||
|
||||
// Digest should propagate change in prop-bound input
|
||||
$rootScope.$apply('value1 = 3');
|
||||
expect(element.textContent).toBe('3 | 2');
|
||||
|
||||
// Digest should propagate change in attr-bound input
|
||||
ng2Component.value1 = 4;
|
||||
$rootScope.$apply('value2 = 5');
|
||||
expect(element.textContent).toBe('4 | 5');
|
||||
|
||||
// Digest should propagate changes that happened before the digest
|
||||
$rootScope.value1 = 6;
|
||||
expect(element.textContent).toBe('4 | 5');
|
||||
|
||||
$rootScope.$digest();
|
||||
expect(element.textContent).toBe('6 | 5');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should not run change-detection on every digest when opted out', async(() => {
|
||||
let ng2Component: Ng2Component;
|
||||
|
||||
@Component({selector: 'ng2', template: '{{ value1 }} | {{ value2 }}'})
|
||||
class Ng2Component {
|
||||
@Input() value1 = -1;
|
||||
@Input() value2 = -1;
|
||||
|
||||
constructor() { ng2Component = this; }
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [BrowserModule, UpgradeModule],
|
||||
declarations: [Ng2Component],
|
||||
entryComponents: [Ng2Component]
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const ng1Module =
|
||||
angular.module('ng1', [])
|
||||
.directive(
|
||||
'ng2', downgradeComponent({component: Ng2Component, propagateDigest: false}))
|
||||
.run(($rootScope: angular.IRootScopeService) => {
|
||||
$rootScope.value1 = 0;
|
||||
$rootScope.value2 = 0;
|
||||
});
|
||||
|
||||
const element = html('<ng2 [value1]="value1" value2="{{ value2 }}"></ng2>');
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then(upgrade => {
|
||||
const $rootScope = upgrade.$injector.get('$rootScope') as angular.IRootScopeService;
|
||||
|
||||
expect(element.textContent).toBe('0 | 0');
|
||||
|
||||
// Digest should not invoke CD
|
||||
$rootScope.$digest();
|
||||
$rootScope.$digest();
|
||||
expect(element.textContent).toBe('0 | 0');
|
||||
|
||||
// Digest should not invoke CD, even if component values have changed (internally)
|
||||
ng2Component.value1 = 1;
|
||||
ng2Component.value2 = 2;
|
||||
$rootScope.$digest();
|
||||
expect(element.textContent).toBe('0 | 0');
|
||||
|
||||
// Digest should invoke CD, if prop-bound input has changed
|
||||
$rootScope.$apply('value1 = 3');
|
||||
expect(element.textContent).toBe('3 | 2');
|
||||
|
||||
// Digest should invoke CD, if attr-bound input has changed
|
||||
ng2Component.value1 = 4;
|
||||
$rootScope.$apply('value2 = 5');
|
||||
expect(element.textContent).toBe('4 | 5');
|
||||
|
||||
// Digest should invoke CD, if input has changed before the digest
|
||||
$rootScope.value1 = 6;
|
||||
$rootScope.$digest();
|
||||
expect(element.textContent).toBe('6 | 5');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should initialize inputs in time for `ngOnChanges`', async(() => {
|
||||
@Component({
|
||||
selector: 'ng2',
|
||||
@ -215,9 +341,12 @@ export function main() {
|
||||
class Ng2 {
|
||||
private _value: any = '';
|
||||
private _onChangeCallback: (_: any) => void = () => {};
|
||||
private _onTouchedCallback: () => void = () => {};
|
||||
constructor() { ng2Instance = this; }
|
||||
writeValue(value: any) { this._value = value; }
|
||||
registerOnChange(fn: any) { this._onChangeCallback = fn; }
|
||||
registerOnTouched(fn: any) { this._onTouchedCallback = fn; }
|
||||
doTouch() { this._onTouchedCallback(); }
|
||||
doChange(newValue: string) {
|
||||
this._value = newValue;
|
||||
this._onChangeCallback(newValue);
|
||||
@ -248,6 +377,13 @@ export function main() {
|
||||
ng2Instance.doChange('C');
|
||||
expect($rootScope.modelA).toBe('C');
|
||||
expect(multiTrim(document.body.textContent)).toEqual('C | C');
|
||||
|
||||
const downgradedElement = <Element>document.body.querySelector('ng2');
|
||||
expect(downgradedElement.classList.contains('ng-touched')).toBe(false);
|
||||
|
||||
ng2Instance.doTouch();
|
||||
$rootScope.$apply();
|
||||
expect(downgradedElement.classList.contains('ng-touched')).toBe(true);
|
||||
});
|
||||
}));
|
||||
|
||||
|
@ -0,0 +1,172 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, Inject, Injector, Input, NgModule, Provider, destroyPlatform} from '@angular/core';
|
||||
import {async} from '@angular/core/testing';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import * as angular from '@angular/upgrade/src/common/angular1';
|
||||
import {$ROOT_SCOPE, INJECTOR_KEY} from '@angular/upgrade/src/common/constants';
|
||||
import {downgradeComponent, downgradeModule} from '@angular/upgrade/static';
|
||||
|
||||
import {html} from '../test_helpers';
|
||||
|
||||
export function main() {
|
||||
describe('lazy-load ng2 module', () => {
|
||||
|
||||
beforeEach(() => destroyPlatform());
|
||||
afterEach(() => destroyPlatform());
|
||||
|
||||
it('should support downgrading a component and propagate inputs', async(() => {
|
||||
@Component({selector: 'ng2A', template: 'a({{ value }}) | <ng2B [value]="value"></ng2B>'})
|
||||
class Ng2AComponent {
|
||||
@Input() value = -1;
|
||||
}
|
||||
|
||||
@Component({selector: 'ng2B', template: 'b({{ value }})'})
|
||||
class Ng2BComponent {
|
||||
@Input() value = -2;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [Ng2AComponent, Ng2BComponent],
|
||||
entryComponents: [Ng2AComponent],
|
||||
imports: [BrowserModule],
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const bootstrapFn = (extraProviders: Provider[]) =>
|
||||
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
||||
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
||||
const ng1Module =
|
||||
angular.module('ng1', [lazyModuleName])
|
||||
.directive(
|
||||
'ng2', downgradeComponent({component: Ng2AComponent, propagateDigest: false}))
|
||||
.run(($rootScope: angular.IRootScopeService) => $rootScope.value = 0);
|
||||
|
||||
const element = html('<div><ng2 [value]="value" ng-if="loadNg2"></ng2></div>');
|
||||
const $injector = angular.bootstrap(element, [ng1Module.name]);
|
||||
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
|
||||
|
||||
expect(element.textContent).toBe('');
|
||||
expect(() => $injector.get(INJECTOR_KEY)).toThrowError();
|
||||
|
||||
$rootScope.$apply('value = 1');
|
||||
expect(element.textContent).toBe('');
|
||||
expect(() => $injector.get(INJECTOR_KEY)).toThrowError();
|
||||
|
||||
$rootScope.$apply('loadNg2 = true');
|
||||
expect(element.textContent).toBe('');
|
||||
expect(() => $injector.get(INJECTOR_KEY)).toThrowError();
|
||||
|
||||
// Wait for the module to be bootstrapped.
|
||||
setTimeout(() => {
|
||||
expect(() => $injector.get(INJECTOR_KEY)).not.toThrow();
|
||||
|
||||
// Wait for `$evalAsync()` to propagate inputs.
|
||||
setTimeout(() => expect(element.textContent).toBe('a(1) | b(1)'));
|
||||
});
|
||||
}));
|
||||
|
||||
it('should support using an upgraded service', async(() => {
|
||||
class Ng2Service {
|
||||
constructor(@Inject('ng1Value') private ng1Value: string) {}
|
||||
getValue = () => `${this.ng1Value}-bar`;
|
||||
}
|
||||
|
||||
@Component({selector: 'ng2', template: '{{ value }}'})
|
||||
class Ng2Component {
|
||||
value: string;
|
||||
constructor(ng2Service: Ng2Service) { this.value = ng2Service.getValue(); }
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [Ng2Component],
|
||||
entryComponents: [Ng2Component],
|
||||
imports: [BrowserModule],
|
||||
providers: [
|
||||
Ng2Service,
|
||||
{
|
||||
provide: 'ng1Value',
|
||||
useFactory: (i: angular.IInjectorService) => i.get('ng1Value'),
|
||||
deps: ['$injector'],
|
||||
},
|
||||
],
|
||||
})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const bootstrapFn = (extraProviders: Provider[]) =>
|
||||
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
||||
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
||||
const ng1Module =
|
||||
angular.module('ng1', [lazyModuleName])
|
||||
.directive(
|
||||
'ng2', downgradeComponent({component: Ng2Component, propagateDigest: false}))
|
||||
.value('ng1Value', 'foo');
|
||||
|
||||
const element = html('<div><ng2 ng-if="loadNg2"></ng2></div>');
|
||||
const $injector = angular.bootstrap(element, [ng1Module.name]);
|
||||
const $rootScope = $injector.get($ROOT_SCOPE) as angular.IRootScopeService;
|
||||
|
||||
expect(element.textContent).toBe('');
|
||||
expect(() => $injector.get(INJECTOR_KEY)).toThrowError();
|
||||
|
||||
$rootScope.$apply('loadNg2 = true');
|
||||
expect(element.textContent).toBe('');
|
||||
expect(() => $injector.get(INJECTOR_KEY)).toThrowError();
|
||||
|
||||
// Wait for the module to be bootstrapped.
|
||||
setTimeout(() => {
|
||||
expect(() => $injector.get(INJECTOR_KEY)).not.toThrow();
|
||||
|
||||
// Wait for `$evalAsync()` to propagate inputs.
|
||||
setTimeout(() => expect(element.textContent).toBe('foo-bar'));
|
||||
});
|
||||
}));
|
||||
|
||||
it('should give access to both injectors in the Angular module\'s constructor', async(() => {
|
||||
let $injectorFromNg2: angular.IInjectorService|null = null;
|
||||
|
||||
@Component({selector: 'ng2', template: ''})
|
||||
class Ng2Component {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [Ng2Component],
|
||||
entryComponents: [Ng2Component],
|
||||
imports: [BrowserModule],
|
||||
})
|
||||
class Ng2Module {
|
||||
constructor(injector: Injector) {
|
||||
$injectorFromNg2 = injector.get<angular.IInjectorService>('$injector' as any);
|
||||
}
|
||||
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
const bootstrapFn = (extraProviders: Provider[]) =>
|
||||
platformBrowserDynamic(extraProviders).bootstrapModule(Ng2Module);
|
||||
const lazyModuleName = downgradeModule<Ng2Module>(bootstrapFn);
|
||||
const ng1Module =
|
||||
angular.module('ng1', [lazyModuleName])
|
||||
.directive(
|
||||
'ng2', downgradeComponent({component: Ng2Component, propagateDigest: false}))
|
||||
.value('ng1Value', 'foo');
|
||||
|
||||
const element = html('<ng2></ng2>');
|
||||
const $injectorFromNg1 = angular.bootstrap(element, [ng1Module.name]);
|
||||
|
||||
// Wait for the module to be bootstrapped.
|
||||
setTimeout(() => expect($injectorFromNg2).toBe($injectorFromNg1));
|
||||
}));
|
||||
});
|
||||
}
|
@ -18,7 +18,7 @@ export function bootstrap(
|
||||
// We bootstrap the Angular module first; then when it is ready (async)
|
||||
// We bootstrap the AngularJS module on the bootstrap element
|
||||
return platform.bootstrapModule(Ng2Module).then(ref => {
|
||||
const upgrade = ref.injector.get(UpgradeModule) as UpgradeModule;
|
||||
const upgrade = ref.injector.get<UpgradeModule>(UpgradeModule);
|
||||
upgrade.bootstrap(element, [ng1Module.name]);
|
||||
return upgrade;
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@angular/tsc-wrapped",
|
||||
"version": "4.3.0",
|
||||
"version": "5.0.0-beta.0",
|
||||
"description": "Wraps the tsc CLI, allowing extensions.",
|
||||
"homepage": "https://github.com/angular/angular/blob/master/tools/@angular/tsc-wrapped",
|
||||
"bugs": "https://github.com/angular/angular/issues",
|
||||
|
4
tools/public_api_guard/upgrade/static.d.ts
vendored
4
tools/public_api_guard/upgrade/static.d.ts
vendored
@ -1,6 +1,7 @@
|
||||
/** @experimental */
|
||||
export declare function downgradeComponent(info: {
|
||||
component: Type<any>;
|
||||
/** @experimental */ propagateDigest?: boolean;
|
||||
/** @deprecated */ inputs?: string[];
|
||||
/** @deprecated */ outputs?: string[];
|
||||
/** @deprecated */ selectors?: string[];
|
||||
@ -9,6 +10,9 @@ export declare function downgradeComponent(info: {
|
||||
/** @experimental */
|
||||
export declare function downgradeInjectable(token: any): Function;
|
||||
|
||||
/** @experimental */
|
||||
export declare function downgradeModule<T>(moduleFactoryOrBootstrapFn: NgModuleFactory<T> | ((extraProviders: Provider[]) => Promise<NgModuleRef<T>>)): string;
|
||||
|
||||
/** @stable */
|
||||
export declare function getAngularLib(): any;
|
||||
|
||||
|
Reference in New Issue
Block a user