Compare commits
185 Commits
2.4.4
...
4.0.0-beta
Author | SHA1 | Date | |
---|---|---|---|
2c294d5dff | |||
e1af25d93e | |||
123943a6e0 | |||
3a4b54daa4 | |||
95cbca20a5 | |||
aeed7373af | |||
2e3ac70e0a | |||
9aeb8c5357 | |||
424e6c4cb9 | |||
5cb2008e6c | |||
78f42c7aa1 | |||
d4d3782d45 | |||
46cb04d575 | |||
8c7e93bebe | |||
5d9cbd7d6f | |||
d061adc02d | |||
6d29faefea | |||
99aa49ab6c | |||
e5c6bb4286 | |||
d9a22dae4f | |||
fb6c4582a1 | |||
8578682dcf | |||
c0178de0e2 | |||
31322e73b7 | |||
9211a22039 | |||
3f67ab074a | |||
4bae4b3bb5 | |||
02dd90faed | |||
1c85e99588 | |||
ccb65893bf | |||
3e90ffd293 | |||
8063b0d9a2 | |||
21030e9a1c | |||
889b48d85f | |||
1bd04e95de | |||
f88cd2f22e | |||
f822f9599c | |||
9898d8f6d9 | |||
2dd6280ab8 | |||
35f9a1c2cb | |||
465516b905 | |||
db49d422f2 | |||
8ed92d75b0 | |||
50e5cb15dd | |||
c5c53f3666 | |||
bb0d23f82b | |||
1e6440e81b | |||
6b02b80a03 | |||
2c0c86e3ce | |||
5b4bea24de | |||
7690d02133 | |||
b2ae7b607e | |||
7c210645a3 | |||
07e0fce8fc | |||
0ac8e102de | |||
e74d8aaf92 | |||
881eb894bc | |||
0eca960494 | |||
eed83443b8 | |||
e5c4e5801f | |||
69fa3bbc03 | |||
445ed43b9a | |||
174334dec3 | |||
9c697030e6 | |||
697690349f | |||
0448e80704 | |||
e85232afd2 | |||
e7ece6c8ce | |||
67380d4b28 | |||
f114e40212 | |||
952471e25d | |||
c65e428778 | |||
842f52e841 | |||
eb2ceff4ba | |||
f49ab56160 | |||
c0f750af4e | |||
bcd37f52fb | |||
e69c1fb36c | |||
9da4c259a5 | |||
fcd116fdc0 | |||
383adc9ad9 | |||
9b8488f007 | |||
1817ddb57b | |||
1ee574c51e | |||
171a9bdc85 | |||
896916af29 | |||
e49c7fae22 | |||
6b65fc1286 | |||
0e3981afc1 | |||
e78508507d | |||
a23fa94ca8 | |||
4568d5ddac | |||
c6e893953f | |||
55dfa1b69d | |||
0fe3cd9a4c | |||
0c19898694 | |||
5b6e8ea3ec | |||
732f446ad2 | |||
f0e092515c | |||
14e785f5b7 | |||
01d1624884 | |||
33910ddfc9 | |||
01ca2db6ae | |||
6cefccb314 | |||
fa9e21e83c | |||
b6078f5887 | |||
c65b4fa9dc | |||
169ed82900 | |||
fd8e15b15d | |||
aa40366a92 | |||
40d8d9c3e3 | |||
ee2ac025ef | |||
aa3769ba69 | |||
d4ddb6004e | |||
84400bcc86 | |||
42d9998cbb | |||
c18d2fe5e3 | |||
d91a86aac6 | |||
d6e5e9283c | |||
eab7e490c9 | |||
3e90605db9 | |||
79671a6f12 | |||
a659259962 | |||
b56474d067 | |||
8395f0e138 | |||
dd0519abad | |||
f238c8ac7a | |||
8c27c62fab | |||
5031adc7a3 | |||
821b8f09d6 | |||
2bf1bbc071 | |||
7b0a86718c | |||
3edca4d37e | |||
a0a05041ac | |||
7256d0ede5 | |||
d62d89319e | |||
f5f1d5f65c | |||
a8d237581d | |||
d036165a19 | |||
d17e690eb4 | |||
714f2af0dd | |||
2b90cd532f | |||
3a64ad895a | |||
9ec0a4e105 | |||
4b3d135193 | |||
1d0ed6f75f | |||
6f330a5fc9 | |||
e23076f767 | |||
7295a5e7f2 | |||
20bed46737 | |||
2a5012d515 | |||
fb38fba8f9 | |||
4c35be3e07 | |||
e9f307f948 | |||
2e500cc85b | |||
56dce0e26d | |||
8a8c53250e | |||
08ff2e5249 | |||
a006c1418a | |||
90c223591f | |||
aaf6e05f56 | |||
3bee521aa4 | |||
95f48292b1 | |||
04cfa1ebdf | |||
4022173d1e | |||
c8baf51f4f | |||
b4db73d0bf | |||
e15a3f273f | |||
213c713409 | |||
9a8423da36 | |||
f0b0762f4a | |||
b5c4bf1c59 | |||
56c361ff6a | |||
562f7a2f8b | |||
6dd5201765 | |||
72361fb68f | |||
5c6ec20c7e | |||
440ef02f29 | |||
4e3d58a792 | |||
61d7c1e0b3 | |||
bf93389615 | |||
4398056146 | |||
1b547886d0 | |||
9591a08dfb | |||
65965c27a8 |
119
CHANGELOG.md
119
CHANGELOG.md
@ -1,16 +1,52 @@
|
||||
<a name="2.4.4"></a>
|
||||
## [2.4.4](https://github.com/angular/angular/compare/2.4.3...2.4.4) (2017-01-19)
|
||||
<a name="4.0.0-beta.3"></a>
|
||||
# [4.0.0-beta.3](https://github.com/angular/angular/compare/4.0.0-beta.2...4.0.0-beta.3) (2017-01-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** fix internal jscompiler issue and AOT quoting ([#13798](https://github.com/angular/angular/issues/13798)) ([261fd16](https://github.com/angular/angular/commit/261fd16))
|
||||
* **common:** support numeric value as discrete cases for NgPlural ([#13876](https://github.com/angular/angular/issues/13876)) ([3d0b1b8](https://github.com/angular/angular/commit/3d0b1b8))
|
||||
* **http:** don't create a blob out of ArrayBuffer when type is application/octet-stream ([#13992](https://github.com/angular/angular/issues/13992)) ([015878a](https://github.com/angular/angular/commit/015878a)), closes [#13973](https://github.com/angular/angular/issues/13973)
|
||||
* **router:** enable loadChildren with function in aot ([#13909](https://github.com/angular/angular/issues/13909)) ([2af5862](https://github.com/angular/angular/commit/2af5862)), closes [#11075](https://github.com/angular/angular/issues/11075)
|
||||
* **router:** routerLinkActive should not throw when not initialized ([#13273](https://github.com/angular/angular/issues/13273)) ([49c4b0f](https://github.com/angular/angular/commit/49c4b0f)), closes [#13270](https://github.com/angular/angular/issues/13270)
|
||||
* **security:** allow calc and gradient functions. ([#13943](https://github.com/angular/angular/issues/13943)) ([bd15110](https://github.com/angular/angular/commit/bd15110))
|
||||
* **upgrade:** detect async downgrade component changes ([#13812](https://github.com/angular/angular/issues/13812)) ([2250082](https://github.com/angular/angular/commit/2250082)), closes [#6385](https://github.com/angular/angular/issues/6385) [#6385](https://github.com/angular/angular/issues/6385) [#10660](https://github.com/angular/angular/issues/10660) [#12318](https://github.com/angular/angular/issues/12318) [#12034](https://github.com/angular/angular/issues/12034)
|
||||
* **compiler:** avoid evaluating arguments to unknown decorators ([d061adc](https://github.com/angular/angular/commit/d061adc)), closes [#13605](https://github.com/angular/angular/issues/13605)
|
||||
* **compiler:** fix template binding parsing (`*directive="-..."`) ([e5c6bb4](https://github.com/angular/angular/commit/e5c6bb4)), closes [#13800](https://github.com/angular/angular/issues/13800)
|
||||
* **compiler-cli:** add support for more than 2 levels of nested lazy routes ([5d9cbd7](https://github.com/angular/angular/commit/5d9cbd7)), closes [angular/angular-cli#3663](https://github.com/angular/angular-cli/issues/3663)
|
||||
* **compiler-cli:** avoid handling functions in loadChildren as lazy load routes paths ([aeed737](https://github.com/angular/angular/commit/aeed737)), closes [angular/angular-cli#3204](https://github.com/angular/angular-cli/issues/3204)
|
||||
* **core:** Add type information to differs ([8c7e93b](https://github.com/angular/angular/commit/8c7e93b)), closes [#13382](https://github.com/angular/angular/issues/13382)
|
||||
* **i18n:** translate attributes inside elements marked for translation ([424e6c4](https://github.com/angular/angular/commit/424e6c4)), closes [#13796](https://github.com/angular/angular/issues/13796) [#13814](https://github.com/angular/angular/issues/13814)
|
||||
* **router:** RouterLink mirrors input `target` as attribute ([d9a22da](https://github.com/angular/angular/commit/d9a22da)), closes [#13837](https://github.com/angular/angular/issues/13837)
|
||||
* **router:** throw an error when navigate to null/undefined path ([46cb04d](https://github.com/angular/angular/commit/46cb04d)), closes [#10560](https://github.com/angular/angular/issues/10560) [#13384](https://github.com/angular/angular/issues/13384)
|
||||
* **router:** fix checking for object intersection ([6d29fae](https://github.com/angular/angular/commit/6d29fae))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **animations:** expose the `element` value within transition events ([4bae4b3](https://github.com/angular/angular/commit/4bae4b3))
|
||||
* **animations:** expose the `triggerName` within the transition event ([3f67ab0](https://github.com/angular/angular/commit/3f67ab0)), closes [#13600](https://github.com/angular/angular/issues/13600)
|
||||
* **animations:** support function types in transitions ([9211a22](https://github.com/angular/angular/commit/9211a22)), closes [#13538](https://github.com/angular/angular/issues/13538) [#13537](https://github.com/angular/angular/issues/13537)
|
||||
* **language-service:** support TS2.2 plugin model ([99aa49a](https://github.com/angular/angular/commit/99aa49a))
|
||||
* **NgComponentOutlet:** add NgComponentOutlet directive ([8578682](https://github.com/angular/angular/commit/8578682)), closes [#11168](https://github.com/angular/angular/issues/11168)
|
||||
* **NgTemplateOutlet:** Make NgTemplateOutlet compatible with * syntax ([c0178de](https://github.com/angular/angular/commit/c0178de))
|
||||
* **router:** call resolver when upstream params change ([#12942](https://github.com/angular/angular/issues/12942)) ([d4d3782](https://github.com/angular/angular/commit/d4d3782))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* core: - `IterableChangeRecord` is now an interface and parameterized on `<V>`.
|
||||
This should not be an issue unless your code does
|
||||
`new IterableChangeRecord` which it should not have a reason to do.
|
||||
- `KeyValueChangeRecord` is now an interface and parameterized on `<V>`.
|
||||
This should not be an issue unless your code does
|
||||
`new IterableChangeRecord` which it should not have a reason to do.
|
||||
|
||||
### DEPRECATION
|
||||
|
||||
* Deprecate `ngOutletContext`. Use `ngTemplateOutletContext` instead.
|
||||
* `CollectionChangeRecord` is renamed to `IterableChangeRecord`.
|
||||
`CollectionChangeRecord` is aliased to `IterableChangeRecord` and is
|
||||
marked as `@deprecated`. It will be removed in `v6.x.x`.
|
||||
* Deprecate `DefaultIterableDiffer` as it is private class which
|
||||
was erroneously exposed.
|
||||
* Deprecate `KeyValueDiffers#factories` as it is private field which
|
||||
was erroneously exposed.
|
||||
* Deprecate `IterableDiffers#factories` as it is private field which
|
||||
was erroneously exposed.
|
||||
|
||||
|
||||
|
||||
@ -42,7 +78,7 @@
|
||||
* **common:** allow null/undefined values for `NgForTrackBy` ([6be55cc](https://github.com/angular/angular/commit/6be55cc)), closes [#13641](https://github.com/angular/angular/issues/13641)
|
||||
* **compiler:** don’t throw when using `ANALYZE_FOR_ENTRY_COMPONENTS` with user classes ([#13679](https://github.com/angular/angular/issues/13679)) ([230e33f](https://github.com/angular/angular/commit/230e33f)), closes [#13565](https://github.com/angular/angular/issues/13565)
|
||||
* **compiler:** query `<template>` elements before their children. ([#13677](https://github.com/angular/angular/issues/13677)) ([1cd73c7](https://github.com/angular/angular/commit/1cd73c7)), closes [#13118](https://github.com/angular/angular/issues/13118) [#13167](https://github.com/angular/angular/issues/13167)
|
||||
* **compiler:** allow "." in attribute selectors ([#13653](https://github.com/angular/angular/issues/13653)) ([29ffdfd](https://github.com/angular/angular/commit/29ffdfd)), closes [#13645](https://github.com/angular/angular/issues/13645) [#13982](https://github.com/angular/angular/issues/13982)
|
||||
* **compiler:** allow "." in attribute selectors ([#13653](https://github.com/angular/angular/issues/13653)) ([29ffdfd](https://github.com/angular/angular/commit/29ffdfd)), closes [#13645](https://github.com/angular/angular/issues/13645)
|
||||
* **core:** animations no longer silently exits if the element is not apart of the DOM ([#13763](https://github.com/angular/angular/issues/13763)) ([f1cde43](https://github.com/angular/angular/commit/f1cde43))
|
||||
* **core:** animations should blend in all previously transitioned styles into next animation if interrupted ([#13148](https://github.com/angular/angular/issues/13148)) ([b245b92](https://github.com/angular/angular/commit/b245b92))
|
||||
* **core:** remove reference to "Angular 2" in dev mode warning ([#13751](https://github.com/angular/angular/issues/13751)) ([21f5f05](https://github.com/angular/angular/commit/21f5f05))
|
||||
@ -56,6 +92,45 @@
|
||||
|
||||
|
||||
|
||||
<a name="4.0.0-beta.2"></a>
|
||||
# [4.0.0-beta.2](https://github.com/angular/angular/compare/4.0.0-beta.1...4.0.0-beta.2) (2017-01-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler:** generate less code for bindings to DOM elements ([db49d42](https://github.com/angular/angular/commit/db49d42))
|
||||
* **compiler:** generate proper reexports in `.ngfactory.ts` files to not need transitive deps for compiling `.ngfactory.ts` files. ([#13524](https://github.com/angular/angular/issues/13524)) ([9c69703](https://github.com/angular/angular/commit/9c69703)), closes [#12787](https://github.com/angular/angular/issues/12787)
|
||||
* **router:** add an extra argument to CanDeactivate interface ([#13560](https://github.com/angular/angular/issues/13560)) ([69fa3bb](https://github.com/angular/angular/commit/69fa3bb)), closes [#9853](https://github.com/angular/angular/issues/9853)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** improve error message for undefined providers ([#13546](https://github.com/angular/angular/issues/13546)) ([6b02b80](https://github.com/angular/angular/commit/6b02b80)), closes [#10835](https://github.com/angular/angular/issues/10835)
|
||||
* **compiler:** improve the error when template is not a string ([2c0c86e](https://github.com/angular/angular/commit/2c0c86e)), closes [#8708](https://github.com/angular/angular/issues/8708) [#13377](https://github.com/angular/angular/issues/13377)
|
||||
* **compiler:** throw an error for invalid provider ([#13544](https://github.com/angular/angular/issues/13544)) ([445ed43](https://github.com/angular/angular/commit/445ed43)), closes [#8870](https://github.com/angular/angular/issues/8870)
|
||||
* **i18n:** parse ICU messages while normalizing templates ([e74d8aa](https://github.com/angular/angular/commit/e74d8aa))
|
||||
|
||||
Note: 4.0.0-beta.2 release also contains all the changes present in the 2.4.2 release.
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **core**: `SimpleChange` now takes an additional argument that defines whether this is the first
|
||||
change or not. This is a low profile API and we don't expect anyone to be affected by this change.
|
||||
If you are impacted by this change please file an issue. ([465516b](https://github.com/angular/angular/commit/465516b))
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="4.0.0-beta.1"></a>
|
||||
# [4.0.0-beta.1](https://github.com/angular/angular/compare/2.4.0-marker...4.0.0-beta.1) (2016-12-22)
|
||||
|
||||
### Features
|
||||
|
||||
* **upgrade:** support the `$doCheck()` lifecycle hook in `UpgradeComponent` ([#13015](https://github.com/angular/angular/issues/13015)) ([9da4c25](https://github.com/angular/angular/commit/9da4c25))
|
||||
|
||||
Note: 4.0.0-beta.1 release also contains all the changes present in the 2.4.0 and the 2.4.1 releases.
|
||||
|
||||
<a name="2.4.1"></a>
|
||||
## [2.4.1](https://github.com/angular/angular/compare/2.4.0...2.4.1) (2016-12-21)
|
||||
|
||||
@ -88,6 +163,25 @@
|
||||
* update to `rxjs@5.0.1` and unpin the rxjs peerDeps via `^5.0.1` ([#13572](https://github.com/angular/angular/issues/13572)) ([8d5da1e](https://github.com/angular/angular/commit/8d5da1e)), closes [#13561](https://github.com/angular/angular/issues/13561) [#13478](https://github.com/angular/angular/issues/13478)
|
||||
|
||||
|
||||
<a name="4.0.0-beta.0"></a>
|
||||
# [4.0.0-beta.0](https://github.com/angular/angular/compare/2.3.0...4.0.0-beta.0) (2016-12-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **common:** add a `titlecase` pipe ([#13324](https://github.com/angular/angular/issues/13324)) ([61d7c1e](https://github.com/angular/angular/commit/61d7c1e)), closes [#11436](https://github.com/angular/angular/issues/11436)
|
||||
* **common:** export NgLocaleLocalization ([#13367](https://github.com/angular/angular/issues/13367)) ([56dce0e](https://github.com/angular/angular/commit/56dce0e)), closes [#11921](https://github.com/angular/angular/issues/11921)
|
||||
* **compiler:** add id property to i18nMessage ([6dd5201](https://github.com/angular/angular/commit/6dd5201))
|
||||
* **compiler:** digest methods return i18nMessage id if sets ([562f7a2](https://github.com/angular/angular/commit/562f7a2))
|
||||
* **forms:** add novalidate by default ([#13092](https://github.com/angular/angular/issues/13092)) ([4c35be3](https://github.com/angular/angular/commit/4c35be3))
|
||||
* **http:** simplify URLSearchParams creation ([#13338](https://github.com/angular/angular/issues/13338)) ([90c2235](https://github.com/angular/angular/commit/90c2235)), closes [#8858](https://github.com/angular/angular/issues/8858)
|
||||
* **language-service:** warn when a method isn't called in an event ([#13437](https://github.com/angular/angular/issues/13437)) ([9ec0a4e](https://github.com/angular/angular/commit/9ec0a4e))
|
||||
* **platform browser:** introduce Meta service ([#12322](https://github.com/angular/angular/issues/12322)) ([72361fb](https://github.com/angular/angular/commit/72361fb))
|
||||
* **router:** routerLink add tabindex attribute ([#13094](https://github.com/angular/angular/issues/13094)) ([a006c14](https://github.com/angular/angular/commit/a006c14)), closes [#10895](https://github.com/angular/angular/issues/10895)
|
||||
* **testing:** add overrideTemplate method ([#13372](https://github.com/angular/angular/issues/13372)) ([169ed82](https://github.com/angular/angular/commit/169ed82)), closes [#10685](https://github.com/angular/angular/issues/10685)
|
||||
* **common** ngIf now supports else; saves condition to local var ([b4db73d](https://github.com/angular/angular/commit/b4db73d)), closes [#13061](https://github.com/angular/angular/issues/13061) [#13297](https://github.com/angular/angular/issues/13297)
|
||||
|
||||
Note: 4.0.0-beta.0 release also contains all the changes present in the 2.3.1 release.
|
||||
|
||||
<a name="2.3.1"></a>
|
||||
## [2.3.1](https://github.com/angular/angular/compare/2.3.0...2.3.1) (2016-12-15)
|
||||
@ -134,15 +228,14 @@
|
||||
|
||||
### Note ###
|
||||
|
||||
Due to regression in the 2.3.0 release that was fixed by [#13464](https://github.com/angular/angular/pull/13464),
|
||||
Due to regression in the 2.3.0 release that was fixed by [#13464](https://github.com/angular/angular/pull/13464),
|
||||
components that have been compiled using 2.3.0 and published to npm will need to be recompiled and republished.
|
||||
|
||||
The >=2.3.1 compiler will issue is the following error if it encounters components compiled with 2.3.0:
|
||||
The >=2.3.1 compiler will issue is the following error if it encounters components compiled with 2.3.0:
|
||||
`Unsupported metadata version 2 for module ${module}. This module should be compiled with a newer version of ngc`.
|
||||
|
||||
We are adding more tests to our test suite to catch these kinds of problems before we cut a release.
|
||||
|
||||
|
||||
<a name="2.3.0"></a>
|
||||
# [2.3.0](https://github.com/angular/angular/compare/2.3.0-rc.0...2.3.0) (2016-12-07)
|
||||
|
||||
|
82
docs/RELEASE_SCHEDULE.md
Normal file
82
docs/RELEASE_SCHEDULE.md
Normal file
@ -0,0 +1,82 @@
|
||||
# Angular Release Schedule
|
||||
|
||||
This document contains historic record of past Angular releases and future release schedule.
|
||||
|
||||
The purpose of this document is to assist coordination among the Angular team, Angular contributors, Angular application teams, and Angular community projects.
|
||||
|
||||
We'll keep this doc up to date when unplanned releases or other schedule changes occur.
|
||||
|
||||
|
||||
## Schedule Caveats and Exceptions
|
||||
|
||||
The dates listed here are approximate – last minute issues, team or community events, etc. can cause us to release a few days sooner or later.
|
||||
|
||||
This page contains only planned and past unplanned releases.
|
||||
Due to serious regressions or other important reasons we reserve the right to release an unplanned patch or minor release.
|
||||
In such case, we'll update this document accordingly.
|
||||
|
||||
The dates past March 2017 are just a guidance and might be adjusted slightly if necessary.
|
||||
|
||||
|
||||
## Tentative Schedule Until March 2017
|
||||
|
||||
<!--
|
||||
The table below is formatted so that it's easy to read and edit in both markdown and rendered html form.
|
||||
|
||||
In order to deal with undesirable line breaks, two special characters are occasionally used:
|
||||
|
||||
- non-breaking hyphen: "‑" http://www.fileformat.info/info/unicode/char/2011/index.htm
|
||||
- non-breaking space: " " http://www.fileformat.info/info/unicode/char/00a0/index.htm
|
||||
|
||||
If you see undesirable wrapping issues in the rendered form, please copy&paste the quoted characters and use them in the table below where needed.
|
||||
-->
|
||||
|
||||
Week Of | Stable Release<br>(@latest npm tag) | Beta/RC Release<br>(@next npm tag) | Note
|
||||
------------- | ----------------------------------- | ---------------------------------- | ---------------------
|
||||
2016-09-14 | 2.0.0 | - | Major Version Release
|
||||
2016-09-21 | 2.0.1 | 2.1.0-beta.0 |
|
||||
2016‑09‑28 | - | - | Angular Connect
|
||||
2016-10-05 | 2.0.2 | 2.1.0-rc.0 |
|
||||
2016-10-12 | 2.1.0 | - | Minor Version Release
|
||||
2016-10-19 | 2.1.1 | 2.2.0-beta.0 |
|
||||
2016-10-26 | 2.1.2 | 2.2.0-beta.1 |
|
||||
2016-11-02 | 2.1.3 | 2.2.0-rc.0 |
|
||||
2016-11-09 | 2.2.0 | - | Minor Version Release
|
||||
2016-11-16 | 2.2.1 | 2.3.0-beta.0 |
|
||||
2016-11-23 | 2.2.2 | 2.3.0-beta.1 |
|
||||
*2016-11-23* | *2.2.3* | - | *Unplanned Patch Release to fix regressions*
|
||||
2016-11-30 | 2.2.4 | 2.3.0-rc.0 |
|
||||
2016-12-07 | 2.3.0 | - | Minor Version Release
|
||||
2016-12-14 | 2.3.1 | 4.0.0-beta.0 |
|
||||
*2016-12-21* | *2.4.0* | - | *Unplanned Minor Release due to release of RxJS 5.0.0*
|
||||
2016-12-21 | 2.4.1 | 4.0.0-beta.1 |
|
||||
2016-12-28 | - | - | Holiday Break
|
||||
2017-01-04 | 2.4.2 | 4.0.0-beta.2 |
|
||||
2017-01-11 | 2.4.3 | 4.0.0-beta.3 |
|
||||
2017-01-18 | 2.4.4 | 4.0.0-beta.4 |
|
||||
2017-01-25 | 2.4.5 | 4.0.0-beta.5 |
|
||||
2017-02-01 | 2.4.6 | 4.0.0-beta.6 |
|
||||
2017-02-08 | 2.4.7 | 4.0.0-rc.0 |
|
||||
2017-02-15 | 2.4.8 | 4.0.0-rc.1 |
|
||||
2017-02-22 | 2.4.9 | 4.0.0-rc.2 |
|
||||
2017-03-01 | 2.4.10 | 4.0.0-rc.3 |
|
||||
2017-03-08 | 4.0.0 + 2.4.11 | - | Major Version Release
|
||||
|
||||
|
||||
## Tentative Schedule After March 2017
|
||||
|
||||
Date | Stable Release | Compatibility`*`
|
||||
---------------------- | -------------- | ----------------
|
||||
September/October 2017 | 5.0.0 | ^4.0.0
|
||||
March 2018 | 6.0.0 | ^5.0.0
|
||||
September/October 2018 | 7.0.0 | ^6.0.0
|
||||
|
||||
`*` The goal of the backwards compatibility promise, is to ensure that changes in the core framework and tooling don't break the existing ecosystem of components and applications and don't put undue upgrade/migration burden on Angular application and component authors.
|
||||
|
||||
|
||||
## More Info & Resources
|
||||
|
||||
In [September 2016 we announced](http://angularjs.blogspot.com/2016/10/versioning-and-releasing-angular.html) that Angular is fully adopting [semantic versioning](http://semver.org/) and that we'll be releasing patch versions on a weekly basis (~50 per year), minor versions monthly for 3 months following a major version release, and every 6 months we'll release a major version that will be backwards compatible with the previous release for most developers, but might remove APIs that have been deprecated two major versions ago (6 or more months ago).
|
||||
|
||||
In [December 2016 we clarified this message](http://angularjs.blogspot.com/2016/12/ok-let-me-explain-its-going-to-be.html), and provided additional details about the plans to release Angular 4.0.0 in March 2017.
|
||||
This document contains updates to the schedule that happened since then.
|
@ -12,9 +12,8 @@
|
||||
* Entry point for all public APIs of the common package.
|
||||
*/
|
||||
export * from './location/index';
|
||||
export {NgLocalization} from './localization';
|
||||
export {NgLocaleLocalization, NgLocalization} from './localization';
|
||||
export {CommonModule} from './common_module';
|
||||
export {NgClass, NgFor, NgIf, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet} from './directives/index';
|
||||
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe} from './pipes/index';
|
||||
export {NgClass, NgFor, NgIf, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet, NgComponentOutlet} from './directives/index';
|
||||
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe} from './pipes/index';
|
||||
export {VERSION} from './version';
|
||||
export {Version} from '@angular/core';
|
||||
|
@ -9,6 +9,7 @@
|
||||
import {Provider} from '@angular/core';
|
||||
|
||||
import {NgClass} from './ng_class';
|
||||
import {NgComponentOutlet} from './ng_component_outlet';
|
||||
import {NgFor} from './ng_for';
|
||||
import {NgIf} from './ng_if';
|
||||
import {NgPlural, NgPluralCase} from './ng_plural';
|
||||
@ -18,6 +19,7 @@ import {NgTemplateOutlet} from './ng_template_outlet';
|
||||
|
||||
export {
|
||||
NgClass,
|
||||
NgComponentOutlet,
|
||||
NgFor,
|
||||
NgIf,
|
||||
NgPlural,
|
||||
@ -29,12 +31,14 @@ export {
|
||||
NgTemplateOutlet
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A collection of Angular directives that are likely to be used in each and every Angular
|
||||
* application.
|
||||
*/
|
||||
export const COMMON_DIRECTIVES: Provider[] = [
|
||||
NgClass,
|
||||
NgComponentOutlet,
|
||||
NgFor,
|
||||
NgIf,
|
||||
NgTemplateOutlet,
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
|
||||
import {Directive, DoCheck, ElementRef, Input, IterableChanges, IterableDiffer, IterableDiffers, KeyValueChanges, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
|
||||
|
||||
import {isListLikeIterable} from '../facade/collection';
|
||||
import {stringify} from '../facade/lang';
|
||||
@ -41,8 +41,8 @@ import {stringify} from '../facade/lang';
|
||||
*/
|
||||
@Directive({selector: '[ngClass]'})
|
||||
export class NgClass implements DoCheck {
|
||||
private _iterableDiffer: IterableDiffer;
|
||||
private _keyValueDiffer: KeyValueDiffer;
|
||||
private _iterableDiffer: IterableDiffer<string>;
|
||||
private _keyValueDiffer: KeyValueDiffer<string, any>;
|
||||
private _initialClasses: string[] = [];
|
||||
private _rawClass: string[]|Set<string>|{[klass: string]: any};
|
||||
|
||||
@ -78,39 +78,35 @@ export class NgClass implements DoCheck {
|
||||
|
||||
ngDoCheck(): void {
|
||||
if (this._iterableDiffer) {
|
||||
const changes = this._iterableDiffer.diff(this._rawClass);
|
||||
if (changes) {
|
||||
this._applyIterableChanges(changes);
|
||||
const iterableChanges = this._iterableDiffer.diff(this._rawClass as string[]);
|
||||
if (iterableChanges) {
|
||||
this._applyIterableChanges(iterableChanges);
|
||||
}
|
||||
} else if (this._keyValueDiffer) {
|
||||
const changes = this._keyValueDiffer.diff(this._rawClass);
|
||||
if (changes) {
|
||||
this._applyKeyValueChanges(changes);
|
||||
const keyValueChanges = this._keyValueDiffer.diff(this._rawClass as{[k: string]: any});
|
||||
if (keyValueChanges) {
|
||||
this._applyKeyValueChanges(keyValueChanges);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _cleanupClasses(rawClassVal: string[]|Set<string>|{[klass: string]: any}): void {
|
||||
private _cleanupClasses(rawClassVal: string[]|{[klass: string]: any}): void {
|
||||
this._applyClasses(rawClassVal, true);
|
||||
this._applyInitialClasses(false);
|
||||
}
|
||||
|
||||
private _applyKeyValueChanges(changes: any): void {
|
||||
changes.forEachAddedItem(
|
||||
(record: KeyValueChangeRecord) => this._toggleClass(record.key, record.currentValue));
|
||||
|
||||
changes.forEachChangedItem(
|
||||
(record: KeyValueChangeRecord) => this._toggleClass(record.key, record.currentValue));
|
||||
|
||||
changes.forEachRemovedItem((record: KeyValueChangeRecord) => {
|
||||
private _applyKeyValueChanges(changes: KeyValueChanges<string, any>): void {
|
||||
changes.forEachAddedItem((record) => this._toggleClass(record.key, record.currentValue));
|
||||
changes.forEachChangedItem((record) => this._toggleClass(record.key, record.currentValue));
|
||||
changes.forEachRemovedItem((record) => {
|
||||
if (record.previousValue) {
|
||||
this._toggleClass(record.key, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _applyIterableChanges(changes: any): void {
|
||||
changes.forEachAddedItem((record: CollectionChangeRecord) => {
|
||||
private _applyIterableChanges(changes: IterableChanges<string>): void {
|
||||
changes.forEachAddedItem((record) => {
|
||||
if (typeof record.item === 'string') {
|
||||
this._toggleClass(record.item, true);
|
||||
} else {
|
||||
@ -119,8 +115,7 @@ export class NgClass implements DoCheck {
|
||||
}
|
||||
});
|
||||
|
||||
changes.forEachRemovedItem(
|
||||
(record: CollectionChangeRecord) => this._toggleClass(record.item, false));
|
||||
changes.forEachRemovedItem((record) => this._toggleClass(record.item, false));
|
||||
}
|
||||
|
||||
private _applyInitialClasses(isCleanup: boolean) {
|
||||
@ -128,7 +123,7 @@ export class NgClass implements DoCheck {
|
||||
}
|
||||
|
||||
private _applyClasses(
|
||||
rawClassVal: string[]|Set<string>|{[key: string]: any}, isCleanup: boolean) {
|
||||
rawClassVal: string[]|Set<string>|{[klass: string]: any}, isCleanup: boolean) {
|
||||
if (rawClassVal) {
|
||||
if (Array.isArray(rawClassVal) || rawClassVal instanceof Set) {
|
||||
(<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, !isCleanup));
|
||||
@ -140,11 +135,11 @@ export class NgClass implements DoCheck {
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleClass(klass: string, enabled: boolean): void {
|
||||
private _toggleClass(klass: string, enabled: any): void {
|
||||
klass = klass.trim();
|
||||
if (klass) {
|
||||
klass.split(/\s+/g).forEach(
|
||||
klass => { this._renderer.setElementClass(this._ngEl.nativeElement, klass, enabled); });
|
||||
klass => { this._renderer.setElementClass(this._ngEl.nativeElement, klass, !!enabled); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* @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 {ComponentFactoryResolver, ComponentRef, Directive, Injector, Input, OnChanges, Provider, SimpleChanges, Type, ViewContainerRef} from '@angular/core';
|
||||
|
||||
|
||||
/**
|
||||
* Instantiates a single {@link Component} type and inserts its Host View into current View.
|
||||
* `NgComponentOutlet` provides a declarative approach for dynamic component creation.
|
||||
*
|
||||
* `NgComponentOutlet` requires a component type, if a falsy value is set the view will clear and
|
||||
* any existing component will get destroyed.
|
||||
*
|
||||
* ### Fine tune control
|
||||
*
|
||||
* You can control the component creation process by using the following optional attributes:
|
||||
*
|
||||
* * `ngOutletInjector`: Optional custom {@link Injector} that will be used as parent for the
|
||||
* Component.
|
||||
* Defaults to the injector of the current view container.
|
||||
*
|
||||
* * `ngOutletProviders`: Optional injectable objects ({@link Provider}) that are visible to the
|
||||
* component.
|
||||
*
|
||||
* * `ngOutletContent`: Optional list of projectable nodes to insert into the content
|
||||
* section of the component, if exists. ({@link NgContent}).
|
||||
*
|
||||
*
|
||||
* ### Syntax
|
||||
*
|
||||
* Simple
|
||||
* ```
|
||||
* <ng-container *ngComponentOutlet="componentTypeExpression"></ng-container>
|
||||
* ```
|
||||
*
|
||||
* Customized
|
||||
* ```
|
||||
* <ng-container *ngComponentOutlet="componentTypeExpression;
|
||||
* injector: injectorExpression;
|
||||
* content: contentNodesExpression">
|
||||
* </ng-container>
|
||||
* ```
|
||||
*
|
||||
* # Example
|
||||
*
|
||||
* {@example common/ngComponentOutlet/ts/module.ts region='SimpleExample'}
|
||||
*
|
||||
* A more complete example with additional options:
|
||||
*
|
||||
* {@example common/ngComponentOutlet/ts/module.ts region='CompleteExample'}
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({selector: '[ngComponentOutlet]'})
|
||||
export class NgComponentOutlet implements OnChanges {
|
||||
@Input() ngComponentOutlet: Type<any>;
|
||||
@Input() ngComponentOutletInjector: Injector;
|
||||
@Input() ngComponentOutletContent: any[][];
|
||||
|
||||
componentRef: ComponentRef<any>;
|
||||
|
||||
constructor(
|
||||
private _cmpFactoryResolver: ComponentFactoryResolver,
|
||||
private _viewContainerRef: ViewContainerRef) {}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (this.componentRef) {
|
||||
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this.componentRef.hostView));
|
||||
}
|
||||
this._viewContainerRef.clear();
|
||||
this.componentRef = null;
|
||||
|
||||
if (this.ngComponentOutlet) {
|
||||
let injector = this.ngComponentOutletInjector || this._viewContainerRef.parentInjector;
|
||||
|
||||
this.componentRef = this._viewContainerRef.createComponent(
|
||||
this._cmpFactoryResolver.resolveComponentFactory(this.ngComponentOutlet),
|
||||
this._viewContainerRef.length, injector, this.ngComponentOutletContent);
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef, isDevMode} from '@angular/core';
|
||||
import {ChangeDetectorRef, Directive, DoCheck, EmbeddedViewRef, Input, IterableChangeRecord, IterableChanges, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef, isDevMode} from '@angular/core';
|
||||
|
||||
import {getTypeNameForDebugging} from '../facade/lang';
|
||||
|
||||
@ -104,7 +104,7 @@ export class NgFor implements DoCheck, OnChanges {
|
||||
|
||||
get ngForTrackBy(): TrackByFn { return this._trackByFn; }
|
||||
|
||||
private _differ: IterableDiffer = null;
|
||||
private _differ: IterableDiffer<any> = null;
|
||||
private _trackByFn: TrackByFn;
|
||||
|
||||
constructor(
|
||||
@ -140,10 +140,10 @@ export class NgFor implements DoCheck, OnChanges {
|
||||
}
|
||||
}
|
||||
|
||||
private _applyChanges(changes: DefaultIterableDiffer) {
|
||||
private _applyChanges(changes: IterableChanges<any>) {
|
||||
const insertTuples: RecordViewTuple[] = [];
|
||||
changes.forEachOperation(
|
||||
(item: CollectionChangeRecord, adjustedPreviousIndex: number, currentIndex: number) => {
|
||||
(item: IterableChangeRecord<any>, adjustedPreviousIndex: number, currentIndex: number) => {
|
||||
if (item.previousIndex == null) {
|
||||
const view = this._viewContainer.createEmbeddedView(
|
||||
this._template, new NgForRow(null, null, null), currentIndex);
|
||||
@ -175,7 +175,7 @@ export class NgFor implements DoCheck, OnChanges {
|
||||
});
|
||||
}
|
||||
|
||||
private _perViewChange(view: EmbeddedViewRef<NgForRow>, record: CollectionChangeRecord) {
|
||||
private _perViewChange(view: EmbeddedViewRef<NgForRow>, record: IterableChangeRecord<any>) {
|
||||
view.context.$implicit = record.item;
|
||||
}
|
||||
}
|
||||
|
@ -6,46 +6,152 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
|
||||
/**
|
||||
* Removes or recreates a portion of the DOM tree based on an {expression}.
|
||||
* Conditionally includes a template based on the value of an `expression`.
|
||||
*
|
||||
* If the expression assigned to `ngIf` evaluates to a falsy value then the element
|
||||
* is removed from the DOM, otherwise a clone of the element is reinserted into the DOM.
|
||||
* `ngIf` evaluates the `expression` and then renders the `then` or `else` template in its place
|
||||
* when expression is truthy or falsy respectively. Typically the:
|
||||
* - `then` template is the inline template of `ngIf` unless bound to a different value.
|
||||
* - `else` template is blank unless it is bound.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/fe0kgemFBtmQOY31b4tw?p=preview)):
|
||||
* # Most common usage
|
||||
*
|
||||
* The most common usage of the `ngIf` directive is to conditionally show the inline template as
|
||||
* seen in this example:
|
||||
* {@example common/ngIf/ts/module.ts region='NgIfSimple'}
|
||||
*
|
||||
* # Showing an alternative template using `else`
|
||||
*
|
||||
* If it is necessary to display a template when the `expression` is falsy use the `else` template
|
||||
* binding as shown. Note that the `else` binding points to a `<template>` labeled `#elseBlock`.
|
||||
* The template can be defined anywhere in the component view but is typically placed right after
|
||||
* `ngIf` for readability.
|
||||
*
|
||||
* {@example common/ngIf/ts/module.ts region='NgIfElse'}
|
||||
*
|
||||
* # Using non-inlined `then` template
|
||||
*
|
||||
* Usually the `then` template is the inlined template of the `ngIf`, but it can be changed using
|
||||
* a binding (just like `else`). Because `then` and `else` are bindings, the template references can
|
||||
* change at runtime as shown in this example.
|
||||
*
|
||||
* {@example common/ngIf/ts/module.ts region='NgIfThenElse'}
|
||||
*
|
||||
* # Storing conditional result in a variable
|
||||
*
|
||||
* A common pattern is that we need to show a set of properties from the same object. If the
|
||||
* object is undefined, then we have to use the safe-traversal-operator `?.` to guard against
|
||||
* dereferencing a `null` value. This is especially the case when waiting on async data such as
|
||||
* when using the `async` pipe as shown in folowing example:
|
||||
*
|
||||
* ```
|
||||
* <div *ngIf="errorCount > 0" class="error">
|
||||
* <!-- Error message displayed when the errorCount property in the current context is greater
|
||||
* than 0. -->
|
||||
* {{errorCount}} errors detected
|
||||
* </div>
|
||||
* Hello {{ (userStream|async)?.last }}, {{ (userStream|async)?.first }}!
|
||||
* ```
|
||||
*
|
||||
* There are several inefficiencies in the above example:
|
||||
* - We create multiple subscriptions on `userStream`. One for each `async` pipe, or two in the
|
||||
* example above.
|
||||
* - We cannot display an alternative screen while waiting for the data to arrive asynchronously.
|
||||
* - We have to use the safe-traversal-operator `?.` to access properties, which is cumbersome.
|
||||
* - We have to place the `async` pipe in parenthesis.
|
||||
*
|
||||
* A better way to do this is to use `ngIf` and store the result of the condition in a local
|
||||
* variable as shown in the the example below:
|
||||
*
|
||||
* {@example common/ngIf/ts/module.ts region='NgIfLet'}
|
||||
*
|
||||
* Notice that:
|
||||
* - We use only one `async` pipe and hence only one subscription gets created.
|
||||
* - `ngIf` stores the result of the `userStream|async` in the local variable `user`.
|
||||
* - The local `user` can then be bound repeatedly in a more efficient way.
|
||||
* - No need to use the safe-traversal-operator `?.` to access properties as `ngIf` will only
|
||||
* display the data if `userStream` returns a value.
|
||||
* - We can display an alternative template while waiting for the data.
|
||||
*
|
||||
* ### Syntax
|
||||
*
|
||||
* Simple form:
|
||||
* - `<div *ngIf="condition">...</div>`
|
||||
* - `<div template="ngIf condition">...</div>`
|
||||
* - `<template [ngIf]="condition"><div>...</div></template>`
|
||||
*
|
||||
* Form with an else block:
|
||||
* ```
|
||||
* <div *ngIf="condition; else elseBlock">...</div>
|
||||
* <template #elseBlock>...</template>
|
||||
* ```
|
||||
*
|
||||
* Form with a `then` and `else` block:
|
||||
* ```
|
||||
* <div *ngIf="condition; then thenBlock else elseBlock"></div>
|
||||
* <template #thenBlock>...</template>
|
||||
* <template #elseBlock>...</template>
|
||||
* ```
|
||||
*
|
||||
* Form with storing the value locally:
|
||||
* ```
|
||||
* <div *ngIf="condition; else elseBlock; let value">{{value}}</div>
|
||||
* <template #elseBlock>...</template>
|
||||
* ```
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Directive({selector: '[ngIf]'})
|
||||
export class NgIf {
|
||||
private _hasView = false;
|
||||
private _context: NgIfContext = new NgIfContext();
|
||||
private _thenTemplateRef: TemplateRef<NgIfContext> = null;
|
||||
private _elseTemplateRef: TemplateRef<NgIfContext> = null;
|
||||
private _thenViewRef: EmbeddedViewRef<NgIfContext> = null;
|
||||
private _elseViewRef: EmbeddedViewRef<NgIfContext> = null;
|
||||
|
||||
constructor(private _viewContainer: ViewContainerRef, private _template: TemplateRef<Object>) {}
|
||||
constructor(private _viewContainer: ViewContainerRef, templateRef: TemplateRef<NgIfContext>) {
|
||||
this._thenTemplateRef = templateRef;
|
||||
}
|
||||
|
||||
@Input()
|
||||
set ngIf(condition: any) {
|
||||
if (condition && !this._hasView) {
|
||||
this._hasView = true;
|
||||
this._viewContainer.createEmbeddedView(this._template);
|
||||
} else if (!condition && this._hasView) {
|
||||
this._hasView = false;
|
||||
this._viewContainer.clear();
|
||||
this._context.$implicit = condition;
|
||||
this._updateView();
|
||||
}
|
||||
|
||||
@Input()
|
||||
set ngIfThen(templateRef: TemplateRef<NgIfContext>) {
|
||||
this._thenTemplateRef = templateRef;
|
||||
this._thenViewRef = null; // clear previous view if any.
|
||||
this._updateView();
|
||||
}
|
||||
|
||||
@Input()
|
||||
set ngIfElse(templateRef: TemplateRef<NgIfContext>) {
|
||||
this._elseTemplateRef = templateRef;
|
||||
this._elseViewRef = null; // clear previous view if any.
|
||||
this._updateView();
|
||||
}
|
||||
|
||||
private _updateView() {
|
||||
if (this._context.$implicit) {
|
||||
if (!this._thenViewRef) {
|
||||
this._viewContainer.clear();
|
||||
this._elseViewRef = null;
|
||||
if (this._thenTemplateRef) {
|
||||
this._thenViewRef =
|
||||
this._viewContainer.createEmbeddedView(this._thenTemplateRef, this._context);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!this._elseViewRef) {
|
||||
this._viewContainer.clear();
|
||||
this._thenViewRef = null;
|
||||
if (this._elseTemplateRef) {
|
||||
this._elseViewRef =
|
||||
this._viewContainer.createEmbeddedView(this._elseTemplateRef, this._context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class NgIfContext { public $implicit: any = null; }
|
||||
|
@ -103,7 +103,6 @@ export class NgPluralCase {
|
||||
constructor(
|
||||
@Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>,
|
||||
viewContainer: ViewContainerRef, @Host() ngPlural: NgPlural) {
|
||||
const isANumber: boolean = !isNaN(Number(value));
|
||||
ngPlural.addCase(isANumber ? `=${value}` : value, new SwitchView(viewContainer, template));
|
||||
ngPlural.addCase(value, new SwitchView(viewContainer, template));
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, DoCheck, ElementRef, Input, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
|
||||
import {Directive, DoCheck, ElementRef, Input, KeyValueChanges, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
|
||||
|
||||
/**
|
||||
* @ngModule CommonModule
|
||||
@ -33,7 +33,7 @@ import {Directive, DoCheck, ElementRef, Input, KeyValueChangeRecord, KeyValueDif
|
||||
@Directive({selector: '[ngStyle]'})
|
||||
export class NgStyle implements DoCheck {
|
||||
private _ngStyle: {[key: string]: string};
|
||||
private _differ: KeyValueDiffer;
|
||||
private _differ: KeyValueDiffer<string, string|number>;
|
||||
|
||||
constructor(
|
||||
private _differs: KeyValueDiffers, private _ngEl: ElementRef, private _renderer: Renderer) {}
|
||||
@ -55,20 +55,16 @@ export class NgStyle implements DoCheck {
|
||||
}
|
||||
}
|
||||
|
||||
private _applyChanges(changes: any): void {
|
||||
changes.forEachRemovedItem((record: KeyValueChangeRecord) => this._setStyle(record.key, null));
|
||||
|
||||
changes.forEachAddedItem(
|
||||
(record: KeyValueChangeRecord) => this._setStyle(record.key, record.currentValue));
|
||||
|
||||
changes.forEachChangedItem(
|
||||
(record: KeyValueChangeRecord) => this._setStyle(record.key, record.currentValue));
|
||||
private _applyChanges(changes: KeyValueChanges<string, string|number>): void {
|
||||
changes.forEachRemovedItem((record) => this._setStyle(record.key, null));
|
||||
changes.forEachAddedItem((record) => this._setStyle(record.key, record.currentValue));
|
||||
changes.forEachChangedItem((record) => this._setStyle(record.key, record.currentValue));
|
||||
}
|
||||
|
||||
private _setStyle(nameAndUnit: string, value: string): void {
|
||||
private _setStyle(nameAndUnit: string, value: string|number): void {
|
||||
const [name, unit] = nameAndUnit.split('.');
|
||||
value = value && unit ? `${value}${unit}` : value;
|
||||
value = value != null && unit ? `${value}${unit}` : value;
|
||||
|
||||
this._renderer.setElementStyle(this._ngEl.nativeElement, name, value);
|
||||
this._renderer.setElementStyle(this._ngEl.nativeElement, name, value as string);
|
||||
}
|
||||
}
|
||||
|
@ -15,42 +15,47 @@ import {Directive, EmbeddedViewRef, Input, OnChanges, SimpleChanges, TemplateRef
|
||||
*
|
||||
* @howToUse
|
||||
* ```
|
||||
* <template [ngTemplateOutlet]="templateRefExpression"
|
||||
* [ngOutletContext]="objectExpression">
|
||||
* </template>
|
||||
* <ng-container *ngTemplateOutlet="templateRefExp; context: contextExp"></ng-container>
|
||||
* ```
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* You can attach a context object to the `EmbeddedViewRef` by setting `[ngOutletContext]`.
|
||||
* `[ngOutletContext]` should be an object, the object's keys will be the local template variables
|
||||
* available within the `TemplateRef`.
|
||||
* You can attach a context object to the `EmbeddedViewRef` by setting `[ngTemplateOutletContext]`.
|
||||
* `[ngTemplateOutletContext]` should be an object, the object's keys will be available for binding
|
||||
* by the local template `let` declarations.
|
||||
*
|
||||
* Note: using the key `$implicit` in the context object will set it's value as default.
|
||||
*
|
||||
* # Example
|
||||
*
|
||||
* {@example common/ngTemplateOutlet/ts/module.ts region='NgTemplateOutlet'}
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
@Directive({selector: '[ngTemplateOutlet]'})
|
||||
export class NgTemplateOutlet implements OnChanges {
|
||||
private _viewRef: EmbeddedViewRef<any>;
|
||||
private _context: Object;
|
||||
private _templateRef: TemplateRef<any>;
|
||||
|
||||
@Input() public ngTemplateOutletContext: Object;
|
||||
|
||||
@Input() public ngTemplateOutlet: TemplateRef<any>;
|
||||
|
||||
constructor(private _viewContainerRef: ViewContainerRef) {}
|
||||
|
||||
/**
|
||||
* @deprecated v4.0.0 - Renamed to ngTemplateOutletContext.
|
||||
*/
|
||||
@Input()
|
||||
set ngOutletContext(context: Object) { this._context = context; }
|
||||
|
||||
@Input()
|
||||
set ngTemplateOutlet(templateRef: TemplateRef<Object>) { this._templateRef = templateRef; }
|
||||
set ngOutletContext(context: Object) { this.ngTemplateOutletContext = context; }
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (this._viewRef) {
|
||||
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._viewRef));
|
||||
}
|
||||
|
||||
if (this._templateRef) {
|
||||
this._viewRef = this._viewContainerRef.createEmbeddedView(this._templateRef, this._context);
|
||||
if (this.ngTemplateOutlet) {
|
||||
this._viewRef = this._viewContainerRef.createEmbeddedView(
|
||||
this.ngTemplateOutlet, this.ngTemplateOutletContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,10 +49,10 @@ export function getPluralCategory(
|
||||
*/
|
||||
@Injectable()
|
||||
export class NgLocaleLocalization extends NgLocalization {
|
||||
constructor(@Inject(LOCALE_ID) private _locale: string) { super(); }
|
||||
constructor(@Inject(LOCALE_ID) protected locale: string) { super(); }
|
||||
|
||||
getPluralCategory(value: any): string {
|
||||
const plural = getPluralCase(this._locale, value);
|
||||
const plural = getPluralCase(this.locale, value);
|
||||
|
||||
switch (plural) {
|
||||
case Plural.Zero:
|
||||
@ -431,4 +431,4 @@ export function getPluralCase(locale: string, nLike: number | string): Plural {
|
||||
default:
|
||||
return Plural.Other;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
72
modules/@angular/common/src/pipes/case_conversion_pipes.ts
Normal file
72
modules/@angular/common/src/pipes/case_conversion_pipes.ts
Normal file
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* @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 {Pipe, PipeTransform} from '@angular/core';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
/**
|
||||
* Transforms text to lowercase.
|
||||
*
|
||||
* {@example common/pipes/ts/lowerupper_pipe.ts region='LowerUpperPipe' }
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Pipe({name: 'lowercase'})
|
||||
export class LowerCasePipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
if (!value) return value;
|
||||
if (typeof value !== 'string') {
|
||||
throw new InvalidPipeArgumentError(LowerCasePipe, value);
|
||||
}
|
||||
return value.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper method to transform a single word to titlecase.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
function titleCaseWord(word: string) {
|
||||
if (!word) return word;
|
||||
return word[0].toUpperCase() + word.substr(1).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms text to titlecase.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Pipe({name: 'titlecase'})
|
||||
export class TitleCasePipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
if (!value) return value;
|
||||
if (typeof value !== 'string') {
|
||||
throw new InvalidPipeArgumentError(TitleCasePipe, value);
|
||||
}
|
||||
|
||||
return value.split(/\b/g).map(word => titleCaseWord(word)).join('');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms text to uppercase.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Pipe({name: 'uppercase'})
|
||||
export class UpperCasePipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
if (!value) return value;
|
||||
if (typeof value !== 'string') {
|
||||
throw new InvalidPipeArgumentError(UpperCasePipe, value);
|
||||
}
|
||||
return value.toUpperCase();
|
||||
}
|
||||
}
|
@ -12,14 +12,13 @@
|
||||
* This module provides a set of common Pipes.
|
||||
*/
|
||||
import {AsyncPipe} from './async_pipe';
|
||||
import {LowerCasePipe, TitleCasePipe, UpperCasePipe} from './case_conversion_pipes';
|
||||
import {DatePipe} from './date_pipe';
|
||||
import {I18nPluralPipe} from './i18n_plural_pipe';
|
||||
import {I18nSelectPipe} from './i18n_select_pipe';
|
||||
import {JsonPipe} from './json_pipe';
|
||||
import {LowerCasePipe} from './lowercase_pipe';
|
||||
import {CurrencyPipe, DecimalPipe, PercentPipe} from './number_pipe';
|
||||
import {SlicePipe} from './slice_pipe';
|
||||
import {UpperCasePipe} from './uppercase_pipe';
|
||||
|
||||
export {
|
||||
AsyncPipe,
|
||||
@ -32,9 +31,11 @@ export {
|
||||
LowerCasePipe,
|
||||
PercentPipe,
|
||||
SlicePipe,
|
||||
TitleCasePipe,
|
||||
UpperCasePipe
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A collection of Angular pipes that are likely to be used in each and every application.
|
||||
*/
|
||||
@ -46,6 +47,7 @@ export const COMMON_PIPES = [
|
||||
SlicePipe,
|
||||
DecimalPipe,
|
||||
PercentPipe,
|
||||
TitleCasePipe,
|
||||
CurrencyPipe,
|
||||
DatePipe,
|
||||
I18nPluralPipe,
|
||||
|
@ -1,37 +0,0 @@
|
||||
/**
|
||||
* @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 {Pipe, PipeTransform} from '@angular/core';
|
||||
import {isBlank} from '../facade/lang';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
|
||||
/**
|
||||
* @ngModule CommonModule
|
||||
* @whatItDoes Transforms string to lowercase.
|
||||
* @howToUse `expression | lowercase`
|
||||
* @description
|
||||
*
|
||||
* Converts value into a lowercase string using `String.prototype.toLowerCase()`.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example common/pipes/ts/lowerupper_pipe.ts region='LowerUpperPipe'}
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Pipe({name: 'lowercase'})
|
||||
export class LowerCasePipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
if (isBlank(value)) return value;
|
||||
if (typeof value !== 'string') {
|
||||
throw new InvalidPipeArgumentError(LowerCasePipe, value);
|
||||
}
|
||||
return value.toLowerCase();
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
/**
|
||||
* @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 {Pipe, PipeTransform} from '@angular/core';
|
||||
import {isBlank} from '../facade/lang';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
/**
|
||||
* @ngModule CommonModule
|
||||
* @whatItDoes Transforms string to uppercase.
|
||||
* @howToUse `expression | uppercase`
|
||||
* @description
|
||||
*
|
||||
* Converts value into an uppercase string using `String.prototype.toUpperCase()`.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example common/pipes/ts/lowerupper_pipe.ts region='LowerUpperPipe'}
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Pipe({name: 'uppercase'})
|
||||
export class UpperCasePipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
if (isBlank(value)) return value;
|
||||
if (typeof value !== 'string') {
|
||||
throw new InvalidPipeArgumentError(UpperCasePipe, value);
|
||||
}
|
||||
return value.toUpperCase();
|
||||
}
|
||||
}
|
@ -0,0 +1,184 @@
|
||||
/**
|
||||
* @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 {CommonModule} from '@angular/common';
|
||||
import {NgComponentOutlet} from '@angular/common/src/directives/ng_component_outlet';
|
||||
import {Component, ComponentRef, Inject, Injector, NO_ERRORS_SCHEMA, NgModule, OpaqueToken, Optional, Provider, QueryList, ReflectiveInjector, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
|
||||
import {TestBed, async} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('insert/remove', () => {
|
||||
|
||||
beforeEach(() => { TestBed.configureTestingModule({imports: [TestModule]}); });
|
||||
|
||||
it('should do nothing if component is null', async(() => {
|
||||
const template = `<template *ngComponentOutlet="currentComponent"></template>`;
|
||||
TestBed.overrideComponent(TestComponent, {set: {template: template}});
|
||||
let fixture = TestBed.createComponent(TestComponent);
|
||||
|
||||
fixture.componentInstance.currentComponent = null;
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
}));
|
||||
|
||||
it('should insert content specified by a component', async(() => {
|
||||
let fixture = TestBed.createComponent(TestComponent);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
|
||||
fixture.componentInstance.currentComponent = InjectedComponent;
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('foo');
|
||||
}));
|
||||
|
||||
it('should emit a ComponentRef once a component was created', async(() => {
|
||||
let fixture = TestBed.createComponent(TestComponent);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
|
||||
fixture.componentInstance.cmpRef = null;
|
||||
fixture.componentInstance.currentComponent = InjectedComponent;
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('foo');
|
||||
expect(fixture.componentInstance.cmpRef).toBeAnInstanceOf(ComponentRef);
|
||||
expect(fixture.componentInstance.cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
|
||||
}));
|
||||
|
||||
|
||||
it('should clear view if component becomes null', async(() => {
|
||||
let fixture = TestBed.createComponent(TestComponent);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
|
||||
fixture.componentInstance.currentComponent = InjectedComponent;
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('foo');
|
||||
|
||||
fixture.componentInstance.currentComponent = null;
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
}));
|
||||
|
||||
|
||||
it('should swap content if component changes', async(() => {
|
||||
let fixture = TestBed.createComponent(TestComponent);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
|
||||
fixture.componentInstance.currentComponent = InjectedComponent;
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('foo');
|
||||
|
||||
fixture.componentInstance.currentComponent = InjectedComponentAgain;
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('bar');
|
||||
}));
|
||||
|
||||
it('should use the injector, if one supplied', async(() => {
|
||||
let fixture = TestBed.createComponent(TestComponent);
|
||||
|
||||
const uniqueValue = {};
|
||||
fixture.componentInstance.currentComponent = InjectedComponent;
|
||||
fixture.componentInstance.injector = ReflectiveInjector.resolveAndCreate(
|
||||
[{provide: TEST_TOKEN, useValue: uniqueValue}], fixture.componentRef.injector);
|
||||
|
||||
fixture.detectChanges();
|
||||
let cmpRef: ComponentRef<InjectedComponent> = fixture.componentInstance.cmpRef;
|
||||
expect(cmpRef).toBeAnInstanceOf(ComponentRef);
|
||||
expect(cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
|
||||
expect(cmpRef.instance.testToken).toBe(uniqueValue);
|
||||
|
||||
}));
|
||||
|
||||
it('should resolve a with injector', async(() => {
|
||||
let fixture = TestBed.createComponent(TestComponent);
|
||||
|
||||
fixture.componentInstance.cmpRef = null;
|
||||
fixture.componentInstance.currentComponent = InjectedComponent;
|
||||
fixture.detectChanges();
|
||||
let cmpRef: ComponentRef<InjectedComponent> = fixture.componentInstance.cmpRef;
|
||||
expect(cmpRef).toBeAnInstanceOf(ComponentRef);
|
||||
expect(cmpRef.instance).toBeAnInstanceOf(InjectedComponent);
|
||||
expect(cmpRef.instance.testToken).toBeNull();
|
||||
}));
|
||||
|
||||
it('should render projectable nodes, if supplied', async(() => {
|
||||
const template = `<template>projected foo</template>${TEST_CMP_TEMPLATE}`;
|
||||
TestBed.overrideComponent(TestComponent, {set: {template: template}})
|
||||
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
|
||||
|
||||
TestBed
|
||||
.overrideComponent(InjectedComponent, {set: {template: `<ng-content></ng-content>`}})
|
||||
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]});
|
||||
|
||||
let fixture = TestBed.createComponent(TestComponent);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('');
|
||||
|
||||
fixture.componentInstance.currentComponent = InjectedComponent;
|
||||
fixture.componentInstance.projectables =
|
||||
[fixture.componentInstance.vcRef
|
||||
.createEmbeddedView(fixture.componentInstance.tplRefs.first)
|
||||
.rootNodes];
|
||||
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('projected foo');
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
const TEST_TOKEN = new OpaqueToken('TestToken');
|
||||
@Component({selector: 'injected-component', template: 'foo'})
|
||||
class InjectedComponent {
|
||||
constructor(@Optional() @Inject(TEST_TOKEN) public testToken: any) {}
|
||||
}
|
||||
|
||||
|
||||
@Component({selector: 'injected-component-again', template: 'bar'})
|
||||
class InjectedComponentAgain {
|
||||
}
|
||||
|
||||
const TEST_CMP_TEMPLATE =
|
||||
`<template *ngComponentOutlet="currentComponent; injector: injector; content: projectables"></template>`;
|
||||
@Component({selector: 'test-cmp', template: TEST_CMP_TEMPLATE})
|
||||
class TestComponent {
|
||||
currentComponent: Type<any>;
|
||||
injector: Injector;
|
||||
projectables: any[][];
|
||||
|
||||
get cmpRef(): ComponentRef<any> { return this.ngComponentOutlet.componentRef; }
|
||||
set cmpRef(value: ComponentRef<any>) { this.ngComponentOutlet.componentRef = value; }
|
||||
|
||||
@ViewChildren(TemplateRef) tplRefs: QueryList<TemplateRef<any>>;
|
||||
@ViewChild(NgComponentOutlet) ngComponentOutlet: NgComponentOutlet;
|
||||
|
||||
constructor(public vcRef: ViewContainerRef) {}
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
declarations: [TestComponent, InjectedComponent, InjectedComponentAgain],
|
||||
exports: [TestComponent, InjectedComponent, InjectedComponentAgain],
|
||||
entryComponents: [InjectedComponent, InjectedComponentAgain]
|
||||
})
|
||||
export class TestModule {
|
||||
}
|
@ -137,6 +137,72 @@ export function main() {
|
||||
|
||||
expect(fixture.nativeElement).toHaveText('hello');
|
||||
}));
|
||||
|
||||
describe('else', () => {
|
||||
it('should support else', async(() => {
|
||||
const template = '<span *ngIf="booleanCondition; else elseBlock">TRUE</span>' +
|
||||
'<template #elseBlock>FALSE</template>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('TRUE');
|
||||
|
||||
getComponent().booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('FALSE');
|
||||
}));
|
||||
|
||||
it('should support then and else', async(() => {
|
||||
const template =
|
||||
'<span *ngIf="booleanCondition; then thenBlock; else elseBlock">IGNORE</span>' +
|
||||
'<template #thenBlock>THEN</template>' +
|
||||
'<template #elseBlock>ELSE</template>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('THEN');
|
||||
|
||||
getComponent().booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('ELSE');
|
||||
}));
|
||||
|
||||
it('should support dynamic else', async(() => {
|
||||
const template =
|
||||
'<span *ngIf="booleanCondition; else nestedBooleanCondition ? b1 : b2">TRUE</span>' +
|
||||
'<template #b1>FALSE1</template>' +
|
||||
'<template #b2>FALSE2</template>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('TRUE');
|
||||
|
||||
getComponent().booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('FALSE1');
|
||||
|
||||
getComponent().nestedBooleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('FALSE2');
|
||||
}));
|
||||
|
||||
it('should support binding to variable', async(() => {
|
||||
const template = '<span *ngIf="booleanCondition; else elseBlock; let v">{{v}}</span>' +
|
||||
'<template #elseBlock let-v>{{v}}</template>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('true');
|
||||
|
||||
getComponent().booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('false');
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('ngPlural', () => {
|
||||
describe('switch', () => {
|
||||
let fixture: ComponentFixture<any>;
|
||||
|
||||
function getComponent(): TestComponent { return fixture.componentInstance; }
|
||||
@ -47,22 +47,6 @@ export function main() {
|
||||
detectChangesAndExpectText('you have one message.');
|
||||
}));
|
||||
|
||||
it('should display the template according to the exact numeric value', async(() => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="0"><li>you have no messages.</li></template>' +
|
||||
'<template ngPluralCase="1"><li>you have one message.</li></template>' +
|
||||
'</ul></div>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
getComponent().switchValue = 0;
|
||||
detectChangesAndExpectText('you have no messages.');
|
||||
|
||||
getComponent().switchValue = 1;
|
||||
detectChangesAndExpectText('you have one message.');
|
||||
}));
|
||||
|
||||
// https://github.com/angular/angular/issues/9868
|
||||
// https://github.com/angular/angular/issues/9882
|
||||
it('should not throw when ngPluralCase contains expressions', async(() => {
|
||||
|
@ -79,14 +79,14 @@ export function main() {
|
||||
|
||||
it('should display template if context is `null`', async(() => {
|
||||
const template = `<template #tpl>foo</template>` +
|
||||
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="null"></ng-container>`;
|
||||
`<ng-container *ngTemplateOutlet="tpl; context: null"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
detectChangesAndExpectText('foo');
|
||||
}));
|
||||
|
||||
it('should reflect initial context and changes', async(() => {
|
||||
const template = `<template let-foo="foo" #tpl>{{foo}}</template>` +
|
||||
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
|
||||
`<ng-container *ngTemplateOutlet="tpl; context: context"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
@ -98,7 +98,7 @@ export function main() {
|
||||
|
||||
it('should reflect user defined `$implicit` property in the context', async(() => {
|
||||
const template = `<template let-ctx #tpl>{{ctx.foo}}</template>` +
|
||||
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
|
||||
`<ng-container *ngTemplateOutlet="tpl; context: context"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
fixture.componentInstance.context = {$implicit: {foo: 'bra'}};
|
||||
detectChangesAndExpectText('bra');
|
||||
@ -106,7 +106,7 @@ export function main() {
|
||||
|
||||
it('should reflect context re-binding', async(() => {
|
||||
const template = `<template let-shawshank="shawshank" #tpl>{{shawshank}}</template>` +
|
||||
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
|
||||
`<ng-container *ngTemplateOutlet="tpl; context: context"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.componentInstance.context = {shawshank: 'brooks'};
|
||||
|
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* @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 {LowerCasePipe, TitleCasePipe, UpperCasePipe} from '@angular/common';
|
||||
|
||||
export function main() {
|
||||
describe('LowerCasePipe', () => {
|
||||
let pipe: LowerCasePipe;
|
||||
|
||||
beforeEach(() => { pipe = new LowerCasePipe(); });
|
||||
|
||||
it('should return lowercase', () => { expect(pipe.transform('FOO')).toEqual('foo'); });
|
||||
|
||||
it('should lowercase when there is a new value', () => {
|
||||
expect(pipe.transform('FOO')).toEqual('foo');
|
||||
expect(pipe.transform('BAr')).toEqual('bar');
|
||||
});
|
||||
|
||||
it('should not support other objects',
|
||||
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
|
||||
});
|
||||
|
||||
describe('TitleCasePipe', () => {
|
||||
let pipe: TitleCasePipe;
|
||||
|
||||
beforeEach(() => { pipe = new TitleCasePipe(); });
|
||||
|
||||
it('should return titlecase', () => { expect(pipe.transform('foo')).toEqual('Foo'); });
|
||||
|
||||
it('should return titlecase for subsequent words',
|
||||
() => { expect(pipe.transform('one TWO Three fouR')).toEqual('One Two Three Four'); });
|
||||
|
||||
it('should support empty strings', () => { expect(pipe.transform('')).toEqual(''); });
|
||||
|
||||
it('should persist whitespace',
|
||||
() => { expect(pipe.transform('one two')).toEqual('One Two'); });
|
||||
|
||||
it('should titlecase when there is a new value', () => {
|
||||
expect(pipe.transform('bar')).toEqual('Bar');
|
||||
expect(pipe.transform('foo')).toEqual('Foo');
|
||||
});
|
||||
|
||||
it('should not support other objects',
|
||||
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
|
||||
});
|
||||
|
||||
describe('UpperCasePipe', () => {
|
||||
let pipe: UpperCasePipe;
|
||||
|
||||
beforeEach(() => { pipe = new UpperCasePipe(); });
|
||||
|
||||
it('should return uppercase', () => { expect(pipe.transform('foo')).toEqual('FOO'); });
|
||||
|
||||
it('should uppercase when there is a new value', () => {
|
||||
expect(pipe.transform('foo')).toEqual('FOO');
|
||||
expect(pipe.transform('bar')).toEqual('BAR');
|
||||
});
|
||||
|
||||
it('should not support other objects',
|
||||
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
|
||||
});
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/**
|
||||
* @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 {LowerCasePipe} from '@angular/common';
|
||||
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
|
||||
export function main() {
|
||||
describe('LowerCasePipe', () => {
|
||||
let upper: string;
|
||||
let lower: string;
|
||||
let pipe: LowerCasePipe;
|
||||
|
||||
beforeEach(() => {
|
||||
lower = 'something';
|
||||
upper = 'SOMETHING';
|
||||
pipe = new LowerCasePipe();
|
||||
});
|
||||
|
||||
describe('transform', () => {
|
||||
it('should return lowercase', () => {
|
||||
const val = pipe.transform(upper);
|
||||
expect(val).toEqual(lower);
|
||||
});
|
||||
|
||||
it('should lowercase when there is a new value', () => {
|
||||
const val = pipe.transform(upper);
|
||||
expect(val).toEqual(lower);
|
||||
const val2 = pipe.transform('WAT');
|
||||
expect(val2).toEqual('wat');
|
||||
});
|
||||
|
||||
it('should not support other objects',
|
||||
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
|
||||
});
|
||||
|
||||
});
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/**
|
||||
* @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 {UpperCasePipe} from '@angular/common';
|
||||
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
|
||||
export function main() {
|
||||
describe('UpperCasePipe', () => {
|
||||
let upper: string;
|
||||
let lower: string;
|
||||
let pipe: UpperCasePipe;
|
||||
|
||||
beforeEach(() => {
|
||||
lower = 'something';
|
||||
upper = 'SOMETHING';
|
||||
pipe = new UpperCasePipe();
|
||||
});
|
||||
|
||||
describe('transform', () => {
|
||||
|
||||
it('should return uppercase', () => {
|
||||
const val = pipe.transform(lower);
|
||||
expect(val).toEqual(upper);
|
||||
});
|
||||
|
||||
it('should uppercase when there is a new value', () => {
|
||||
const val = pipe.transform(lower);
|
||||
expect(val).toEqual(upper);
|
||||
const val2 = pipe.transform('wat');
|
||||
expect(val2).toEqual('WAT');
|
||||
});
|
||||
|
||||
it('should not support other objects',
|
||||
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
|
||||
});
|
||||
|
||||
});
|
||||
}
|
@ -8,6 +8,10 @@
|
||||
|
||||
import {AUTO_STYLE, Component, animate, state, style, transition, trigger} from '@angular/core';
|
||||
|
||||
export function anyToAny(stateA: string, stateB: string): boolean {
|
||||
return Math.random() != Math.random();
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'animate-cmp',
|
||||
animations: [trigger(
|
||||
@ -16,7 +20,7 @@ import {AUTO_STYLE, Component, animate, state, style, transition, trigger} from
|
||||
state('*', style({height: AUTO_STYLE, color: 'black', borderColor: 'black'})),
|
||||
state('closed, void', style({height: '0px', color: 'maroon', borderColor: 'maroon'})),
|
||||
state('open', style({height: AUTO_STYLE, borderColor: 'green', color: 'green'})),
|
||||
transition('* => *', animate(500))
|
||||
transition(anyToAny, animate('1s')), transition('* => *', animate(500))
|
||||
])],
|
||||
template: `
|
||||
<button (click)="setAsOpen()">Open</button>
|
||||
|
@ -9,7 +9,7 @@
|
||||
"ng-xi18n": "./src/extract_i18n.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/tsc-wrapped": "0.5.1",
|
||||
"@angular/tsc-wrapped": "4.0.0-beta.2",
|
||||
"reflect-metadata": "^0.1.2",
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
|
@ -36,6 +36,29 @@ export class CodeGenerator {
|
||||
public host: ts.CompilerHost, private compiler: compiler.AotCompiler,
|
||||
private ngCompilerHost: CompilerHost) {}
|
||||
|
||||
// Write codegen in a directory structure matching the sources.
|
||||
private calculateEmitPath(filePath: string): string {
|
||||
let root = this.options.basePath;
|
||||
for (const eachRootDir of this.options.rootDirs || []) {
|
||||
if (this.options.trace) {
|
||||
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
|
||||
}
|
||||
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
|
||||
root = eachRootDir;
|
||||
}
|
||||
}
|
||||
|
||||
// transplant the codegen path to be inside the `genDir`
|
||||
let relativePath: string = path.relative(root, filePath);
|
||||
while (relativePath.startsWith('..' + path.sep)) {
|
||||
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
|
||||
// into `genDir`.
|
||||
relativePath = relativePath.substr(3);
|
||||
}
|
||||
|
||||
return path.join(this.options.genDir, relativePath);
|
||||
}
|
||||
|
||||
codegen(): Promise<any> {
|
||||
return this.compiler
|
||||
.compileAll(this.program.getSourceFiles().map(
|
||||
@ -43,7 +66,7 @@ export class CodeGenerator {
|
||||
.then(generatedModules => {
|
||||
generatedModules.forEach(generatedModule => {
|
||||
const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl);
|
||||
const emitPath = this.ngCompilerHost.calculateEmitPath(generatedModule.genFileUrl);
|
||||
const emitPath = this.calculateEmitPath(generatedModule.genFileUrl);
|
||||
const source = GENERATED_META_FILES.test(emitPath) ? generatedModule.source :
|
||||
PREAMBLE + generatedModule.source;
|
||||
this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]);
|
||||
|
@ -261,29 +261,6 @@ export class CompilerHost implements AotCompilerHost {
|
||||
this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
|
||||
return !excludeRegex.test(filePath);
|
||||
}
|
||||
|
||||
calculateEmitPath(filePath: string): string {
|
||||
// Write codegen in a directory structure matching the sources.
|
||||
let root = this.options.basePath;
|
||||
for (const eachRootDir of this.options.rootDirs || []) {
|
||||
if (this.options.trace) {
|
||||
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
|
||||
}
|
||||
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
|
||||
root = eachRootDir;
|
||||
}
|
||||
}
|
||||
|
||||
// transplant the codegen path to be inside the `genDir`
|
||||
let relativePath: string = path.relative(root, filePath);
|
||||
while (relativePath.startsWith('..' + path.sep)) {
|
||||
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
|
||||
// into `genDir`.
|
||||
relativePath = relativePath.substr(3);
|
||||
}
|
||||
|
||||
return path.join(this.options.genDir, relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
export class CompilerHostContextAdapter {
|
||||
|
@ -5,6 +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 {StaticSymbol} from '../aot/static_symbol';
|
||||
|
||||
export abstract class AnimationAst {
|
||||
public startTime: number = 0;
|
||||
@ -49,6 +50,10 @@ export class AnimationStateTransitionExpression {
|
||||
constructor(public fromState: string, public toState: string) {}
|
||||
}
|
||||
|
||||
export class AnimationStateTransitionFnExpression extends AnimationStateTransitionExpression {
|
||||
constructor(public fn: Function|StaticSymbol) { super(null, null); }
|
||||
}
|
||||
|
||||
export class AnimationStateTransitionAst extends AnimationStateAst {
|
||||
constructor(
|
||||
public stateChanges: AnimationStateTransitionExpression[],
|
||||
|
@ -12,7 +12,7 @@ import {Identifiers, createIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ANY_STATE, DEFAULT_STATE, EMPTY_STATE} from '../private_import_core';
|
||||
|
||||
import {AnimationAst, AnimationAstVisitor, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStepAst, AnimationStylesAst} from './animation_ast';
|
||||
import {AnimationAst, AnimationAstVisitor, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStateTransitionFnExpression, AnimationStepAst, AnimationStylesAst} from './animation_ast';
|
||||
|
||||
export class AnimationEntryCompileResult {
|
||||
constructor(public name: string, public statements: o.Statement[], public fnExp: o.Expression) {}
|
||||
@ -162,16 +162,22 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
const stateChangePreconditions: o.Expression[] = [];
|
||||
|
||||
ast.stateChanges.forEach(stateChange => {
|
||||
stateChangePreconditions.push(
|
||||
_compareToAnimationStateExpr(_ANIMATION_CURRENT_STATE_VAR, stateChange.fromState)
|
||||
.and(_compareToAnimationStateExpr(_ANIMATION_NEXT_STATE_VAR, stateChange.toState)));
|
||||
if (stateChange instanceof AnimationStateTransitionFnExpression) {
|
||||
stateChangePreconditions.push(o.importExpr({reference: stateChange.fn}).callFn([
|
||||
_ANIMATION_CURRENT_STATE_VAR, _ANIMATION_NEXT_STATE_VAR
|
||||
]));
|
||||
} else {
|
||||
stateChangePreconditions.push(
|
||||
_compareToAnimationStateExpr(_ANIMATION_CURRENT_STATE_VAR, stateChange.fromState)
|
||||
.and(_compareToAnimationStateExpr(_ANIMATION_NEXT_STATE_VAR, stateChange.toState)));
|
||||
|
||||
if (stateChange.fromState != ANY_STATE) {
|
||||
context.stateMap.registerState(stateChange.fromState);
|
||||
}
|
||||
if (stateChange.fromState != ANY_STATE) {
|
||||
context.stateMap.registerState(stateChange.fromState);
|
||||
}
|
||||
|
||||
if (stateChange.toState != ANY_STATE) {
|
||||
context.stateMap.registerState(stateChange.toState);
|
||||
if (stateChange.toState != ANY_STATE) {
|
||||
context.stateMap.registerState(stateChange.toState);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -294,8 +300,8 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
|
||||
statements.push(new o.ReturnStatement(
|
||||
o.importExpr(createIdentifier(Identifiers.AnimationTransition)).instantiate([
|
||||
_ANIMATION_PLAYER_VAR, _ANIMATION_CURRENT_STATE_VAR, _ANIMATION_NEXT_STATE_VAR,
|
||||
_ANIMATION_TIME_VAR
|
||||
_ANIMATION_PLAYER_VAR, _ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName),
|
||||
_ANIMATION_CURRENT_STATE_VAR, _ANIMATION_NEXT_STATE_VAR, _ANIMATION_TIME_VAR
|
||||
])));
|
||||
|
||||
return o.fn(
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StaticSymbol} from '../aot/static_symbol';
|
||||
import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata, CompileDirectiveMetadata, identifierName} from '../compile_metadata';
|
||||
import {StringMapWrapper} from '../facade/collection';
|
||||
import {isBlank, isPresent} from '../facade/lang';
|
||||
@ -14,7 +15,7 @@ import {ParseError} from '../parse_util';
|
||||
import {ANY_STATE, FILL_STYLE_FLAG} from '../private_import_core';
|
||||
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
||||
|
||||
import {AnimationAst, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStateTransitionExpression, AnimationStepAst, AnimationStylesAst, AnimationWithStepsAst} from './animation_ast';
|
||||
import {AnimationAst, AnimationEntryAst, AnimationGroupAst, AnimationKeyframeAst, AnimationSequenceAst, AnimationStateDeclarationAst, AnimationStateTransitionAst, AnimationStateTransitionExpression, AnimationStateTransitionFnExpression, AnimationStepAst, AnimationStylesAst, AnimationWithStepsAst} from './animation_ast';
|
||||
import {StylesCollection} from './styles_collection';
|
||||
|
||||
const _INITIAL_KEYFRAME = 0;
|
||||
@ -110,9 +111,12 @@ function _parseAnimationStateTransition(
|
||||
errors: AnimationParseError[]): AnimationStateTransitionAst {
|
||||
const styles = new StylesCollection();
|
||||
const transitionExprs: AnimationStateTransitionExpression[] = [];
|
||||
const transitionStates = transitionStateMetadata.stateChangeExpr.split(/\s*,\s*/);
|
||||
const stateChangeExpr = transitionStateMetadata.stateChangeExpr;
|
||||
const transitionStates: Array<Function|StaticSymbol|string> = typeof stateChangeExpr == 'string' ?
|
||||
(<string>stateChangeExpr).split(/\s*,\s*/) :
|
||||
[<Function|StaticSymbol>stateChangeExpr];
|
||||
transitionStates.forEach(
|
||||
expr => { transitionExprs.push(..._parseAnimationTransitionExpr(expr, errors)); });
|
||||
expr => transitionExprs.push(..._parseAnimationTransitionExpr(expr, errors)));
|
||||
const entry = _normalizeAnimationEntry(transitionStateMetadata.steps);
|
||||
const animation = _normalizeStyleSteps(entry, stateStyles, schema, errors);
|
||||
const animationAst = _parseTransitionAnimation(animation, 0, styles, stateStyles, errors);
|
||||
@ -141,25 +145,32 @@ function _parseAnimationAlias(alias: string, errors: AnimationParseError[]): str
|
||||
}
|
||||
|
||||
function _parseAnimationTransitionExpr(
|
||||
eventStr: string, errors: AnimationParseError[]): AnimationStateTransitionExpression[] {
|
||||
transitionValue: string | Function | StaticSymbol,
|
||||
errors: AnimationParseError[]): AnimationStateTransitionExpression[] {
|
||||
const expressions: AnimationStateTransitionExpression[] = [];
|
||||
if (eventStr[0] == ':') {
|
||||
eventStr = _parseAnimationAlias(eventStr, errors);
|
||||
}
|
||||
const match = eventStr.match(/^(\*|[-\w]+)\s*(<?[=-]>)\s*(\*|[-\w]+)$/);
|
||||
if (!isPresent(match) || match.length < 4) {
|
||||
errors.push(new AnimationParseError(`the provided ${eventStr} is not of a supported format`));
|
||||
return expressions;
|
||||
}
|
||||
if (typeof transitionValue == 'string') {
|
||||
let eventStr = <string>transitionValue;
|
||||
if (eventStr[0] == ':') {
|
||||
eventStr = _parseAnimationAlias(eventStr, errors);
|
||||
}
|
||||
const match = eventStr.match(/^(\*|[-\w]+)\s*(<?[=-]>)\s*(\*|[-\w]+)$/);
|
||||
if (!isPresent(match) || match.length < 4) {
|
||||
errors.push(new AnimationParseError(`the provided ${eventStr} is not of a supported format`));
|
||||
return expressions;
|
||||
}
|
||||
|
||||
const fromState = match[1];
|
||||
const separator = match[2];
|
||||
const toState = match[3];
|
||||
expressions.push(new AnimationStateTransitionExpression(fromState, toState));
|
||||
const fromState = match[1];
|
||||
const separator = match[2];
|
||||
const toState = match[3];
|
||||
expressions.push(new AnimationStateTransitionExpression(fromState, toState));
|
||||
|
||||
const isFullAnyStateExpr = fromState == ANY_STATE && toState == ANY_STATE;
|
||||
if (separator[0] == '<' && !isFullAnyStateExpr) {
|
||||
expressions.push(new AnimationStateTransitionExpression(toState, fromState));
|
||||
const isFullAnyStateExpr = fromState == ANY_STATE && toState == ANY_STATE;
|
||||
if (separator[0] == '<' && !isFullAnyStateExpr) {
|
||||
expressions.push(new AnimationStateTransitionExpression(toState, fromState));
|
||||
}
|
||||
} else {
|
||||
expressions.push(
|
||||
new AnimationStateTransitionFnExpression(<Function|StaticSymbol>transitionValue));
|
||||
}
|
||||
return expressions;
|
||||
}
|
||||
|
@ -8,9 +8,8 @@
|
||||
|
||||
import {AnimationCompiler} from '../animation/animation_compiler';
|
||||
import {AnimationParser} from '../animation/animation_parser';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileTypeSummary, createHostComponentMeta, identifierModuleUrl, identifierName} from '../compile_metadata';
|
||||
import {DirectiveNormalizer} from '../directive_normalizer';
|
||||
import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, componentFactoryName, createHostComponentMeta, identifierName} from '../compile_metadata';
|
||||
import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {Identifiers, createIdentifier, createIdentifierToken} from '../identifiers';
|
||||
import {CompileMetadataResolver} from '../metadata_resolver';
|
||||
@ -20,13 +19,14 @@ import * as o from '../output/output_ast';
|
||||
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||
import {SummaryResolver} from '../summary_resolver';
|
||||
import {TemplateParser} from '../template_parser/template_parser';
|
||||
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler';
|
||||
import {ViewCompiler} from '../view_compiler/view_compiler';
|
||||
|
||||
import {AotCompilerHost} from './compiler_host';
|
||||
import {GeneratedFile} from './generated_file';
|
||||
import {StaticSymbol} from './static_symbol';
|
||||
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
|
||||
import {serializeSummaries, summaryFileName} from './summary_serializer';
|
||||
import {StaticSymbolResolver} from './static_symbol_resolver';
|
||||
import {serializeSummaries} from './summary_serializer';
|
||||
import {ngfactoryFilePath, splitTypescriptSuffix, summaryFileName} from './util';
|
||||
|
||||
export class AotCompiler {
|
||||
private _animationCompiler = new AnimationCompiler();
|
||||
@ -63,12 +63,13 @@ export class AotCompiler {
|
||||
srcFileUrl: string, ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
|
||||
directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: StaticSymbol[],
|
||||
injectables: StaticSymbol[]): GeneratedFile[] {
|
||||
const fileSuffix = _splitTypescriptSuffix(srcFileUrl)[1];
|
||||
const fileSuffix = splitTypescriptSuffix(srcFileUrl)[1];
|
||||
const statements: o.Statement[] = [];
|
||||
const exportedVars: string[] = [];
|
||||
const generatedFiles: GeneratedFile[] = [];
|
||||
|
||||
generatedFiles.push(this._createSummary(srcFileUrl, directives, pipes, ngModules, injectables));
|
||||
generatedFiles.push(this._createSummary(
|
||||
srcFileUrl, directives, pipes, ngModules, injectables, statements, exportedVars));
|
||||
|
||||
// compile all ng modules
|
||||
exportedVars.push(
|
||||
@ -107,7 +108,7 @@ export class AotCompiler {
|
||||
});
|
||||
if (statements.length > 0) {
|
||||
const srcModule = this._codegenSourceModule(
|
||||
srcFileUrl, _ngfactoryModuleUrl(srcFileUrl), statements, exportedVars);
|
||||
srcFileUrl, ngfactoryFilePath(srcFileUrl), statements, exportedVars);
|
||||
generatedFiles.unshift(srcModule);
|
||||
}
|
||||
return generatedFiles;
|
||||
@ -115,7 +116,8 @@ export class AotCompiler {
|
||||
|
||||
private _createSummary(
|
||||
srcFileUrl: string, directives: StaticSymbol[], pipes: StaticSymbol[],
|
||||
ngModules: StaticSymbol[], injectables: StaticSymbol[]): GeneratedFile {
|
||||
ngModules: StaticSymbol[], injectables: StaticSymbol[], targetStatements: o.Statement[],
|
||||
targetExportedVars: string[]): GeneratedFile {
|
||||
const symbolSummaries = this._symbolResolver.getSymbolsOf(srcFileUrl)
|
||||
.map(symbol => this._symbolResolver.resolveSymbol(symbol));
|
||||
const typeSummaries = [
|
||||
@ -124,8 +126,13 @@ export class AotCompiler {
|
||||
...pipes.map(ref => this._metadataResolver.getPipeSummary(ref)),
|
||||
...injectables.map(ref => this._metadataResolver.getInjectableSummary(ref))
|
||||
];
|
||||
const json = serializeSummaries(
|
||||
this._host, this._summaryResolver, this._symbolResolver, symbolSummaries, typeSummaries);
|
||||
const {json, exportAs} = serializeSummaries(
|
||||
this._summaryResolver, this._symbolResolver, symbolSummaries, typeSummaries);
|
||||
exportAs.forEach((entry) => {
|
||||
targetStatements.push(
|
||||
o.variable(entry.exportAs).set(o.importExpr({reference: entry.symbol})).toDeclStmt());
|
||||
targetExportedVars.push(entry.exportAs);
|
||||
});
|
||||
return new GeneratedFile(srcFileUrl, summaryFileName(srcFileUrl), json);
|
||||
}
|
||||
|
||||
@ -148,12 +155,6 @@ export class AotCompiler {
|
||||
}
|
||||
|
||||
const appCompileResult = this._ngModuleCompiler.compile(ngModule, providers);
|
||||
|
||||
appCompileResult.dependencies.forEach((dep) => {
|
||||
dep.placeholder.reference = this._symbolResolver.getStaticSymbol(
|
||||
_ngfactoryModuleUrl(identifierModuleUrl(dep.comp)), _componentFactoryName(dep.comp));
|
||||
});
|
||||
|
||||
targetStatements.push(...appCompileResult.statements);
|
||||
return appCompileResult.ngModuleFactoryVar;
|
||||
}
|
||||
@ -170,13 +171,12 @@ export class AotCompiler {
|
||||
private _compileComponentFactory(
|
||||
compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata, fileSuffix: string,
|
||||
targetStatements: o.Statement[]): string {
|
||||
const hostType = this._metadataResolver.getHostComponentType(compMeta.type.reference);
|
||||
const hostMeta = createHostComponentMeta(
|
||||
this._symbolResolver.getStaticSymbol(
|
||||
identifierModuleUrl(compMeta.type), `${identifierName(compMeta.type)}_Host`),
|
||||
compMeta);
|
||||
hostType, compMeta, this._metadataResolver.getHostComponentViewClass(hostType));
|
||||
const hostViewFactoryVar = this._compileComponent(
|
||||
hostMeta, ngModule, [compMeta.type], null, fileSuffix, targetStatements);
|
||||
const compFactoryVar = _componentFactoryName(compMeta.type);
|
||||
const compFactoryVar = componentFactoryName(compMeta.type.reference);
|
||||
targetStatements.push(
|
||||
o.variable(compFactoryVar)
|
||||
.set(o.importExpr(
|
||||
@ -217,7 +217,7 @@ export class AotCompiler {
|
||||
..._resolveStyleStatements(this._symbolResolver, componentStyles, fileSuffix));
|
||||
}
|
||||
compiledAnimations.forEach(entry => targetStatements.push(...entry.statements));
|
||||
targetStatements.push(..._resolveViewStatements(this._symbolResolver, viewResult));
|
||||
targetStatements.push(...viewResult.statements);
|
||||
return viewResult.viewClassVar;
|
||||
}
|
||||
|
||||
@ -239,27 +239,6 @@ export class AotCompiler {
|
||||
}
|
||||
}
|
||||
|
||||
function _resolveViewStatements(
|
||||
reflector: StaticSymbolResolver, compileResult: ViewCompileResult): o.Statement[] {
|
||||
compileResult.dependencies.forEach((dep) => {
|
||||
if (dep instanceof ViewClassDependency) {
|
||||
const vfd = <ViewClassDependency>dep;
|
||||
vfd.placeholder.reference =
|
||||
reflector.getStaticSymbol(_ngfactoryModuleUrl(identifierModuleUrl(vfd.comp)), dep.name);
|
||||
} else if (dep instanceof ComponentFactoryDependency) {
|
||||
const cfd = <ComponentFactoryDependency>dep;
|
||||
cfd.placeholder.reference = reflector.getStaticSymbol(
|
||||
_ngfactoryModuleUrl(identifierModuleUrl(cfd.comp)), _componentFactoryName(cfd.comp));
|
||||
} else if (dep instanceof DirectiveWrapperDependency) {
|
||||
const dwd = <DirectiveWrapperDependency>dep;
|
||||
dwd.placeholder.reference =
|
||||
reflector.getStaticSymbol(_ngfactoryModuleUrl(identifierModuleUrl(dwd.dir)), dwd.name);
|
||||
}
|
||||
});
|
||||
return compileResult.statements;
|
||||
}
|
||||
|
||||
|
||||
function _resolveStyleStatements(
|
||||
reflector: StaticSymbolResolver, compileResult: CompiledStylesheet,
|
||||
fileSuffix: string): o.Statement[] {
|
||||
@ -270,15 +249,6 @@ function _resolveStyleStatements(
|
||||
return compileResult.statements;
|
||||
}
|
||||
|
||||
function _ngfactoryModuleUrl(dirUrl: string): string {
|
||||
const urlWithSuffix = _splitTypescriptSuffix(dirUrl);
|
||||
return `${urlWithSuffix[0]}.ngfactory${urlWithSuffix[1]}`;
|
||||
}
|
||||
|
||||
function _componentFactoryName(comp: CompileIdentifierMetadata): string {
|
||||
return `${identifierName(comp)}NgFactory`;
|
||||
}
|
||||
|
||||
function _stylesModuleUrl(stylesheetUrl: string, shim: boolean, suffix: string): string {
|
||||
return `${stylesheetUrl}${shim ? '.shim' : ''}.ngstyle${suffix}`;
|
||||
}
|
||||
@ -290,20 +260,6 @@ function _assertComponent(meta: CompileDirectiveMetadata) {
|
||||
}
|
||||
}
|
||||
|
||||
function _splitTypescriptSuffix(path: string): string[] {
|
||||
if (path.endsWith('.d.ts')) {
|
||||
return [path.slice(0, -5), '.ts'];
|
||||
}
|
||||
|
||||
const lastDot = path.lastIndexOf('.');
|
||||
|
||||
if (lastDot !== -1) {
|
||||
return [path.substring(0, lastDot), path.substring(lastDot)];
|
||||
}
|
||||
|
||||
return [path, ''];
|
||||
}
|
||||
|
||||
export interface NgAnalyzedModules {
|
||||
ngModules: CompileNgModuleMetadata[];
|
||||
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>;
|
||||
|
@ -34,11 +34,12 @@ import {AotCompilerHost} from './compiler_host';
|
||||
import {AotCompilerOptions} from './compiler_options';
|
||||
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
||||
import {StaticReflector} from './static_reflector';
|
||||
import {StaticSymbolCache} from './static_symbol';
|
||||
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
|
||||
import {StaticSymbolResolver} from './static_symbol_resolver';
|
||||
import {AotSummaryResolver} from './summary_resolver';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new AotCompiler based on options and a host.
|
||||
*/
|
||||
@ -69,13 +70,19 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom
|
||||
const resolver = new CompileMetadataResolver(
|
||||
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
|
||||
new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer,
|
||||
staticReflector);
|
||||
symbolCache, staticReflector);
|
||||
// TODO(vicb): do not pass options.i18nFormat here
|
||||
const importResolver = {
|
||||
getImportAs: (symbol: StaticSymbol) => symbolResolver.getImportAs(symbol),
|
||||
fileNameToModuleName: (fileName: string, containingFilePath: string) =>
|
||||
compilerHost.fileNameToModuleName(fileName, containingFilePath)
|
||||
};
|
||||
const compiler = new AotCompiler(
|
||||
compilerHost, resolver, tmplParser, new StyleCompiler(urlResolver),
|
||||
new ViewCompiler(config, elementSchemaRegistry),
|
||||
new DirectiveWrapperCompiler(config, expressionParser, elementSchemaRegistry, console),
|
||||
new NgModuleCompiler(), new TypeScriptEmitter(compilerHost), summaryResolver, options.locale,
|
||||
options.i18nFormat, new AnimationParser(elementSchemaRegistry), symbolResolver);
|
||||
new NgModuleCompiler(), new TypeScriptEmitter(importResolver), summaryResolver,
|
||||
options.locale, options.i18nFormat, new AnimationParser(elementSchemaRegistry),
|
||||
symbolResolver);
|
||||
return {compiler, reflector: staticReflector};
|
||||
}
|
||||
|
@ -6,19 +6,23 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ImportResolver} from '../output/path_util';
|
||||
|
||||
import {StaticSymbol} from './static_symbol';
|
||||
import {StaticSymbolResolverHost} from './static_symbol_resolver';
|
||||
import {AotSummaryResolverHost} from './summary_resolver';
|
||||
import {AotSummarySerializerHost} from './summary_serializer';
|
||||
|
||||
/**
|
||||
* The host of the AotCompiler disconnects the implementation from TypeScript / other language
|
||||
* services and from underlying file systems.
|
||||
*/
|
||||
export interface AotCompilerHost extends StaticSymbolResolverHost, ImportResolver,
|
||||
AotSummaryResolverHost, AotSummarySerializerHost {
|
||||
export interface AotCompilerHost extends StaticSymbolResolverHost, AotSummaryResolverHost {
|
||||
/**
|
||||
* Converts a file path to a module name that can be used as an `import.
|
||||
* I.e. `path/to/importedFile.ts` should be imported by `path/to/containingFile.ts`.
|
||||
*
|
||||
* See ImportResolver.
|
||||
*/
|
||||
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string
|
||||
/*|null*/;
|
||||
|
||||
/**
|
||||
* Loads a resource (e.g. html / css)
|
||||
*/
|
||||
|
@ -83,8 +83,11 @@ export class StaticReflector implements ReflectorReader {
|
||||
annotations = [];
|
||||
const classMetadata = this.getTypeMetadata(type);
|
||||
if (classMetadata['extends']) {
|
||||
const parentAnnotations = this.annotations(this.simplify(type, classMetadata['extends']));
|
||||
annotations.push(...parentAnnotations);
|
||||
const parentType = this.simplify(type, classMetadata['extends']);
|
||||
if (parentType && (parentType instanceof StaticSymbol)) {
|
||||
const parentAnnotations = this.annotations(parentType);
|
||||
annotations.push(...parentAnnotations);
|
||||
}
|
||||
}
|
||||
if (classMetadata['decorators']) {
|
||||
const ownAnnotations: any[] = this.simplify(type, classMetadata['decorators']);
|
||||
@ -101,10 +104,13 @@ export class StaticReflector implements ReflectorReader {
|
||||
const classMetadata = this.getTypeMetadata(type);
|
||||
propMetadata = {};
|
||||
if (classMetadata['extends']) {
|
||||
const parentPropMetadata = this.propMetadata(this.simplify(type, classMetadata['extends']));
|
||||
Object.keys(parentPropMetadata).forEach((parentProp) => {
|
||||
propMetadata[parentProp] = parentPropMetadata[parentProp];
|
||||
});
|
||||
const parentType = this.simplify(type, classMetadata['extends']);
|
||||
if (parentType instanceof StaticSymbol) {
|
||||
const parentPropMetadata = this.propMetadata(parentType);
|
||||
Object.keys(parentPropMetadata).forEach((parentProp) => {
|
||||
propMetadata[parentProp] = parentPropMetadata[parentProp];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const members = classMetadata['members'] || {};
|
||||
@ -156,7 +162,10 @@ export class StaticReflector implements ReflectorReader {
|
||||
parameters.push(nestedResult);
|
||||
});
|
||||
} else if (classMetadata['extends']) {
|
||||
parameters = this.parameters(this.simplify(type, classMetadata['extends']));
|
||||
const parentType = this.simplify(type, classMetadata['extends']);
|
||||
if (parentType instanceof StaticSymbol) {
|
||||
parameters = this.parameters(parentType);
|
||||
}
|
||||
}
|
||||
if (!parameters) {
|
||||
parameters = [];
|
||||
@ -176,10 +185,13 @@ export class StaticReflector implements ReflectorReader {
|
||||
const classMetadata = this.getTypeMetadata(type);
|
||||
methodNames = {};
|
||||
if (classMetadata['extends']) {
|
||||
const parentMethodNames = this._methodNames(this.simplify(type, classMetadata['extends']));
|
||||
Object.keys(parentMethodNames).forEach((parentProp) => {
|
||||
methodNames[parentProp] = parentMethodNames[parentProp];
|
||||
});
|
||||
const parentType = this.simplify(type, classMetadata['extends']);
|
||||
if (parentType instanceof StaticSymbol) {
|
||||
const parentMethodNames = this._methodNames(parentType);
|
||||
Object.keys(parentMethodNames).forEach((parentProp) => {
|
||||
methodNames[parentProp] = parentMethodNames[parentProp];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const members = classMetadata['members'] || {};
|
||||
|
@ -12,7 +12,14 @@
|
||||
* This token is unique for a filePath and name and can be used as a hash table key.
|
||||
*/
|
||||
export class StaticSymbol {
|
||||
constructor(public filePath: string, public name: string, public members?: string[]) {}
|
||||
constructor(public filePath: string, public name: string, public members: string[]) {}
|
||||
|
||||
assertNoMembers() {
|
||||
if (this.members.length) {
|
||||
throw new Error(
|
||||
`Illegal state: symbol without members expected, but got ${JSON.stringify(this)}.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,11 +45,18 @@ const SUPPORTED_SCHEMA_VERSION = 3;
|
||||
/**
|
||||
* This class is responsible for loading metadata per symbol,
|
||||
* and normalizing references between symbols.
|
||||
*
|
||||
* Internally, it only uses symbols without members,
|
||||
* and deduces the values for symbols with members based
|
||||
* on these symbols.
|
||||
*/
|
||||
export class StaticSymbolResolver {
|
||||
private metadataCache = new Map<string, {[key: string]: any}>();
|
||||
// Note: this will only contain StaticSymbols without members!
|
||||
private resolvedSymbols = new Map<StaticSymbol, ResolvedStaticSymbol>();
|
||||
private resolvedFilePaths = new Set<string>();
|
||||
// Note: this will only contain StaticSymbols without members!
|
||||
private importAs = new Map<StaticSymbol, StaticSymbol>();
|
||||
|
||||
constructor(
|
||||
private host: StaticSymbolResolverHost, private staticSymbolCache: StaticSymbolCache,
|
||||
@ -60,13 +67,33 @@ export class StaticSymbolResolver {
|
||||
if (staticSymbol.members.length > 0) {
|
||||
return this._resolveSymbolMembers(staticSymbol);
|
||||
}
|
||||
let result = this._resolveSymbolFromSummary(staticSymbol);
|
||||
let result = this.resolvedSymbols.get(staticSymbol);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
result = this._resolveSymbolFromSummary(staticSymbol);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
// Note: Some users use libraries that were not compiled with ngc, i.e. they don't
|
||||
// have summaries, only .d.ts files. So we always need to check both, the summary
|
||||
// and metadata.
|
||||
this._createSymbolsOf(staticSymbol.filePath);
|
||||
result = this.resolvedSymbols.get(staticSymbol);
|
||||
return result;
|
||||
}
|
||||
|
||||
getImportAs(staticSymbol: StaticSymbol): StaticSymbol {
|
||||
if (staticSymbol.members.length) {
|
||||
const baseSymbol = this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name);
|
||||
const baseImportAs = this.getImportAs(baseSymbol);
|
||||
return baseImportAs ?
|
||||
this.getStaticSymbol(baseImportAs.filePath, baseImportAs.name, staticSymbol.members) :
|
||||
null;
|
||||
}
|
||||
let result = this.summaryResolver.getImportAs(staticSymbol);
|
||||
if (!result) {
|
||||
// Note: Some users use libraries that were not compiled with ngc, i.e. they don't
|
||||
// have summaries, only .d.ts files. So we always need to check both, the summary
|
||||
// and metadata.
|
||||
this._createSymbolsOf(staticSymbol.filePath);
|
||||
result = this.resolvedSymbols.get(staticSymbol);
|
||||
result = this.importAs.get(staticSymbol);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -135,10 +162,13 @@ export class StaticSymbolResolver {
|
||||
const metadata = this.getModuleMetadata(filePath);
|
||||
if (metadata['metadata']) {
|
||||
// handle direct declarations of the symbol
|
||||
Object.keys(metadata['metadata']).forEach((symbolName) => {
|
||||
const symbolMeta = metadata['metadata'][symbolName];
|
||||
resolvedSymbols.push(
|
||||
this.createResolvedSymbol(this.getStaticSymbol(filePath, symbolName), symbolMeta));
|
||||
const topLevelSymbolNames =
|
||||
new Set<string>(Object.keys(metadata['metadata']).map(unescapeIdentifier));
|
||||
Object.keys(metadata['metadata']).forEach((metadataKey) => {
|
||||
const symbolMeta = metadata['metadata'][metadataKey];
|
||||
resolvedSymbols.push(this.createResolvedSymbol(
|
||||
this.getStaticSymbol(filePath, unescapeIdentifier(metadataKey)), topLevelSymbolNames,
|
||||
symbolMeta));
|
||||
});
|
||||
}
|
||||
|
||||
@ -154,15 +184,16 @@ export class StaticSymbolResolver {
|
||||
} else {
|
||||
symbolName = exportSymbol.as;
|
||||
}
|
||||
symbolName = unescapeIdentifier(symbolName);
|
||||
let symName = symbolName;
|
||||
if (typeof exportSymbol !== 'string') {
|
||||
symName = exportSymbol.name;
|
||||
symName = unescapeIdentifier(exportSymbol.name);
|
||||
}
|
||||
const resolvedModule = this.resolveModule(moduleExport.from, filePath);
|
||||
if (resolvedModule) {
|
||||
const targetSymbol = this.getStaticSymbol(resolvedModule, symName);
|
||||
const sourceSymbol = this.getStaticSymbol(filePath, symbolName);
|
||||
resolvedSymbols.push(new ResolvedStaticSymbol(sourceSymbol, targetSymbol));
|
||||
resolvedSymbols.push(this.createExport(sourceSymbol, targetSymbol));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@ -172,7 +203,7 @@ export class StaticSymbolResolver {
|
||||
const nestedExports = this.getSymbolsOf(resolvedModule);
|
||||
nestedExports.forEach((targetSymbol) => {
|
||||
const sourceSymbol = this.getStaticSymbol(filePath, targetSymbol.name);
|
||||
resolvedSymbols.push(new ResolvedStaticSymbol(sourceSymbol, targetSymbol));
|
||||
resolvedSymbols.push(this.createExport(sourceSymbol, targetSymbol));
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -182,7 +213,9 @@ export class StaticSymbolResolver {
|
||||
(resolvedSymbol) => this.resolvedSymbols.set(resolvedSymbol.symbol, resolvedSymbol));
|
||||
}
|
||||
|
||||
private createResolvedSymbol(sourceSymbol: StaticSymbol, metadata: any): ResolvedStaticSymbol {
|
||||
private createResolvedSymbol(
|
||||
sourceSymbol: StaticSymbol, topLevelSymbolNames: Set<string>,
|
||||
metadata: any): ResolvedStaticSymbol {
|
||||
const self = this;
|
||||
|
||||
class ReferenceTransformer extends ValueTransformer {
|
||||
@ -196,7 +229,7 @@ export class StaticSymbolResolver {
|
||||
return result;
|
||||
} else if (symbolic === 'reference') {
|
||||
const module = map['module'];
|
||||
const name = map['name'];
|
||||
const name = map['name'] ? unescapeIdentifier(map['name']) : map['name'];
|
||||
if (!name) {
|
||||
return null;
|
||||
}
|
||||
@ -209,28 +242,43 @@ export class StaticSymbolResolver {
|
||||
message: `Could not resolve ${module} relative to ${sourceSymbol.filePath}.`
|
||||
};
|
||||
}
|
||||
} else {
|
||||
const isFunctionParam = functionParams.indexOf(name) >= 0;
|
||||
if (!isFunctionParam) {
|
||||
filePath = sourceSymbol.filePath;
|
||||
}
|
||||
}
|
||||
if (filePath) {
|
||||
return self.getStaticSymbol(filePath, name);
|
||||
} else {
|
||||
} else if (functionParams.indexOf(name) >= 0) {
|
||||
// reference to a function parameter
|
||||
return {__symbolic: 'reference', name: name};
|
||||
} else {
|
||||
if (topLevelSymbolNames.has(name)) {
|
||||
return self.getStaticSymbol(sourceSymbol.filePath, name);
|
||||
}
|
||||
// ambient value
|
||||
null;
|
||||
}
|
||||
} else {
|
||||
return super.visitStringMap(map, functionParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const transformedMeta = visitValue(metadata, new ReferenceTransformer(), []);
|
||||
if (transformedMeta instanceof StaticSymbol) {
|
||||
return this.createExport(sourceSymbol, transformedMeta);
|
||||
}
|
||||
return new ResolvedStaticSymbol(sourceSymbol, transformedMeta);
|
||||
}
|
||||
|
||||
private createExport(sourceSymbol: StaticSymbol, targetSymbol: StaticSymbol):
|
||||
ResolvedStaticSymbol {
|
||||
sourceSymbol.assertNoMembers();
|
||||
targetSymbol.assertNoMembers();
|
||||
if (this.summaryResolver.isLibraryFile(sourceSymbol.filePath)) {
|
||||
// This case is for an ng library importing symbols from a plain ts library
|
||||
// transitively.
|
||||
// Note: We rely on the fact that we discover symbols in the direction
|
||||
// from source files to library files
|
||||
this.importAs.set(targetSymbol, this.getImportAs(sourceSymbol) || sourceSymbol);
|
||||
}
|
||||
return new ResolvedStaticSymbol(sourceSymbol, targetSymbol);
|
||||
}
|
||||
|
||||
private reportError(error: Error, context: StaticSymbol, path?: string) {
|
||||
if (this.errorRecorder) {
|
||||
this.errorRecorder(error, (context && context.filePath) || path);
|
||||
@ -287,3 +335,9 @@ export class StaticSymbolResolver {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove extra underscore from escaped identifier.
|
||||
// See https://github.com/Microsoft/TypeScript/blob/master/src/compiler/utilities.ts
|
||||
export function unescapeIdentifier(identifier: string): string {
|
||||
return identifier.startsWith('___') ? identifier.substr(1) : identifier;
|
||||
}
|
@ -9,10 +9,8 @@
|
||||
import {Summary, SummaryResolver} from '../summary_resolver';
|
||||
|
||||
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
|
||||
import {ResolvedStaticSymbol} from './static_symbol_resolver';
|
||||
import {deserializeSummaries, summaryFileName} from './summary_serializer';
|
||||
|
||||
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||
import {deserializeSummaries} from './summary_serializer';
|
||||
import {ngfactoryFilePath, stripNgFactory, summaryFileName} from './util';
|
||||
|
||||
export interface AotSummaryResolverHost {
|
||||
/**
|
||||
@ -24,23 +22,34 @@ export interface AotSummaryResolverHost {
|
||||
* Returns whether a file is a source file or not.
|
||||
*/
|
||||
isSourceFile(sourceFilePath: string): boolean;
|
||||
/**
|
||||
* Returns the output file path of a source file.
|
||||
* E.g.
|
||||
* `some_file.ts` -> `some_file.d.ts`
|
||||
*/
|
||||
getOutputFileName(sourceFilePath: string): string;
|
||||
}
|
||||
|
||||
export class AotSummaryResolver implements SummaryResolver<StaticSymbol> {
|
||||
// Note: this will only contain StaticSymbols without members!
|
||||
private summaryCache = new Map<StaticSymbol, Summary<StaticSymbol>>();
|
||||
private loadedFilePaths = new Set<string>();
|
||||
// Note: this will only contain StaticSymbols without members!
|
||||
private importAs = new Map<StaticSymbol, StaticSymbol>();
|
||||
|
||||
constructor(private host: AotSummaryResolverHost, private staticSymbolCache: StaticSymbolCache) {}
|
||||
|
||||
private _assertNoMembers(symbol: StaticSymbol) {
|
||||
if (symbol.members.length) {
|
||||
throw new Error(
|
||||
`Internal state: StaticSymbols in summaries can't have members! ${JSON.stringify(symbol)}`);
|
||||
}
|
||||
isLibraryFile(filePath: string): boolean {
|
||||
// Note: We need to strip the .ngfactory. file path,
|
||||
// so this method also works for generated files
|
||||
// (for which host.isSourceFile will always return false).
|
||||
return !this.host.isSourceFile(stripNgFactory(filePath));
|
||||
}
|
||||
|
||||
getLibraryFileName(filePath: string) { return this.host.getOutputFileName(filePath); }
|
||||
|
||||
resolveSummary(staticSymbol: StaticSymbol): Summary<StaticSymbol> {
|
||||
this._assertNoMembers(staticSymbol);
|
||||
staticSymbol.assertNoMembers();
|
||||
let summary = this.summaryCache.get(staticSymbol);
|
||||
if (!summary) {
|
||||
this._loadSummaryFile(staticSymbol.filePath);
|
||||
@ -54,12 +63,17 @@ export class AotSummaryResolver implements SummaryResolver<StaticSymbol> {
|
||||
return Array.from(this.summaryCache.keys()).filter((symbol) => symbol.filePath === filePath);
|
||||
}
|
||||
|
||||
getImportAs(staticSymbol: StaticSymbol): StaticSymbol {
|
||||
staticSymbol.assertNoMembers();
|
||||
return this.importAs.get(staticSymbol);
|
||||
}
|
||||
|
||||
private _loadSummaryFile(filePath: string) {
|
||||
if (this.loadedFilePaths.has(filePath)) {
|
||||
return;
|
||||
}
|
||||
this.loadedFilePaths.add(filePath);
|
||||
if (!this.host.isSourceFile(filePath)) {
|
||||
if (this.isLibraryFile(filePath)) {
|
||||
const summaryFilePath = summaryFileName(filePath);
|
||||
let json: string;
|
||||
try {
|
||||
@ -69,8 +83,13 @@ export class AotSummaryResolver implements SummaryResolver<StaticSymbol> {
|
||||
throw e;
|
||||
}
|
||||
if (json) {
|
||||
const readSummaries = deserializeSummaries(this.staticSymbolCache, json);
|
||||
readSummaries.forEach((summary) => { this.summaryCache.set(summary.symbol, summary); });
|
||||
const {summaries, importAs} = deserializeSummaries(this.staticSymbolCache, json);
|
||||
summaries.forEach((summary) => this.summaryCache.set(summary.symbol, summary));
|
||||
importAs.forEach((importAs) => {
|
||||
this.importAs.set(
|
||||
importAs.symbol,
|
||||
this.staticSymbolCache.get(ngfactoryFilePath(filePath), importAs.importAs));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,28 +12,12 @@ import {ValueTransformer, visitValue} from '../util';
|
||||
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
|
||||
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
|
||||
|
||||
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||
|
||||
|
||||
export interface AotSummarySerializerHost {
|
||||
/**
|
||||
* Returns the output file path of a source file.
|
||||
* E.g.
|
||||
* `some_file.ts` -> `some_file.d.ts`
|
||||
*/
|
||||
getOutputFileName(sourceFilePath: string): string;
|
||||
/**
|
||||
* Returns whether a file is a source file or not.
|
||||
*/
|
||||
isSourceFile(sourceFilePath: string): boolean;
|
||||
}
|
||||
|
||||
export function serializeSummaries(
|
||||
host: AotSummarySerializerHost, summaryResolver: SummaryResolver<StaticSymbol>,
|
||||
symbolResolver: StaticSymbolResolver,
|
||||
|
||||
symbols: ResolvedStaticSymbol[], types: CompileTypeSummary[]): string {
|
||||
const serializer = new Serializer(host);
|
||||
summaryResolver: SummaryResolver<StaticSymbol>, symbolResolver: StaticSymbolResolver,
|
||||
symbols: ResolvedStaticSymbol[], types: CompileTypeSummary[]):
|
||||
{json: string, exportAs: {symbol: StaticSymbol, exportAs: string}[]} {
|
||||
const serializer = new Serializer(symbolResolver, summaryResolver);
|
||||
|
||||
// for symbols, we use everything except for the class metadata itself
|
||||
// (we keep the statics though), as the class metadata is contained in the
|
||||
@ -46,7 +30,7 @@ export function serializeSummaries(
|
||||
// we execute the loop!
|
||||
for (let processedIndex = 0; processedIndex < serializer.symbols.length; processedIndex++) {
|
||||
const symbol = serializer.symbols[processedIndex];
|
||||
if (!host.isSourceFile(symbol.filePath)) {
|
||||
if (summaryResolver.isLibraryFile(symbol.filePath)) {
|
||||
let summary = summaryResolver.resolveSummary(symbol);
|
||||
if (!summary) {
|
||||
// some symbols might originate from a plain typescript library
|
||||
@ -74,8 +58,11 @@ export function serializeSummaries(
|
||||
const ngModuleSummary = <CompileNgModuleSummary>typeSummary;
|
||||
ngModuleSummary.exportedDirectives.concat(ngModuleSummary.exportedPipes).forEach((id) => {
|
||||
const symbol: StaticSymbol = id.reference;
|
||||
if (!host.isSourceFile(symbol.filePath)) {
|
||||
serializer.addOrMergeSummary(summaryResolver.resolveSummary(symbol));
|
||||
if (summaryResolver.isLibraryFile(symbol.filePath)) {
|
||||
const summary = summaryResolver.resolveSummary(symbol);
|
||||
if (summary) {
|
||||
serializer.addOrMergeSummary(summary);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -83,18 +70,14 @@ export function serializeSummaries(
|
||||
return serializer.serialize();
|
||||
}
|
||||
|
||||
export function deserializeSummaries(
|
||||
symbolCache: StaticSymbolCache, json: string): Summary<StaticSymbol>[] {
|
||||
export function deserializeSummaries(symbolCache: StaticSymbolCache, json: string):
|
||||
{summaries: Summary<StaticSymbol>[], importAs: {symbol: StaticSymbol, importAs: string}[]} {
|
||||
const deserializer = new Deserializer(symbolCache);
|
||||
return deserializer.deserialize(json);
|
||||
}
|
||||
|
||||
export function summaryFileName(fileName: string): string {
|
||||
const fileNameWithoutSuffix = fileName.replace(STRIP_SRC_FILE_SUFFIXES, '');
|
||||
return `${fileNameWithoutSuffix}.ngsummary.json`;
|
||||
}
|
||||
|
||||
class Serializer extends ValueTransformer {
|
||||
// Note: This only contains symbols without members.
|
||||
symbols: StaticSymbol[] = [];
|
||||
private indexBySymbol = new Map<StaticSymbol, number>();
|
||||
// This now contains a `__symbol: number` in the place of
|
||||
@ -102,7 +85,11 @@ class Serializer extends ValueTransformer {
|
||||
private processedSummaryBySymbol = new Map<StaticSymbol, any>();
|
||||
private processedSummaries: any[] = [];
|
||||
|
||||
constructor(private host: AotSummarySerializerHost) { super(); }
|
||||
constructor(
|
||||
private symbolResolver: StaticSymbolResolver,
|
||||
private summaryResolver: SummaryResolver<StaticSymbol>) {
|
||||
super();
|
||||
}
|
||||
|
||||
addOrMergeSummary(summary: Summary<StaticSymbol>) {
|
||||
let symbolMeta = summary.metadata;
|
||||
@ -129,34 +116,44 @@ class Serializer extends ValueTransformer {
|
||||
}
|
||||
}
|
||||
|
||||
serialize(): string {
|
||||
return JSON.stringify({
|
||||
serialize(): {json: string, exportAs: {symbol: StaticSymbol, exportAs: string}[]} {
|
||||
const exportAs: {symbol: StaticSymbol, exportAs: string}[] = [];
|
||||
const json = JSON.stringify({
|
||||
summaries: this.processedSummaries,
|
||||
symbols: this.symbols.map((symbol, index) => {
|
||||
symbol.assertNoMembers();
|
||||
let importAs: string;
|
||||
if (this.summaryResolver.isLibraryFile(symbol.filePath)) {
|
||||
importAs = `${symbol.name}_${index}`;
|
||||
exportAs.push({symbol, exportAs: importAs});
|
||||
}
|
||||
return {
|
||||
__symbol: index,
|
||||
name: symbol.name,
|
||||
// We convert the source filenames tinto output filenames,
|
||||
// as the generated summary file will be used when teh current
|
||||
// compilation unit is used as a library
|
||||
filePath: this.host.getOutputFileName(symbol.filePath)
|
||||
filePath: this.summaryResolver.getLibraryFileName(symbol.filePath),
|
||||
importAs: importAs
|
||||
};
|
||||
})
|
||||
});
|
||||
return {json, exportAs};
|
||||
}
|
||||
|
||||
private processValue(value: any): any { return visitValue(value, this, null); }
|
||||
|
||||
visitOther(value: any, context: any): any {
|
||||
if (value instanceof StaticSymbol) {
|
||||
let index = this.indexBySymbol.get(value);
|
||||
const baseSymbol = this.symbolResolver.getStaticSymbol(value.filePath, value.name);
|
||||
let index = this.indexBySymbol.get(baseSymbol);
|
||||
// Note: == by purpose to compare with undefined!
|
||||
if (index == null) {
|
||||
index = this.indexBySymbol.size;
|
||||
this.indexBySymbol.set(value, index);
|
||||
this.symbols.push(value);
|
||||
this.indexBySymbol.set(baseSymbol, index);
|
||||
this.symbols.push(baseSymbol);
|
||||
}
|
||||
return {__symbol: index};
|
||||
return {__symbol: index, members: value.members};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -166,18 +163,30 @@ class Deserializer extends ValueTransformer {
|
||||
|
||||
constructor(private symbolCache: StaticSymbolCache) { super(); }
|
||||
|
||||
deserialize(json: string): Summary<StaticSymbol>[] {
|
||||
deserialize(json: string):
|
||||
{summaries: Summary<StaticSymbol>[], importAs: {symbol: StaticSymbol, importAs: string}[]} {
|
||||
const data: {summaries: any[], symbols: any[]} = JSON.parse(json);
|
||||
this.symbols = data.symbols.map(
|
||||
serializedSymbol => this.symbolCache.get(serializedSymbol.filePath, serializedSymbol.name));
|
||||
return visitValue(data.summaries, this, null);
|
||||
const importAs: {symbol: StaticSymbol, importAs: string}[] = [];
|
||||
this.symbols = [];
|
||||
data.symbols.forEach((serializedSymbol) => {
|
||||
const symbol = this.symbolCache.get(serializedSymbol.filePath, serializedSymbol.name);
|
||||
this.symbols.push(symbol);
|
||||
if (serializedSymbol.importAs) {
|
||||
importAs.push({symbol: symbol, importAs: serializedSymbol.importAs});
|
||||
}
|
||||
});
|
||||
const summaries = visitValue(data.summaries, this, null);
|
||||
return {summaries, importAs};
|
||||
}
|
||||
|
||||
visitStringMap(map: {[key: string]: any}, context: any): any {
|
||||
if ('__symbol' in map) {
|
||||
return this.symbols[map['__symbol']];
|
||||
const baseSymbol = this.symbols[map['__symbol']];
|
||||
const members = map['members'];
|
||||
return members.length ? this.symbolCache.get(baseSymbol.filePath, baseSymbol.name, members) :
|
||||
baseSymbol;
|
||||
} else {
|
||||
return super.visitStringMap(map, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
37
modules/@angular/compiler/src/aot/util.ts
Normal file
37
modules/@angular/compiler/src/aot/util.ts
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||
|
||||
export function ngfactoryFilePath(filePath: string): string {
|
||||
const urlWithSuffix = splitTypescriptSuffix(filePath);
|
||||
return `${urlWithSuffix[0]}.ngfactory${urlWithSuffix[1]}`;
|
||||
}
|
||||
|
||||
export function stripNgFactory(filePath: string): string {
|
||||
return filePath.replace(/\.ngfactory\./, '.');
|
||||
}
|
||||
|
||||
export function splitTypescriptSuffix(path: string): string[] {
|
||||
if (path.endsWith('.d.ts')) {
|
||||
return [path.slice(0, -5), '.ts'];
|
||||
}
|
||||
|
||||
const lastDot = path.lastIndexOf('.');
|
||||
|
||||
if (lastDot !== -1) {
|
||||
return [path.substring(0, lastDot), path.substring(lastDot)];
|
||||
}
|
||||
|
||||
return [path, ''];
|
||||
}
|
||||
|
||||
export function summaryFileName(fileName: string): string {
|
||||
const fileNameWithoutSuffix = fileName.replace(STRIP_SRC_FILE_SUFFIXES, '');
|
||||
return `${fileNameWithoutSuffix}.ngsummary.json`;
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectionStrategy, SchemaMetadata, Type, ViewEncapsulation} from '@angular/core';
|
||||
import {ChangeDetectionStrategy, ComponentFactory, SchemaMetadata, Type, ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {StaticSymbol} from './aot/static_symbol';
|
||||
import {ListWrapper} from './facade/collection';
|
||||
@ -39,7 +39,11 @@ export class CompileAnimationStateDeclarationMetadata extends CompileAnimationSt
|
||||
}
|
||||
|
||||
export class CompileAnimationStateTransitionMetadata extends CompileAnimationStateMetadata {
|
||||
constructor(public stateChangeExpr: string, public steps: CompileAnimationMetadata) { super(); }
|
||||
constructor(
|
||||
public stateChangeExpr: string|StaticSymbol|((stateA: string, stateB: string) => boolean),
|
||||
public steps: CompileAnimationMetadata) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class CompileAnimationMetadata {}
|
||||
@ -112,6 +116,24 @@ export function identifierModuleUrl(compileIdentifier: CompileIdentifierMetadata
|
||||
return reflector.importUri(ref);
|
||||
}
|
||||
|
||||
export function viewClassName(compType: any, embeddedTemplateIndex: number): string {
|
||||
return `View_${identifierName({reference: compType})}_${embeddedTemplateIndex}`;
|
||||
}
|
||||
|
||||
export function hostViewClassName(compType: any): string {
|
||||
return `HostView_${identifierName({reference: compType})}`;
|
||||
}
|
||||
|
||||
export function dirWrapperClassName(dirType: any) {
|
||||
return `Wrapper_${identifierName({reference: dirType})}`;
|
||||
}
|
||||
|
||||
export function componentFactoryName(compType: any): string {
|
||||
return `${identifierName({reference: compType})}NgFactory`;
|
||||
}
|
||||
|
||||
export interface ProxyClass { setDelegate(delegate: any): void; }
|
||||
|
||||
export interface CompileIdentifierMetadata { reference: any; }
|
||||
|
||||
export enum CompileSummaryKind {
|
||||
@ -241,7 +263,7 @@ export class CompileTemplateMetadata {
|
||||
externalStylesheets?: CompileStylesheetMetadata[],
|
||||
ngContentSelectors?: string[],
|
||||
animations?: CompileAnimationEntryMetadata[],
|
||||
interpolation?: [string, string]
|
||||
interpolation?: [string, string],
|
||||
} = {}) {
|
||||
this.encapsulation = encapsulation;
|
||||
this.template = template;
|
||||
@ -261,11 +283,16 @@ export class CompileTemplateMetadata {
|
||||
return {
|
||||
animations: this.animations.map(anim => anim.name),
|
||||
ngContentSelectors: this.ngContentSelectors,
|
||||
encapsulation: this.encapsulation
|
||||
encapsulation: this.encapsulation,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export interface CompileEntryComponentMetadata {
|
||||
componentType: any;
|
||||
componentFactory: StaticSymbol|ComponentFactory<any>;
|
||||
}
|
||||
|
||||
// Note: This should only use interfaces as nested data types
|
||||
// as we need to be able to serialize this from/to JSON!
|
||||
export interface CompileDirectiveSummary extends CompileTypeSummary {
|
||||
@ -281,9 +308,12 @@ export interface CompileDirectiveSummary extends CompileTypeSummary {
|
||||
providers: CompileProviderMetadata[];
|
||||
viewProviders: CompileProviderMetadata[];
|
||||
queries: CompileQueryMetadata[];
|
||||
entryComponents: CompileIdentifierMetadata[];
|
||||
entryComponents: CompileEntryComponentMetadata[];
|
||||
changeDetection: ChangeDetectionStrategy;
|
||||
template: CompileTemplateSummary;
|
||||
wrapperType: StaticSymbol|ProxyClass;
|
||||
componentViewType: StaticSymbol|ProxyClass;
|
||||
componentFactory: StaticSymbol|ComponentFactory<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -292,7 +322,8 @@ export interface CompileDirectiveSummary extends CompileTypeSummary {
|
||||
export class CompileDirectiveMetadata {
|
||||
static create(
|
||||
{isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs, host,
|
||||
providers, viewProviders, queries, viewQueries, entryComponents, template}: {
|
||||
providers, viewProviders, queries, viewQueries, entryComponents, template, wrapperType,
|
||||
componentViewType, componentFactory}: {
|
||||
isHost?: boolean,
|
||||
type?: CompileTypeMetadata,
|
||||
isComponent?: boolean,
|
||||
@ -306,8 +337,11 @@ export class CompileDirectiveMetadata {
|
||||
viewProviders?: CompileProviderMetadata[],
|
||||
queries?: CompileQueryMetadata[],
|
||||
viewQueries?: CompileQueryMetadata[],
|
||||
entryComponents?: CompileIdentifierMetadata[],
|
||||
template?: CompileTemplateMetadata
|
||||
entryComponents?: CompileEntryComponentMetadata[],
|
||||
template?: CompileTemplateMetadata,
|
||||
wrapperType?: StaticSymbol|ProxyClass,
|
||||
componentViewType?: StaticSymbol|ProxyClass,
|
||||
componentFactory?: StaticSymbol|ComponentFactory<any>,
|
||||
} = {}): CompileDirectiveMetadata {
|
||||
const hostListeners: {[key: string]: string} = {};
|
||||
const hostProperties: {[key: string]: string} = {};
|
||||
@ -359,6 +393,9 @@ export class CompileDirectiveMetadata {
|
||||
viewQueries,
|
||||
entryComponents,
|
||||
template,
|
||||
wrapperType,
|
||||
componentViewType,
|
||||
componentFactory,
|
||||
});
|
||||
}
|
||||
isHost: boolean;
|
||||
@ -376,32 +413,39 @@ export class CompileDirectiveMetadata {
|
||||
viewProviders: CompileProviderMetadata[];
|
||||
queries: CompileQueryMetadata[];
|
||||
viewQueries: CompileQueryMetadata[];
|
||||
entryComponents: CompileIdentifierMetadata[];
|
||||
entryComponents: CompileEntryComponentMetadata[];
|
||||
|
||||
template: CompileTemplateMetadata;
|
||||
|
||||
constructor(
|
||||
{isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs,
|
||||
hostListeners, hostProperties, hostAttributes, providers, viewProviders, queries,
|
||||
viewQueries, entryComponents, template}: {
|
||||
isHost?: boolean,
|
||||
type?: CompileTypeMetadata,
|
||||
isComponent?: boolean,
|
||||
selector?: string,
|
||||
exportAs?: string,
|
||||
changeDetection?: ChangeDetectionStrategy,
|
||||
inputs?: {[key: string]: string},
|
||||
outputs?: {[key: string]: string},
|
||||
hostListeners?: {[key: string]: string},
|
||||
hostProperties?: {[key: string]: string},
|
||||
hostAttributes?: {[key: string]: string},
|
||||
providers?: CompileProviderMetadata[],
|
||||
viewProviders?: CompileProviderMetadata[],
|
||||
queries?: CompileQueryMetadata[],
|
||||
viewQueries?: CompileQueryMetadata[],
|
||||
entryComponents?: CompileIdentifierMetadata[],
|
||||
template?: CompileTemplateMetadata,
|
||||
} = {}) {
|
||||
wrapperType: StaticSymbol|ProxyClass;
|
||||
componentViewType: StaticSymbol|ProxyClass;
|
||||
componentFactory: StaticSymbol|ComponentFactory<any>;
|
||||
|
||||
constructor({isHost, type, isComponent, selector, exportAs,
|
||||
changeDetection, inputs, outputs, hostListeners, hostProperties,
|
||||
hostAttributes, providers, viewProviders, queries, viewQueries,
|
||||
entryComponents, template, wrapperType, componentViewType, componentFactory}: {
|
||||
isHost?: boolean,
|
||||
type?: CompileTypeMetadata,
|
||||
isComponent?: boolean,
|
||||
selector?: string,
|
||||
exportAs?: string,
|
||||
changeDetection?: ChangeDetectionStrategy,
|
||||
inputs?: {[key: string]: string},
|
||||
outputs?: {[key: string]: string},
|
||||
hostListeners?: {[key: string]: string},
|
||||
hostProperties?: {[key: string]: string},
|
||||
hostAttributes?: {[key: string]: string},
|
||||
providers?: CompileProviderMetadata[],
|
||||
viewProviders?: CompileProviderMetadata[],
|
||||
queries?: CompileQueryMetadata[],
|
||||
viewQueries?: CompileQueryMetadata[],
|
||||
entryComponents?: CompileEntryComponentMetadata[],
|
||||
template?: CompileTemplateMetadata,
|
||||
wrapperType?: StaticSymbol|ProxyClass,
|
||||
componentViewType?: StaticSymbol|ProxyClass,
|
||||
componentFactory?: StaticSymbol|ComponentFactory<any>,
|
||||
} = {}) {
|
||||
this.isHost = !!isHost;
|
||||
this.type = type;
|
||||
this.isComponent = isComponent;
|
||||
@ -418,8 +462,11 @@ export class CompileDirectiveMetadata {
|
||||
this.queries = _normalizeArray(queries);
|
||||
this.viewQueries = _normalizeArray(viewQueries);
|
||||
this.entryComponents = _normalizeArray(entryComponents);
|
||||
|
||||
this.template = template;
|
||||
|
||||
this.wrapperType = wrapperType;
|
||||
this.componentViewType = componentViewType;
|
||||
this.componentFactory = componentFactory;
|
||||
}
|
||||
|
||||
toSummary(): CompileDirectiveSummary {
|
||||
@ -439,7 +486,10 @@ export class CompileDirectiveMetadata {
|
||||
queries: this.queries,
|
||||
entryComponents: this.entryComponents,
|
||||
changeDetection: this.changeDetection,
|
||||
template: this.template && this.template.toSummary()
|
||||
template: this.template && this.template.toSummary(),
|
||||
wrapperType: this.wrapperType,
|
||||
componentViewType: this.componentViewType,
|
||||
componentFactory: this.componentFactory
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -448,11 +498,12 @@ export class CompileDirectiveMetadata {
|
||||
* Construct {@link CompileDirectiveMetadata} from {@link ComponentTypeMetadata} and a selector.
|
||||
*/
|
||||
export function createHostComponentMeta(
|
||||
typeReference: any, compMeta: CompileDirectiveMetadata): CompileDirectiveMetadata {
|
||||
hostTypeReference: any, compMeta: CompileDirectiveMetadata,
|
||||
hostViewType: StaticSymbol | ProxyClass): CompileDirectiveMetadata {
|
||||
const template = CssSelector.parse(compMeta.selector)[0].getMatchingElementTemplate();
|
||||
return CompileDirectiveMetadata.create({
|
||||
isHost: true,
|
||||
type: {reference: typeReference, diDeps: [], lifecycleHooks: []},
|
||||
type: {reference: hostTypeReference, diDeps: [], lifecycleHooks: []},
|
||||
template: new CompileTemplateMetadata({
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
template: template,
|
||||
@ -471,7 +522,8 @@ export function createHostComponentMeta(
|
||||
providers: [],
|
||||
viewProviders: [],
|
||||
queries: [],
|
||||
viewQueries: []
|
||||
viewQueries: [],
|
||||
componentViewType: hostViewType
|
||||
});
|
||||
}
|
||||
|
||||
@ -517,7 +569,7 @@ export interface CompileNgModuleSummary extends CompileTypeSummary {
|
||||
exportedPipes: CompileIdentifierMetadata[];
|
||||
|
||||
// Note: This is transitive.
|
||||
entryComponents: CompileIdentifierMetadata[];
|
||||
entryComponents: CompileEntryComponentMetadata[];
|
||||
// Note: This is transitive.
|
||||
providers: {provider: CompileProviderMetadata, module: CompileIdentifierMetadata}[];
|
||||
// Note: This is transitive.
|
||||
@ -534,7 +586,7 @@ export class CompileNgModuleMetadata {
|
||||
declaredPipes: CompileIdentifierMetadata[];
|
||||
|
||||
exportedPipes: CompileIdentifierMetadata[];
|
||||
entryComponents: CompileIdentifierMetadata[];
|
||||
entryComponents: CompileEntryComponentMetadata[];
|
||||
bootstrapComponents: CompileIdentifierMetadata[];
|
||||
providers: CompileProviderMetadata[];
|
||||
|
||||
@ -555,7 +607,7 @@ export class CompileNgModuleMetadata {
|
||||
exportedDirectives?: CompileIdentifierMetadata[],
|
||||
declaredPipes?: CompileIdentifierMetadata[],
|
||||
exportedPipes?: CompileIdentifierMetadata[],
|
||||
entryComponents?: CompileIdentifierMetadata[],
|
||||
entryComponents?: CompileEntryComponentMetadata[],
|
||||
bootstrapComponents?: CompileIdentifierMetadata[],
|
||||
importedModules?: CompileNgModuleSummary[],
|
||||
exportedModules?: CompileNgModuleSummary[],
|
||||
@ -603,7 +655,7 @@ export class TransitiveCompileNgModuleMetadata {
|
||||
modulesSet = new Set<any>();
|
||||
modules: CompileTypeMetadata[] = [];
|
||||
entryComponentsSet = new Set<any>();
|
||||
entryComponents: CompileIdentifierMetadata[] = [];
|
||||
entryComponents: CompileEntryComponentMetadata[] = [];
|
||||
|
||||
providers: {provider: CompileProviderMetadata, module: CompileIdentifierMetadata}[] = [];
|
||||
|
||||
@ -641,10 +693,10 @@ export class TransitiveCompileNgModuleMetadata {
|
||||
this.modules.push(id);
|
||||
}
|
||||
}
|
||||
addEntryComponent(id: CompileIdentifierMetadata) {
|
||||
if (!this.entryComponentsSet.has(id.reference)) {
|
||||
this.entryComponentsSet.add(id.reference);
|
||||
this.entryComponents.push(id);
|
||||
addEntryComponent(ec: CompileEntryComponentMetadata) {
|
||||
if (!this.entryComponentsSet.has(ec.componentType)) {
|
||||
this.entryComponentsSet.add(ec.componentType);
|
||||
this.entryComponents.push(ec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,12 +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 {Identifiers, createIdentifier} from '../identifiers';
|
||||
|
||||
import {ClassBuilder} from '../output/class_builder';
|
||||
import * as o from '../output/output_ast';
|
||||
|
||||
import {ConvertPropertyBindingResult} from './expression_converter';
|
||||
|
||||
export class CheckBindingField {
|
||||
constructor(public expression: o.ReadPropExpr, public bindingId: string) {}
|
||||
}
|
||||
@ -20,28 +18,14 @@ export function createCheckBindingField(builder: ClassBuilder): CheckBindingFiel
|
||||
const fieldExpr = createBindFieldExpr(bindingId);
|
||||
// private is fine here as no child view will reference the cached value...
|
||||
builder.fields.push(new o.ClassField(fieldExpr.name, null, [o.StmtModifier.Private]));
|
||||
builder.ctorStmts.push(o.THIS_EXPR.prop(fieldExpr.name)
|
||||
.set(o.importExpr(createIdentifier(Identifiers.UNINITIALIZED)))
|
||||
.toStmt());
|
||||
builder.ctorStmts.push(o.THIS_EXPR.prop(fieldExpr.name).set(o.literal(undefined)).toStmt());
|
||||
return new CheckBindingField(fieldExpr, bindingId);
|
||||
}
|
||||
|
||||
export function createCheckBindingStmt(
|
||||
evalResult: ConvertPropertyBindingResult, fieldExpr: o.ReadPropExpr,
|
||||
throwOnChangeVar: o.Expression, actions: o.Statement[]): o.Statement[] {
|
||||
let condition: o.Expression = o.importExpr(createIdentifier(Identifiers.checkBinding)).callFn([
|
||||
throwOnChangeVar, fieldExpr, evalResult.currValExpr
|
||||
]);
|
||||
if (evalResult.forceUpdate) {
|
||||
condition = evalResult.forceUpdate.or(condition);
|
||||
}
|
||||
return [
|
||||
...evalResult.stmts, new o.IfStmt(condition, actions.concat([
|
||||
<o.Statement>o.THIS_EXPR.prop(fieldExpr.name).set(evalResult.currValExpr).toStmt()
|
||||
]))
|
||||
];
|
||||
}
|
||||
|
||||
function createBindFieldExpr(bindingId: string): o.ReadPropExpr {
|
||||
return o.THIS_EXPR.prop(`_expr_${bindingId}`);
|
||||
}
|
||||
|
||||
export function isFirstViewCheck(view: o.Expression): o.Expression {
|
||||
return o.not(view.prop('numberOfChecks'));
|
||||
}
|
@ -7,75 +7,69 @@
|
||||
*/
|
||||
import {SecurityContext} from '@angular/core';
|
||||
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {Identifiers, createIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {EMPTY_STATE as EMPTY_ANIMATION_STATE} from '../private_import_core';
|
||||
import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType} from '../template_parser/template_ast';
|
||||
|
||||
import {isFirstViewCheck} from './binding_util';
|
||||
import {ConvertPropertyBindingResult} from './expression_converter';
|
||||
import {createEnumExpression} from './identifier_util';
|
||||
|
||||
export function writeToRenderer(
|
||||
view: o.Expression, boundProp: BoundElementPropertyAst, renderElement: o.Expression,
|
||||
renderValue: o.Expression, logBindingUpdate: boolean,
|
||||
export function createCheckRenderBindingStmt(
|
||||
view: o.Expression, renderElement: o.Expression, boundProp: BoundElementPropertyAst,
|
||||
oldValue: o.ReadPropExpr, evalResult: ConvertPropertyBindingResult,
|
||||
securityContextExpression?: o.Expression): o.Statement[] {
|
||||
const updateStmts: o.Statement[] = [];
|
||||
const renderer = view.prop('renderer');
|
||||
renderValue = sanitizedValue(view, boundProp, renderValue, securityContextExpression);
|
||||
const checkStmts: o.Statement[] = [...evalResult.stmts];
|
||||
const securityContext = calcSecurityContext(boundProp, securityContextExpression);
|
||||
switch (boundProp.type) {
|
||||
case PropertyBindingType.Property:
|
||||
if (logBindingUpdate) {
|
||||
updateStmts.push(
|
||||
o.importExpr(createIdentifier(Identifiers.setBindingDebugInfo))
|
||||
.callFn([renderer, renderElement, o.literal(boundProp.name), renderValue])
|
||||
.toStmt());
|
||||
}
|
||||
updateStmts.push(
|
||||
renderer
|
||||
.callMethod(
|
||||
'setElementProperty', [renderElement, o.literal(boundProp.name), renderValue])
|
||||
.toStmt());
|
||||
checkStmts.push(o.importExpr(createIdentifier(Identifiers.checkRenderProperty))
|
||||
.callFn([
|
||||
view, renderElement, o.literal(boundProp.name), oldValue,
|
||||
oldValue.set(evalResult.currValExpr),
|
||||
evalResult.forceUpdate || o.literal(false), securityContext
|
||||
])
|
||||
.toStmt());
|
||||
break;
|
||||
case PropertyBindingType.Attribute:
|
||||
renderValue =
|
||||
renderValue.isBlank().conditional(o.NULL_EXPR, renderValue.callMethod('toString', []));
|
||||
updateStmts.push(
|
||||
renderer
|
||||
.callMethod(
|
||||
'setElementAttribute', [renderElement, o.literal(boundProp.name), renderValue])
|
||||
.toStmt());
|
||||
checkStmts.push(o.importExpr(createIdentifier(Identifiers.checkRenderAttribute))
|
||||
.callFn([
|
||||
view, renderElement, o.literal(boundProp.name), oldValue,
|
||||
oldValue.set(evalResult.currValExpr),
|
||||
evalResult.forceUpdate || o.literal(false), securityContext
|
||||
])
|
||||
.toStmt());
|
||||
break;
|
||||
case PropertyBindingType.Class:
|
||||
updateStmts.push(
|
||||
renderer
|
||||
.callMethod(
|
||||
'setElementClass', [renderElement, o.literal(boundProp.name), renderValue])
|
||||
checkStmts.push(
|
||||
o.importExpr(createIdentifier(Identifiers.checkRenderClass))
|
||||
.callFn([
|
||||
view, renderElement, o.literal(boundProp.name), oldValue,
|
||||
oldValue.set(evalResult.currValExpr), evalResult.forceUpdate || o.literal(false)
|
||||
])
|
||||
.toStmt());
|
||||
break;
|
||||
case PropertyBindingType.Style:
|
||||
let strValue: o.Expression = renderValue.callMethod('toString', []);
|
||||
if (isPresent(boundProp.unit)) {
|
||||
strValue = strValue.plus(o.literal(boundProp.unit));
|
||||
}
|
||||
|
||||
renderValue = renderValue.isBlank().conditional(o.NULL_EXPR, strValue);
|
||||
updateStmts.push(
|
||||
renderer
|
||||
.callMethod(
|
||||
'setElementStyle', [renderElement, o.literal(boundProp.name), renderValue])
|
||||
checkStmts.push(
|
||||
o.importExpr(createIdentifier(Identifiers.checkRenderStyle))
|
||||
.callFn([
|
||||
view, renderElement, o.literal(boundProp.name), o.literal(boundProp.unit), oldValue,
|
||||
oldValue.set(evalResult.currValExpr), evalResult.forceUpdate || o.literal(false),
|
||||
securityContext
|
||||
])
|
||||
.toStmt());
|
||||
break;
|
||||
case PropertyBindingType.Animation:
|
||||
throw new Error('Illegal state: Should not come here!');
|
||||
}
|
||||
return updateStmts;
|
||||
return checkStmts;
|
||||
}
|
||||
|
||||
function sanitizedValue(
|
||||
view: o.Expression, boundProp: BoundElementPropertyAst, renderValue: o.Expression,
|
||||
securityContextExpression?: o.Expression): o.Expression {
|
||||
function calcSecurityContext(
|
||||
boundProp: BoundElementPropertyAst, securityContextExpression?: o.Expression): o.Expression {
|
||||
if (boundProp.securityContext === SecurityContext.NONE) {
|
||||
return renderValue; // No sanitization needed.
|
||||
return o.NULL_EXPR; // No sanitization needed.
|
||||
}
|
||||
if (!boundProp.needsRuntimeSecurityContext) {
|
||||
securityContextExpression =
|
||||
@ -84,15 +78,13 @@ function sanitizedValue(
|
||||
if (!securityContextExpression) {
|
||||
throw new Error(`internal error, no SecurityContext given ${boundProp.name}`);
|
||||
}
|
||||
const ctx = view.prop('viewUtils').prop('sanitizer');
|
||||
const args = [securityContextExpression, renderValue];
|
||||
return ctx.callMethod('sanitize', args);
|
||||
return securityContextExpression;
|
||||
}
|
||||
|
||||
export function triggerAnimation(
|
||||
export function createCheckAnimationBindingStmts(
|
||||
view: o.Expression, componentView: o.Expression, boundProp: BoundElementPropertyAst,
|
||||
boundOutputs: BoundEventAst[], eventListener: o.Expression, renderElement: o.Expression,
|
||||
renderValue: o.Expression, lastRenderValue: o.Expression) {
|
||||
oldValue: o.ReadPropExpr, evalResult: ConvertPropertyBindingResult) {
|
||||
const detachStmts: o.Statement[] = [];
|
||||
const updateStmts: o.Statement[] = [];
|
||||
|
||||
@ -104,22 +96,21 @@ export function triggerAnimation(
|
||||
// it's important to normalize the void value as `void` explicitly
|
||||
// so that the styles data can be obtained from the stringmap
|
||||
const emptyStateValue = o.literal(EMPTY_ANIMATION_STATE);
|
||||
const unitializedValue = o.importExpr(createIdentifier(Identifiers.UNINITIALIZED));
|
||||
const animationTransitionVar = o.variable('animationTransition_' + animationName);
|
||||
|
||||
updateStmts.push(
|
||||
animationTransitionVar
|
||||
.set(animationFnExpr.callFn([
|
||||
view, renderElement,
|
||||
lastRenderValue.equals(unitializedValue).conditional(emptyStateValue, lastRenderValue),
|
||||
renderValue.equals(unitializedValue).conditional(emptyStateValue, renderValue)
|
||||
view, renderElement, isFirstViewCheck(view).conditional(emptyStateValue, oldValue),
|
||||
evalResult.currValExpr
|
||||
]))
|
||||
.toDeclStmt());
|
||||
updateStmts.push(oldValue.set(evalResult.currValExpr).toStmt());
|
||||
|
||||
detachStmts.push(
|
||||
animationTransitionVar
|
||||
.set(animationFnExpr.callFn([view, renderElement, lastRenderValue, emptyStateValue]))
|
||||
.toDeclStmt());
|
||||
detachStmts.push(animationTransitionVar
|
||||
.set(animationFnExpr.callFn(
|
||||
[view, renderElement, evalResult.currValExpr, emptyStateValue]))
|
||||
.toDeclStmt());
|
||||
|
||||
const registerStmts: o.Statement[] = [];
|
||||
const animationStartMethodExists = boundOutputs.find(
|
||||
@ -151,5 +142,14 @@ export function triggerAnimation(
|
||||
updateStmts.push(...registerStmts);
|
||||
detachStmts.push(...registerStmts);
|
||||
|
||||
return {updateStmts, detachStmts};
|
||||
const checkUpdateStmts: o.Statement[] = [
|
||||
...evalResult.stmts,
|
||||
new o.IfStmt(
|
||||
o.importExpr(createIdentifier(Identifiers.checkBinding)).callFn([
|
||||
view, oldValue, evalResult.currValExpr, evalResult.forceUpdate || o.literal(false)
|
||||
]),
|
||||
updateStmts)
|
||||
];
|
||||
const checkDetachStmts: o.Statement[] = [...evalResult.stmts, ...detachStmts];
|
||||
return {checkUpdateStmts, checkDetachStmts};
|
||||
}
|
||||
|
@ -6,11 +6,11 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, ViewEncapsulation} from '@angular/core';
|
||||
import {ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata} from './compile_metadata';
|
||||
import {CompilerConfig} from './config';
|
||||
import {isBlank, isPresent, stringify} from './facade/lang';
|
||||
import {stringify} from './facade/lang';
|
||||
import {CompilerInjectable} from './injectable';
|
||||
import * as html from './ml_parser/ast';
|
||||
import {HtmlParser} from './ml_parser/html_parser';
|
||||
@ -41,9 +41,9 @@ export class DirectiveNormalizer {
|
||||
private _resourceLoader: ResourceLoader, private _urlResolver: UrlResolver,
|
||||
private _htmlParser: HtmlParser, private _config: CompilerConfig) {}
|
||||
|
||||
clearCache() { this._resourceLoaderCache.clear(); }
|
||||
clearCache(): void { this._resourceLoaderCache.clear(); }
|
||||
|
||||
clearCacheFor(normalizedDirective: CompileDirectiveMetadata) {
|
||||
clearCacheFor(normalizedDirective: CompileDirectiveMetadata): void {
|
||||
if (!normalizedDirective.isComponent) {
|
||||
return;
|
||||
}
|
||||
@ -65,10 +65,18 @@ export class DirectiveNormalizer {
|
||||
SyncAsyncResult<CompileTemplateMetadata> {
|
||||
let normalizedTemplateSync: CompileTemplateMetadata = null;
|
||||
let normalizedTemplateAsync: Promise<CompileTemplateMetadata>;
|
||||
if (isPresent(prenormData.template)) {
|
||||
if (prenormData.template != null) {
|
||||
if (typeof prenormData.template !== 'string') {
|
||||
throw new SyntaxError(
|
||||
`The template specified for component ${stringify(prenormData.componentType)} is not a string`);
|
||||
}
|
||||
normalizedTemplateSync = this.normalizeTemplateSync(prenormData);
|
||||
normalizedTemplateAsync = Promise.resolve(normalizedTemplateSync);
|
||||
} else if (prenormData.templateUrl) {
|
||||
if (typeof prenormData.templateUrl !== 'string') {
|
||||
throw new SyntaxError(
|
||||
`The templateUrl specified for component ${stringify(prenormData.componentType)} is not a string`);
|
||||
}
|
||||
normalizedTemplateAsync = this.normalizeTemplateAsync(prenormData);
|
||||
} else {
|
||||
throw new SyntaxError(
|
||||
@ -102,11 +110,12 @@ export class DirectiveNormalizer {
|
||||
templateAbsUrl: string): CompileTemplateMetadata {
|
||||
const interpolationConfig = InterpolationConfig.fromArray(prenomData.interpolation);
|
||||
const rootNodesAndErrors = this._htmlParser.parse(
|
||||
template, stringify(prenomData.componentType), false, interpolationConfig);
|
||||
template, stringify(prenomData.componentType), true, interpolationConfig);
|
||||
if (rootNodesAndErrors.errors.length > 0) {
|
||||
const errorString = rootNodesAndErrors.errors.join('\n');
|
||||
throw new SyntaxError(`Template parse errors:\n${errorString}`);
|
||||
}
|
||||
|
||||
const templateMetadataStyles = this.normalizeStylesheet(new CompileStylesheetMetadata({
|
||||
styles: prenomData.styles,
|
||||
styleUrls: prenomData.styleUrls,
|
||||
@ -119,7 +128,7 @@ export class DirectiveNormalizer {
|
||||
{styles: visitor.styles, styleUrls: visitor.styleUrls, moduleUrl: templateAbsUrl}));
|
||||
|
||||
let encapsulation = prenomData.encapsulation;
|
||||
if (isBlank(encapsulation)) {
|
||||
if (encapsulation == null) {
|
||||
encapsulation = this._config.defaultEncapsulation;
|
||||
}
|
||||
|
||||
@ -228,9 +237,13 @@ class TemplatePreparseVisitor implements html.Visitor {
|
||||
return null;
|
||||
}
|
||||
|
||||
visitExpansion(ast: html.Expansion, context: any): any { html.visitAll(this, ast.cases); }
|
||||
|
||||
visitExpansionCase(ast: html.ExpansionCase, context: any): any {
|
||||
html.visitAll(this, ast.expression);
|
||||
}
|
||||
|
||||
visitComment(ast: html.Comment, context: any): any { return null; }
|
||||
visitAttribute(ast: html.Attribute, context: any): any { return null; }
|
||||
visitText(ast: html.Text, context: any): any { return null; }
|
||||
visitExpansion(ast: html.Expansion, context: any): any { return null; }
|
||||
visitExpansionCase(ast: html.ExpansionCase, context: any): any { return null; }
|
||||
}
|
||||
|
@ -6,10 +6,10 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
|
||||
import {createCheckBindingField, createCheckBindingStmt} from './compiler_util/binding_util';
|
||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, dirWrapperClassName, identifierModuleUrl, identifierName} from './compile_metadata';
|
||||
import {createCheckBindingField, isFirstViewCheck} from './compiler_util/binding_util';
|
||||
import {EventHandlerVars, convertActionBinding, convertPropertyBinding} from './compiler_util/expression_converter';
|
||||
import {triggerAnimation, writeToRenderer} from './compiler_util/render_util';
|
||||
import {createCheckAnimationBindingStmts, createCheckRenderBindingStmt} from './compiler_util/render_util';
|
||||
import {CompilerConfig} from './config';
|
||||
import {Parser} from './expression_parser/parser';
|
||||
import {Identifiers, createIdentifier} from './identifiers';
|
||||
@ -32,8 +32,8 @@ const CHANGES_FIELD_NAME = '_changes';
|
||||
const CHANGED_FIELD_NAME = '_changed';
|
||||
const EVENT_HANDLER_FIELD_NAME = '_eventHandler';
|
||||
|
||||
const CHANGE_VAR = o.variable('change');
|
||||
const CURR_VALUE_VAR = o.variable('currValue');
|
||||
const THROW_ON_CHANGE_VAR = o.variable('throwOnChange');
|
||||
const FORCE_UPDATE_VAR = o.variable('forceUpdate');
|
||||
const VIEW_VAR = o.variable('view');
|
||||
const COMPONENT_VIEW_VAR = o.variable('componentView');
|
||||
@ -52,10 +52,6 @@ const RESET_CHANGES_STMT = o.THIS_EXPR.prop(CHANGES_FIELD_NAME).set(o.literalMap
|
||||
*/
|
||||
@CompilerInjectable()
|
||||
export class DirectiveWrapperCompiler {
|
||||
static dirWrapperClassName(id: CompileIdentifierMetadata) {
|
||||
return `Wrapper_${identifierName(id)}`;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private compilerConfig: CompilerConfig, private _exprParser: Parser,
|
||||
private _schemaRegistry: ElementSchemaRegistry, private _console: Console) {}
|
||||
@ -133,8 +129,9 @@ class DirectiveWrapperBuilder implements ClassBuilder {
|
||||
new o.ClassField(CONTEXT_FIELD_NAME, o.importType(this.dirMeta.type)),
|
||||
new o.ClassField(CHANGED_FIELD_NAME, o.BOOL_TYPE, [o.StmtModifier.Private]),
|
||||
];
|
||||
const ctorStmts: o.Statement[] =
|
||||
[o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(false)).toStmt()];
|
||||
const ctorStmts: o.Statement[] = [
|
||||
o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(false)).toStmt(),
|
||||
];
|
||||
if (this.genChanges) {
|
||||
fields.push(new o.ClassField(
|
||||
CHANGES_FIELD_NAME, new o.MapType(o.DYNAMIC_TYPE), [o.StmtModifier.Private]));
|
||||
@ -148,7 +145,7 @@ class DirectiveWrapperBuilder implements ClassBuilder {
|
||||
.toStmt());
|
||||
|
||||
return createClassStmt({
|
||||
name: DirectiveWrapperCompiler.dirWrapperClassName(this.dirMeta.type),
|
||||
name: dirWrapperClassName(this.dirMeta.type.reference),
|
||||
ctorParams: dirDepParamNames.map((paramName) => new o.FnParam(paramName, o.DYNAMIC_TYPE)),
|
||||
builders: [{fields, ctorStmts, methods}, this]
|
||||
});
|
||||
@ -183,14 +180,14 @@ function addNgDoCheckMethod(builder: DirectiveWrapperBuilder) {
|
||||
|
||||
if (builder.ngOnInit) {
|
||||
lifecycleStmts.push(new o.IfStmt(
|
||||
VIEW_VAR.prop('numberOfChecks').identical(new o.LiteralExpr(0)),
|
||||
isFirstViewCheck(VIEW_VAR),
|
||||
[o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngOnInit', []).toStmt()]));
|
||||
}
|
||||
if (builder.ngDoCheck) {
|
||||
lifecycleStmts.push(o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).callMethod('ngDoCheck', []).toStmt());
|
||||
}
|
||||
if (lifecycleStmts.length > 0) {
|
||||
stmts.push(new o.IfStmt(o.not(THROW_ON_CHANGE_VAR), lifecycleStmts));
|
||||
stmts.push(new o.IfStmt(o.not(VIEW_VAR.prop('throwOnChange')), lifecycleStmts));
|
||||
}
|
||||
stmts.push(new o.ReturnStatement(changedVar));
|
||||
|
||||
@ -200,7 +197,6 @@ function addNgDoCheckMethod(builder: DirectiveWrapperBuilder) {
|
||||
new o.FnParam(
|
||||
VIEW_VAR.name, o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
||||
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
|
||||
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
|
||||
],
|
||||
stmts, o.BOOL_TYPE));
|
||||
}
|
||||
@ -210,24 +206,35 @@ function addCheckInputMethod(input: string, builder: DirectiveWrapperBuilder) {
|
||||
const onChangeStatements: o.Statement[] = [
|
||||
o.THIS_EXPR.prop(CHANGED_FIELD_NAME).set(o.literal(true)).toStmt(),
|
||||
o.THIS_EXPR.prop(CONTEXT_FIELD_NAME).prop(input).set(CURR_VALUE_VAR).toStmt(),
|
||||
field.expression.set(CURR_VALUE_VAR).toStmt()
|
||||
];
|
||||
let methodBody: o.Statement[];
|
||||
if (builder.genChanges) {
|
||||
onChangeStatements.push(o.THIS_EXPR.prop(CHANGES_FIELD_NAME)
|
||||
.key(o.literal(input))
|
||||
.set(o.importExpr(createIdentifier(Identifiers.SimpleChange))
|
||||
.instantiate([field.expression, CURR_VALUE_VAR]))
|
||||
.toStmt());
|
||||
onChangeStatements.push(
|
||||
o.THIS_EXPR.prop(CHANGES_FIELD_NAME).key(o.literal(input)).set(CHANGE_VAR).toStmt());
|
||||
methodBody = [
|
||||
CHANGE_VAR
|
||||
.set(o.importExpr(createIdentifier(Identifiers.checkBindingChange)).callFn([
|
||||
VIEW_VAR, field.expression, CURR_VALUE_VAR, FORCE_UPDATE_VAR
|
||||
]))
|
||||
.toDeclStmt(),
|
||||
new o.IfStmt(CHANGE_VAR, onChangeStatements)
|
||||
];
|
||||
} else {
|
||||
methodBody = [new o.IfStmt(
|
||||
o.importExpr(createIdentifier(Identifiers.checkBinding)).callFn([
|
||||
VIEW_VAR, field.expression, CURR_VALUE_VAR, FORCE_UPDATE_VAR
|
||||
]),
|
||||
onChangeStatements)];
|
||||
}
|
||||
|
||||
const methodBody: o.Statement[] = createCheckBindingStmt(
|
||||
{currValExpr: CURR_VALUE_VAR, forceUpdate: FORCE_UPDATE_VAR, stmts: []}, field.expression,
|
||||
THROW_ON_CHANGE_VAR, onChangeStatements);
|
||||
builder.methods.push(new o.ClassMethod(
|
||||
`check_${input}`,
|
||||
[
|
||||
new o.FnParam(
|
||||
VIEW_VAR.name, o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
||||
new o.FnParam(CURR_VALUE_VAR.name, o.DYNAMIC_TYPE),
|
||||
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
|
||||
new o.FnParam(FORCE_UPDATE_VAR.name, o.BOOL_TYPE),
|
||||
new o.FnParam(FORCE_UPDATE_VAR.name, o.BOOL_TYPE)
|
||||
],
|
||||
methodBody));
|
||||
}
|
||||
@ -243,7 +250,6 @@ function addCheckHostMethod(
|
||||
COMPONENT_VIEW_VAR.name,
|
||||
o.importType(createIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE])),
|
||||
new o.FnParam(RENDER_EL_VAR.name, o.DYNAMIC_TYPE),
|
||||
new o.FnParam(THROW_ON_CHANGE_VAR.name, o.BOOL_TYPE),
|
||||
];
|
||||
hostProps.forEach((hostProp, hostPropIdx) => {
|
||||
const field = createCheckBindingField(builder);
|
||||
@ -258,23 +264,18 @@ function addCheckHostMethod(
|
||||
methodParams.push(new o.FnParam(
|
||||
securityContextExpr.name, o.importType(createIdentifier(Identifiers.SecurityContext))));
|
||||
}
|
||||
let checkBindingStmts: o.Statement[];
|
||||
if (hostProp.isAnimation) {
|
||||
const {updateStmts, detachStmts} = triggerAnimation(
|
||||
const {checkUpdateStmts, checkDetachStmts} = createCheckAnimationBindingStmts(
|
||||
VIEW_VAR, COMPONENT_VIEW_VAR, hostProp, hostEvents,
|
||||
o.THIS_EXPR.prop(EVENT_HANDLER_FIELD_NAME)
|
||||
.or(o.importExpr(createIdentifier(Identifiers.noop))),
|
||||
RENDER_EL_VAR, evalResult.currValExpr, field.expression);
|
||||
checkBindingStmts = updateStmts;
|
||||
builder.detachStmts.push(...detachStmts);
|
||||
RENDER_EL_VAR, field.expression, evalResult);
|
||||
builder.detachStmts.push(...checkDetachStmts);
|
||||
stmts.push(...checkUpdateStmts);
|
||||
} else {
|
||||
checkBindingStmts = writeToRenderer(
|
||||
VIEW_VAR, hostProp, RENDER_EL_VAR, evalResult.currValExpr,
|
||||
builder.compilerConfig.logBindingUpdate, securityContextExpr);
|
||||
stmts.push(...createCheckRenderBindingStmt(
|
||||
VIEW_VAR, RENDER_EL_VAR, hostProp, field.expression, evalResult, securityContextExpr));
|
||||
}
|
||||
|
||||
stmts.push(...createCheckBindingStmt(
|
||||
evalResult, field.expression, THROW_ON_CHANGE_VAR, checkBindingStmts));
|
||||
});
|
||||
builder.methods.push(new o.ClassMethod('checkHost', methodParams, stmts));
|
||||
}
|
||||
@ -384,20 +385,19 @@ export class DirectiveWrapperExpressions {
|
||||
return dirWrapper.prop(CONTEXT_FIELD_NAME);
|
||||
}
|
||||
|
||||
static ngDoCheck(
|
||||
dirWrapper: o.Expression, view: o.Expression, renderElement: o.Expression,
|
||||
throwOnChange: o.Expression): o.Expression {
|
||||
return dirWrapper.callMethod('ngDoCheck', [view, renderElement, throwOnChange]);
|
||||
static ngDoCheck(dirWrapper: o.Expression, view: o.Expression, renderElement: o.Expression, ):
|
||||
o.Expression {
|
||||
return dirWrapper.callMethod('ngDoCheck', [view, renderElement]);
|
||||
}
|
||||
static checkHost(
|
||||
hostProps: BoundElementPropertyAst[], dirWrapper: o.Expression, view: o.Expression,
|
||||
componentView: o.Expression, renderElement: o.Expression, throwOnChange: o.Expression,
|
||||
componentView: o.Expression, renderElement: o.Expression,
|
||||
runtimeSecurityContexts: o.Expression[]): o.Statement[] {
|
||||
if (hostProps.length) {
|
||||
return [dirWrapper
|
||||
.callMethod(
|
||||
'checkHost', [view, componentView, renderElement, throwOnChange].concat(
|
||||
runtimeSecurityContexts))
|
||||
'checkHost',
|
||||
[view, componentView, renderElement].concat(runtimeSecurityContexts))
|
||||
.toStmt()];
|
||||
} else {
|
||||
return [];
|
||||
|
@ -9,13 +9,13 @@
|
||||
import * as i18n from './i18n_ast';
|
||||
|
||||
export function digest(message: i18n.Message): string {
|
||||
return sha1(serializeNodes(message.nodes).join('') + `[${message.meaning}]`);
|
||||
return message.id || sha1(serializeNodes(message.nodes).join('') + `[${message.meaning}]`);
|
||||
}
|
||||
|
||||
export function decimalDigest(message: i18n.Message): string {
|
||||
const visitor = new _SerializerIgnoreIcuExpVisitor();
|
||||
const parts = message.nodes.map(a => a.visit(visitor, null));
|
||||
return computeMsgId(parts.join(''), message.meaning);
|
||||
return message.id || computeMsgId(parts.join(''), message.meaning);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -109,7 +109,7 @@ export class Extractor {
|
||||
const resolver = new CompileMetadataResolver(
|
||||
new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector),
|
||||
new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer,
|
||||
staticReflector);
|
||||
symbolCache, staticReflector);
|
||||
|
||||
// TODO(vicb): implicit tags & attributes
|
||||
const messageBundle = new MessageBundle(htmlParser, [], {});
|
||||
|
@ -18,6 +18,8 @@ import {TranslationBundle} from './translation_bundle';
|
||||
const _I18N_ATTR = 'i18n';
|
||||
const _I18N_ATTR_PREFIX = 'i18n-';
|
||||
const _I18N_COMMENT_PREFIX_REGEXP = /^i18n:?/;
|
||||
const MEANING_SEPARATOR = '|';
|
||||
const ID_SEPARATOR = '@@';
|
||||
|
||||
/**
|
||||
* Extract translatable messages from an html AST
|
||||
@ -79,7 +81,7 @@ class _Visitor implements html.Visitor {
|
||||
// _VisitorMode.Merge only
|
||||
private _translations: TranslationBundle;
|
||||
private _createI18nMessage:
|
||||
(msg: html.Node[], meaning: string, description: string) => i18n.Message;
|
||||
(msg: html.Node[], meaning: string, description: string, id: string) => i18n.Message;
|
||||
|
||||
|
||||
constructor(private _implicitTags: string[], private _implicitAttrs: {[k: string]: string[]}) {}
|
||||
@ -311,15 +313,15 @@ class _Visitor implements html.Visitor {
|
||||
}
|
||||
|
||||
// add a translatable message
|
||||
private _addMessage(ast: html.Node[], meaningAndDesc?: string): i18n.Message {
|
||||
private _addMessage(ast: html.Node[], msgMeta?: string): i18n.Message {
|
||||
if (ast.length == 0 ||
|
||||
ast.length == 1 && ast[0] instanceof html.Attribute && !(<html.Attribute>ast[0]).value) {
|
||||
// Do not create empty messages
|
||||
return;
|
||||
}
|
||||
|
||||
const [meaning, description] = _splitMeaningAndDesc(meaningAndDesc);
|
||||
const message = this._createI18nMessage(ast, meaning, description);
|
||||
const {meaning, description, id} = _parseMessageMeta(msgMeta);
|
||||
const message = this._createI18nMessage(ast, meaning, description, id);
|
||||
this._messages.push(message);
|
||||
return message;
|
||||
}
|
||||
@ -349,7 +351,7 @@ class _Visitor implements html.Visitor {
|
||||
attributes.forEach(attr => {
|
||||
if (attr.name.startsWith(_I18N_ATTR_PREFIX)) {
|
||||
i18nAttributeMeanings[attr.name.slice(_I18N_ATTR_PREFIX.length)] =
|
||||
_splitMeaningAndDesc(attr.value)[0];
|
||||
_parseMessageMeta(attr.value).meaning;
|
||||
}
|
||||
});
|
||||
|
||||
@ -363,7 +365,7 @@ class _Visitor implements html.Visitor {
|
||||
|
||||
if (attr.value && attr.value != '' && i18nAttributeMeanings.hasOwnProperty(attr.name)) {
|
||||
const meaning = i18nAttributeMeanings[attr.name];
|
||||
const message: i18n.Message = this._createI18nMessage([attr], meaning, '');
|
||||
const message: i18n.Message = this._createI18nMessage([attr], meaning, '', '');
|
||||
const nodes = this._translations.get(message);
|
||||
if (nodes) {
|
||||
if (nodes[0] instanceof html.Text) {
|
||||
@ -401,7 +403,7 @@ class _Visitor implements html.Visitor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the start of a section, see `_endSection`
|
||||
* Marks the start of a section, see `_closeTranslatableSection`
|
||||
*/
|
||||
private _openTranslatableSection(node: html.Node): void {
|
||||
if (this._isInTranslatableSection) {
|
||||
@ -477,8 +479,16 @@ function _getI18nAttr(p: html.Element): html.Attribute {
|
||||
return p.attrs.find(attr => attr.name === _I18N_ATTR) || null;
|
||||
}
|
||||
|
||||
function _splitMeaningAndDesc(i18n: string): [string, string] {
|
||||
if (!i18n) return ['', ''];
|
||||
const pipeIndex = i18n.indexOf('|');
|
||||
return pipeIndex == -1 ? ['', i18n] : [i18n.slice(0, pipeIndex), i18n.slice(pipeIndex + 1)];
|
||||
function _parseMessageMeta(i18n: string): {meaning: string, description: string, id: string} {
|
||||
if (!i18n) return {meaning: '', description: '', id: ''};
|
||||
|
||||
const idIndex = i18n.indexOf(ID_SEPARATOR);
|
||||
const descIndex = i18n.indexOf(MEANING_SEPARATOR);
|
||||
const [meaningAndDesc, id] =
|
||||
(idIndex > -1) ? [i18n.slice(0, idIndex), i18n.slice(idIndex + 2)] : [i18n, ''];
|
||||
const [meaning, description] = (descIndex > -1) ?
|
||||
[meaningAndDesc.slice(0, descIndex), meaningAndDesc.slice(descIndex + 1)] :
|
||||
['', meaningAndDesc];
|
||||
|
||||
return {meaning, description, id};
|
||||
}
|
||||
|
@ -15,11 +15,12 @@ export class Message {
|
||||
* @param placeholderToMessage maps placeholder names to messages (used for nested ICU messages)
|
||||
* @param meaning
|
||||
* @param description
|
||||
* @param id
|
||||
*/
|
||||
constructor(
|
||||
public nodes: Node[], public placeholders: {[phName: string]: string},
|
||||
public placeholderToMessage: {[phName: string]: Message}, public meaning: string,
|
||||
public description: string) {}
|
||||
public description: string, public id: string) {}
|
||||
}
|
||||
|
||||
export interface Node {
|
||||
|
@ -22,11 +22,11 @@ const _expParser = new ExpressionParser(new ExpressionLexer());
|
||||
* Returns a function converting html nodes to an i18n Message given an interpolationConfig
|
||||
*/
|
||||
export function createI18nMessageFactory(interpolationConfig: InterpolationConfig): (
|
||||
nodes: html.Node[], meaning: string, description: string) => i18n.Message {
|
||||
nodes: html.Node[], meaning: string, description: string, id: string) => i18n.Message {
|
||||
const visitor = new _I18nVisitor(_expParser, interpolationConfig);
|
||||
|
||||
return (nodes: html.Node[], meaning: string, description: string) =>
|
||||
visitor.toI18nMessage(nodes, meaning, description);
|
||||
return (nodes: html.Node[], meaning: string, description: string, id: string) =>
|
||||
visitor.toI18nMessage(nodes, meaning, description, id);
|
||||
}
|
||||
|
||||
class _I18nVisitor implements html.Visitor {
|
||||
@ -40,7 +40,8 @@ class _I18nVisitor implements html.Visitor {
|
||||
private _expressionParser: ExpressionParser,
|
||||
private _interpolationConfig: InterpolationConfig) {}
|
||||
|
||||
public toI18nMessage(nodes: html.Node[], meaning: string, description: string): i18n.Message {
|
||||
public toI18nMessage(nodes: html.Node[], meaning: string, description: string, id: string):
|
||||
i18n.Message {
|
||||
this._isIcu = nodes.length == 1 && nodes[0] instanceof html.Expansion;
|
||||
this._icuDepth = 0;
|
||||
this._placeholderRegistry = new PlaceholderRegistry();
|
||||
@ -50,7 +51,7 @@ class _I18nVisitor implements html.Visitor {
|
||||
const i18nodes: i18n.Node[] = html.visitAll(this, nodes, {});
|
||||
|
||||
return new i18n.Message(
|
||||
i18nodes, this._placeholderToContent, this._placeholderToMessage, meaning, description);
|
||||
i18nodes, this._placeholderToContent, this._placeholderToMessage, meaning, description, id);
|
||||
}
|
||||
|
||||
visitElement(el: html.Element, context: any): i18n.Node {
|
||||
@ -115,7 +116,7 @@ class _I18nVisitor implements html.Visitor {
|
||||
// TODO(vicb): add a html.Node -> i18n.Message cache to avoid having to re-create the msg
|
||||
const phName = this._placeholderRegistry.getPlaceholderName('ICU', icu.sourceSpan.toString());
|
||||
const visitor = new _I18nVisitor(this._expressionParser, this._interpolationConfig);
|
||||
this._placeholderToMessage[phName] = visitor.toI18nMessage([icu], '', '');
|
||||
this._placeholderToMessage[phName] = visitor.toI18nMessage([icu], '', '', '');
|
||||
return new i18n.IcuPlaceholder(i18nIcu, phName, icu.sourceSpan);
|
||||
}
|
||||
|
||||
|
@ -8,9 +8,8 @@
|
||||
|
||||
import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {StaticSymbol} from './aot/static_symbol';
|
||||
import {CompileIdentifierMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
|
||||
import {AnimationGroupPlayer, AnimationKeyframe, AnimationSequencePlayer, AnimationStyles, AnimationTransition, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, ComponentRef_, DebugAppView, DebugContext, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, UNINITIALIZED, ValueUnwrapper, ViewContainer, ViewType, balanceAnimationKeyframes, clearStyles, collectAndResolveStyles, devModeEqual, prepareFinalAnimationStyles, reflector, registerModuleFactory, renderStyles, view_utils} from './private_import_core';
|
||||
import {CompileIdentifierMetadata, CompileTokenMetadata} from './compile_metadata';
|
||||
import {AnimationGroupPlayer, AnimationKeyframe, AnimationSequencePlayer, AnimationStyles, AnimationTransition, AppView, ChangeDetectorStatus, CodegenComponentFactoryResolver, ComponentRef_, DebugAppView, DebugContext, NgModuleInjector, NoOpAnimationPlayer, StaticNodeDebugInfo, TemplateRef_, ValueUnwrapper, ViewContainer, ViewType, balanceAnimationKeyframes, clearStyles, collectAndResolveStyles, devModeEqual, prepareFinalAnimationStyles, reflector, registerModuleFactory, renderStyles, view_utils} from './private_import_core';
|
||||
|
||||
const APP_VIEW_MODULE_URL = assetUrl('core', 'linker/view');
|
||||
const VIEW_UTILS_MODULE_URL = assetUrl('core', 'linker/view_utils');
|
||||
@ -161,8 +160,6 @@ export class Identifiers {
|
||||
};
|
||||
static SimpleChange:
|
||||
IdentifierSpec = {name: 'SimpleChange', moduleUrl: CD_MODULE_URL, runtime: SimpleChange};
|
||||
static UNINITIALIZED:
|
||||
IdentifierSpec = {name: 'UNINITIALIZED', moduleUrl: CD_MODULE_URL, runtime: UNINITIALIZED};
|
||||
static ChangeDetectorStatus: IdentifierSpec = {
|
||||
name: 'ChangeDetectorStatus',
|
||||
moduleUrl: CD_MODULE_URL,
|
||||
@ -173,6 +170,36 @@ export class Identifiers {
|
||||
moduleUrl: VIEW_UTILS_MODULE_URL,
|
||||
runtime: view_utils.checkBinding
|
||||
};
|
||||
static checkBindingChange: IdentifierSpec = {
|
||||
name: 'checkBindingChange',
|
||||
moduleUrl: VIEW_UTILS_MODULE_URL,
|
||||
runtime: view_utils.checkBindingChange
|
||||
};
|
||||
static checkRenderText: IdentifierSpec = {
|
||||
name: 'checkRenderText',
|
||||
moduleUrl: VIEW_UTILS_MODULE_URL,
|
||||
runtime: view_utils.checkRenderText
|
||||
};
|
||||
static checkRenderProperty: IdentifierSpec = {
|
||||
name: 'checkRenderProperty',
|
||||
moduleUrl: VIEW_UTILS_MODULE_URL,
|
||||
runtime: view_utils.checkRenderProperty
|
||||
};
|
||||
static checkRenderAttribute: IdentifierSpec = {
|
||||
name: 'checkRenderAttribute',
|
||||
moduleUrl: VIEW_UTILS_MODULE_URL,
|
||||
runtime: view_utils.checkRenderAttribute
|
||||
};
|
||||
static checkRenderClass: IdentifierSpec = {
|
||||
name: 'checkRenderClass',
|
||||
moduleUrl: VIEW_UTILS_MODULE_URL,
|
||||
runtime: view_utils.checkRenderClass
|
||||
};
|
||||
static checkRenderStyle: IdentifierSpec = {
|
||||
name: 'checkRenderStyle',
|
||||
moduleUrl: VIEW_UTILS_MODULE_URL,
|
||||
runtime: view_utils.checkRenderStyle
|
||||
};
|
||||
static devModeEqual:
|
||||
IdentifierSpec = {name: 'devModeEqual', moduleUrl: CD_MODULE_URL, runtime: devModeEqual};
|
||||
static inlineInterpolate: IdentifierSpec = {
|
||||
|
@ -6,13 +6,12 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Compiler, ComponentFactory, Injector, ModuleWithComponentFactories, NgModuleFactory, SchemaMetadata, Type} from '@angular/core';
|
||||
import {Compiler, ComponentFactory, Injector, ModuleWithComponentFactories, NgModuleFactory, Type} from '@angular/core';
|
||||
|
||||
import {AnimationCompiler} from '../animation/animation_compiler';
|
||||
import {AnimationParser} from '../animation/animation_parser';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, ProviderMeta, createHostComponentMeta, identifierName} from '../compile_metadata';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, ProviderMeta, ProxyClass, createHostComponentMeta, identifierName} from '../compile_metadata';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {DirectiveNormalizer} from '../directive_normalizer';
|
||||
import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler';
|
||||
import {stringify} from '../facade/lang';
|
||||
import {CompilerInjectable} from '../injectable';
|
||||
@ -21,10 +20,11 @@ import {NgModuleCompiler} from '../ng_module_compiler';
|
||||
import * as ir from '../output/output_ast';
|
||||
import {interpretStatements} from '../output/output_interpreter';
|
||||
import {jitStatements} from '../output/output_jit';
|
||||
import {view_utils} from '../private_import_core';
|
||||
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
|
||||
import {TemplateParser} from '../template_parser/template_parser';
|
||||
import {SyncAsyncResult} from '../util';
|
||||
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompiler} from '../view_compiler/view_compiler';
|
||||
import {ViewCompiler} from '../view_compiler/view_compiler';
|
||||
|
||||
|
||||
|
||||
@ -130,10 +130,6 @@ export class JitCompiler implements Compiler {
|
||||
const extraProviders = [this._metadataResolver.getProviderMetadata(new ProviderMeta(
|
||||
Compiler, {useFactory: () => new ModuleBoundCompiler(this, moduleMeta.type.reference)}))];
|
||||
const compileResult = this._ngModuleCompiler.compile(moduleMeta, extraProviders);
|
||||
compileResult.dependencies.forEach((dep) => {
|
||||
dep.placeholder.reference =
|
||||
this._assertComponentKnown(dep.comp.reference, true).proxyComponentFactory;
|
||||
});
|
||||
if (!this._compilerConfig.useJit) {
|
||||
ngModuleFactory =
|
||||
interpretStatements(compileResult.statements, compileResult.ngModuleFactoryVar);
|
||||
@ -168,7 +164,7 @@ export class JitCompiler implements Compiler {
|
||||
const template =
|
||||
this._createCompiledHostTemplate(dirMeta.type.reference, localModuleMeta);
|
||||
templates.add(template);
|
||||
allComponentFactories.push(template.proxyComponentFactory);
|
||||
allComponentFactories.push(<ComponentFactory<any>>dirMeta.componentFactory);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -180,15 +176,16 @@ export class JitCompiler implements Compiler {
|
||||
const dirMeta = this._metadataResolver.getDirectiveMetadata(dirIdentifier.reference);
|
||||
if (dirMeta.isComponent) {
|
||||
dirMeta.entryComponents.forEach((entryComponentType) => {
|
||||
const moduleMeta = moduleByDirective.get(entryComponentType.reference);
|
||||
const moduleMeta = moduleByDirective.get(entryComponentType.componentType);
|
||||
templates.add(
|
||||
this._createCompiledHostTemplate(entryComponentType.reference, moduleMeta));
|
||||
this._createCompiledHostTemplate(entryComponentType.componentType, moduleMeta));
|
||||
});
|
||||
}
|
||||
});
|
||||
localModuleMeta.entryComponents.forEach((entryComponentType) => {
|
||||
const moduleMeta = moduleByDirective.get(entryComponentType.reference);
|
||||
templates.add(this._createCompiledHostTemplate(entryComponentType.reference, moduleMeta));
|
||||
const moduleMeta = moduleByDirective.get(entryComponentType.componentType);
|
||||
templates.add(
|
||||
this._createCompiledHostTemplate(entryComponentType.componentType, moduleMeta));
|
||||
});
|
||||
});
|
||||
templates.forEach((template) => this._compileTemplate(template));
|
||||
@ -222,12 +219,12 @@ export class JitCompiler implements Compiler {
|
||||
const compMeta = this._metadataResolver.getDirectiveMetadata(compType);
|
||||
assertComponent(compMeta);
|
||||
|
||||
const HostClass = function HostClass() {};
|
||||
(<any>HostClass).overriddenName = `${identifierName(compMeta.type)}_Host`;
|
||||
|
||||
const hostMeta = createHostComponentMeta(HostClass, compMeta);
|
||||
compiledTemplate = new CompiledTemplate(
|
||||
true, compMeta.selector, compMeta.type, hostMeta, ngModule, [compMeta.type]);
|
||||
const componentFactory = <ComponentFactory<any>>compMeta.componentFactory;
|
||||
const hostClass = this._metadataResolver.getHostComponentType(compType);
|
||||
const hostMeta = createHostComponentMeta(
|
||||
hostClass, compMeta, <any>view_utils.getComponentFactoryViewClass(componentFactory));
|
||||
compiledTemplate =
|
||||
new CompiledTemplate(true, compMeta.type, hostMeta, ngModule, [compMeta.type]);
|
||||
this._compiledHostTemplateCache.set(compType, compiledTemplate);
|
||||
}
|
||||
return compiledTemplate;
|
||||
@ -239,32 +236,12 @@ export class JitCompiler implements Compiler {
|
||||
if (!compiledTemplate) {
|
||||
assertComponent(compMeta);
|
||||
compiledTemplate = new CompiledTemplate(
|
||||
false, compMeta.selector, compMeta.type, compMeta, ngModule,
|
||||
ngModule.transitiveModule.directives);
|
||||
false, compMeta.type, compMeta, ngModule, ngModule.transitiveModule.directives);
|
||||
this._compiledTemplateCache.set(compMeta.type.reference, compiledTemplate);
|
||||
}
|
||||
return compiledTemplate;
|
||||
}
|
||||
|
||||
private _assertComponentKnown(compType: any, isHost: boolean): CompiledTemplate {
|
||||
const compiledTemplate = isHost ? this._compiledHostTemplateCache.get(compType) :
|
||||
this._compiledTemplateCache.get(compType);
|
||||
if (!compiledTemplate) {
|
||||
throw new Error(
|
||||
`Illegal state: Compiled view for component ${stringify(compType)} (host: ${isHost}) does not exist!`);
|
||||
}
|
||||
return compiledTemplate;
|
||||
}
|
||||
|
||||
private _assertDirectiveWrapper(dirType: any): Type<any> {
|
||||
const dirWrapper = this._compiledDirectiveWrapperCache.get(dirType);
|
||||
if (!dirWrapper) {
|
||||
throw new Error(
|
||||
`Illegal state: Directive wrapper for ${stringify(dirType)} has not been compiled!`);
|
||||
}
|
||||
return dirWrapper;
|
||||
}
|
||||
|
||||
private _compileDirectiveWrapper(
|
||||
dirMeta: CompileDirectiveMetadata, moduleMeta: CompileNgModuleMetadata): void {
|
||||
const compileResult = this._directiveWrapperCompiler.compile(dirMeta);
|
||||
@ -277,6 +254,7 @@ export class JitCompiler implements Compiler {
|
||||
`/${identifierName(moduleMeta.type)}/${identifierName(dirMeta.type)}/wrapper.ngfactory.js`,
|
||||
statements, compileResult.dirWrapperClassVar);
|
||||
}
|
||||
(<ProxyClass>dirMeta.wrapperType).setDelegate(directiveWrapperClass);
|
||||
this._compiledDirectiveWrapperCache.set(dirMeta.type.reference, directiveWrapperClass);
|
||||
}
|
||||
|
||||
@ -304,21 +282,6 @@ export class JitCompiler implements Compiler {
|
||||
const compileResult = this._viewCompiler.compileComponent(
|
||||
compMeta, parsedTemplate, ir.variable(stylesCompileResult.componentStylesheet.stylesVar),
|
||||
pipes, compiledAnimations);
|
||||
compileResult.dependencies.forEach((dep) => {
|
||||
let depTemplate: CompiledTemplate;
|
||||
if (dep instanceof ViewClassDependency) {
|
||||
const vfd = <ViewClassDependency>dep;
|
||||
depTemplate = this._assertComponentKnown(vfd.comp.reference, false);
|
||||
vfd.placeholder.reference = depTemplate.proxyViewClass;
|
||||
} else if (dep instanceof ComponentFactoryDependency) {
|
||||
const cfd = <ComponentFactoryDependency>dep;
|
||||
depTemplate = this._assertComponentKnown(cfd.comp.reference, true);
|
||||
cfd.placeholder.reference = depTemplate.proxyComponentFactory;
|
||||
} else if (dep instanceof DirectiveWrapperDependency) {
|
||||
const dwd = <DirectiveWrapperDependency>dep;
|
||||
dwd.placeholder.reference = this._assertDirectiveWrapper(dwd.dir.reference);
|
||||
}
|
||||
});
|
||||
const statements = stylesCompileResult.componentStylesheet.statements
|
||||
.concat(...compiledAnimations.map(ca => ca.statements))
|
||||
.concat(compileResult.statements);
|
||||
@ -358,30 +321,16 @@ export class JitCompiler implements Compiler {
|
||||
|
||||
class CompiledTemplate {
|
||||
private _viewClass: Function = null;
|
||||
proxyViewClass: Type<any>;
|
||||
proxyComponentFactory: ComponentFactory<any>;
|
||||
isCompiled = false;
|
||||
|
||||
constructor(
|
||||
public isHost: boolean, selector: string, public compType: CompileIdentifierMetadata,
|
||||
public isHost: boolean, public compType: CompileIdentifierMetadata,
|
||||
public compMeta: CompileDirectiveMetadata, public ngModule: CompileNgModuleMetadata,
|
||||
public directives: CompileIdentifierMetadata[]) {
|
||||
const self = this;
|
||||
this.proxyViewClass = <any>function() {
|
||||
if (!self._viewClass) {
|
||||
throw new Error(
|
||||
`Illegal state: CompiledTemplate for ${stringify(self.compType)} is not compiled yet!`);
|
||||
}
|
||||
return self._viewClass.apply(this, arguments);
|
||||
};
|
||||
this.proxyComponentFactory = isHost ?
|
||||
new ComponentFactory<any>(selector, this.proxyViewClass, compType.reference) :
|
||||
null;
|
||||
}
|
||||
public directives: CompileIdentifierMetadata[]) {}
|
||||
|
||||
compiled(viewClass: Function) {
|
||||
this._viewClass = viewClass;
|
||||
this.proxyViewClass.prototype = viewClass.prototype;
|
||||
(<ProxyClass>this.compMeta.componentViewType).setDelegate(viewClass);
|
||||
this.isCompiled = true;
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
|
||||
import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, OpaqueToken, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
|
||||
|
||||
import {AnimationParser} from '../animation/animation_parser';
|
||||
import {CompilerConfig} from '../config';
|
||||
@ -40,6 +40,8 @@ const _NO_RESOURCE_LOADER: ResourceLoader = {
|
||||
`No ResourceLoader implementation has been provided. Can't read the url "${url}"`);}
|
||||
};
|
||||
|
||||
const baseHtmlParser = new OpaqueToken('HtmlParser');
|
||||
|
||||
/**
|
||||
* A set of providers that provide `JitCompiler` and its dependencies to use for
|
||||
* template compilation.
|
||||
@ -52,17 +54,24 @@ export const COMPILER_PROVIDERS: Array<any|Type<any>|{[k: string]: any}|any[]> =
|
||||
Console,
|
||||
Lexer,
|
||||
Parser,
|
||||
HtmlParser,
|
||||
{
|
||||
provide: baseHtmlParser,
|
||||
useClass: HtmlParser,
|
||||
},
|
||||
{
|
||||
provide: i18n.I18NHtmlParser,
|
||||
useFactory: (parser: HtmlParser, translations: string, format: string) =>
|
||||
new i18n.I18NHtmlParser(parser, translations, format),
|
||||
deps: [
|
||||
HtmlParser,
|
||||
baseHtmlParser,
|
||||
[new Optional(), new Inject(TRANSLATIONS)],
|
||||
[new Optional(), new Inject(TRANSLATIONS_FORMAT)],
|
||||
]
|
||||
},
|
||||
{
|
||||
provide: HtmlParser,
|
||||
useExisting: i18n.I18NHtmlParser,
|
||||
},
|
||||
TemplateParser,
|
||||
DirectiveNormalizer,
|
||||
CompileMetadataResolver,
|
||||
|
@ -6,16 +6,16 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AnimationAnimateMetadata, AnimationEntryMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationStateDeclarationMetadata, AnimationStateMetadata, AnimationStateTransitionMetadata, AnimationStyleMetadata, AnimationWithStepsMetadata, Attribute, ChangeDetectionStrategy, Component, Directive, Host, Inject, Injectable, ModuleWithProviders, OpaqueToken, Optional, Provider, Query, SchemaMetadata, Self, SkipSelf, Type, resolveForwardRef} from '@angular/core';
|
||||
import {AnimationAnimateMetadata, AnimationEntryMetadata, AnimationGroupMetadata, AnimationKeyframesSequenceMetadata, AnimationMetadata, AnimationStateDeclarationMetadata, AnimationStateMetadata, AnimationStateTransitionMetadata, AnimationStyleMetadata, AnimationWithStepsMetadata, Attribute, ChangeDetectionStrategy, Component, ComponentFactory, Directive, Host, Inject, Injectable, ModuleWithProviders, OpaqueToken, Optional, Provider, Query, SchemaMetadata, Self, SkipSelf, Type, resolveForwardRef} from '@angular/core';
|
||||
|
||||
import {StaticSymbol} from './aot/static_symbol';
|
||||
import {StaticSymbol, StaticSymbolCache} from './aot/static_symbol';
|
||||
import {ngfactoryFilePath} from './aot/util';
|
||||
import {assertArrayOfStrings, assertInterpolationSymbols} from './assertions';
|
||||
import * as cpl from './compile_metadata';
|
||||
import {DirectiveNormalizer} from './directive_normalizer';
|
||||
import {DirectiveResolver} from './directive_resolver';
|
||||
import {ListWrapper, StringMapWrapper} from './facade/collection';
|
||||
import {isBlank, isPresent, stringify} from './facade/lang';
|
||||
import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers';
|
||||
import {stringify} from './facade/lang';
|
||||
import {Identifiers, resolveIdentifier} from './identifiers';
|
||||
import {CompilerInjectable} from './injectable';
|
||||
import {hasLifecycleHook} from './lifecycle_reflector';
|
||||
import {NgModuleResolver} from './ng_module_resolver';
|
||||
@ -24,7 +24,7 @@ import {ComponentStillLoadingError, LIFECYCLE_HOOKS_VALUES, ReflectorReader, ref
|
||||
import {ElementSchemaRegistry} from './schema/element_schema_registry';
|
||||
import {SummaryResolver} from './summary_resolver';
|
||||
import {getUrlScheme} from './url_resolver';
|
||||
import {MODULE_SUFFIX, SyncAsyncResult, SyntaxError, ValueTransformer, visitValue} from './util';
|
||||
import {MODULE_SUFFIX, SyntaxError, ValueTransformer, visitValue} from './util';
|
||||
|
||||
export type ErrorCollector = (error: any, type?: any) => void;
|
||||
export const ERROR_COLLECTOR_TOKEN = new OpaqueToken('ErrorCollector');
|
||||
@ -38,6 +38,8 @@ export const ERROR_COLLECTOR_TOKEN = new OpaqueToken('ErrorCollector');
|
||||
// to wait correctly.
|
||||
@CompilerInjectable()
|
||||
export class CompileMetadataResolver {
|
||||
private _nonNormalizedDirectiveCache =
|
||||
new Map<Type<any>, {annotation: Directive, metadata: cpl.CompileDirectiveMetadata}>();
|
||||
private _directiveCache = new Map<Type<any>, cpl.CompileDirectiveMetadata>();
|
||||
private _summaryCache = new Map<Type<any>, cpl.CompileTypeSummary>();
|
||||
private _pipeCache = new Map<Type<any>, cpl.CompilePipeMetadata>();
|
||||
@ -49,12 +51,14 @@ export class CompileMetadataResolver {
|
||||
private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver<any>,
|
||||
private _schemaRegistry: ElementSchemaRegistry,
|
||||
private _directiveNormalizer: DirectiveNormalizer,
|
||||
@Optional() private _staticSymbolCache: StaticSymbolCache,
|
||||
private _reflector: ReflectorReader = reflector,
|
||||
@Optional() @Inject(ERROR_COLLECTOR_TOKEN) private _errorCollector?: ErrorCollector) {}
|
||||
|
||||
clearCacheFor(type: Type<any>) {
|
||||
const dirMeta = this._directiveCache.get(type);
|
||||
this._directiveCache.delete(type);
|
||||
this._nonNormalizedDirectiveCache.delete(type);
|
||||
this._summaryCache.delete(type);
|
||||
this._pipeCache.delete(type);
|
||||
this._ngModuleOfTypes.delete(type);
|
||||
@ -65,8 +69,9 @@ export class CompileMetadataResolver {
|
||||
}
|
||||
}
|
||||
|
||||
clearCache() {
|
||||
clearCache(): void {
|
||||
this._directiveCache.clear();
|
||||
this._nonNormalizedDirectiveCache.clear();
|
||||
this._summaryCache.clear();
|
||||
this._pipeCache.clear();
|
||||
this._ngModuleCache.clear();
|
||||
@ -74,6 +79,66 @@ export class CompileMetadataResolver {
|
||||
this._directiveNormalizer.clearCache();
|
||||
}
|
||||
|
||||
private _createProxyClass(baseType: any, name: string): cpl.ProxyClass {
|
||||
let delegate: any = null;
|
||||
const proxyClass: cpl.ProxyClass = <any>function() {
|
||||
if (!delegate) {
|
||||
throw new Error(
|
||||
`Illegal state: Class ${name} for type ${stringify(baseType)} is not compiled yet!`);
|
||||
}
|
||||
return delegate.apply(this, arguments);
|
||||
};
|
||||
proxyClass.setDelegate = (d) => {
|
||||
delegate = d;
|
||||
(<any>proxyClass).prototype = d.prototype;
|
||||
};
|
||||
// Make stringify work correctly
|
||||
(<any>proxyClass).overriddenName = name;
|
||||
return proxyClass;
|
||||
}
|
||||
|
||||
private getGeneratedClass(dirType: any, name: string): StaticSymbol|cpl.ProxyClass {
|
||||
if (dirType instanceof StaticSymbol) {
|
||||
return this._staticSymbolCache.get(ngfactoryFilePath(dirType.filePath), name);
|
||||
} else {
|
||||
return this._createProxyClass(dirType, name);
|
||||
}
|
||||
}
|
||||
|
||||
private getDirectiveWrapperClass(dirType: any): StaticSymbol|cpl.ProxyClass {
|
||||
return this.getGeneratedClass(dirType, cpl.dirWrapperClassName(dirType));
|
||||
}
|
||||
|
||||
private getComponentViewClass(dirType: any): StaticSymbol|cpl.ProxyClass {
|
||||
return this.getGeneratedClass(dirType, cpl.viewClassName(dirType, 0));
|
||||
}
|
||||
|
||||
getHostComponentViewClass(dirType: any): StaticSymbol|cpl.ProxyClass {
|
||||
return this.getGeneratedClass(dirType, cpl.hostViewClassName(dirType));
|
||||
}
|
||||
|
||||
getHostComponentType(dirType: any): StaticSymbol|Type<any> {
|
||||
const name = `${cpl.identifierName({reference: dirType})}_Host`;
|
||||
if (dirType instanceof StaticSymbol) {
|
||||
return this._staticSymbolCache.get(dirType.filePath, name);
|
||||
} else {
|
||||
const HostClass = <any>function HostClass() {};
|
||||
HostClass.overriddenName = name;
|
||||
|
||||
return HostClass;
|
||||
}
|
||||
}
|
||||
|
||||
private getComponentFactory(selector: string, dirType: any): StaticSymbol|ComponentFactory<any> {
|
||||
if (dirType instanceof StaticSymbol) {
|
||||
return this._staticSymbolCache.get(
|
||||
ngfactoryFilePath(dirType.filePath), cpl.componentFactoryName(dirType));
|
||||
} else {
|
||||
const hostView = this.getHostComponentViewClass(dirType);
|
||||
return new ComponentFactory(selector, <any>hostView, dirType);
|
||||
}
|
||||
}
|
||||
|
||||
getAnimationEntryMetadata(entry: AnimationEntryMetadata): cpl.CompileAnimationEntryMetadata {
|
||||
const defs = entry.definitions.map(def => this._getAnimationStateMetadata(def));
|
||||
return new cpl.CompileAnimationEntryMetadata(entry.name, defs);
|
||||
@ -162,6 +227,9 @@ export class CompileMetadataResolver {
|
||||
queries: metadata.queries,
|
||||
viewQueries: metadata.viewQueries,
|
||||
entryComponents: metadata.entryComponents,
|
||||
wrapperType: metadata.wrapperType,
|
||||
componentViewType: metadata.componentViewType,
|
||||
componentFactory: metadata.componentFactory,
|
||||
template: templateMetadata
|
||||
});
|
||||
this._directiveCache.set(directiveType, normalizedDirMeta);
|
||||
@ -201,7 +269,14 @@ export class CompileMetadataResolver {
|
||||
getNonNormalizedDirectiveMetadata(directiveType: any):
|
||||
{annotation: Directive, metadata: cpl.CompileDirectiveMetadata} {
|
||||
directiveType = resolveForwardRef(directiveType);
|
||||
const dirMeta = this._directiveResolver.resolve(directiveType);
|
||||
if (!directiveType) {
|
||||
return null;
|
||||
}
|
||||
let cacheEntry = this._nonNormalizedDirectiveCache.get(directiveType);
|
||||
if (cacheEntry) {
|
||||
return cacheEntry;
|
||||
}
|
||||
const dirMeta = this._directiveResolver.resolve(directiveType, false);
|
||||
if (!dirMeta) {
|
||||
return null;
|
||||
}
|
||||
@ -230,7 +305,7 @@ export class CompileMetadataResolver {
|
||||
|
||||
let changeDetectionStrategy: ChangeDetectionStrategy = null;
|
||||
let viewProviders: cpl.CompileProviderMetadata[] = [];
|
||||
let entryComponentMetadata: cpl.CompileIdentifierMetadata[] = [];
|
||||
let entryComponentMetadata: cpl.CompileEntryComponentMetadata[] = [];
|
||||
let selector = dirMeta.selector;
|
||||
|
||||
if (dirMeta instanceof Component) {
|
||||
@ -243,7 +318,7 @@ export class CompileMetadataResolver {
|
||||
}
|
||||
if (dirMeta.entryComponents) {
|
||||
entryComponentMetadata = flattenAndDedupeArray(dirMeta.entryComponents)
|
||||
.map((type) => this._getIdentifierMetadata(type))
|
||||
.map((type) => this._getEntryComponentMetadata(type))
|
||||
.concat(entryComponentMetadata);
|
||||
}
|
||||
if (!selector) {
|
||||
@ -261,14 +336,14 @@ export class CompileMetadataResolver {
|
||||
}
|
||||
|
||||
let providers: cpl.CompileProviderMetadata[] = [];
|
||||
if (isPresent(dirMeta.providers)) {
|
||||
if (dirMeta.providers != null) {
|
||||
providers = this._getProvidersMetadata(
|
||||
dirMeta.providers, entryComponentMetadata,
|
||||
`providers for "${stringifyType(directiveType)}"`, [], directiveType);
|
||||
}
|
||||
let queries: cpl.CompileQueryMetadata[] = [];
|
||||
let viewQueries: cpl.CompileQueryMetadata[] = [];
|
||||
if (isPresent(dirMeta.queries)) {
|
||||
if (dirMeta.queries != null) {
|
||||
queries = this._getQueriesMetadata(dirMeta.queries, false, directiveType);
|
||||
viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType);
|
||||
}
|
||||
@ -287,9 +362,17 @@ export class CompileMetadataResolver {
|
||||
viewProviders: viewProviders,
|
||||
queries: queries,
|
||||
viewQueries: viewQueries,
|
||||
entryComponents: entryComponentMetadata
|
||||
entryComponents: entryComponentMetadata,
|
||||
wrapperType: this.getDirectiveWrapperClass(directiveType),
|
||||
componentViewType: nonNormalizedTemplateMetadata ? this.getComponentViewClass(directiveType) :
|
||||
undefined,
|
||||
componentFactory: nonNormalizedTemplateMetadata ?
|
||||
this.getComponentFactory(selector, directiveType) :
|
||||
undefined
|
||||
});
|
||||
return {metadata, annotation: dirMeta};
|
||||
cacheEntry = {metadata, annotation: dirMeta};
|
||||
this._nonNormalizedDirectiveCache.set(directiveType, cacheEntry);
|
||||
return cacheEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -371,7 +454,7 @@ export class CompileMetadataResolver {
|
||||
const importedModules: cpl.CompileNgModuleSummary[] = [];
|
||||
const exportedModules: cpl.CompileNgModuleSummary[] = [];
|
||||
const providers: cpl.CompileProviderMetadata[] = [];
|
||||
const entryComponents: cpl.CompileIdentifierMetadata[] = [];
|
||||
const entryComponents: cpl.CompileEntryComponentMetadata[] = [];
|
||||
const bootstrapComponents: cpl.CompileIdentifierMetadata[] = [];
|
||||
const schemas: SchemaMetadata[] = [];
|
||||
|
||||
@ -488,7 +571,7 @@ export class CompileMetadataResolver {
|
||||
|
||||
if (meta.entryComponents) {
|
||||
entryComponents.push(...flattenAndDedupeArray(meta.entryComponents)
|
||||
.map(type => this._getIdentifierMetadata(type)));
|
||||
.map(type => this._getEntryComponentMetadata(type)));
|
||||
}
|
||||
|
||||
if (meta.bootstrap) {
|
||||
@ -504,7 +587,8 @@ export class CompileMetadataResolver {
|
||||
});
|
||||
}
|
||||
|
||||
entryComponents.push(...bootstrapComponents);
|
||||
entryComponents.push(
|
||||
...bootstrapComponents.map(type => this._getEntryComponentMetadata(type.reference)));
|
||||
|
||||
if (meta.schemas) {
|
||||
schemas.push(...flattenAndDedupeArray(meta.schemas));
|
||||
@ -722,14 +806,14 @@ export class CompileMetadataResolver {
|
||||
token = paramEntry.attributeName;
|
||||
} else if (paramEntry instanceof Inject) {
|
||||
token = paramEntry.token;
|
||||
} else if (isValidType(paramEntry) && isBlank(token)) {
|
||||
} else if (isValidType(paramEntry) && token == null) {
|
||||
token = paramEntry;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
token = param;
|
||||
}
|
||||
if (isBlank(token)) {
|
||||
if (token == null) {
|
||||
hasUnknownDeps = true;
|
||||
return null;
|
||||
}
|
||||
@ -769,7 +853,7 @@ export class CompileMetadataResolver {
|
||||
}
|
||||
|
||||
private _getProvidersMetadata(
|
||||
providers: Provider[], targetEntryComponents: cpl.CompileIdentifierMetadata[],
|
||||
providers: Provider[], targetEntryComponents: cpl.CompileEntryComponentMetadata[],
|
||||
debugInfo?: string, compileProviders: cpl.CompileProviderMetadata[] = [],
|
||||
type?: any): cpl.CompileProviderMetadata[] {
|
||||
providers.forEach((provider: any, providerIdx: number) => {
|
||||
@ -778,10 +862,14 @@ export class CompileMetadataResolver {
|
||||
} else {
|
||||
provider = resolveForwardRef(provider);
|
||||
let providerMeta: cpl.ProviderMeta;
|
||||
if (provider && typeof provider == 'object' && provider.hasOwnProperty('provide')) {
|
||||
if (provider && typeof provider === 'object' && provider.hasOwnProperty('provide')) {
|
||||
this._validateProvider(provider);
|
||||
providerMeta = new cpl.ProviderMeta(provider.provide, provider);
|
||||
} else if (isValidType(provider)) {
|
||||
providerMeta = new cpl.ProviderMeta(provider, {useClass: provider});
|
||||
} else if (provider === void 0) {
|
||||
this._reportError(new SyntaxError(
|
||||
`Encountered undefined provider! Usually this means you have a circular dependencies (might be caused by using 'barrel' index.ts files.`));
|
||||
} else {
|
||||
const providersInfo =
|
||||
(<string[]>providers.reduce(
|
||||
@ -812,9 +900,19 @@ export class CompileMetadataResolver {
|
||||
return compileProviders;
|
||||
}
|
||||
|
||||
private _validateProvider(provider: any): void {
|
||||
if (provider.hasOwnProperty('useClass') && provider.useClass == null) {
|
||||
this._reportError(new SyntaxError(
|
||||
`Invalid provider for ${stringifyType(provider.provide)}. useClass cannot be ${provider.useClass}.
|
||||
Usually it happens when:
|
||||
1. There's a circular dependency (might be caused by using index.ts (barrel) files).
|
||||
2. Class was used before it was declared. Use forwardRef in this case.`));
|
||||
}
|
||||
}
|
||||
|
||||
private _getEntryComponentsFromProvider(provider: cpl.ProviderMeta, type?: any):
|
||||
cpl.CompileIdentifierMetadata[] {
|
||||
const components: cpl.CompileIdentifierMetadata[] = [];
|
||||
cpl.CompileEntryComponentMetadata[] {
|
||||
const components: cpl.CompileEntryComponentMetadata[] = [];
|
||||
const collectedIdentifiers: cpl.CompileIdentifierMetadata[] = [];
|
||||
|
||||
if (provider.useFactory || provider.useExisting || provider.useClass) {
|
||||
@ -832,14 +930,27 @@ export class CompileMetadataResolver {
|
||||
|
||||
extractIdentifiers(provider.useValue, collectedIdentifiers);
|
||||
collectedIdentifiers.forEach((identifier) => {
|
||||
if (this._directiveResolver.isDirective(identifier.reference) ||
|
||||
this._loadSummary(identifier.reference, cpl.CompileSummaryKind.Directive)) {
|
||||
components.push(identifier);
|
||||
const entry = this._getEntryComponentMetadata(identifier.reference);
|
||||
if (entry) {
|
||||
components.push(entry);
|
||||
}
|
||||
});
|
||||
return components;
|
||||
}
|
||||
|
||||
private _getEntryComponentMetadata(dirType: any): cpl.CompileEntryComponentMetadata {
|
||||
const dirMeta = this.getNonNormalizedDirectiveMetadata(dirType);
|
||||
if (dirMeta) {
|
||||
return {componentType: dirType, componentFactory: dirMeta.metadata.componentFactory};
|
||||
} else {
|
||||
const dirSummary =
|
||||
<cpl.CompileDirectiveSummary>this._loadSummary(dirType, cpl.CompileSummaryKind.Directive);
|
||||
if (dirSummary) {
|
||||
return {componentType: dirType, componentFactory: dirSummary.componentFactory};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getProviderMetadata(provider: cpl.ProviderMeta): cpl.CompileProviderMetadata {
|
||||
let compileDeps: cpl.CompileDiDependencyMetadata[];
|
||||
let compileTypeMetadata: cpl.CompileTypeMetadata = null;
|
||||
@ -987,4 +1098,4 @@ function stringifyType(type: any): string {
|
||||
} else {
|
||||
return stringify(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,9 +19,12 @@ import {LifecycleHooks} from './private_import_core';
|
||||
import {NgModuleProviderAnalyzer} from './provider_analyzer';
|
||||
import {ProviderAst} from './template_parser/template_ast';
|
||||
|
||||
/**
|
||||
* This is currently not read, but will probably be used in the future.
|
||||
* We keep it as we already pass it through all the rigth places...
|
||||
*/
|
||||
export class ComponentFactoryDependency {
|
||||
constructor(
|
||||
public comp: CompileIdentifierMetadata, public placeholder: CompileIdentifierMetadata) {}
|
||||
constructor(public compType: any) {}
|
||||
}
|
||||
|
||||
export class NgModuleCompileResult {
|
||||
@ -46,13 +49,12 @@ export class NgModuleCompiler {
|
||||
const bootstrapComponentFactories: CompileIdentifierMetadata[] = [];
|
||||
const entryComponentFactories =
|
||||
ngModuleMeta.transitiveModule.entryComponents.map((entryComponent) => {
|
||||
const id: CompileIdentifierMetadata = {reference: null};
|
||||
if (ngModuleMeta.bootstrapComponents.some(
|
||||
(id) => id.reference === entryComponent.reference)) {
|
||||
bootstrapComponentFactories.push(id);
|
||||
(id) => id.reference === entryComponent.componentType)) {
|
||||
bootstrapComponentFactories.push({reference: entryComponent.componentFactory});
|
||||
}
|
||||
deps.push(new ComponentFactoryDependency(entryComponent, id));
|
||||
return id;
|
||||
deps.push(new ComponentFactoryDependency(entryComponent.componentType));
|
||||
return {reference: entryComponent.componentFactory};
|
||||
});
|
||||
const builder = new _InjectorBuilder(
|
||||
ngModuleMeta, entryComponentFactories, bootstrapComponentFactories, sourceSpan);
|
||||
|
@ -7,8 +7,9 @@
|
||||
*/
|
||||
|
||||
|
||||
import {identifierModuleUrl, identifierName} from '../compile_metadata';
|
||||
import {isBlank, isPresent} from '../facade/lang';
|
||||
import {StaticSymbol} from '../aot/static_symbol';
|
||||
import {CompileIdentifierMetadata} from '../compile_metadata';
|
||||
import {isBlank} from '../facade/lang';
|
||||
|
||||
import {EmitterVisitorContext, OutputEmitter} from './abstract_emitter';
|
||||
import {AbstractJsEmitterVisitor} from './abstract_js_emitter';
|
||||
@ -16,17 +17,17 @@ import * as o from './output_ast';
|
||||
import {ImportResolver} from './path_util';
|
||||
|
||||
export class JavaScriptEmitter implements OutputEmitter {
|
||||
constructor(private _importGenerator: ImportResolver) {}
|
||||
emitStatements(moduleUrl: string, stmts: o.Statement[], exportedVars: string[]): string {
|
||||
const converter = new JsEmitterVisitor(moduleUrl);
|
||||
constructor(private _importResolver: ImportResolver) {}
|
||||
emitStatements(genFilePath: string, stmts: o.Statement[], exportedVars: string[]): string {
|
||||
const converter = new JsEmitterVisitor(genFilePath, this._importResolver);
|
||||
const ctx = EmitterVisitorContext.createRoot(exportedVars);
|
||||
converter.visitAllStatements(stmts, ctx);
|
||||
const srcParts: string[] = [];
|
||||
converter.importsWithPrefixes.forEach((prefix, importedModuleUrl) => {
|
||||
converter.importsWithPrefixes.forEach((prefix, importedFilePath) => {
|
||||
// Note: can't write the real word for import as it screws up system.js auto detection...
|
||||
srcParts.push(
|
||||
`var ${prefix} = req` +
|
||||
`uire('${this._importGenerator.fileNameToModuleName(importedModuleUrl, moduleUrl)}');`);
|
||||
`uire('${this._importResolver.fileNameToModuleName(importedFilePath, genFilePath)}');`);
|
||||
});
|
||||
srcParts.push(ctx.toSource());
|
||||
return srcParts.join('\n');
|
||||
@ -36,20 +37,23 @@ export class JavaScriptEmitter implements OutputEmitter {
|
||||
class JsEmitterVisitor extends AbstractJsEmitterVisitor {
|
||||
importsWithPrefixes = new Map<string, string>();
|
||||
|
||||
constructor(private _moduleUrl: string) { super(); }
|
||||
constructor(private _genFilePath: string, private _importResolver: ImportResolver) { super(); }
|
||||
|
||||
private _resolveStaticSymbol(value: CompileIdentifierMetadata): StaticSymbol {
|
||||
const reference = value.reference;
|
||||
if (!(reference instanceof StaticSymbol)) {
|
||||
throw new Error(`Internal error: unknown identifier ${JSON.stringify(value)}`);
|
||||
}
|
||||
return this._importResolver.getImportAs(reference) || reference;
|
||||
}
|
||||
|
||||
visitExternalExpr(ast: o.ExternalExpr, ctx: EmitterVisitorContext): any {
|
||||
const name = identifierName(ast.value);
|
||||
const moduleUrl = identifierModuleUrl(ast.value);
|
||||
if (isBlank(name)) {
|
||||
console.error('>>>', ast.value);
|
||||
throw new Error(`Internal error: unknown identifier ${ast.value}`);
|
||||
}
|
||||
if (isPresent(moduleUrl) && moduleUrl != this._moduleUrl) {
|
||||
let prefix = this.importsWithPrefixes.get(moduleUrl);
|
||||
const {name, filePath} = this._resolveStaticSymbol(ast.value);
|
||||
if (filePath != this._genFilePath) {
|
||||
let prefix = this.importsWithPrefixes.get(filePath);
|
||||
if (isBlank(prefix)) {
|
||||
prefix = `import${this.importsWithPrefixes.size}`;
|
||||
this.importsWithPrefixes.set(moduleUrl, prefix);
|
||||
this.importsWithPrefixes.set(filePath, prefix);
|
||||
}
|
||||
ctx.print(`${prefix}.`);
|
||||
}
|
||||
|
@ -6,6 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StaticSymbol} from '../aot/static_symbol';
|
||||
|
||||
/**
|
||||
* Interface that defines how import statements should be generated.
|
||||
*/
|
||||
@ -16,4 +18,10 @@ export abstract class ImportResolver {
|
||||
*/
|
||||
abstract fileNameToModuleName(importedFilePath: string, containingFilePath: string): string
|
||||
/*|null*/;
|
||||
|
||||
/**
|
||||
* Converts the given StaticSymbol into another StaticSymbol that should be used
|
||||
* to generate the import from.
|
||||
*/
|
||||
abstract getImportAs(symbol: StaticSymbol): StaticSymbol /*|null*/;
|
||||
}
|
||||
|
@ -7,18 +7,22 @@
|
||||
*/
|
||||
|
||||
|
||||
import {CompileIdentifierMetadata, identifierModuleUrl, identifierName} from '../compile_metadata';
|
||||
import {StaticSymbol} from '../aot/static_symbol';
|
||||
import {CompileIdentifierMetadata} from '../compile_metadata';
|
||||
import {isBlank, isPresent} from '../facade/lang';
|
||||
|
||||
import {AbstractEmitterVisitor, CATCH_ERROR_VAR, CATCH_STACK_VAR, EmitterVisitorContext, OutputEmitter} from './abstract_emitter';
|
||||
import * as o from './output_ast';
|
||||
import {ImportResolver} from './path_util';
|
||||
|
||||
const _debugModuleUrl = '/debug/lib';
|
||||
const _debugFilePath = '/debug/lib';
|
||||
|
||||
export function debugOutputAstAsTypeScript(ast: o.Statement | o.Expression | o.Type | any[]):
|
||||
string {
|
||||
const converter = new _TsEmitterVisitor(_debugModuleUrl);
|
||||
const converter = new _TsEmitterVisitor(_debugFilePath, {
|
||||
fileNameToModuleName(filePath: string, containingFilePath: string) { return filePath; },
|
||||
getImportAs(symbol: StaticSymbol) { return null; }
|
||||
});
|
||||
const ctx = EmitterVisitorContext.createRoot([]);
|
||||
const asts: any[] = Array.isArray(ast) ? ast : [ast];
|
||||
|
||||
@ -37,17 +41,23 @@ export function debugOutputAstAsTypeScript(ast: o.Statement | o.Expression | o.T
|
||||
}
|
||||
|
||||
export class TypeScriptEmitter implements OutputEmitter {
|
||||
constructor(private _importGenerator: ImportResolver) {}
|
||||
emitStatements(moduleUrl: string, stmts: o.Statement[], exportedVars: string[]): string {
|
||||
const converter = new _TsEmitterVisitor(moduleUrl);
|
||||
constructor(private _importResolver: ImportResolver) {}
|
||||
emitStatements(genFilePath: string, stmts: o.Statement[], exportedVars: string[]): string {
|
||||
const converter = new _TsEmitterVisitor(genFilePath, this._importResolver);
|
||||
const ctx = EmitterVisitorContext.createRoot(exportedVars);
|
||||
converter.visitAllStatements(stmts, ctx);
|
||||
const srcParts: string[] = [];
|
||||
converter.importsWithPrefixes.forEach((prefix, importedModuleUrl) => {
|
||||
converter.reexports.forEach((reexports, exportedFilePath) => {
|
||||
const reexportsCode =
|
||||
reexports.map(reexport => `${reexport.name} as ${reexport.as}`).join(',');
|
||||
srcParts.push(
|
||||
`export {${reexportsCode}} from '${this._importResolver.fileNameToModuleName(exportedFilePath, genFilePath)}';`);
|
||||
});
|
||||
converter.importsWithPrefixes.forEach((prefix, importedFilePath) => {
|
||||
// Note: can't write the real word for import as it screws up system.js auto detection...
|
||||
srcParts.push(
|
||||
`imp` +
|
||||
`ort * as ${prefix} from '${this._importGenerator.fileNameToModuleName(importedModuleUrl, moduleUrl)}';`);
|
||||
`ort * as ${prefix} from '${this._importResolver.fileNameToModuleName(importedFilePath, genFilePath)}';`);
|
||||
});
|
||||
srcParts.push(ctx.toSource());
|
||||
return srcParts.join('\n');
|
||||
@ -55,9 +65,12 @@ export class TypeScriptEmitter implements OutputEmitter {
|
||||
}
|
||||
|
||||
class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor {
|
||||
constructor(private _moduleUrl: string) { super(false); }
|
||||
constructor(private _genFilePath: string, private _importResolver: ImportResolver) {
|
||||
super(false);
|
||||
}
|
||||
|
||||
importsWithPrefixes = new Map<string, string>();
|
||||
reexports = new Map<string, {name: string, as: string}[]>();
|
||||
|
||||
visitType(t: o.Type, ctx: EmitterVisitorContext, defaultType: string = 'any') {
|
||||
if (isPresent(t)) {
|
||||
@ -98,6 +111,19 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor
|
||||
}
|
||||
|
||||
visitDeclareVarStmt(stmt: o.DeclareVarStmt, ctx: EmitterVisitorContext): any {
|
||||
if (ctx.isExportedVar(stmt.name) && stmt.value instanceof o.ExternalExpr && !stmt.type) {
|
||||
// check for a reexport
|
||||
const {name, filePath, members} = this._resolveStaticSymbol(stmt.value.value);
|
||||
if (members.length === 0 && filePath !== this._genFilePath) {
|
||||
let reexports = this.reexports.get(filePath);
|
||||
if (!reexports) {
|
||||
reexports = [];
|
||||
this.reexports.set(filePath, reexports);
|
||||
}
|
||||
reexports.push({name, as: stmt.name});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (ctx.isExportedVar(stmt.name)) {
|
||||
ctx.print(`export `);
|
||||
}
|
||||
@ -320,25 +346,29 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor
|
||||
}, params, ctx, ',');
|
||||
}
|
||||
|
||||
private _resolveStaticSymbol(value: CompileIdentifierMetadata): StaticSymbol {
|
||||
const reference = value.reference;
|
||||
if (!(reference instanceof StaticSymbol)) {
|
||||
throw new Error(`Internal error: unknown identifier ${JSON.stringify(value)}`);
|
||||
}
|
||||
return this._importResolver.getImportAs(reference) || reference;
|
||||
}
|
||||
|
||||
private _visitIdentifier(
|
||||
value: CompileIdentifierMetadata, typeParams: o.Type[], ctx: EmitterVisitorContext): void {
|
||||
const name = identifierName(value);
|
||||
const moduleUrl = identifierModuleUrl(value);
|
||||
if (isBlank(name)) {
|
||||
throw new Error(`Internal error: unknown identifier ${value}`);
|
||||
}
|
||||
if (isPresent(moduleUrl) && moduleUrl != this._moduleUrl) {
|
||||
let prefix = this.importsWithPrefixes.get(moduleUrl);
|
||||
const {name, filePath, members} = this._resolveStaticSymbol(value);
|
||||
if (filePath != this._genFilePath) {
|
||||
let prefix = this.importsWithPrefixes.get(filePath);
|
||||
if (isBlank(prefix)) {
|
||||
prefix = `import${this.importsWithPrefixes.size}`;
|
||||
this.importsWithPrefixes.set(moduleUrl, prefix);
|
||||
this.importsWithPrefixes.set(filePath, prefix);
|
||||
}
|
||||
ctx.print(`${prefix}.`);
|
||||
}
|
||||
if (value.reference && value.reference.members && value.reference.members.length) {
|
||||
ctx.print(value.reference.name);
|
||||
if (members.length) {
|
||||
ctx.print(name);
|
||||
ctx.print('.');
|
||||
ctx.print(value.reference.members.join('.'));
|
||||
ctx.print(members.join('.'));
|
||||
} else {
|
||||
ctx.print(name);
|
||||
}
|
||||
|
@ -32,7 +32,6 @@ export const view_utils: typeof r.view_utils = r.view_utils;
|
||||
export const DebugContext: typeof r.DebugContext = r.DebugContext;
|
||||
export const StaticNodeDebugInfo: typeof r.StaticNodeDebugInfo = r.StaticNodeDebugInfo;
|
||||
export const devModeEqual: typeof r.devModeEqual = r.devModeEqual;
|
||||
export const UNINITIALIZED: typeof r.UNINITIALIZED = r.UNINITIALIZED;
|
||||
export const ValueUnwrapper: typeof r.ValueUnwrapper = r.ValueUnwrapper;
|
||||
export const TemplateRef_: typeof r.TemplateRef_ = r.TemplateRef_;
|
||||
export type RenderDebugInfo = typeof r._RenderDebugInfo;
|
||||
|
@ -16,6 +16,9 @@ export interface Summary<T> {
|
||||
|
||||
@CompilerInjectable()
|
||||
export class SummaryResolver<T> {
|
||||
isLibraryFile(fileName: string): boolean { return false; };
|
||||
getLibraryFileName(fileName: string): string { return null; }
|
||||
resolveSummary(reference: T): Summary<T> { return null; };
|
||||
getSymbolsOf(filePath: string): T[] { return []; }
|
||||
getImportAs(reference: T): T { return reference; }
|
||||
}
|
||||
|
@ -7,9 +7,9 @@
|
||||
*/
|
||||
|
||||
|
||||
import {CompileDiDependencyMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, identifierName, tokenName, tokenReference} from '../compile_metadata';
|
||||
import {CompileDiDependencyMetadata, CompileDirectiveSummary, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, tokenName, tokenReference} from '../compile_metadata';
|
||||
import {createDiTokenExpression} from '../compiler_util/identifier_util';
|
||||
import {DirectiveWrapperCompiler, DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
|
||||
import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {Identifiers, createIdentifier, createIdentifierToken, identifierToken, resolveIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
@ -97,12 +97,11 @@ export class CompileElement extends CompileNode {
|
||||
}
|
||||
|
||||
private _createComponentFactoryResolver() {
|
||||
const entryComponents =
|
||||
this.component.entryComponents.map((entryComponent: CompileIdentifierMetadata) => {
|
||||
const id: CompileIdentifierMetadata = {reference: null};
|
||||
this.view.targetDependencies.push(new ComponentFactoryDependency(entryComponent, id));
|
||||
return id;
|
||||
});
|
||||
const entryComponents = this.component.entryComponents.map((entryComponent) => {
|
||||
this.view.targetDependencies.push(
|
||||
new ComponentFactoryDependency(entryComponent.componentType));
|
||||
return {reference: entryComponent.componentFactory};
|
||||
});
|
||||
if (!entryComponents || entryComponents.length === 0) {
|
||||
return;
|
||||
}
|
||||
@ -179,11 +178,11 @@ export class CompileElement extends CompileNode {
|
||||
const depsExpr =
|
||||
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep));
|
||||
if (isDirectiveWrapper) {
|
||||
const directiveWrapperIdentifier: CompileIdentifierMetadata = {reference: null};
|
||||
this.view.targetDependencies.push(new DirectiveWrapperDependency(
|
||||
provider.useClass, DirectiveWrapperCompiler.dirWrapperClassName(provider.useClass),
|
||||
directiveWrapperIdentifier));
|
||||
return DirectiveWrapperExpressions.create(directiveWrapperIdentifier, depsExpr);
|
||||
const dirMeta =
|
||||
this._directives.find(dir => dir.type.reference === provider.useClass.reference);
|
||||
this.view.targetDependencies.push(
|
||||
new DirectiveWrapperDependency(dirMeta.type.reference));
|
||||
return DirectiveWrapperExpressions.create({reference: dirMeta.wrapperType}, depsExpr);
|
||||
} else {
|
||||
return o.importExpr(provider.useClass)
|
||||
.instantiate(depsExpr, o.importType(provider.useClass));
|
||||
|
@ -7,9 +7,8 @@
|
||||
*/
|
||||
|
||||
import {AnimationEntryCompileResult} from '../animation/animation_compiler';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompilePipeSummary, tokenName} from '../compile_metadata';
|
||||
import {CompileDirectiveMetadata, CompilePipeSummary, tokenName, viewClassName} from '../compile_metadata';
|
||||
import {EventHandlerVars, NameResolver} from '../compiler_util/expression_converter';
|
||||
import {createPureProxy} from '../compiler_util/identifier_util';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import * as o from '../output/output_ast';
|
||||
@ -19,8 +18,8 @@ import {CompileElement, CompileNode} from './compile_element';
|
||||
import {CompileMethod} from './compile_method';
|
||||
import {CompilePipe} from './compile_pipe';
|
||||
import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query';
|
||||
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency} from './deps';
|
||||
import {getPropertyInView, getViewClassName} from './util';
|
||||
import {ComponentFactoryDependency, ComponentViewDependency, DirectiveWrapperDependency} from './deps';
|
||||
import {getPropertyInView} from './util';
|
||||
|
||||
export enum CompileViewRootNodeType {
|
||||
Node,
|
||||
@ -86,7 +85,7 @@ export class CompileView implements NameResolver {
|
||||
public animations: AnimationEntryCompileResult[], public viewIndex: number,
|
||||
public declarationElement: CompileElement, public templateVariableBindings: string[][],
|
||||
public targetDependencies:
|
||||
Array<ViewClassDependency|ComponentFactoryDependency|DirectiveWrapperDependency>) {
|
||||
Array<ComponentViewDependency|ComponentFactoryDependency|DirectiveWrapperDependency>) {
|
||||
this.createMethod = new CompileMethod(this);
|
||||
this.animationBindingsMethod = new CompileMethod(this);
|
||||
this.injectorGetMethod = new CompileMethod(this);
|
||||
@ -102,7 +101,7 @@ export class CompileView implements NameResolver {
|
||||
this.detachMethod = new CompileMethod(this);
|
||||
|
||||
this.viewType = getViewType(component, viewIndex);
|
||||
this.className = getViewClassName(component, viewIndex);
|
||||
this.className = viewClassName(component.type.reference, viewIndex);
|
||||
this.classType = o.expressionType(o.variable(this.className));
|
||||
this.classExpr = o.variable(this.className);
|
||||
if (this.viewType === ViewType.COMPONENT || this.viewType === ViewType.HOST) {
|
||||
|
@ -41,6 +41,7 @@ export class ViewConstructorVars {
|
||||
export class ViewProperties {
|
||||
static renderer = o.THIS_EXPR.prop('renderer');
|
||||
static viewUtils = o.THIS_EXPR.prop('viewUtils');
|
||||
static throwOnChange = o.THIS_EXPR.prop('throwOnChange');
|
||||
}
|
||||
|
||||
export class InjectMethodVars {
|
||||
@ -48,9 +49,3 @@ export class InjectMethodVars {
|
||||
static requestNodeIndex = o.variable('requestNodeIndex');
|
||||
static notFoundResult = o.variable('notFoundResult');
|
||||
}
|
||||
|
||||
export class DetectChangesVars {
|
||||
static throwOnChange = o.variable(`throwOnChange`);
|
||||
static changes = o.variable(`changes`);
|
||||
static changed = o.variable(`changed`);
|
||||
}
|
||||
|
@ -6,21 +6,26 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CompileIdentifierMetadata} from '../compile_metadata';
|
||||
|
||||
export class ViewClassDependency {
|
||||
constructor(
|
||||
public comp: CompileIdentifierMetadata, public name: string,
|
||||
public placeholder: CompileIdentifierMetadata) {}
|
||||
/**
|
||||
* This is currently not read, but will probably be used in the future.
|
||||
* We keep it as we already pass it through all the right places...
|
||||
*/
|
||||
export class ComponentViewDependency {
|
||||
constructor(public compType: any) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is currently not read, but will probably be used in the future.
|
||||
* We keep it as we already pass it through all the right places...
|
||||
*/
|
||||
export class ComponentFactoryDependency {
|
||||
constructor(
|
||||
public comp: CompileIdentifierMetadata, public placeholder: CompileIdentifierMetadata) {}
|
||||
constructor(public compType: any) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is currently not read, but will probably be used in the future.
|
||||
* We keep it as we already pass it through all the right places...
|
||||
*/
|
||||
export class DirectiveWrapperDependency {
|
||||
constructor(
|
||||
public dir: CompileIdentifierMetadata, public name: string,
|
||||
public placeholder: CompileIdentifierMetadata) {}
|
||||
constructor(public dirType: any) {}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata';
|
||||
import {isFirstViewCheck} from '../compiler_util/binding_util';
|
||||
import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
|
||||
import * as o from '../output/output_ast';
|
||||
import {LifecycleHooks} from '../private_import_core';
|
||||
@ -14,10 +15,6 @@ import {DirectiveAst, ProviderAst, ProviderAstType} from '../template_parser/tem
|
||||
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileView} from './compile_view';
|
||||
import {DetectChangesVars} from './constants';
|
||||
|
||||
const STATE_IS_NEVER_CHECKED = o.THIS_EXPR.prop('numberOfChecks').identical(new o.LiteralExpr(0));
|
||||
const NOT_THROW_ON_CHANGES = o.not(DetectChangesVars.throwOnChange);
|
||||
|
||||
export function bindDirectiveAfterContentLifecycleCallbacks(
|
||||
directiveMeta: CompileDirectiveSummary, directiveInstance: o.Expression,
|
||||
@ -29,7 +26,8 @@ export function bindDirectiveAfterContentLifecycleCallbacks(
|
||||
compileElement.nodeIndex, compileElement.sourceAst);
|
||||
if (lifecycleHooks.indexOf(LifecycleHooks.AfterContentInit) !== -1) {
|
||||
afterContentLifecycleCallbacksMethod.addStmt(new o.IfStmt(
|
||||
STATE_IS_NEVER_CHECKED, [directiveInstance.callMethod('ngAfterContentInit', []).toStmt()]));
|
||||
isFirstViewCheck(o.THIS_EXPR),
|
||||
[directiveInstance.callMethod('ngAfterContentInit', []).toStmt()]));
|
||||
}
|
||||
if (lifecycleHooks.indexOf(LifecycleHooks.AfterContentChecked) !== -1) {
|
||||
afterContentLifecycleCallbacksMethod.addStmt(
|
||||
@ -47,7 +45,8 @@ export function bindDirectiveAfterViewLifecycleCallbacks(
|
||||
compileElement.nodeIndex, compileElement.sourceAst);
|
||||
if (lifecycleHooks.indexOf(LifecycleHooks.AfterViewInit) !== -1) {
|
||||
afterViewLifecycleCallbacksMethod.addStmt(new o.IfStmt(
|
||||
STATE_IS_NEVER_CHECKED, [directiveInstance.callMethod('ngAfterViewInit', []).toStmt()]));
|
||||
isFirstViewCheck(o.THIS_EXPR),
|
||||
[directiveInstance.callMethod('ngAfterViewInit', []).toStmt()]));
|
||||
}
|
||||
if (lifecycleHooks.indexOf(LifecycleHooks.AfterViewChecked) !== -1) {
|
||||
afterViewLifecycleCallbacksMethod.addStmt(
|
||||
|
@ -8,19 +8,19 @@
|
||||
|
||||
import {SecurityContext} from '@angular/core';
|
||||
|
||||
import {createCheckBindingField, createCheckBindingStmt} from '../compiler_util/binding_util';
|
||||
import {ConvertPropertyBindingResult, convertPropertyBinding} from '../compiler_util/expression_converter';
|
||||
import {createCheckBindingField} from '../compiler_util/binding_util';
|
||||
import {convertPropertyBinding} from '../compiler_util/expression_converter';
|
||||
import {createEnumExpression} from '../compiler_util/identifier_util';
|
||||
import {triggerAnimation, writeToRenderer} from '../compiler_util/render_util';
|
||||
import {createCheckAnimationBindingStmts, createCheckRenderBindingStmt} from '../compiler_util/render_util';
|
||||
import {DirectiveWrapperExpressions} from '../directive_wrapper_compiler';
|
||||
import {Identifiers, createIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {isDefaultChangeDetectionStrategy} from '../private_import_core';
|
||||
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
||||
import {BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, PropertyBindingType} from '../template_parser/template_ast';
|
||||
|
||||
import {CompileElement, CompileNode} from './compile_element';
|
||||
import {CompileView} from './compile_view';
|
||||
import {DetectChangesVars} from './constants';
|
||||
import {getHandleEventMethodName} from './util';
|
||||
|
||||
export function bindRenderText(
|
||||
@ -33,11 +33,15 @@ export function bindRenderText(
|
||||
}
|
||||
|
||||
view.detectChangesRenderPropertiesMethod.resetDebugInfo(compileNode.nodeIndex, boundText);
|
||||
view.detectChangesRenderPropertiesMethod.addStmts(createCheckBindingStmt(
|
||||
evalResult, valueField.expression, DetectChangesVars.throwOnChange,
|
||||
[o.THIS_EXPR.prop('renderer')
|
||||
.callMethod('setText', [compileNode.renderNode, evalResult.currValExpr])
|
||||
.toStmt()]));
|
||||
view.detectChangesRenderPropertiesMethod.addStmts(evalResult.stmts);
|
||||
view.detectChangesRenderPropertiesMethod.addStmt(
|
||||
o.importExpr(createIdentifier(Identifiers.checkRenderText))
|
||||
.callFn([
|
||||
o.THIS_EXPR, compileNode.renderNode, valueField.expression,
|
||||
valueField.expression.set(evalResult.currValExpr),
|
||||
evalResult.forceUpdate || o.literal(false)
|
||||
])
|
||||
.toStmt());
|
||||
}
|
||||
|
||||
export function bindRenderInputs(
|
||||
@ -54,31 +58,27 @@ export function bindRenderInputs(
|
||||
if (!evalResult) {
|
||||
return;
|
||||
}
|
||||
const checkBindingStmts: o.Statement[] = [];
|
||||
let compileMethod = view.detectChangesRenderPropertiesMethod;
|
||||
switch (boundProp.type) {
|
||||
case PropertyBindingType.Property:
|
||||
case PropertyBindingType.Attribute:
|
||||
case PropertyBindingType.Class:
|
||||
case PropertyBindingType.Style:
|
||||
checkBindingStmts.push(...writeToRenderer(
|
||||
o.THIS_EXPR, boundProp, renderNode, evalResult.currValExpr,
|
||||
view.genConfig.logBindingUpdate));
|
||||
compileMethod.addStmts(createCheckRenderBindingStmt(
|
||||
o.THIS_EXPR, renderNode, boundProp, bindingField.expression, evalResult));
|
||||
break;
|
||||
case PropertyBindingType.Animation:
|
||||
compileMethod = view.animationBindingsMethod;
|
||||
const {updateStmts, detachStmts} = triggerAnimation(
|
||||
const {checkUpdateStmts, checkDetachStmts} = createCheckAnimationBindingStmts(
|
||||
o.THIS_EXPR, o.THIS_EXPR, boundProp, boundOutputs,
|
||||
(hasEvents ? o.THIS_EXPR.prop(getHandleEventMethodName(compileElement.nodeIndex)) :
|
||||
o.importExpr(createIdentifier(Identifiers.noop)))
|
||||
.callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR]),
|
||||
compileElement.renderNode, evalResult.currValExpr, bindingField.expression);
|
||||
checkBindingStmts.push(...updateStmts);
|
||||
view.detachMethod.addStmts(detachStmts);
|
||||
compileElement.renderNode, bindingField.expression, evalResult);
|
||||
view.detachMethod.addStmts(checkDetachStmts);
|
||||
compileMethod.addStmts(checkUpdateStmts);
|
||||
break;
|
||||
}
|
||||
compileMethod.addStmts(createCheckBindingStmt(
|
||||
evalResult, bindingField.expression, DetectChangesVars.throwOnChange, checkBindingStmts));
|
||||
});
|
||||
}
|
||||
|
||||
@ -108,7 +108,7 @@ export function bindDirectiveHostProps(
|
||||
DirectiveWrapperExpressions.checkHost(
|
||||
directiveAst.hostProperties, directiveWrapperInstance, o.THIS_EXPR,
|
||||
compileElement.compViewExpr || o.THIS_EXPR, compileElement.renderNode,
|
||||
DetectChangesVars.throwOnChange, runtimeSecurityCtxExprs));
|
||||
runtimeSecurityCtxExprs));
|
||||
}
|
||||
|
||||
export function bindDirectiveInputs(
|
||||
@ -132,17 +132,13 @@ export function bindDirectiveInputs(
|
||||
directiveWrapperInstance
|
||||
.callMethod(
|
||||
`check_${input.directiveName}`,
|
||||
[
|
||||
evalResult.currValExpr, DetectChangesVars.throwOnChange,
|
||||
evalResult.forceUpdate || o.literal(false)
|
||||
])
|
||||
[o.THIS_EXPR, evalResult.currValExpr, evalResult.forceUpdate || o.literal(false)])
|
||||
.toStmt());
|
||||
});
|
||||
const isOnPushComp = directiveAst.directive.isComponent &&
|
||||
!isDefaultChangeDetectionStrategy(directiveAst.directive.changeDetection);
|
||||
const directiveDetectChangesExpr = DirectiveWrapperExpressions.ngDoCheck(
|
||||
directiveWrapperInstance, o.THIS_EXPR, compileElement.renderNode,
|
||||
DetectChangesVars.throwOnChange);
|
||||
directiveWrapperInstance, o.THIS_EXPR, compileElement.renderNode);
|
||||
const directiveDetectChangesStmt = isOnPushComp ?
|
||||
new o.IfStmt(
|
||||
directiveDetectChangesExpr,
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
|
||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileTokenMetadata, identifierName} from '../compile_metadata';
|
||||
import {CompileTokenMetadata} from '../compile_metadata';
|
||||
import {createDiTokenExpression} from '../compiler_util/identifier_util';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ViewType} from '../private_import_core';
|
||||
@ -69,12 +69,6 @@ export function injectFromViewParentInjector(
|
||||
return viewExpr.callMethod('injectorGet', args);
|
||||
}
|
||||
|
||||
export function getViewClassName(
|
||||
component: CompileDirectiveSummary | CompileDirectiveMetadata,
|
||||
embeddedTemplateIndex: number): string {
|
||||
return `View_${identifierName(component.type)}${embeddedTemplateIndex}`;
|
||||
}
|
||||
|
||||
export function getHandleEventMethodName(elementIndex: number): string {
|
||||
return `handleEvent_${elementIndex}`;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName} from '../compile_metadata';
|
||||
import {CompileDirectiveSummary, identifierModuleUrl, identifierName} from '../compile_metadata';
|
||||
import {createSharedBindingVariablesIfNeeded} from '../compiler_util/expression_converter';
|
||||
import {createDiTokenExpression, createInlineArray} from '../compiler_util/identifier_util';
|
||||
import {isPresent} from '../facade/lang';
|
||||
@ -20,9 +20,8 @@ import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventA
|
||||
|
||||
import {CompileElement, CompileNode} from './compile_element';
|
||||
import {CompileView, CompileViewRootNode, CompileViewRootNodeType} from './compile_view';
|
||||
import {ChangeDetectorStatusEnum, DetectChangesVars, InjectMethodVars, ViewConstructorVars, ViewEncapsulationEnum, ViewProperties, ViewTypeEnum} from './constants';
|
||||
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency} from './deps';
|
||||
import {getViewClassName} from './util';
|
||||
import {ChangeDetectorStatusEnum, InjectMethodVars, ViewConstructorVars, ViewEncapsulationEnum, ViewProperties, ViewTypeEnum} from './constants';
|
||||
import {ComponentFactoryDependency, ComponentViewDependency, DirectiveWrapperDependency} from './deps';
|
||||
|
||||
const IMPLICIT_TEMPLATE_VAR = '\$implicit';
|
||||
const CLASS_ATTR = 'class';
|
||||
@ -35,7 +34,8 @@ const rootSelectorVar = o.variable('rootSelector');
|
||||
export function buildView(
|
||||
view: CompileView, template: TemplateAst[],
|
||||
targetDependencies:
|
||||
Array<ViewClassDependency|ComponentFactoryDependency|DirectiveWrapperDependency>): number {
|
||||
Array<ComponentViewDependency|ComponentFactoryDependency|DirectiveWrapperDependency>):
|
||||
number {
|
||||
const builderVisitor = new ViewBuilderVisitor(view, targetDependencies);
|
||||
const parentEl =
|
||||
view.declarationElement.isNull() ? view.declarationElement : view.declarationElement.parent;
|
||||
@ -65,7 +65,7 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
|
||||
constructor(
|
||||
public view: CompileView,
|
||||
public targetDependencies:
|
||||
Array<ViewClassDependency|ComponentFactoryDependency|DirectiveWrapperDependency>) {}
|
||||
Array<ComponentViewDependency|ComponentFactoryDependency|DirectiveWrapperDependency>) {}
|
||||
|
||||
private _isRootNode(parent: CompileElement): boolean { return parent.view !== this.view; }
|
||||
|
||||
@ -216,9 +216,7 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
|
||||
this.view.nodes.push(compileElement);
|
||||
let compViewExpr: o.ReadPropExpr = null;
|
||||
if (isPresent(component)) {
|
||||
const nestedComponentIdentifier: CompileIdentifierMetadata = {reference: null};
|
||||
this.targetDependencies.push(new ViewClassDependency(
|
||||
component.type, getViewClassName(component, 0), nestedComponentIdentifier));
|
||||
this.targetDependencies.push(new ComponentViewDependency(component.type.reference));
|
||||
|
||||
compViewExpr = o.THIS_EXPR.prop(`compView_${nodeIndex}`); // fix highlighting: `
|
||||
this.view.fields.push(new o.ClassField(
|
||||
@ -228,7 +226,7 @@ class ViewBuilderVisitor implements TemplateAstVisitor {
|
||||
compileElement.setComponentView(compViewExpr);
|
||||
this.view.createMethod.addStmt(
|
||||
compViewExpr
|
||||
.set(o.importExpr(nestedComponentIdentifier).instantiate([
|
||||
.set(o.importExpr({reference: component.componentViewType}).instantiate([
|
||||
ViewProperties.viewUtils, o.THIS_EXPR, o.literal(nodeIndex), renderNode
|
||||
]))
|
||||
.toStmt());
|
||||
@ -404,9 +402,8 @@ function createViewTopLevelStmts(view: CompileView, targetStatements: o.Statemen
|
||||
o.literal(view.component.template.ngContentSelectors.length),
|
||||
ViewEncapsulationEnum.fromValue(view.component.template.encapsulation),
|
||||
view.styles,
|
||||
o.literalMap(
|
||||
view.animations.map((entry): [string, o.Expression] => [entry.name, entry.fnExp]),
|
||||
null, true),
|
||||
o.literalMap(view.animations.map(
|
||||
(entry): [string, o.Expression] => [entry.name, entry.fnExp])),
|
||||
]))
|
||||
.toDeclStmt(o.importType(createIdentifier(Identifiers.RenderComponentType))));
|
||||
}
|
||||
@ -485,9 +482,7 @@ function createViewClass(
|
||||
],
|
||||
addReturnValuefNotEmpty(view.injectorGetMethod.finish(), InjectMethodVars.notFoundResult),
|
||||
o.DYNAMIC_TYPE),
|
||||
new o.ClassMethod(
|
||||
'detectChangesInternal', [new o.FnParam(DetectChangesVars.throwOnChange.name, o.BOOL_TYPE)],
|
||||
generateDetectChangesMethod(view)),
|
||||
new o.ClassMethod('detectChangesInternal', [], generateDetectChangesMethod(view)),
|
||||
new o.ClassMethod('dirtyParentQueriesInternal', [], view.dirtyParentQueriesMethod.finish()),
|
||||
new o.ClassMethod('destroyInternal', [], generateDestroyMethod(view)),
|
||||
new o.ClassMethod('detachInternal', [], view.detachMethod.finish()),
|
||||
@ -571,36 +566,26 @@ function generateDetectChangesMethod(view: CompileView): o.Statement[] {
|
||||
stmts.push(...view.detectChangesInInputsMethod.finish());
|
||||
view.viewContainers.forEach((viewContainer) => {
|
||||
stmts.push(
|
||||
viewContainer.callMethod('detectChangesInNestedViews', [DetectChangesVars.throwOnChange])
|
||||
viewContainer.callMethod('detectChangesInNestedViews', [ViewProperties.throwOnChange])
|
||||
.toStmt());
|
||||
});
|
||||
const afterContentStmts = view.updateContentQueriesMethod.finish().concat(
|
||||
view.afterContentLifecycleCallbacksMethod.finish());
|
||||
if (afterContentStmts.length > 0) {
|
||||
stmts.push(new o.IfStmt(o.not(DetectChangesVars.throwOnChange), afterContentStmts));
|
||||
stmts.push(new o.IfStmt(o.not(ViewProperties.throwOnChange), afterContentStmts));
|
||||
}
|
||||
stmts.push(...view.detectChangesRenderPropertiesMethod.finish());
|
||||
view.viewChildren.forEach((viewChild) => {
|
||||
stmts.push(
|
||||
viewChild.callMethod('internalDetectChanges', [DetectChangesVars.throwOnChange]).toStmt());
|
||||
viewChild.callMethod('internalDetectChanges', [ViewProperties.throwOnChange]).toStmt());
|
||||
});
|
||||
const afterViewStmts =
|
||||
view.updateViewQueriesMethod.finish().concat(view.afterViewLifecycleCallbacksMethod.finish());
|
||||
if (afterViewStmts.length > 0) {
|
||||
stmts.push(new o.IfStmt(o.not(DetectChangesVars.throwOnChange), afterViewStmts));
|
||||
stmts.push(new o.IfStmt(o.not(ViewProperties.throwOnChange), afterViewStmts));
|
||||
}
|
||||
|
||||
const varStmts: any[] = [];
|
||||
const readVars = o.findReadVarNames(stmts);
|
||||
if (readVars.has(DetectChangesVars.changed.name)) {
|
||||
varStmts.push(DetectChangesVars.changed.set(o.literal(true)).toDeclStmt(o.BOOL_TYPE));
|
||||
}
|
||||
if (readVars.has(DetectChangesVars.changes.name)) {
|
||||
varStmts.push(
|
||||
DetectChangesVars.changes.set(o.NULL_EXPR)
|
||||
.toDeclStmt(new o.MapType(o.importType(createIdentifier(Identifiers.SimpleChange)))));
|
||||
}
|
||||
varStmts.push(...createSharedBindingVariablesIfNeeded(stmts));
|
||||
const varStmts = createSharedBindingVariablesIfNeeded(stmts);
|
||||
return varStmts.concat(stmts);
|
||||
}
|
||||
|
||||
|
@ -16,17 +16,17 @@ import {TemplateAst} from '../template_parser/template_ast';
|
||||
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileView} from './compile_view';
|
||||
import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency} from './deps';
|
||||
import {ComponentFactoryDependency, ComponentViewDependency, DirectiveWrapperDependency} from './deps';
|
||||
import {bindView} from './view_binder';
|
||||
import {buildView, finishView} from './view_builder';
|
||||
|
||||
export {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency} from './deps';
|
||||
export {ComponentFactoryDependency, ComponentViewDependency, DirectiveWrapperDependency} from './deps';
|
||||
|
||||
export class ViewCompileResult {
|
||||
constructor(
|
||||
public statements: o.Statement[], public viewClassVar: string,
|
||||
public dependencies:
|
||||
Array<ViewClassDependency|ComponentFactoryDependency|DirectiveWrapperDependency>) {}
|
||||
Array<ComponentViewDependency|ComponentFactoryDependency|DirectiveWrapperDependency>) {}
|
||||
}
|
||||
|
||||
@CompilerInjectable()
|
||||
@ -38,7 +38,7 @@ export class ViewCompiler {
|
||||
pipes: CompilePipeSummary[],
|
||||
compiledAnimations: AnimationEntryCompileResult[]): ViewCompileResult {
|
||||
const dependencies:
|
||||
Array<ViewClassDependency|ComponentFactoryDependency|DirectiveWrapperDependency> = [];
|
||||
Array<ComponentViewDependency|ComponentFactoryDependency|DirectiveWrapperDependency> = [];
|
||||
const view = new CompileView(
|
||||
component, this._genConfig, pipes, styles, compiledAnimations, 0,
|
||||
CompileElement.createNull(), [], dependencies);
|
||||
|
@ -521,6 +521,8 @@ describe('StaticReflector', () => {
|
||||
export class Child extends Parent {}
|
||||
|
||||
export class ChildNoDecorators extends Parent {}
|
||||
|
||||
export class ChildInvalidParent extends a.InvalidParent {}
|
||||
`
|
||||
});
|
||||
|
||||
@ -534,6 +536,10 @@ describe('StaticReflector', () => {
|
||||
expect(
|
||||
reflector.annotations(reflector.getStaticSymbol('/tmp/src/main.ts', 'ChildNoDecorators')))
|
||||
.toEqual([new ClassDecorator('parent')]);
|
||||
|
||||
expect(reflector.annotations(
|
||||
reflector.getStaticSymbol('/tmp/src/main.ts', 'ChildInvalidParent')))
|
||||
.toEqual([]);
|
||||
});
|
||||
|
||||
it('should inherit parameters', () => {
|
||||
@ -554,6 +560,8 @@ describe('StaticReflector', () => {
|
||||
export class ChildWithCtor extends Parent {
|
||||
constructor(@ParamDecorator('c') c: C) {}
|
||||
}
|
||||
|
||||
export class ChildInvalidParent extends a.InvalidParent {}
|
||||
`
|
||||
});
|
||||
|
||||
@ -571,6 +579,10 @@ describe('StaticReflector', () => {
|
||||
|
||||
expect(reflector.parameters(reflector.getStaticSymbol('/tmp/src/main.ts', 'ChildWithCtor')))
|
||||
.toEqual([[reflector.getStaticSymbol('/tmp/src/main.ts', 'C'), new ParamDecorator('c')]]);
|
||||
|
||||
expect(
|
||||
reflector.parameters(reflector.getStaticSymbol('/tmp/src/main.ts', 'ChildInvalidParent')))
|
||||
.toEqual([]);
|
||||
});
|
||||
|
||||
it('should inherit property metadata', () => {
|
||||
@ -595,6 +607,8 @@ describe('StaticReflector', () => {
|
||||
@PropDecorator('c')
|
||||
c: C;
|
||||
}
|
||||
|
||||
export class ChildInvalidParent extends a.InvalidParent {}
|
||||
`
|
||||
});
|
||||
|
||||
@ -611,6 +625,10 @@ describe('StaticReflector', () => {
|
||||
'b': [new PropDecorator('b1'), new PropDecorator('b2')],
|
||||
'c': [new PropDecorator('c')]
|
||||
});
|
||||
|
||||
expect(reflector.propMetadata(
|
||||
reflector.getStaticSymbol('/tmp/src/main.ts', 'ChildInvalidParent')))
|
||||
.toEqual({});
|
||||
});
|
||||
|
||||
it('should inherit lifecycle hooks', () => {
|
||||
@ -625,6 +643,8 @@ describe('StaticReflector', () => {
|
||||
hook2() {}
|
||||
hook3() {}
|
||||
}
|
||||
|
||||
export class ChildInvalidParent extends a.InvalidParent {}
|
||||
`
|
||||
});
|
||||
|
||||
@ -640,6 +660,10 @@ describe('StaticReflector', () => {
|
||||
expect(hooks(reflector.getStaticSymbol('/tmp/src/main.ts', 'Child'), [
|
||||
'hook1', 'hook2', 'hook3'
|
||||
])).toEqual([true, true, true]);
|
||||
|
||||
expect(hooks(reflector.getStaticSymbol('/tmp/src/main.ts', 'ChildInvalidParent'), [
|
||||
'hook1', 'hook2', 'hook3'
|
||||
])).toEqual([false, false, false]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -16,7 +16,7 @@ import * as ts from 'typescript';
|
||||
const TS_EXT = /(^.|(?!\.d)..)\.ts$/;
|
||||
|
||||
describe('StaticSymbolResolver', () => {
|
||||
const noContext = new StaticSymbol('', '');
|
||||
const noContext = new StaticSymbol('', '', []);
|
||||
let host: StaticSymbolResolverHost;
|
||||
let symbolResolver: StaticSymbolResolver;
|
||||
let symbolCache: StaticSymbolCache;
|
||||
@ -24,10 +24,11 @@ describe('StaticSymbolResolver', () => {
|
||||
beforeEach(() => { symbolCache = new StaticSymbolCache(); });
|
||||
|
||||
function init(
|
||||
testData: {[key: string]: any} = DEFAULT_TEST_DATA, summaries: Summary<StaticSymbol>[] = []) {
|
||||
testData: {[key: string]: any} = DEFAULT_TEST_DATA, summaries: Summary<StaticSymbol>[] = [],
|
||||
summaryImportAs: {symbol: StaticSymbol, importAs: StaticSymbol}[] = []) {
|
||||
host = new MockStaticSymbolResolverHost(testData);
|
||||
symbolResolver =
|
||||
new StaticSymbolResolver(host, symbolCache, new MockSummaryResolver(summaries));
|
||||
symbolResolver = new StaticSymbolResolver(
|
||||
host, symbolCache, new MockSummaryResolver(summaries, summaryImportAs));
|
||||
}
|
||||
|
||||
beforeEach(() => init());
|
||||
@ -137,6 +138,73 @@ describe('StaticSymbolResolver', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
describe('importAs', () => {
|
||||
|
||||
it('should calculate importAs relationship for non source files without summaries', () => {
|
||||
init(
|
||||
{
|
||||
'/test.d.ts': [{
|
||||
'__symbolic': 'module',
|
||||
'version': 3,
|
||||
'metadata': {
|
||||
'a': {'__symbolic': 'reference', 'name': 'b', 'module': './test2'},
|
||||
}
|
||||
}],
|
||||
'/test2.d.ts': [{
|
||||
'__symbolic': 'module',
|
||||
'version': 3,
|
||||
'metadata': {
|
||||
'b': {'__symbolic': 'reference', 'name': 'c', 'module': './test3'},
|
||||
}
|
||||
}]
|
||||
},
|
||||
[]);
|
||||
symbolResolver.getSymbolsOf('/test.d.ts');
|
||||
symbolResolver.getSymbolsOf('/test2.d.ts');
|
||||
|
||||
expect(symbolResolver.getImportAs(symbolCache.get('/test2.d.ts', 'b')))
|
||||
.toBe(symbolCache.get('/test.d.ts', 'a'));
|
||||
expect(symbolResolver.getImportAs(symbolCache.get('/test3.d.ts', 'c')))
|
||||
.toBe(symbolCache.get('/test.d.ts', 'a'));
|
||||
});
|
||||
|
||||
it('should calculate importAs relationship for non source files with summaries', () => {
|
||||
init(
|
||||
{
|
||||
'/test.ts': `
|
||||
export {a} from './test2';
|
||||
`
|
||||
},
|
||||
[], [{
|
||||
symbol: symbolCache.get('/test2.d.ts', 'a'),
|
||||
importAs: symbolCache.get('/test3.d.ts', 'b')
|
||||
}]);
|
||||
symbolResolver.getSymbolsOf('/test.ts');
|
||||
|
||||
expect(symbolResolver.getImportAs(symbolCache.get('/test2.d.ts', 'a')))
|
||||
.toBe(symbolCache.get('/test3.d.ts', 'b'));
|
||||
});
|
||||
|
||||
it('should calculate importAs for symbols with members based on importAs for symbols without',
|
||||
() => {
|
||||
init(
|
||||
{
|
||||
'/test.ts': `
|
||||
export {a} from './test2';
|
||||
`
|
||||
},
|
||||
[], [{
|
||||
symbol: symbolCache.get('/test2.d.ts', 'a'),
|
||||
importAs: symbolCache.get('/test3.d.ts', 'b')
|
||||
}]);
|
||||
symbolResolver.getSymbolsOf('/test.ts');
|
||||
|
||||
expect(symbolResolver.getImportAs(symbolCache.get('/test2.d.ts', 'a', ['someMember'])))
|
||||
.toBe(symbolCache.get('/test3.d.ts', 'b', ['someMember']));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should replace references by StaticSymbols', () => {
|
||||
init({
|
||||
'/test.ts': `
|
||||
@ -180,6 +248,42 @@ describe('StaticSymbolResolver', () => {
|
||||
.toBeFalsy();
|
||||
});
|
||||
|
||||
it('should fill references to ambient symbols with undefined', () => {
|
||||
init({
|
||||
'/test.ts': `
|
||||
export var y = 1;
|
||||
export var z = [window, z];
|
||||
`
|
||||
});
|
||||
|
||||
expect(symbolResolver.resolveSymbol(symbolCache.get('/test.ts', 'z')).metadata).toEqual([
|
||||
undefined, symbolCache.get('/test.ts', 'z')
|
||||
]);
|
||||
});
|
||||
|
||||
it('should allow to use symbols with __', () => {
|
||||
init({
|
||||
'/test.ts': `
|
||||
export {__a__ as __b__} from './test2';
|
||||
import {__c__} from './test2';
|
||||
|
||||
export var __x__ = 1;
|
||||
export var __y__ = __c__;
|
||||
`
|
||||
});
|
||||
|
||||
expect(symbolResolver.resolveSymbol(symbolCache.get('/test.ts', '__x__')).metadata).toBe(1);
|
||||
expect(symbolResolver.resolveSymbol(symbolCache.get('/test.ts', '__y__')).metadata)
|
||||
.toBe(symbolCache.get('/test2.d.ts', '__c__'));
|
||||
expect(symbolResolver.resolveSymbol(symbolCache.get('/test.ts', '__b__')).metadata)
|
||||
.toBe(symbolCache.get('/test2.d.ts', '__a__'));
|
||||
|
||||
expect(symbolResolver.getSymbolsOf('/test.ts')).toEqual([
|
||||
symbolCache.get('/test.ts', '__x__'), symbolCache.get('/test.ts', '__y__'),
|
||||
symbolCache.get('/test.ts', '__b__')
|
||||
]);
|
||||
});
|
||||
|
||||
it('should be able to trace a named export', () => {
|
||||
const symbol = symbolResolver
|
||||
.resolveSymbol(symbolResolver.getSymbolByModule(
|
||||
@ -240,7 +344,10 @@ describe('StaticSymbolResolver', () => {
|
||||
});
|
||||
|
||||
export class MockSummaryResolver implements SummaryResolver<StaticSymbol> {
|
||||
constructor(private summaries: Summary<StaticSymbol>[] = []) {}
|
||||
constructor(private summaries: Summary<StaticSymbol>[] = [], private importAs: {
|
||||
symbol: StaticSymbol,
|
||||
importAs: StaticSymbol
|
||||
}[] = []) {}
|
||||
|
||||
resolveSummary(reference: StaticSymbol): Summary<StaticSymbol> {
|
||||
return this.summaries.find(summary => summary.symbol === reference);
|
||||
@ -249,6 +356,13 @@ export class MockSummaryResolver implements SummaryResolver<StaticSymbol> {
|
||||
return this.summaries.filter(summary => summary.symbol.filePath === filePath)
|
||||
.map(summary => summary.symbol);
|
||||
}
|
||||
getImportAs(symbol: StaticSymbol): StaticSymbol {
|
||||
const entry = this.importAs.find(entry => entry.symbol === symbol);
|
||||
return entry ? entry.importAs : undefined;
|
||||
}
|
||||
|
||||
isLibraryFile(filePath: string): boolean { return filePath.endsWith('.d.ts'); }
|
||||
getLibraryFileName(filePath: string): string { return filePath.replace(/(\.d)?\.ts$/, '.d.ts'); }
|
||||
}
|
||||
|
||||
export class MockStaticSymbolResolverHost implements StaticSymbolResolverHost {
|
||||
|
@ -7,12 +7,12 @@
|
||||
*/
|
||||
|
||||
import {AotSummaryResolver, AotSummaryResolverHost, CompileSummaryKind, CompileTypeSummary, ResolvedStaticSymbol, StaticSymbol, StaticSymbolCache, StaticSymbolResolver} from '@angular/compiler';
|
||||
import {AotSummarySerializerHost, deserializeSummaries, serializeSummaries} from '@angular/compiler/src/aot/summary_serializer';
|
||||
import {deserializeSummaries, serializeSummaries} from '@angular/compiler/src/aot/summary_serializer';
|
||||
import * as path from 'path';
|
||||
|
||||
import {MockStaticSymbolResolverHost, MockSummaryResolver} from './static_symbol_resolver_spec';
|
||||
|
||||
const EXT = /\.ts$|.d.ts$/;
|
||||
const EXT = /(\.d)?\.ts$/;
|
||||
|
||||
export function main() {
|
||||
describe('AotSummaryResolver', () => {
|
||||
@ -32,8 +32,7 @@ export function main() {
|
||||
const mockSummaryResolver = new MockSummaryResolver([]);
|
||||
const symbolResolver = new StaticSymbolResolver(
|
||||
new MockStaticSymbolResolverHost({}), symbolCache, mockSummaryResolver);
|
||||
return serializeSummaries(
|
||||
new MockAotSummarySerializerHost(), mockSummaryResolver, symbolResolver, symbols, types);
|
||||
return serializeSummaries(mockSummaryResolver, symbolResolver, symbols, types).json;
|
||||
}
|
||||
|
||||
it('should load serialized summary files', () => {
|
||||
@ -56,17 +55,48 @@ export function main() {
|
||||
expect(summaryResolver.resolveSummary(asymbol)).toBe(summaryResolver.resolveSummary(asymbol));
|
||||
});
|
||||
|
||||
it('should return all sumbols in a summary', () => {
|
||||
it('should return all symbols in a summary', () => {
|
||||
const asymbol = symbolCache.get('/a.d.ts', 'a');
|
||||
init({'/a.ngsummary.json': serialize([{symbol: asymbol, metadata: 1}], [])});
|
||||
expect(summaryResolver.getSymbolsOf('/a.d.ts')).toEqual([asymbol]);
|
||||
});
|
||||
|
||||
it('should fill importAs for deep symbols', () => {
|
||||
const libSymbol = symbolCache.get('/lib.d.ts', 'Lib');
|
||||
const srcSymbol = symbolCache.get('/src.ts', 'Src');
|
||||
init({
|
||||
'/src.ngsummary.json':
|
||||
serialize([{symbol: srcSymbol, metadata: 1}, {symbol: libSymbol, metadata: 2}], [])
|
||||
});
|
||||
summaryResolver.getSymbolsOf('/src.d.ts');
|
||||
|
||||
expect(summaryResolver.getImportAs(symbolCache.get('/src.d.ts', 'Src'))).toBeFalsy();
|
||||
expect(summaryResolver.getImportAs(libSymbol))
|
||||
.toBe(symbolCache.get('/src.ngfactory.ts', 'Lib_1'));
|
||||
});
|
||||
|
||||
describe('isLibraryFile', () => {
|
||||
it('should use host.isSourceFile to calculate the result', () => {
|
||||
init();
|
||||
expect(summaryResolver.isLibraryFile('someFile.ts')).toBe(false);
|
||||
expect(summaryResolver.isLibraryFile('someFile.d.ts')).toBe(true);
|
||||
});
|
||||
|
||||
it('should calculate the result for generated files based on the result for non generated files',
|
||||
() => {
|
||||
init();
|
||||
spyOn(host, 'isSourceFile').and.callThrough();
|
||||
expect(summaryResolver.isLibraryFile('someFile.ngfactory.ts')).toBe(false);
|
||||
expect(host.isSourceFile).toHaveBeenCalledWith('someFile.ts');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export class MockAotSummarySerializerHost implements AotSummarySerializerHost {
|
||||
export class MockAotSummaryResolverHost implements AotSummaryResolverHost {
|
||||
constructor(private summaries: {[fileName: string]: string}) {}
|
||||
|
||||
fileNameToModuleName(fileName: string): string {
|
||||
return './' + path.basename(fileName).replace(EXT, '');
|
||||
}
|
||||
@ -76,11 +106,6 @@ export class MockAotSummarySerializerHost implements AotSummarySerializerHost {
|
||||
}
|
||||
|
||||
isSourceFile(filePath: string) { return !filePath.endsWith('.d.ts'); }
|
||||
}
|
||||
|
||||
export class MockAotSummaryResolverHost extends MockAotSummarySerializerHost implements
|
||||
AotSummaryResolverHost {
|
||||
constructor(private summaries: {[fileName: string]: string}) { super(); }
|
||||
|
||||
loadSummary(filePath: string): string { return this.summaries[filePath]; }
|
||||
}
|
@ -7,7 +7,8 @@
|
||||
*/
|
||||
|
||||
import {AotSummaryResolver, AotSummaryResolverHost, CompileSummaryKind, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost} from '@angular/compiler';
|
||||
import {AotSummarySerializerHost, deserializeSummaries, serializeSummaries, summaryFileName} from '@angular/compiler/src/aot/summary_serializer';
|
||||
import {deserializeSummaries, serializeSummaries} from '@angular/compiler/src/aot/summary_serializer';
|
||||
import {summaryFileName} from '@angular/compiler/src/aot/util';
|
||||
|
||||
import {MockStaticSymbolResolverHost} from './static_symbol_resolver_spec';
|
||||
import {MockAotSummaryResolverHost} from './summary_resolver_spec';
|
||||
@ -42,7 +43,7 @@ export function main() {
|
||||
it('should serialize various data correctly', () => {
|
||||
init();
|
||||
const serializedData = serializeSummaries(
|
||||
host, summaryResolver, symbolResolver,
|
||||
summaryResolver, symbolResolver,
|
||||
[
|
||||
{
|
||||
symbol: symbolCache.get('/tmp/some_values.ts', 'Values'),
|
||||
@ -50,7 +51,9 @@ export function main() {
|
||||
aNumber: 1,
|
||||
aString: 'hello',
|
||||
anArray: [1, 2],
|
||||
aStaticSymbol: symbolCache.get('/tmp/some_symbol.ts', 'someName')
|
||||
aStaticSymbol: symbolCache.get('/tmp/some_symbol.ts', 'someName'),
|
||||
aStaticSymbolWithMembers:
|
||||
symbolCache.get('/tmp/some_symbol.ts', 'someName', ['someMember']),
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -66,11 +69,11 @@ export function main() {
|
||||
summaryKind: CompileSummaryKind.Injectable,
|
||||
type: {
|
||||
reference: symbolCache.get('/tmp/some_service.ts', 'SomeService'),
|
||||
},
|
||||
}
|
||||
}]);
|
||||
|
||||
|
||||
const summaries = deserializeSummaries(symbolCache, serializedData);
|
||||
const summaries = deserializeSummaries(symbolCache, serializedData.json).summaries;
|
||||
expect(summaries.length).toBe(2);
|
||||
|
||||
// Note: change from .ts to .d.ts is expected
|
||||
@ -79,7 +82,9 @@ export function main() {
|
||||
aNumber: 1,
|
||||
aString: 'hello',
|
||||
anArray: [1, 2],
|
||||
aStaticSymbol: symbolCache.get('/tmp/some_symbol.d.ts', 'someName')
|
||||
aStaticSymbol: symbolCache.get('/tmp/some_symbol.d.ts', 'someName'),
|
||||
aStaticSymbolWithMembers:
|
||||
symbolCache.get('/tmp/some_symbol.d.ts', 'someName', ['someMember'])
|
||||
});
|
||||
|
||||
expect(summaries[1].symbol).toBe(symbolCache.get('/tmp/some_service.d.ts', 'SomeService'));
|
||||
@ -91,8 +96,8 @@ export function main() {
|
||||
|
||||
it('should automatically add exported directives / pipes of NgModules that are not source files',
|
||||
() => {
|
||||
init({});
|
||||
const externalSerialized = serializeSummaries(host, summaryResolver, symbolResolver, [], [
|
||||
init();
|
||||
const externalSerialized = serializeSummaries(summaryResolver, symbolResolver, [], [
|
||||
<any>{
|
||||
summaryKind: CompileSummaryKind.Pipe,
|
||||
type: {
|
||||
@ -107,11 +112,11 @@ export function main() {
|
||||
}
|
||||
]);
|
||||
init({
|
||||
'/tmp/external.ngsummary.json': externalSerialized,
|
||||
'/tmp/external.ngsummary.json': externalSerialized.json,
|
||||
});
|
||||
|
||||
const serialized = serializeSummaries(
|
||||
host, summaryResolver, symbolResolver, [], [<any>{
|
||||
summaryResolver, symbolResolver, [], [<any>{
|
||||
summaryKind: CompileSummaryKind.NgModule,
|
||||
type: {reference: symbolCache.get('/tmp/some_module.ts', 'SomeModule')},
|
||||
exportedPipes: [
|
||||
@ -124,7 +129,7 @@ export function main() {
|
||||
]
|
||||
}]);
|
||||
|
||||
const summaries = deserializeSummaries(symbolCache, serialized);
|
||||
const summaries = deserializeSummaries(symbolCache, serialized.json).summaries;
|
||||
expect(summaries.length).toBe(3);
|
||||
expect(summaries[0].symbol).toBe(symbolCache.get('/tmp/some_module.d.ts', 'SomeModule'));
|
||||
expect(summaries[1].symbol).toBe(symbolCache.get('/tmp/external.d.ts', 'SomeExternalDir'));
|
||||
@ -134,8 +139,9 @@ export function main() {
|
||||
|
||||
it('should automatically add the metadata of referenced symbols that are not in the soure files',
|
||||
() => {
|
||||
init();
|
||||
const externalSerialized = serializeSummaries(
|
||||
host, summaryResolver, symbolResolver,
|
||||
summaryResolver, symbolResolver,
|
||||
[
|
||||
{
|
||||
symbol: symbolCache.get('/tmp/external.ts', 'PROVIDERS'),
|
||||
@ -154,7 +160,7 @@ export function main() {
|
||||
}]);
|
||||
init(
|
||||
{
|
||||
'/tmp/external.ngsummary.json': externalSerialized,
|
||||
'/tmp/external.ngsummary.json': externalSerialized.json,
|
||||
},
|
||||
{
|
||||
'/tmp/local.ts': `
|
||||
@ -164,7 +170,7 @@ export function main() {
|
||||
{__symbolic: 'module', version: 3, metadata: {'external': 'b'}}
|
||||
});
|
||||
const serialized = serializeSummaries(
|
||||
host, summaryResolver, symbolResolver, [{
|
||||
summaryResolver, symbolResolver, [{
|
||||
symbol: symbolCache.get('/tmp/test.ts', 'main'),
|
||||
metadata: {
|
||||
local: symbolCache.get('/tmp/local.ts', 'local'),
|
||||
@ -174,7 +180,7 @@ export function main() {
|
||||
}],
|
||||
[]);
|
||||
|
||||
const summaries = deserializeSummaries(symbolCache, serialized);
|
||||
const summaries = deserializeSummaries(symbolCache, serialized.json).summaries;
|
||||
// Note: local should not show up!
|
||||
expect(summaries.length).toBe(4);
|
||||
expect(summaries[0].symbol).toBe(symbolCache.get('/tmp/test.d.ts', 'main'));
|
||||
@ -195,5 +201,28 @@ export function main() {
|
||||
expect(summaries[3].type.type.reference)
|
||||
.toBe(symbolCache.get('/tmp/external_svc.d.ts', 'SomeService'));
|
||||
});
|
||||
|
||||
it('should create "importAs" names for non source symbols', () => {
|
||||
init();
|
||||
const serialized = serializeSummaries(
|
||||
summaryResolver, symbolResolver, [{
|
||||
symbol: symbolCache.get('/tmp/test.ts', 'main'),
|
||||
metadata: [
|
||||
symbolCache.get('/tmp/external.d.ts', 'lib'),
|
||||
symbolCache.get('/tmp/external.d.ts', 'lib', ['someMember']),
|
||||
]
|
||||
}],
|
||||
[]);
|
||||
// Note: no entry for the symbol with members!
|
||||
expect(serialized.exportAs).toEqual([
|
||||
{symbol: symbolCache.get('/tmp/external.d.ts', 'lib'), exportAs: 'lib_1'}
|
||||
]);
|
||||
|
||||
const deserialized = deserializeSummaries(symbolCache, serialized.json);
|
||||
// Note: no entry for the symbol with members!
|
||||
expect(deserialized.importAs).toEqual([
|
||||
{symbol: symbolCache.get('/tmp/external.d.ts', 'lib'), importAs: 'lib_1'}
|
||||
]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -33,6 +33,22 @@ export function main() {
|
||||
moduleUrl: SOME_MODULE_URL,
|
||||
})).toThrowError(SyntaxError, 'No template specified for component SomeComp');
|
||||
}));
|
||||
it('should throw if template is not a string',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
expect(
|
||||
() => normalizer.normalizeTemplate(
|
||||
{componentType: SomeComp, moduleUrl: SOME_MODULE_URL, template: <any>{}}))
|
||||
.toThrowError(
|
||||
SyntaxError, 'The template specified for component SomeComp is not a string');
|
||||
}));
|
||||
it('should throw if templateUrl is not a string',
|
||||
inject([DirectiveNormalizer], (normalizer: DirectiveNormalizer) => {
|
||||
expect(
|
||||
() => normalizer.normalizeTemplate(
|
||||
{componentType: SomeComp, moduleUrl: SOME_MODULE_URL, templateUrl: <any>{}}))
|
||||
.toThrowError(
|
||||
SyntaxError, 'The templateUrl specified for component SomeComp is not a string');
|
||||
}));
|
||||
});
|
||||
|
||||
describe('normalizeTemplateSync', () => {
|
||||
@ -535,4 +551,4 @@ function programResourceLoaderSpy(spy: SpyResourceLoader, results: {[key: string
|
||||
});
|
||||
}
|
||||
|
||||
class SomeComp {}
|
||||
class SomeComp {}
|
||||
|
@ -6,10 +6,23 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {computeMsgId, sha1} from '../../src/i18n/digest';
|
||||
import {computeMsgId, digest, sha1} from '../../src/i18n/digest';
|
||||
|
||||
export function main(): void {
|
||||
describe('digest', () => {
|
||||
describe('digest', () => {
|
||||
it('must return the ID if it\'s explicit', () => {
|
||||
expect(digest({
|
||||
id: 'i',
|
||||
nodes: [],
|
||||
placeholders: {},
|
||||
placeholderToMessage: {},
|
||||
meaning: '',
|
||||
description: '',
|
||||
})).toEqual('i');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sha1', () => {
|
||||
it('should work on empty strings',
|
||||
() => { expect(sha1('')).toEqual('da39a3ee5e6b4b0d3255bfef95601890afd80709'); });
|
||||
|
@ -20,7 +20,10 @@ export function main() {
|
||||
describe('elements', () => {
|
||||
it('should extract from elements', () => {
|
||||
expect(extract('<div i18n="m|d|e">text<span>nested</span></div>')).toEqual([
|
||||
[['text', '<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], 'm', 'd|e'],
|
||||
[
|
||||
['text', '<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], 'm', 'd|e',
|
||||
''
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -29,11 +32,45 @@ export function main() {
|
||||
extract(
|
||||
'<div i18n="m1|d1"><span i18n-title="m2|d2" title="single child">nested</span></div>'))
|
||||
.toEqual([
|
||||
[['<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], 'm1', 'd1'],
|
||||
[['single child'], 'm2', 'd2'],
|
||||
[['<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], 'm1', 'd1', ''],
|
||||
[['single child'], 'm2', 'd2', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract from attributes with id', () => {
|
||||
expect(
|
||||
extract(
|
||||
'<div i18n="m1|d1@@i1"><span i18n-title="m2|d2@@i2" title="single child">nested</span></div>'))
|
||||
.toEqual([
|
||||
[
|
||||
['<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], 'm1', 'd1',
|
||||
'i1'
|
||||
],
|
||||
[['single child'], 'm2', 'd2', 'i2'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract from attributes without meaning and with id', () => {
|
||||
expect(
|
||||
extract(
|
||||
'<div i18n="d1@@i1"><span i18n-title="d2@@i2" title="single child">nested</span></div>'))
|
||||
.toEqual([
|
||||
[['<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], '', 'd1', 'i1'],
|
||||
[['single child'], '', 'd2', 'i2'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract from attributes with id only', () => {
|
||||
expect(
|
||||
extract(
|
||||
'<div i18n="@@i1"><span i18n-title="@@i2" title="single child">nested</span></div>'))
|
||||
.toEqual([
|
||||
[['<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], '', '', 'i1'],
|
||||
[['single child'], '', '', 'i2'],
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
it('should extract from ICU messages', () => {
|
||||
expect(
|
||||
extract(
|
||||
@ -43,10 +80,10 @@ export function main() {
|
||||
[
|
||||
'{count, plural, =0 {[<ph tag name="START_PARAGRAPH"></ph name="CLOSE_PARAGRAPH">]}}'
|
||||
],
|
||||
'm', 'd'
|
||||
'm', 'd', ''
|
||||
],
|
||||
[['title'], '', ''],
|
||||
[['desc'], '', ''],
|
||||
[['title'], '', '', ''],
|
||||
[['desc'], '', '', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -55,7 +92,7 @@ export function main() {
|
||||
|
||||
it('should ignore implicit elements in translatable elements', () => {
|
||||
expect(extract('<div i18n="m|d"><p></p></div>', ['p'])).toEqual([
|
||||
[['<ph tag name="START_PARAGRAPH"></ph name="CLOSE_PARAGRAPH">'], 'm', 'd']
|
||||
[['<ph tag name="START_PARAGRAPH"></ph name="CLOSE_PARAGRAPH">'], 'm', 'd', '']
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -64,17 +101,19 @@ export function main() {
|
||||
it('should extract from blocks', () => {
|
||||
expect(extract(`<!-- i18n: meaning1|desc1 -->message1<!-- /i18n -->
|
||||
<!-- i18n: desc2 -->message2<!-- /i18n -->
|
||||
<!-- i18n -->message3<!-- /i18n -->`))
|
||||
<!-- i18n -->message3<!-- /i18n -->
|
||||
<!-- i18n: meaning4|desc4@@id4 -->message4<!-- /i18n -->
|
||||
<!-- i18n: @@id5 -->message5<!-- /i18n -->`))
|
||||
.toEqual([
|
||||
[['message1'], 'meaning1', 'desc1'],
|
||||
[['message2'], '', 'desc2'],
|
||||
[['message3'], '', ''],
|
||||
[['message1'], 'meaning1', 'desc1', ''], [['message2'], '', 'desc2', ''],
|
||||
[['message3'], '', '', ''], [['message4'], 'meaning4', 'desc4', 'id4'],
|
||||
[['message5'], '', '', 'id5']
|
||||
]);
|
||||
});
|
||||
|
||||
it('should ignore implicit elements in blocks', () => {
|
||||
expect(extract('<!-- i18n:m|d --><p></p><!-- /i18n -->', ['p'])).toEqual([
|
||||
[['<ph tag name="START_PARAGRAPH"></ph name="CLOSE_PARAGRAPH">'], 'm', 'd']
|
||||
[['<ph tag name="START_PARAGRAPH"></ph name="CLOSE_PARAGRAPH">'], 'm', 'd', '']
|
||||
]);
|
||||
});
|
||||
|
||||
@ -88,7 +127,7 @@ export function main() {
|
||||
[
|
||||
'{count, plural, =0 {[<ph tag name="START_TAG_SPAN">html</ph name="CLOSE_TAG_SPAN">]}}'
|
||||
],
|
||||
'', ''
|
||||
'', '', ''
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -98,15 +137,15 @@ export function main() {
|
||||
' name="START_TAG_SPAN">html</ph name="CLOSE_TAG_SPAN">]}}</ph>',
|
||||
'[<ph name="INTERPOLATION">interp</ph>]'
|
||||
],
|
||||
'', ''
|
||||
'', '', ''
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should ignore other comments', () => {
|
||||
expect(extract(`<!-- i18n: meaning1|desc1 --><!-- other -->message1<!-- /i18n -->`))
|
||||
expect(extract(`<!-- i18n: meaning1|desc1@@id1 --><!-- other -->message1<!-- /i18n -->`))
|
||||
.toEqual([
|
||||
[['message1'], 'meaning1', 'desc1'],
|
||||
[['message1'], 'meaning1', 'desc1', 'id1'],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -118,34 +157,37 @@ export function main() {
|
||||
it('should extract ICU messages from translatable elements', () => {
|
||||
// single message when ICU is the only children
|
||||
expect(extract('<div i18n="m|d">{count, plural, =0 {text}}</div>')).toEqual([
|
||||
[['{count, plural, =0 {[text]}}'], 'm', 'd'],
|
||||
[['{count, plural, =0 {[text]}}'], 'm', 'd', ''],
|
||||
]);
|
||||
|
||||
// single message when ICU is the only (implicit) children
|
||||
expect(extract('<div>{count, plural, =0 {text}}</div>', ['div'])).toEqual([
|
||||
[['{count, plural, =0 {[text]}}'], '', ''],
|
||||
[['{count, plural, =0 {[text]}}'], '', '', ''],
|
||||
]);
|
||||
|
||||
// one message for the element content and one message for the ICU
|
||||
expect(extract('<div i18n="m|d">before{count, plural, =0 {text}}after</div>')).toEqual([
|
||||
[['before', '<ph icu name="ICU">{count, plural, =0 {[text]}}</ph>', 'after'], 'm', 'd'],
|
||||
[['{count, plural, =0 {[text]}}'], '', ''],
|
||||
expect(extract('<div i18n="m|d@@i">before{count, plural, =0 {text}}after</div>')).toEqual([
|
||||
[
|
||||
['before', '<ph icu name="ICU">{count, plural, =0 {[text]}}</ph>', 'after'], 'm', 'd',
|
||||
'i'
|
||||
],
|
||||
[['{count, plural, =0 {[text]}}'], '', '', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract ICU messages from translatable block', () => {
|
||||
// single message when ICU is the only children
|
||||
expect(extract('<!-- i18n:m|d -->{count, plural, =0 {text}}<!-- /i18n -->')).toEqual([
|
||||
[['{count, plural, =0 {[text]}}'], 'm', 'd'],
|
||||
[['{count, plural, =0 {[text]}}'], 'm', 'd', ''],
|
||||
]);
|
||||
|
||||
// one message for the block content and one message for the ICU
|
||||
expect(extract('<!-- i18n:m|d -->before{count, plural, =0 {text}}after<!-- /i18n -->'))
|
||||
.toEqual([
|
||||
[['{count, plural, =0 {[text]}}'], '', ''],
|
||||
[['{count, plural, =0 {[text]}}'], '', '', ''],
|
||||
[
|
||||
['before', '<ph icu name="ICU">{count, plural, =0 {[text]}}</ph>', 'after'], 'm',
|
||||
'd'
|
||||
'd', ''
|
||||
],
|
||||
]);
|
||||
});
|
||||
@ -156,20 +198,20 @@ export function main() {
|
||||
it('should ignore nested ICU messages', () => {
|
||||
expect(extract('<div i18n="m|d">{count, plural, =0 { {sex, select, male {m}} }}</div>'))
|
||||
.toEqual([
|
||||
[['{count, plural, =0 {[{sex, select, male {[m]}}, ]}}'], 'm', 'd'],
|
||||
[['{count, plural, =0 {[{sex, select, male {[m]}}, ]}}'], 'm', 'd', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should ignore implicit elements in non translatable ICU messages', () => {
|
||||
expect(
|
||||
extract(
|
||||
'<div i18n="m|d">{count, plural, =0 { {sex, select, male {<p>ignore</p>}} }}</div>',
|
||||
['p']))
|
||||
expect(extract(
|
||||
'<div i18n="m|d@@i">{count, plural, =0 { {sex, select, male {<p>ignore</p>}}' +
|
||||
' }}</div>',
|
||||
['p']))
|
||||
.toEqual([[
|
||||
[
|
||||
'{count, plural, =0 {[{sex, select, male {[<ph tag name="START_PARAGRAPH">ignore</ph name="CLOSE_PARAGRAPH">]}}, ]}}'
|
||||
],
|
||||
'm', 'd'
|
||||
'm', 'd', 'i'
|
||||
]]);
|
||||
});
|
||||
|
||||
@ -181,46 +223,45 @@ export function main() {
|
||||
|
||||
describe('attributes', () => {
|
||||
it('should extract from attributes outside of translatable sections', () => {
|
||||
expect(extract('<div i18n-title="m|d" title="msg"></div>')).toEqual([
|
||||
[['msg'], 'm', 'd'],
|
||||
expect(extract('<div i18n-title="m|d@@i" title="msg"></div>')).toEqual([
|
||||
[['msg'], 'm', 'd', 'i'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract from attributes in translatable elements', () => {
|
||||
expect(extract('<div i18n><p><b i18n-title="m|d" title="msg"></b></p></div>')).toEqual([
|
||||
expect(extract('<div i18n><p><b i18n-title="m|d@@i" title="msg"></b></p></div>')).toEqual([
|
||||
[
|
||||
['<ph tag name="START_PARAGRAPH"><ph tag name="START_BOLD_TEXT"></ph' +
|
||||
' name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">'],
|
||||
'', ''
|
||||
'', '', ''
|
||||
],
|
||||
[['msg'], 'm', 'd'],
|
||||
[['msg'], 'm', 'd', 'i'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract from attributes in translatable blocks', () => {
|
||||
expect(extract('<!-- i18n --><p><b i18n-title="m|d" title="msg"></b></p><!-- /i18n -->'))
|
||||
.toEqual([
|
||||
[['msg'], 'm', 'd'],
|
||||
[['msg'], 'm', 'd', ''],
|
||||
[
|
||||
['<ph tag name="START_PARAGRAPH"><ph tag name="START_BOLD_TEXT"></ph' +
|
||||
' name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">'],
|
||||
'', ''
|
||||
'', '', ''
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract from attributes in translatable ICUs', () => {
|
||||
expect(
|
||||
extract(
|
||||
'<!-- i18n -->{count, plural, =0 {<p><b i18n-title="m|d" title="msg"></b></p>}}<!-- /i18n -->'))
|
||||
expect(extract(`<!-- i18n -->{count, plural, =0 {<p><b i18n-title="m|d@@i"
|
||||
title="msg"></b></p>}}<!-- /i18n -->`))
|
||||
.toEqual([
|
||||
[['msg'], 'm', 'd'],
|
||||
[['msg'], 'm', 'd', 'i'],
|
||||
[
|
||||
[
|
||||
'{count, plural, =0 {[<ph tag name="START_PARAGRAPH"><ph tag' +
|
||||
' name="START_BOLD_TEXT"></ph name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">]}}'
|
||||
],
|
||||
'', ''
|
||||
'', '', ''
|
||||
],
|
||||
]);
|
||||
});
|
||||
@ -228,7 +269,7 @@ export function main() {
|
||||
it('should extract from attributes in non translatable ICUs', () => {
|
||||
expect(extract('{count, plural, =0 {<p><b i18n-title="m|d" title="msg"></b></p>}}'))
|
||||
.toEqual([
|
||||
[['msg'], 'm', 'd'],
|
||||
[['msg'], 'm', 'd', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -239,7 +280,7 @@ export function main() {
|
||||
describe('implicit elements', () => {
|
||||
it('should extract from implicit elements', () => {
|
||||
expect(extract('<b>bold</b><i>italic</i>', ['b'])).toEqual([
|
||||
[['bold'], '', ''],
|
||||
[['bold'], '', '', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -251,7 +292,7 @@ export function main() {
|
||||
}).not.toThrow();
|
||||
|
||||
expect(result).toEqual([
|
||||
[['outer', '<ph tag name="START_TAG_DIV">inner</ph name="CLOSE_TAG_DIV">'], '', ''],
|
||||
[['outer', '<ph tag name="START_TAG_DIV">inner</ph name="CLOSE_TAG_DIV">'], '', '', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -261,7 +302,7 @@ export function main() {
|
||||
it('should extract implicit attributes', () => {
|
||||
expect(extract('<b title="bb">bold</b><i title="ii">italic</i>', [], {'b': ['title']}))
|
||||
.toEqual([
|
||||
[['bb'], '', ''],
|
||||
[['bb'], '', '', ''],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -433,7 +474,7 @@ function extract(
|
||||
// clang-format off
|
||||
// https://github.com/angular/clang-format/issues/35
|
||||
return result.messages.map(
|
||||
message => [serializeI18nNodes(message.nodes), message.meaning, message.description, ]) as [string[], string, string][];
|
||||
message => [serializeI18nNodes(message.nodes), message.meaning, message.description, message.id]) as [string[], string, string][];
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,10 @@
|
||||
|
||||
import {NgLocalization} from '@angular/common';
|
||||
import {ResourceLoader} from '@angular/compiler';
|
||||
import {MessageBundle} from '@angular/compiler/src/i18n/message_bundle';
|
||||
import {Xmb} from '@angular/compiler/src/i18n/serializers/xmb';
|
||||
import {HtmlParser} from '@angular/compiler/src/ml_parser/html_parser';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG} from '@angular/compiler/src/ml_parser/interpolation_config';
|
||||
import {Component, DebugElement, TRANSLATIONS, TRANSLATIONS_FORMAT} from '@angular/core';
|
||||
import {TestBed, async} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
@ -32,9 +36,16 @@ export function main() {
|
||||
TestBed.configureTestingModule({declarations: [I18nComponent]});
|
||||
}));
|
||||
|
||||
it('should extract from templates', () => {
|
||||
const catalog = new MessageBundle(new HtmlParser, [], {});
|
||||
const serializer = new Xmb();
|
||||
catalog.updateFromTemplate(HTML, '', DEFAULT_INTERPOLATION_CONFIG);
|
||||
|
||||
it('translate templates', () => {
|
||||
const tb = TestBed.createComponent(I18nComponent);
|
||||
expect(catalog.write(serializer)).toContain(XMB);
|
||||
});
|
||||
|
||||
it('should translate templates', () => {
|
||||
const tb = TestBed.overrideTemplate(I18nComponent, HTML).createComponent(I18nComponent);
|
||||
const cmp = tb.componentInstance;
|
||||
const el = tb.debugElement;
|
||||
|
||||
@ -59,14 +70,17 @@ export function main() {
|
||||
tb.detectChanges();
|
||||
expect(el.query(By.css('#i18n-7')).nativeElement).toHaveText('un');
|
||||
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('un');
|
||||
expect(el.query(By.css('#i18n-17')).nativeElement).toHaveText('un');
|
||||
cmp.count = 2;
|
||||
tb.detectChanges();
|
||||
expect(el.query(By.css('#i18n-7')).nativeElement).toHaveText('deux');
|
||||
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('deux');
|
||||
expect(el.query(By.css('#i18n-17')).nativeElement).toHaveText('deux');
|
||||
cmp.count = 3;
|
||||
tb.detectChanges();
|
||||
expect(el.query(By.css('#i18n-7')).nativeElement).toHaveText('beaucoup');
|
||||
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('beaucoup');
|
||||
expect(el.query(By.css('#i18n-17')).nativeElement).toHaveText('beaucoup');
|
||||
|
||||
cmp.sex = 'm';
|
||||
cmp.sexB = 'f';
|
||||
@ -90,8 +104,10 @@ export function main() {
|
||||
.toEqual('<h1 id="i18n-12">Balises dans les commentaires html</h1>');
|
||||
expectHtml(el, '#i18n-13')
|
||||
.toBe('<div id="i18n-13" title="dans une section traductible"></div>');
|
||||
|
||||
expectHtml(el, '#i18n-15').toMatch(/ca <b>devrait<\/b> marcher/);
|
||||
expectHtml(el, '#i18n-16').toMatch(/avec un ID explicite/);
|
||||
expectHtml(el, '#i18n-18')
|
||||
.toEqual('<div id="i18n-18">FOO<a title="dans une section traductible">BAR</a></div>');
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -102,51 +118,13 @@ function expectHtml(el: DebugElement, cssSelector: string): any {
|
||||
|
||||
@Component({
|
||||
selector: 'i18n-cmp',
|
||||
template: `
|
||||
<div>
|
||||
<h1 i18n>i18n attribute on tags</h1>
|
||||
|
||||
<div id="i18n-1"><p i18n>nested</p></div>
|
||||
|
||||
<div id="i18n-2"><p i18n="different meaning|">nested</p></div>
|
||||
|
||||
<div id="i18n-3"><p i18n><i>with placeholders</i></p></div>
|
||||
<div id="i18n-3b"><p i18n><i class="preserved-on-placeholders">with placeholders</i></p></div>
|
||||
|
||||
<div>
|
||||
<p id="i18n-4" i18n-title title="on not translatable node"></p>
|
||||
<p id="i18n-5" i18n i18n-title title="on translatable node"></p>
|
||||
<p id="i18n-6" i18n-title title></p>
|
||||
</div>
|
||||
|
||||
<!-- no ph below because the ICU node is the only child of the div, i.e. no text nodes -->
|
||||
<div i18n id="i18n-7">{count, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>}}</div>
|
||||
|
||||
<div i18n id="i18n-8">
|
||||
{sex, select, m {male} f {female}}
|
||||
</div>
|
||||
<div i18n id="i18n-8b">
|
||||
{sexB, select, m {male} f {female}}
|
||||
</div>
|
||||
|
||||
<div i18n id="i18n-9">{{ "count = " + count }}</div>
|
||||
<div i18n id="i18n-10">sex = {{ sex }}</div>
|
||||
<div i18n id="i18n-11">{{ "custom name" //i18n(ph="CUSTOM_NAME") }}</div>
|
||||
</div>
|
||||
|
||||
<!-- i18n -->
|
||||
<h1 id="i18n-12" >Markers in html comments</h1>
|
||||
<div id="i18n-13" i18n-title title="in a translatable section"></div>
|
||||
<div id="i18n-14">{count, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>}}</div>
|
||||
<!-- /i18n -->
|
||||
|
||||
<div id="i18n-15"><ng-container i18n>it <b>should</b> work</ng-container></div>
|
||||
`
|
||||
template: '',
|
||||
})
|
||||
class I18nComponent {
|
||||
count: number;
|
||||
sex: string;
|
||||
sexB: string;
|
||||
response: any = {getItemsList: (): any[] => []};
|
||||
}
|
||||
|
||||
class FrLocalization extends NgLocalization {
|
||||
@ -182,14 +160,14 @@ const XTB = `
|
||||
<ph name="START_TAG_DIV_1"/><ph name="ICU"/><ph name="CLOSE_TAG_DIV"></ph>
|
||||
</translation>
|
||||
<translation id="1491627405349178954">ca <ph name="START_BOLD_TEXT"/>devrait<ph name="CLOSE_BOLD_TEXT"/> marcher</translation>
|
||||
<translation id="i18n16">avec un ID explicite</translation>
|
||||
<translation id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {un} =2 {deux} other {<ph
|
||||
name="START_BOLD_TEXT"><ex><b></ex></ph>beaucoup<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph>} }</translation>
|
||||
<translation id="4085484936881858615">{VAR_PLURAL, plural, =0 {Pas de réponse} =1 {une réponse} other {<ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph> réponse} }</translation>
|
||||
<translation id="4035252431381981115">FOO<ph name="START_LINK"><ex><a></ex></ph>BAR<ph name="CLOSE_LINK"><ex></a></ex></ph></translation>
|
||||
</translationbundle>`;
|
||||
|
||||
// unused, for reference only
|
||||
// can be generated from xmb_spec as follow:
|
||||
// `fit('extract xmb', () => { console.log(toXmb(HTML)); });`
|
||||
const XMB = `
|
||||
<messagebundle>
|
||||
<msg id="615790887472569365">i18n attribute on tags</msg>
|
||||
const XMB = ` <msg id="615790887472569365">i18n attribute on tags</msg>
|
||||
<msg id="3707494640264351337">nested</msg>
|
||||
<msg id="5539162898278769904" meaning="different meaning">nested</msg>
|
||||
<msg id="3780349238193953556"><ph name="START_ITALIC_TEXT"><ex><i></ex></ph>with placeholders<ph name="CLOSE_ITALIC_TEXT"><ex></i></ex></ph></msg>
|
||||
@ -197,20 +175,23 @@ const XMB = `
|
||||
<msg id="8670732454866344690">on translatable node</msg>
|
||||
<msg id="4593805537723189714">{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex><b></ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph>} }</msg>
|
||||
<msg id="1746565782635215">
|
||||
<ph name="ICU"/>
|
||||
<ph name="ICU"><ex>ICU</ex></ph>
|
||||
</msg>
|
||||
<msg id="5868084092545682515">{VAR_SELECT, select, m {male} f {female} }</msg>
|
||||
<msg id="4851788426695310455"><ph name="INTERPOLATION"/></msg>
|
||||
<msg id="9013357158046221374">sex = <ph name="INTERPOLATION"/></msg>
|
||||
<msg id="8324617391167353662"><ph name="CUSTOM_NAME"/></msg>
|
||||
<msg id="4851788426695310455"><ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
|
||||
<msg id="9013357158046221374">sex = <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
|
||||
<msg id="8324617391167353662"><ph name="CUSTOM_NAME"><ex>CUSTOM_NAME</ex></ph></msg>
|
||||
<msg id="7685649297917455806">in a translatable section</msg>
|
||||
<msg id="2387287228265107305">
|
||||
<ph name="START_HEADING_LEVEL1"><ex><h1></ex></ph>Markers in html comments<ph name="CLOSE_HEADING_LEVEL1"><ex></h1></ex></ph>
|
||||
<ph name="START_TAG_DIV"><ex><div></ex></ph><ph name="CLOSE_TAG_DIV"><ex></div></ex></ph>
|
||||
<ph name="START_TAG_DIV_1"><ex><div></ex></ph><ph name="ICU"/><ph name="CLOSE_TAG_DIV"><ex></div></ex></ph>
|
||||
<ph name="START_TAG_DIV_1"><ex><div></ex></ph><ph name="ICU"><ex>ICU</ex></ph><ph name="CLOSE_TAG_DIV"><ex></div></ex></ph>
|
||||
</msg>
|
||||
<msg id="1491627405349178954">it <ph name="START_BOLD_TEXT"><ex><b></ex></ph>should<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph> work</msg>
|
||||
</messagebundle>`;
|
||||
<msg id="i18n16">with an explicit ID</msg>
|
||||
<msg id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex><b></ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph>} }</msg>
|
||||
<msg id="4085484936881858615" desc="desc">{VAR_PLURAL, plural, =0 {Found no results} =1 {Found one result} other {Found <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph> results} }</msg>
|
||||
<msg id="4035252431381981115">foo<ph name="START_LINK"><ex><a></ex></ph>bar<ph name="CLOSE_LINK"><ex></a></ex></ph></msg>`;
|
||||
|
||||
const HTML = `
|
||||
<div>
|
||||
@ -252,6 +233,9 @@ const HTML = `
|
||||
|
||||
<div id="i18n-15"><ng-container i18n>it <b>should</b> work</ng-container></div>
|
||||
|
||||
<div id="i18n-16" i18n="@@i18n16">with an explicit ID</div>
|
||||
<div id="i18n-17" i18n="@@i18n17">{count, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>}}</div>
|
||||
|
||||
<!-- make sure that ICU messages are not treated as text nodes -->
|
||||
<div i18n="desc">{
|
||||
response.getItemsList().length,
|
||||
|
@ -18,6 +18,8 @@ const HTML = `
|
||||
<p i18n-title title="translatable attribute">not translatable</p>
|
||||
<p i18n>translatable element <b>with placeholders</b> {{ interpolation}}</p>
|
||||
<p i18n="m|d">foo</p>
|
||||
<p i18n="m|d@@i">foo</p>
|
||||
<p i18n="@@bar">foo</p>
|
||||
<p i18n="ph names"><br><img><div></div></p>
|
||||
`;
|
||||
|
||||
@ -39,6 +41,16 @@ const WRITE_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<note priority="1" from="description">d</note>
|
||||
<note priority="1" from="meaning">m</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="i" datatype="html">
|
||||
<source>foo</source>
|
||||
<target/>
|
||||
<note priority="1" from="description">d</note>
|
||||
<note priority="1" from="meaning">m</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="bar" datatype="html">
|
||||
<source>foo</source>
|
||||
<target/>
|
||||
</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/>
|
||||
@ -67,6 +79,16 @@ const LOAD_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<note priority="1" from="description">d</note>
|
||||
<note priority="1" from="meaning">m</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="i" datatype="html">
|
||||
<source>foo</source>
|
||||
<target>toto</target>
|
||||
<note priority="1" from="description">d</note>
|
||||
<note priority="1" from="meaning">m</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="bar" datatype="html">
|
||||
<source>foo</source>
|
||||
<target>tata</target>
|
||||
</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><x id="START_TAG_DIV" ctype="x-div"/><x id="CLOSE_TAG_DIV" ctype="x-div"/><x id="TAG_IMG" ctype="image"/><x id="LINE_BREAK" ctype="lb"/></target>
|
||||
@ -107,6 +129,8 @@ export function main(): void {
|
||||
'ec1d033f2436133c14ab038286c4f5df4697484a':
|
||||
'<ph name="INTERPOLATION"/> footnemele elbatalsnart <ph name="START_BOLD_TEXT"/>sredlohecalp htiw<ph name="CLOSE_BOLD_TEXT"/>',
|
||||
'db3e0a6a5a96481f60aec61d98c3eecddef5ac23': 'oof',
|
||||
'i': 'toto',
|
||||
'bar': 'tata',
|
||||
'd7fa2d59aaedcaa5309f13028c59af8c85b8c49d':
|
||||
'<ph name="START_TAG_DIV"/><ph name="CLOSE_TAG_DIV"/><ph name="TAG_IMG"/><ph name="LINE_BREAK"/>',
|
||||
});
|
||||
|
@ -18,6 +18,9 @@ export function main(): void {
|
||||
<p i18n>translatable element <b>with placeholders</b> {{ interpolation}}</p>
|
||||
<!-- i18n -->{ count, plural, =0 {<p>test</p>}}<!-- /i18n -->
|
||||
<p i18n="m|d">foo</p>
|
||||
<p i18n="m|d@@i">foo</p>
|
||||
<p i18n="@@bar">foo</p>
|
||||
<p i18n="@@baz">{ count, plural, =0 { { sex, select, other {<p>deeply nested</p>}} }}</p>
|
||||
<p i18n>{ count, plural, =0 { { sex, select, other {<p>deeply nested</p>}} }}</p>`;
|
||||
|
||||
const XMB = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
@ -46,6 +49,9 @@ export function main(): void {
|
||||
<msg id="7056919470098446707">translatable element <ph name="START_BOLD_TEXT"><ex><b></ex></ph>with placeholders<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph> <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph></msg>
|
||||
<msg id="2981514368455622387">{VAR_PLURAL, plural, =0 {<ph name="START_PARAGRAPH"><ex><p></ex></ph>test<ph name="CLOSE_PARAGRAPH"><ex></p></ex></ph>} }</msg>
|
||||
<msg id="7999024498831672133" desc="d" meaning="m">foo</msg>
|
||||
<msg id="i" desc="d" meaning="m">foo</msg>
|
||||
<msg id="bar">foo</msg>
|
||||
<msg id="baz">{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<ph name="START_PARAGRAPH"><ex><p></ex></ph>deeply nested<ph name="CLOSE_PARAGRAPH"><ex></p></ex></ph>} } } }</msg>
|
||||
<msg id="2015957479576096115">{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<ph name="START_PARAGRAPH"><ex><p></ex></ph>deeply nested<ph name="CLOSE_PARAGRAPH"><ex></p></ex></ph>} } } }</msg>
|
||||
</messagebundle>
|
||||
`;
|
||||
|
@ -21,7 +21,7 @@ export function main(): void {
|
||||
it('should translate a plain message', () => {
|
||||
const msgMap = {foo: [new i18n.Text('bar', null)]};
|
||||
const tb = new TranslationBundle(msgMap, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
expect(serializeNodes(tb.get(msg))).toEqual(['bar']);
|
||||
});
|
||||
|
||||
@ -36,7 +36,7 @@ export function main(): void {
|
||||
ph1: '*phContent*',
|
||||
};
|
||||
const tb = new TranslationBundle(msgMap, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], phMap, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], phMap, {}, 'm', 'd', 'i');
|
||||
expect(serializeNodes(tb.get(msg))).toEqual(['bar*phContent*']);
|
||||
});
|
||||
|
||||
@ -51,8 +51,8 @@ export function main(): void {
|
||||
new i18n.Text('*refMsg*', null),
|
||||
],
|
||||
};
|
||||
const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], {}, {ph1: refMsg}, 'm', 'd');
|
||||
const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
const msg = new i18n.Message([srcNode], {}, {ph1: refMsg}, 'm', 'd', 'i');
|
||||
let count = 0;
|
||||
const digest = (_: any) => count++ ? 'ref' : 'foo';
|
||||
const tb = new TranslationBundle(msgMap, digest);
|
||||
@ -69,13 +69,13 @@ export function main(): void {
|
||||
]
|
||||
};
|
||||
const tb = new TranslationBundle(msgMap, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
expect(() => tb.get(msg)).toThrowError(/Unknown placeholder/);
|
||||
});
|
||||
|
||||
it('should report missing translation', () => {
|
||||
const tb = new TranslationBundle({}, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
expect(() => tb.get(msg)).toThrowError(/Missing translation for message foo/);
|
||||
});
|
||||
|
||||
@ -83,8 +83,8 @@ export function main(): void {
|
||||
const msgMap = {
|
||||
foo: [new i18n.Placeholder('', 'ph1', span)],
|
||||
};
|
||||
const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], {}, {ph1: refMsg}, 'm', 'd');
|
||||
const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
const msg = new i18n.Message([srcNode], {}, {ph1: refMsg}, 'm', 'd', 'i');
|
||||
let count = 0;
|
||||
const digest = (_: any) => count++ ? 'ref' : 'foo';
|
||||
const tb = new TranslationBundle(msgMap, digest);
|
||||
@ -102,7 +102,7 @@ export function main(): void {
|
||||
ph1: '</b>',
|
||||
};
|
||||
const tb = new TranslationBundle(msgMap, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], phMap, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], phMap, {}, 'm', 'd', 'i');
|
||||
expect(() => tb.get(msg)).toThrowError(/Unexpected closing tag "b"/);
|
||||
});
|
||||
});
|
||||
|
@ -222,6 +222,27 @@ export function main() {
|
||||
SyntaxError, `Can't resolve all parameters for NonAnnotatedService: (?).`);
|
||||
}));
|
||||
|
||||
it('should throw with descriptive error message when encounter invalid provider',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({providers: [{provide: SimpleService, useClass: undefined}]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, true))
|
||||
.toThrowError(
|
||||
SyntaxError, /Invalid provider for SimpleService. useClass cannot be undefined./);
|
||||
}));
|
||||
|
||||
it('should throw with descriptive error message when provider is undefined',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({providers: [undefined]})
|
||||
class SomeModule {
|
||||
}
|
||||
|
||||
expect(() => resolver.loadNgModuleDirectiveAndPipeMetadata(SomeModule, true))
|
||||
.toThrowError(SyntaxError, /Encountered undefined provider!/);
|
||||
}));
|
||||
|
||||
it('should throw with descriptive error message when one of providers is not present',
|
||||
inject([CompileMetadataResolver], (resolver: CompileMetadataResolver) => {
|
||||
@NgModule({declarations: [MyBrokenComp3]})
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StaticSymbol} from '@angular/compiler/src/aot/static_symbol';
|
||||
import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata';
|
||||
import {JavaScriptEmitter} from '@angular/compiler/src/output/js_emitter';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
@ -15,16 +16,17 @@ const someModuleUrl = 'somePackage/somePath';
|
||||
const anotherModuleUrl = 'somePackage/someOtherPath';
|
||||
|
||||
const sameModuleIdentifier: CompileIdentifierMetadata = {
|
||||
reference: {name: 'someLocalId', filePath: someModuleUrl}
|
||||
reference: new StaticSymbol(someModuleUrl, 'someLocalId', [])
|
||||
};
|
||||
const externalModuleIdentifier: CompileIdentifierMetadata = {
|
||||
reference: {name: 'someExternalId', filePath: anotherModuleUrl}
|
||||
reference: new StaticSymbol(anotherModuleUrl, 'someExternalId', [])
|
||||
};
|
||||
|
||||
class SimpleJsImportGenerator implements ImportResolver {
|
||||
fileNameToModuleName(importedUrlStr: string, moduleUrlStr: string): string {
|
||||
return importedUrlStr;
|
||||
}
|
||||
getImportAs(symbol: StaticSymbol): StaticSymbol { return null; }
|
||||
}
|
||||
|
||||
export function main() {
|
||||
@ -33,11 +35,13 @@ export function main() {
|
||||
// - declaring fields
|
||||
|
||||
describe('JavaScriptEmitter', () => {
|
||||
let importResolver: ImportResolver;
|
||||
let emitter: JavaScriptEmitter;
|
||||
let someVar: o.ReadVarExpr;
|
||||
|
||||
beforeEach(() => {
|
||||
emitter = new JavaScriptEmitter(new SimpleJsImportGenerator());
|
||||
importResolver = new SimpleJsImportGenerator();
|
||||
emitter = new JavaScriptEmitter(importResolver);
|
||||
someVar = o.variable('someVar');
|
||||
});
|
||||
|
||||
@ -124,6 +128,16 @@ export function main() {
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
it('should support `importAs` for external identifiers', () => {
|
||||
spyOn(importResolver, 'getImportAs')
|
||||
.and.returnValue(new StaticSymbol('somePackage/importAsModule', 'importAsName', []));
|
||||
expect(emitStmt(o.importExpr(externalModuleIdentifier).toStmt())).toEqual([
|
||||
`var import0 = re` +
|
||||
`quire('somePackage/importAsModule');`,
|
||||
`import0.importAsName;`
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
it('should support operators', () => {
|
||||
const lhs = o.variable('lhs');
|
||||
const rhs = o.variable('rhs');
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StaticSymbol} from '@angular/compiler/src/aot/static_symbol';
|
||||
import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata';
|
||||
import {assetUrl, createIdentifier} from '@angular/compiler/src/identifiers';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
@ -262,4 +263,5 @@ export class SimpleJsImportGenerator implements ImportResolver {
|
||||
fileNameToModuleName(importedUrlStr: string, moduleUrlStr: string): string {
|
||||
return importedUrlStr;
|
||||
}
|
||||
getImportAs(symbol: StaticSymbol): StaticSymbol { return null; }
|
||||
}
|
||||
|
@ -74,4 +74,5 @@ class StubReflectorHost implements StaticSymbolResolverHost {
|
||||
|
||||
class StubImportResolver extends ImportResolver {
|
||||
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string { return ''; }
|
||||
getImportAs(symbol: StaticSymbol): StaticSymbol { return null; }
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {StaticSymbol} from '@angular/compiler/src/aot/static_symbol';
|
||||
import {CompileIdentifierMetadata} from '@angular/compiler/src/compile_metadata';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
import {ImportResolver} from '@angular/compiler/src/output/path_util';
|
||||
@ -15,38 +16,42 @@ const someModuleUrl = 'somePackage/somePath';
|
||||
const anotherModuleUrl = 'somePackage/someOtherPath';
|
||||
|
||||
const sameModuleIdentifier: CompileIdentifierMetadata = {
|
||||
reference: {name: 'someLocalId', filePath: someModuleUrl}
|
||||
reference: new StaticSymbol(someModuleUrl, 'someLocalId', [])
|
||||
};
|
||||
|
||||
const externalModuleIdentifier: CompileIdentifierMetadata = {
|
||||
reference: {name: 'someExternalId', filePath: anotherModuleUrl}
|
||||
reference: new StaticSymbol(anotherModuleUrl, 'someExternalId', [])
|
||||
};
|
||||
|
||||
class SimpleJsImportGenerator implements ImportResolver {
|
||||
fileNameToModuleName(importedUrlStr: string, moduleUrlStr: string): string {
|
||||
return importedUrlStr;
|
||||
}
|
||||
getImportAs(symbol: StaticSymbol): StaticSymbol { return null; }
|
||||
}
|
||||
|
||||
export function main() {
|
||||
// Note supported features of our OutputAsti n TS:
|
||||
// Not supported features of our OutputAst in TS:
|
||||
// - real `const` like in Dart
|
||||
// - final fields
|
||||
|
||||
describe('TypeScriptEmitter', () => {
|
||||
let importResolver: ImportResolver;
|
||||
let emitter: TypeScriptEmitter;
|
||||
let someVar: o.ReadVarExpr;
|
||||
|
||||
beforeEach(() => {
|
||||
emitter = new TypeScriptEmitter(new SimpleJsImportGenerator());
|
||||
importResolver = new SimpleJsImportGenerator();
|
||||
emitter = new TypeScriptEmitter(importResolver);
|
||||
someVar = o.variable('someVar');
|
||||
});
|
||||
|
||||
function emitStmt(stmt: o.Statement, exportedVars: string[] = null): string {
|
||||
function emitStmt(stmt: o.Statement | o.Statement[], exportedVars: string[] = null): string {
|
||||
if (!exportedVars) {
|
||||
exportedVars = [];
|
||||
}
|
||||
return emitter.emitStatements(someModuleUrl, [stmt], exportedVars);
|
||||
const stmts = Array.isArray(stmt) ? stmt : [stmt];
|
||||
return emitter.emitStatements(someModuleUrl, stmts, exportedVars);
|
||||
}
|
||||
|
||||
it('should declare variables', () => {
|
||||
@ -59,6 +64,79 @@ export function main() {
|
||||
.toEqual(`var someVar:number = 1;`);
|
||||
});
|
||||
|
||||
describe('declare variables with ExternExpressions as values', () => {
|
||||
it('should create no reexport if the identifier is in the same module', () => {
|
||||
// identifier is in the same module -> no reexport
|
||||
expect(emitStmt(someVar.set(o.importExpr(sameModuleIdentifier)).toDeclStmt(), ['someVar']))
|
||||
.toEqual('export var someVar:any = someLocalId;');
|
||||
});
|
||||
|
||||
it('should create no reexport if the variable is not exported', () => {
|
||||
expect(emitStmt(someVar.set(o.importExpr(externalModuleIdentifier)).toDeclStmt())).toEqual([
|
||||
`import * as import0 from 'somePackage/someOtherPath';`,
|
||||
`var someVar:any = import0.someExternalId;`
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
it('should create no reexport if the variable is typed', () => {
|
||||
expect(emitStmt(
|
||||
someVar.set(o.importExpr(externalModuleIdentifier)).toDeclStmt(o.DYNAMIC_TYPE),
|
||||
['someVar']))
|
||||
.toEqual([
|
||||
`import * as import0 from 'somePackage/someOtherPath';`,
|
||||
`export var someVar:any = import0.someExternalId;`
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
it('should create no reexport if the identifier has members', () => {
|
||||
const externalModuleIdentifierWithMembers: CompileIdentifierMetadata = {
|
||||
reference: new StaticSymbol(anotherModuleUrl, 'someExternalId', ['a'])
|
||||
};
|
||||
expect(emitStmt(
|
||||
someVar.set(o.importExpr(externalModuleIdentifierWithMembers)).toDeclStmt(),
|
||||
['someVar']))
|
||||
.toEqual([
|
||||
`import * as import0 from 'somePackage/someOtherPath';`,
|
||||
`export var someVar:any = import0.someExternalId.a;`
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
it('should create a reexport', () => {
|
||||
expect(
|
||||
emitStmt(someVar.set(o.importExpr(externalModuleIdentifier)).toDeclStmt(), ['someVar']))
|
||||
.toEqual([
|
||||
`export {someExternalId as someVar} from 'somePackage/someOtherPath';`, ``
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
it('should create multiple reexports from the same file', () => {
|
||||
const someVar2 = o.variable('someVar2');
|
||||
const externalModuleIdentifier2: CompileIdentifierMetadata = {
|
||||
reference: new StaticSymbol(anotherModuleUrl, 'someExternalId2', [])
|
||||
};
|
||||
expect(emitStmt(
|
||||
[
|
||||
someVar.set(o.importExpr(externalModuleIdentifier)).toDeclStmt(),
|
||||
someVar2.set(o.importExpr(externalModuleIdentifier2)).toDeclStmt()
|
||||
],
|
||||
['someVar', 'someVar2']))
|
||||
.toEqual([
|
||||
`export {someExternalId as someVar,someExternalId2 as someVar2} from 'somePackage/someOtherPath';`,
|
||||
``
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
it('should use `importAs` for reexports', () => {
|
||||
spyOn(importResolver, 'getImportAs')
|
||||
.and.returnValue(new StaticSymbol('somePackage/importAsModule', 'importAsName', []));
|
||||
expect(
|
||||
emitStmt(someVar.set(o.importExpr(externalModuleIdentifier)).toDeclStmt(), ['someVar']))
|
||||
.toEqual([
|
||||
`export {importAsName as someVar} from 'somePackage/importAsModule';`, ``
|
||||
].join('\n'));
|
||||
});
|
||||
});
|
||||
|
||||
it('should read and write variables', () => {
|
||||
expect(emitStmt(someVar.toStmt())).toEqual(`someVar;`);
|
||||
expect(emitStmt(someVar.set(o.literal(1)).toStmt())).toEqual(`someVar = 1;`);
|
||||
@ -134,6 +212,14 @@ export function main() {
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
it('should support `importAs` for external identifiers', () => {
|
||||
spyOn(importResolver, 'getImportAs')
|
||||
.and.returnValue(new StaticSymbol('somePackage/importAsModule', 'importAsName', []));
|
||||
expect(emitStmt(o.importExpr(externalModuleIdentifier).toStmt())).toEqual([
|
||||
`import * as import0 from 'somePackage/importAsModule';`, `import0.importAsName;`
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
it('should support operators', () => {
|
||||
const lhs = o.variable('lhs');
|
||||
const rhs = o.variable('rhs');
|
||||
@ -332,6 +418,16 @@ export function main() {
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
it('should support `importAs` for external types', () => {
|
||||
spyOn(importResolver, 'getImportAs')
|
||||
.and.returnValue(new StaticSymbol('somePackage/importAsModule', 'importAsName', []));
|
||||
const writeVarExpr = o.variable('a').set(o.NULL_EXPR);
|
||||
expect(emitStmt(writeVarExpr.toDeclStmt(o.importType(externalModuleIdentifier)))).toEqual([
|
||||
`import * as import0 from 'somePackage/importAsModule';`,
|
||||
`var a:import0.importAsName = (null as any);`
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
it('should support expression types', () => {
|
||||
expect(emitStmt(o.variable('a')
|
||||
.set(o.NULL_EXPR)
|
||||
|
@ -108,9 +108,12 @@ export const platformCoreDynamicTesting: (extraProviders?: any[]) => PlatformRef
|
||||
provide: COMPILER_OPTIONS,
|
||||
useValue: {
|
||||
providers: [
|
||||
MockPipeResolver, {provide: PipeResolver, useExisting: MockPipeResolver},
|
||||
MockDirectiveResolver, {provide: DirectiveResolver, useExisting: MockDirectiveResolver},
|
||||
MockNgModuleResolver, {provide: NgModuleResolver, useExisting: MockNgModuleResolver}
|
||||
MockPipeResolver,
|
||||
{provide: PipeResolver, useExisting: MockPipeResolver},
|
||||
MockDirectiveResolver,
|
||||
{provide: DirectiveResolver, useExisting: MockDirectiveResolver},
|
||||
MockNgModuleResolver,
|
||||
{provide: NgModuleResolver, useExisting: MockNgModuleResolver},
|
||||
]
|
||||
},
|
||||
multi: true
|
||||
|
@ -5,20 +5,24 @@
|
||||
* 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 {ElementRef} from '../linker/element_ref';
|
||||
|
||||
import {AnimationPlayer} from './animation_player';
|
||||
import {AnimationTransitionEvent} from './animation_transition_event';
|
||||
|
||||
export class AnimationTransition {
|
||||
constructor(
|
||||
private _player: AnimationPlayer, private _fromState: string, private _toState: string,
|
||||
private _totalTime: number) {}
|
||||
private _player: AnimationPlayer, private _element: ElementRef, private _triggerName: string,
|
||||
private _fromState: string, private _toState: string, private _totalTime: number) {}
|
||||
|
||||
private _createEvent(phaseName: string): AnimationTransitionEvent {
|
||||
return new AnimationTransitionEvent({
|
||||
fromState: this._fromState,
|
||||
toState: this._toState,
|
||||
totalTime: this._totalTime,
|
||||
phaseName: phaseName
|
||||
phaseName: phaseName,
|
||||
element: this._element,
|
||||
triggerName: this._triggerName
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -5,6 +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 {ElementRef} from '../linker/element_ref';
|
||||
|
||||
/**
|
||||
* An instance of this class is returned as an event parameter when an animation
|
||||
@ -42,12 +43,22 @@ export class AnimationTransitionEvent {
|
||||
public toState: string;
|
||||
public totalTime: number;
|
||||
public phaseName: string;
|
||||
public element: ElementRef;
|
||||
public triggerName: string;
|
||||
|
||||
constructor({fromState, toState, totalTime, phaseName}:
|
||||
{fromState: string, toState: string, totalTime: number, phaseName: string}) {
|
||||
constructor({fromState, toState, totalTime, phaseName, element, triggerName}: {
|
||||
fromState: string,
|
||||
toState: string,
|
||||
totalTime: number,
|
||||
phaseName: string,
|
||||
element: any,
|
||||
triggerName: string
|
||||
}) {
|
||||
this.fromState = fromState;
|
||||
this.toState = toState;
|
||||
this.totalTime = totalTime;
|
||||
this.phaseName = phaseName;
|
||||
this.element = new ElementRef(element);
|
||||
this.triggerName = triggerName;
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,11 @@ export class AnimationStateDeclarationMetadata extends AnimationStateMetadata {
|
||||
* @experimental Animation support is experimental.
|
||||
*/
|
||||
export class AnimationStateTransitionMetadata extends AnimationStateMetadata {
|
||||
constructor(public stateChangeExpr: string, public steps: AnimationMetadata) { super(); }
|
||||
constructor(
|
||||
public stateChangeExpr: string|((fromState: string, toState: string) => boolean),
|
||||
public steps: AnimationMetadata) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -471,6 +475,10 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe
|
||||
* which consists
|
||||
* of two known states (use an asterix (`*`) to refer to a dynamic starting and/or ending state).
|
||||
*
|
||||
* A function can also be provided as the `stateChangeExpr` argument for a transition and this
|
||||
* function will be executed each time a state change occurs. If the value returned within the
|
||||
* function is true then the associated animation will be run.
|
||||
*
|
||||
* Animation transitions are placed within an {@link trigger animation trigger}. For an transition
|
||||
* to animate to
|
||||
* a state value and persist its styles then one or more {@link state animation states} is expected
|
||||
@ -511,6 +519,12 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe
|
||||
*
|
||||
* // this will capture a state change between any states
|
||||
* transition("* => *", animate("1s 0s")),
|
||||
*
|
||||
* // you can also go full out and include a function
|
||||
* transition((fromState, toState) => {
|
||||
* // when `true` then it will allow the animation below to be invoked
|
||||
* return fromState == "off" && toState == "on";
|
||||
* }, animate("1s 0s"))
|
||||
* ])
|
||||
* ```
|
||||
*
|
||||
@ -562,8 +576,9 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe
|
||||
*
|
||||
* @experimental Animation support is experimental.
|
||||
*/
|
||||
export function transition(stateChangeExpr: string, steps: AnimationMetadata | AnimationMetadata[]):
|
||||
AnimationStateTransitionMetadata {
|
||||
export function transition(
|
||||
stateChangeExpr: string | ((fromState: string, toState: string) => boolean),
|
||||
steps: AnimationMetadata | AnimationMetadata[]): AnimationStateTransitionMetadata {
|
||||
const animationData = Array.isArray(steps) ? new AnimationSequenceMetadata(steps) : steps;
|
||||
return new AnimationStateTransitionMetadata(stateChangeExpr, animationData);
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import {isPromise} from '../src/util/lang';
|
||||
|
||||
import {ApplicationInitStatus} from './application_init';
|
||||
import {APP_BOOTSTRAP_LISTENER, PLATFORM_INITIALIZER} from './application_tokens';
|
||||
import {ChangeDetectorRef} from './change_detection/change_detector_ref';
|
||||
import {Console} from './console';
|
||||
import {Injectable, Injector, OpaqueToken, Optional, Provider, ReflectiveInjector} from './di';
|
||||
import {CompilerFactory, CompilerOptions} from './linker/compiler';
|
||||
|
@ -12,4 +12,4 @@
|
||||
* Change detection enables data binding in Angular.
|
||||
*/
|
||||
|
||||
export {ChangeDetectionStrategy, ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, IterableDiffer, IterableDifferFactory, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDifferFactory, KeyValueDiffers, PipeTransform, SimpleChange, SimpleChanges, TrackByFn, WrappedValue} from './change_detection/change_detection';
|
||||
export {ChangeDetectionStrategy, ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, IterableChangeRecord, IterableChanges, IterableDiffer, IterableDifferFactory, IterableDiffers, KeyValueChangeRecord, KeyValueChanges, KeyValueDiffer, KeyValueDifferFactory, KeyValueDiffers, PipeTransform, SimpleChange, SimpleChanges, TrackByFn, WrappedValue} from './change_detection/change_detection';
|
||||
|
@ -7,19 +7,19 @@
|
||||
*/
|
||||
|
||||
import {DefaultIterableDifferFactory} from './differs/default_iterable_differ';
|
||||
import {DefaultKeyValueDifferFactory, KeyValueChangeRecord} from './differs/default_keyvalue_differ';
|
||||
import {DefaultKeyValueDifferFactory} from './differs/default_keyvalue_differ';
|
||||
import {IterableDifferFactory, IterableDiffers} from './differs/iterable_differs';
|
||||
import {KeyValueDifferFactory, KeyValueDiffers} from './differs/keyvalue_differs';
|
||||
|
||||
export {SimpleChanges} from '../metadata/lifecycle_hooks';
|
||||
export {SimpleChange, UNINITIALIZED, ValueUnwrapper, WrappedValue, devModeEqual, looseIdentical} from './change_detection_util';
|
||||
export {SimpleChange, ValueUnwrapper, WrappedValue, devModeEqual, looseIdentical} from './change_detection_util';
|
||||
export {ChangeDetectorRef} from './change_detector_ref';
|
||||
export {ChangeDetectionStrategy, ChangeDetectorStatus, isDefaultChangeDetectionStrategy} from './constants';
|
||||
export {CollectionChangeRecord, DefaultIterableDifferFactory} from './differs/default_iterable_differ';
|
||||
export {DefaultIterableDifferFactory} from './differs/default_iterable_differ';
|
||||
export {DefaultIterableDiffer} from './differs/default_iterable_differ';
|
||||
export {DefaultKeyValueDifferFactory, KeyValueChangeRecord} from './differs/default_keyvalue_differ';
|
||||
export {IterableDiffer, IterableDifferFactory, IterableDiffers, TrackByFn} from './differs/iterable_differs';
|
||||
export {KeyValueDiffer, KeyValueDifferFactory, KeyValueDiffers} from './differs/keyvalue_differs';
|
||||
export {DefaultKeyValueDifferFactory} from './differs/default_keyvalue_differ';
|
||||
export {CollectionChangeRecord, IterableChangeRecord, IterableChanges, IterableDiffer, IterableDifferFactory, IterableDiffers, TrackByFn} from './differs/iterable_differs';
|
||||
export {KeyValueChangeRecord, KeyValueChanges, KeyValueDiffer, KeyValueDifferFactory, KeyValueDiffers} from './differs/keyvalue_differs';
|
||||
export {PipeTransform} from './pipe_transform';
|
||||
|
||||
|
||||
@ -27,12 +27,12 @@ export {PipeTransform} from './pipe_transform';
|
||||
/**
|
||||
* Structural diffing for `Object`s and `Map`s.
|
||||
*/
|
||||
export const keyValDiff: KeyValueDifferFactory[] = [new DefaultKeyValueDifferFactory()];
|
||||
const keyValDiff: KeyValueDifferFactory[] = [new DefaultKeyValueDifferFactory()];
|
||||
|
||||
/**
|
||||
* Structural diffing for `Iterable` types such as `Array`s.
|
||||
*/
|
||||
export const iterableDiff: IterableDifferFactory[] = [new DefaultIterableDifferFactory()];
|
||||
const iterableDiff: IterableDifferFactory[] = [new DefaultIterableDifferFactory()];
|
||||
|
||||
export const defaultIterableDiffers = new IterableDiffers(iterableDiff);
|
||||
|
||||
|
@ -11,10 +11,6 @@ import {isPrimitive, looseIdentical} from '../facade/lang';
|
||||
|
||||
export {looseIdentical} from '../facade/lang';
|
||||
|
||||
export const UNINITIALIZED = {
|
||||
toString: () => 'CD_INIT_VALUE'
|
||||
};
|
||||
|
||||
export function devModeEqual(a: any, b: any): boolean {
|
||||
if (isListLikeIterable(a) && isListLikeIterable(b)) {
|
||||
return areIterablesEqual(a, b, devModeEqual);
|
||||
@ -75,10 +71,10 @@ export class ValueUnwrapper {
|
||||
* @stable
|
||||
*/
|
||||
export class SimpleChange {
|
||||
constructor(public previousValue: any, public currentValue: any) {}
|
||||
constructor(public previousValue: any, public currentValue: any, public firstChange: boolean) {}
|
||||
|
||||
/**
|
||||
* Check whether the new value is the first value assigned.
|
||||
*/
|
||||
isFirstChange(): boolean { return this.previousValue === UNINITIALIZED; }
|
||||
isFirstChange(): boolean { return this.firstChange; }
|
||||
}
|
||||
|
@ -10,41 +10,41 @@ import {isListLikeIterable, iterateListLike} from '../../facade/collection';
|
||||
import {isBlank, looseIdentical, stringify} from '../../facade/lang';
|
||||
import {ChangeDetectorRef} from '../change_detector_ref';
|
||||
|
||||
import {IterableDiffer, IterableDifferFactory, TrackByFn} from './iterable_differs';
|
||||
import {IterableChangeRecord, IterableChanges, IterableDiffer, IterableDifferFactory, TrackByFn} from './iterable_differs';
|
||||
|
||||
|
||||
export class DefaultIterableDifferFactory implements IterableDifferFactory {
|
||||
constructor() {}
|
||||
supports(obj: Object): boolean { return isListLikeIterable(obj); }
|
||||
create(cdRef: ChangeDetectorRef, trackByFn?: TrackByFn): DefaultIterableDiffer {
|
||||
return new DefaultIterableDiffer(trackByFn);
|
||||
create<V>(cdRef: ChangeDetectorRef, trackByFn?: TrackByFn): DefaultIterableDiffer<V> {
|
||||
return new DefaultIterableDiffer<V>(trackByFn);
|
||||
}
|
||||
}
|
||||
|
||||
const trackByIdentity = (index: number, item: any) => item;
|
||||
|
||||
/**
|
||||
* @stable
|
||||
* @deprecated v4.0.0 - Should not be part of public API.
|
||||
*/
|
||||
export class DefaultIterableDiffer implements IterableDiffer {
|
||||
export class DefaultIterableDiffer<V> implements IterableDiffer<V>, IterableChanges<V> {
|
||||
private _length: number = null;
|
||||
private _collection: any /** TODO #9100 */ = null;
|
||||
private _collection: V[]|Set<V>[]|any /* |Iterable<V> */ = null;
|
||||
// Keeps track of the used records at any point in time (during & across `_check()` calls)
|
||||
private _linkedRecords: _DuplicateMap = null;
|
||||
private _linkedRecords: _DuplicateMap<V> = null;
|
||||
// Keeps track of the removed records at any point in time during `_check()` calls.
|
||||
private _unlinkedRecords: _DuplicateMap = null;
|
||||
private _previousItHead: CollectionChangeRecord = null;
|
||||
private _itHead: CollectionChangeRecord = null;
|
||||
private _itTail: CollectionChangeRecord = null;
|
||||
private _additionsHead: CollectionChangeRecord = null;
|
||||
private _additionsTail: CollectionChangeRecord = null;
|
||||
private _movesHead: CollectionChangeRecord = null;
|
||||
private _movesTail: CollectionChangeRecord = null;
|
||||
private _removalsHead: CollectionChangeRecord = null;
|
||||
private _removalsTail: CollectionChangeRecord = null;
|
||||
private _unlinkedRecords: _DuplicateMap<V> = null;
|
||||
private _previousItHead: IterableChangeRecord_<V> = null;
|
||||
private _itHead: IterableChangeRecord_<V> = null;
|
||||
private _itTail: IterableChangeRecord_<V> = null;
|
||||
private _additionsHead: IterableChangeRecord_<V> = null;
|
||||
private _additionsTail: IterableChangeRecord_<V> = null;
|
||||
private _movesHead: IterableChangeRecord_<V> = null;
|
||||
private _movesTail: IterableChangeRecord_<V> = null;
|
||||
private _removalsHead: IterableChangeRecord_<V> = null;
|
||||
private _removalsTail: IterableChangeRecord_<V> = null;
|
||||
// Keeps track of records where custom track by is the same, but item identity has changed
|
||||
private _identityChangesHead: CollectionChangeRecord = null;
|
||||
private _identityChangesTail: CollectionChangeRecord = null;
|
||||
private _identityChangesHead: IterableChangeRecord_<V> = null;
|
||||
private _identityChangesTail: IterableChangeRecord_<V> = null;
|
||||
|
||||
constructor(private _trackByFn?: TrackByFn) {
|
||||
this._trackByFn = this._trackByFn || trackByIdentity;
|
||||
@ -54,15 +54,15 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
|
||||
get length(): number { return this._length; }
|
||||
|
||||
forEachItem(fn: Function) {
|
||||
let record: CollectionChangeRecord;
|
||||
forEachItem(fn: (record: IterableChangeRecord_<V>) => void) {
|
||||
let record: IterableChangeRecord_<V>;
|
||||
for (record = this._itHead; record !== null; record = record._next) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachOperation(
|
||||
fn: (item: CollectionChangeRecord, previousIndex: number, currentIndex: number) => void) {
|
||||
fn: (item: IterableChangeRecord_<V>, previousIndex: number, currentIndex: number) => void) {
|
||||
let nextIt = this._itHead;
|
||||
let nextRemove = this._removalsHead;
|
||||
let addRemoveOffset = 0;
|
||||
@ -111,42 +111,42 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
}
|
||||
}
|
||||
|
||||
forEachPreviousItem(fn: Function) {
|
||||
let record: CollectionChangeRecord;
|
||||
forEachPreviousItem(fn: (record: IterableChangeRecord_<V>) => void) {
|
||||
let record: IterableChangeRecord_<V>;
|
||||
for (record = this._previousItHead; record !== null; record = record._nextPrevious) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachAddedItem(fn: Function) {
|
||||
let record: CollectionChangeRecord;
|
||||
forEachAddedItem(fn: (record: IterableChangeRecord_<V>) => void) {
|
||||
let record: IterableChangeRecord_<V>;
|
||||
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachMovedItem(fn: Function) {
|
||||
let record: CollectionChangeRecord;
|
||||
forEachMovedItem(fn: (record: IterableChangeRecord_<V>) => void) {
|
||||
let record: IterableChangeRecord_<V>;
|
||||
for (record = this._movesHead; record !== null; record = record._nextMoved) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachRemovedItem(fn: Function) {
|
||||
let record: CollectionChangeRecord;
|
||||
forEachRemovedItem(fn: (record: IterableChangeRecord_<V>) => void) {
|
||||
let record: IterableChangeRecord_<V>;
|
||||
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachIdentityChange(fn: Function) {
|
||||
let record: CollectionChangeRecord;
|
||||
forEachIdentityChange(fn: (record: IterableChangeRecord_<V>) => void) {
|
||||
let record: IterableChangeRecord_<V>;
|
||||
for (record = this._identityChangesHead; record !== null; record = record._nextIdentityChange) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
diff(collection: any): DefaultIterableDiffer {
|
||||
diff(collection: V[]|Set<V>[]|any /* |Iterable<V> */): DefaultIterableDiffer<V> {
|
||||
if (isBlank(collection)) collection = [];
|
||||
if (!isListLikeIterable(collection)) {
|
||||
throw new Error(`Error trying to diff '${collection}'`);
|
||||
@ -162,17 +162,17 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
onDestroy() {}
|
||||
|
||||
// todo(vicb): optim for UnmodifiableListView (frozen arrays)
|
||||
check(collection: any): boolean {
|
||||
check(collection: V[]|Set<V>[]|any /* |Iterable<V> */): boolean {
|
||||
this._reset();
|
||||
|
||||
let record: CollectionChangeRecord = this._itHead;
|
||||
let record: IterableChangeRecord_<V> = this._itHead;
|
||||
let mayBeDirty: boolean = false;
|
||||
let index: number;
|
||||
let item: any;
|
||||
let item: V;
|
||||
let itemTrackBy: any;
|
||||
if (Array.isArray(collection)) {
|
||||
const list = collection;
|
||||
this._length = collection.length;
|
||||
const list = collection as V[];
|
||||
this._length = list.length;
|
||||
|
||||
for (let index = 0; index < this._length; index++) {
|
||||
item = list[index];
|
||||
@ -192,7 +192,7 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
}
|
||||
} else {
|
||||
index = 0;
|
||||
iterateListLike(collection, (item: any /** TODO #9100 */) => {
|
||||
iterateListLike(collection, (item: V) => {
|
||||
itemTrackBy = this._trackByFn(index, item);
|
||||
if (record === null || !looseIdentical(record.trackById, itemTrackBy)) {
|
||||
record = this._mismatch(record, item, itemTrackBy, index);
|
||||
@ -233,8 +233,8 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
*/
|
||||
_reset() {
|
||||
if (this.isDirty) {
|
||||
let record: CollectionChangeRecord;
|
||||
let nextRecord: CollectionChangeRecord;
|
||||
let record: IterableChangeRecord_<V>;
|
||||
let nextRecord: IterableChangeRecord_<V>;
|
||||
|
||||
for (record = this._previousItHead = this._itHead; record !== null; record = record._next) {
|
||||
record._nextPrevious = record._next;
|
||||
@ -268,10 +268,10 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_mismatch(record: CollectionChangeRecord, item: any, itemTrackBy: any, index: number):
|
||||
CollectionChangeRecord {
|
||||
_mismatch(record: IterableChangeRecord_<V>, item: V, itemTrackBy: any, index: number):
|
||||
IterableChangeRecord_<V> {
|
||||
// The previous record after which we will append the current one.
|
||||
let previousRecord: CollectionChangeRecord;
|
||||
let previousRecord: IterableChangeRecord_<V>;
|
||||
|
||||
if (record === null) {
|
||||
previousRecord = this._itTail;
|
||||
@ -301,7 +301,7 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
} else {
|
||||
// It is a new item: add it.
|
||||
record =
|
||||
this._addAfter(new CollectionChangeRecord(item, itemTrackBy), previousRecord, index);
|
||||
this._addAfter(new IterableChangeRecord_<V>(item, itemTrackBy), previousRecord, index);
|
||||
}
|
||||
}
|
||||
return record;
|
||||
@ -334,9 +334,9 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_verifyReinsertion(record: CollectionChangeRecord, item: any, itemTrackBy: any, index: number):
|
||||
CollectionChangeRecord {
|
||||
const reinsertRecord: CollectionChangeRecord =
|
||||
_verifyReinsertion(record: IterableChangeRecord_<V>, item: V, itemTrackBy: any, index: number):
|
||||
IterableChangeRecord_<V> {
|
||||
let reinsertRecord: IterableChangeRecord_<V> =
|
||||
this._unlinkedRecords === null ? null : this._unlinkedRecords.get(itemTrackBy);
|
||||
if (reinsertRecord !== null) {
|
||||
record = this._reinsertAfter(reinsertRecord, record._prev, index);
|
||||
@ -348,16 +348,16 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rid of any excess {@link CollectionChangeRecord}s from the previous collection
|
||||
* Get rid of any excess {@link IterableChangeRecord_}s from the previous collection
|
||||
*
|
||||
* - `record` The first excess {@link CollectionChangeRecord}.
|
||||
* - `record` The first excess {@link IterableChangeRecord_}.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_truncate(record: CollectionChangeRecord) {
|
||||
_truncate(record: IterableChangeRecord_<V>) {
|
||||
// Anything after that needs to be removed;
|
||||
while (record !== null) {
|
||||
const nextRecord: CollectionChangeRecord = record._next;
|
||||
const nextRecord: IterableChangeRecord_<V> = record._next;
|
||||
this._addToRemovals(this._unlink(record));
|
||||
record = nextRecord;
|
||||
}
|
||||
@ -383,8 +383,9 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_reinsertAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord, index: number):
|
||||
CollectionChangeRecord {
|
||||
_reinsertAfter(
|
||||
record: IterableChangeRecord_<V>, prevRecord: IterableChangeRecord_<V>,
|
||||
index: number): IterableChangeRecord_<V> {
|
||||
if (this._unlinkedRecords !== null) {
|
||||
this._unlinkedRecords.remove(record);
|
||||
}
|
||||
@ -408,8 +409,8 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_moveAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord, index: number):
|
||||
CollectionChangeRecord {
|
||||
_moveAfter(record: IterableChangeRecord_<V>, prevRecord: IterableChangeRecord_<V>, index: number):
|
||||
IterableChangeRecord_<V> {
|
||||
this._unlink(record);
|
||||
this._insertAfter(record, prevRecord, index);
|
||||
this._addToMoves(record, index);
|
||||
@ -417,8 +418,8 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_addAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord, index: number):
|
||||
CollectionChangeRecord {
|
||||
_addAfter(record: IterableChangeRecord_<V>, prevRecord: IterableChangeRecord_<V>, index: number):
|
||||
IterableChangeRecord_<V> {
|
||||
this._insertAfter(record, prevRecord, index);
|
||||
|
||||
if (this._additionsTail === null) {
|
||||
@ -435,14 +436,15 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_insertAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord, index: number):
|
||||
CollectionChangeRecord {
|
||||
_insertAfter(
|
||||
record: IterableChangeRecord_<V>, prevRecord: IterableChangeRecord_<V>,
|
||||
index: number): IterableChangeRecord_<V> {
|
||||
// todo(vicb)
|
||||
// assert(record != prevRecord);
|
||||
// assert(record._next === null);
|
||||
// assert(record._prev === null);
|
||||
|
||||
const next: CollectionChangeRecord = prevRecord === null ? this._itHead : prevRecord._next;
|
||||
const next: IterableChangeRecord_<V> = prevRecord === null ? this._itHead : prevRecord._next;
|
||||
// todo(vicb)
|
||||
// assert(next != record);
|
||||
// assert(prevRecord != record);
|
||||
@ -460,7 +462,7 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
}
|
||||
|
||||
if (this._linkedRecords === null) {
|
||||
this._linkedRecords = new _DuplicateMap();
|
||||
this._linkedRecords = new _DuplicateMap<V>();
|
||||
}
|
||||
this._linkedRecords.put(record);
|
||||
|
||||
@ -469,12 +471,12 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_remove(record: CollectionChangeRecord): CollectionChangeRecord {
|
||||
_remove(record: IterableChangeRecord_<V>): IterableChangeRecord_<V> {
|
||||
return this._addToRemovals(this._unlink(record));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_unlink(record: CollectionChangeRecord): CollectionChangeRecord {
|
||||
_unlink(record: IterableChangeRecord_<V>): IterableChangeRecord_<V> {
|
||||
if (this._linkedRecords !== null) {
|
||||
this._linkedRecords.remove(record);
|
||||
}
|
||||
@ -501,7 +503,7 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_addToMoves(record: CollectionChangeRecord, toIndex: number): CollectionChangeRecord {
|
||||
_addToMoves(record: IterableChangeRecord_<V>, toIndex: number): IterableChangeRecord_<V> {
|
||||
// todo(vicb)
|
||||
// assert(record._nextMoved === null);
|
||||
|
||||
@ -522,10 +524,9 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
return record;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_addToRemovals(record: CollectionChangeRecord): CollectionChangeRecord {
|
||||
private _addToRemovals(record: IterableChangeRecord_<V>): IterableChangeRecord_<V> {
|
||||
if (this._unlinkedRecords === null) {
|
||||
this._unlinkedRecords = new _DuplicateMap();
|
||||
this._unlinkedRecords = new _DuplicateMap<V>();
|
||||
}
|
||||
this._unlinkedRecords.put(record);
|
||||
record.currentIndex = null;
|
||||
@ -547,7 +548,7 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_addIdentityChange(record: CollectionChangeRecord, item: any) {
|
||||
_addIdentityChange(record: IterableChangeRecord_<V>, item: V) {
|
||||
record.item = item;
|
||||
if (this._identityChangesTail === null) {
|
||||
this._identityChangesTail = this._identityChangesHead = record;
|
||||
@ -559,23 +560,23 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
|
||||
|
||||
toString(): string {
|
||||
const list: any[] /** TODO #9100 */ = [];
|
||||
this.forEachItem((record: any /** TODO #9100 */) => list.push(record));
|
||||
const list: IterableChangeRecord_<V>[] = [];
|
||||
this.forEachItem((record: IterableChangeRecord_<V>) => list.push(record));
|
||||
|
||||
const previous: any[] /** TODO #9100 */ = [];
|
||||
this.forEachPreviousItem((record: any /** TODO #9100 */) => previous.push(record));
|
||||
const previous: IterableChangeRecord_<V>[] = [];
|
||||
this.forEachPreviousItem((record: IterableChangeRecord_<V>) => previous.push(record));
|
||||
|
||||
const additions: any[] /** TODO #9100 */ = [];
|
||||
this.forEachAddedItem((record: any /** TODO #9100 */) => additions.push(record));
|
||||
const additions: IterableChangeRecord_<V>[] = [];
|
||||
this.forEachAddedItem((record: IterableChangeRecord_<V>) => additions.push(record));
|
||||
|
||||
const moves: any[] /** TODO #9100 */ = [];
|
||||
this.forEachMovedItem((record: any /** TODO #9100 */) => moves.push(record));
|
||||
const moves: IterableChangeRecord_<V>[] = [];
|
||||
this.forEachMovedItem((record: IterableChangeRecord_<V>) => moves.push(record));
|
||||
|
||||
const removals: any[] /** TODO #9100 */ = [];
|
||||
this.forEachRemovedItem((record: any /** TODO #9100 */) => removals.push(record));
|
||||
const removals: IterableChangeRecord_<V>[] = [];
|
||||
this.forEachRemovedItem((record: IterableChangeRecord_<V>) => removals.push(record));
|
||||
|
||||
const identityChanges: any[] /** TODO #9100 */ = [];
|
||||
this.forEachIdentityChange((record: any /** TODO #9100 */) => identityChanges.push(record));
|
||||
const identityChanges: IterableChangeRecord_<V>[] = [];
|
||||
this.forEachIdentityChange((record: IterableChangeRecord_<V>) => identityChanges.push(record));
|
||||
|
||||
return 'collection: ' + list.join(', ') + '\n' +
|
||||
'previous: ' + previous.join(', ') + '\n' +
|
||||
@ -589,33 +590,33 @@ export class DefaultIterableDiffer implements IterableDiffer {
|
||||
/**
|
||||
* @stable
|
||||
*/
|
||||
export class CollectionChangeRecord {
|
||||
export class IterableChangeRecord_<V> implements IterableChangeRecord<V> {
|
||||
currentIndex: number = null;
|
||||
previousIndex: number = null;
|
||||
|
||||
/** @internal */
|
||||
_nextPrevious: CollectionChangeRecord = null;
|
||||
_nextPrevious: IterableChangeRecord_<V> = null;
|
||||
/** @internal */
|
||||
_prev: CollectionChangeRecord = null;
|
||||
_prev: IterableChangeRecord_<V> = null;
|
||||
/** @internal */
|
||||
_next: CollectionChangeRecord = null;
|
||||
_next: IterableChangeRecord_<V> = null;
|
||||
/** @internal */
|
||||
_prevDup: CollectionChangeRecord = null;
|
||||
_prevDup: IterableChangeRecord_<V> = null;
|
||||
/** @internal */
|
||||
_nextDup: CollectionChangeRecord = null;
|
||||
_nextDup: IterableChangeRecord_<V> = null;
|
||||
/** @internal */
|
||||
_prevRemoved: CollectionChangeRecord = null;
|
||||
_prevRemoved: IterableChangeRecord_<V> = null;
|
||||
/** @internal */
|
||||
_nextRemoved: CollectionChangeRecord = null;
|
||||
_nextRemoved: IterableChangeRecord_<V> = null;
|
||||
/** @internal */
|
||||
_nextAdded: CollectionChangeRecord = null;
|
||||
_nextAdded: IterableChangeRecord_<V> = null;
|
||||
/** @internal */
|
||||
_nextMoved: CollectionChangeRecord = null;
|
||||
_nextMoved: IterableChangeRecord_<V> = null;
|
||||
/** @internal */
|
||||
_nextIdentityChange: CollectionChangeRecord = null;
|
||||
_nextIdentityChange: IterableChangeRecord_<V> = null;
|
||||
|
||||
|
||||
constructor(public item: any, public trackById: any) {}
|
||||
constructor(public item: V, public trackById: any) {}
|
||||
|
||||
toString(): string {
|
||||
return this.previousIndex === this.currentIndex ? stringify(this.item) :
|
||||
@ -624,19 +625,19 @@ export class CollectionChangeRecord {
|
||||
}
|
||||
}
|
||||
|
||||
// A linked list of CollectionChangeRecords with the same CollectionChangeRecord.item
|
||||
class _DuplicateItemRecordList {
|
||||
// A linked list of CollectionChangeRecords with the same IterableChangeRecord_.item
|
||||
class _DuplicateItemRecordList<V> {
|
||||
/** @internal */
|
||||
_head: CollectionChangeRecord = null;
|
||||
_head: IterableChangeRecord_<V> = null;
|
||||
/** @internal */
|
||||
_tail: CollectionChangeRecord = null;
|
||||
_tail: IterableChangeRecord_<V> = null;
|
||||
|
||||
/**
|
||||
* Append the record to the list of duplicates.
|
||||
*
|
||||
* Note: by design all records in the list of duplicates hold the same value in record.item.
|
||||
*/
|
||||
add(record: CollectionChangeRecord): void {
|
||||
add(record: IterableChangeRecord_<V>): void {
|
||||
if (this._head === null) {
|
||||
this._head = this._tail = record;
|
||||
record._nextDup = null;
|
||||
@ -652,10 +653,10 @@ class _DuplicateItemRecordList {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a CollectionChangeRecord having CollectionChangeRecord.trackById == trackById and
|
||||
// CollectionChangeRecord.currentIndex >= afterIndex
|
||||
get(trackById: any, afterIndex: number): CollectionChangeRecord {
|
||||
let record: CollectionChangeRecord;
|
||||
// Returns a IterableChangeRecord_ having IterableChangeRecord_.trackById == trackById and
|
||||
// IterableChangeRecord_.currentIndex >= afterIndex
|
||||
get(trackById: any, afterIndex: number): IterableChangeRecord_<V> {
|
||||
let record: IterableChangeRecord_<V>;
|
||||
for (record = this._head; record !== null; record = record._nextDup) {
|
||||
if ((afterIndex === null || afterIndex < record.currentIndex) &&
|
||||
looseIdentical(record.trackById, trackById)) {
|
||||
@ -666,22 +667,22 @@ class _DuplicateItemRecordList {
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove one {@link CollectionChangeRecord} from the list of duplicates.
|
||||
* Remove one {@link IterableChangeRecord_} from the list of duplicates.
|
||||
*
|
||||
* Returns whether the list of duplicates is empty.
|
||||
*/
|
||||
remove(record: CollectionChangeRecord): boolean {
|
||||
remove(record: IterableChangeRecord_<V>): boolean {
|
||||
// todo(vicb)
|
||||
// assert(() {
|
||||
// // verify that the record being removed is in the list.
|
||||
// for (CollectionChangeRecord cursor = _head; cursor != null; cursor = cursor._nextDup) {
|
||||
// for (IterableChangeRecord_ cursor = _head; cursor != null; cursor = cursor._nextDup) {
|
||||
// if (identical(cursor, record)) return true;
|
||||
// }
|
||||
// return false;
|
||||
//});
|
||||
|
||||
const prev: CollectionChangeRecord = record._prevDup;
|
||||
const next: CollectionChangeRecord = record._nextDup;
|
||||
const prev: IterableChangeRecord_<V> = record._prevDup;
|
||||
const next: IterableChangeRecord_<V> = record._nextDup;
|
||||
if (prev === null) {
|
||||
this._head = next;
|
||||
} else {
|
||||
@ -696,41 +697,41 @@ class _DuplicateItemRecordList {
|
||||
}
|
||||
}
|
||||
|
||||
class _DuplicateMap {
|
||||
map = new Map<any, _DuplicateItemRecordList>();
|
||||
class _DuplicateMap<V> {
|
||||
map = new Map<any, _DuplicateItemRecordList<V>>();
|
||||
|
||||
put(record: CollectionChangeRecord) {
|
||||
put(record: IterableChangeRecord_<V>) {
|
||||
const key = record.trackById;
|
||||
|
||||
let duplicates = this.map.get(key);
|
||||
if (!duplicates) {
|
||||
duplicates = new _DuplicateItemRecordList();
|
||||
duplicates = new _DuplicateItemRecordList<V>();
|
||||
this.map.set(key, duplicates);
|
||||
}
|
||||
duplicates.add(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the `value` using key. Because the CollectionChangeRecord value may be one which we
|
||||
* Retrieve the `value` using key. Because the IterableChangeRecord_ value may be one which we
|
||||
* have already iterated over, we use the afterIndex to pretend it is not there.
|
||||
*
|
||||
* Use case: `[a, b, c, a, a]` if we are at index `3` which is the second `a` then asking if we
|
||||
* have any more `a`s needs to return the last `a` not the first or second.
|
||||
*/
|
||||
get(trackById: any, afterIndex: number = null): CollectionChangeRecord {
|
||||
get(trackById: any, afterIndex: number = null): IterableChangeRecord_<V> {
|
||||
const key = trackById;
|
||||
const recordList = this.map.get(key);
|
||||
return recordList ? recordList.get(trackById, afterIndex) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a {@link CollectionChangeRecord} from the list of duplicates.
|
||||
* Removes a {@link IterableChangeRecord_} from the list of duplicates.
|
||||
*
|
||||
* The list of duplicates also is removed from the map if it gets empty.
|
||||
*/
|
||||
remove(record: CollectionChangeRecord): CollectionChangeRecord {
|
||||
remove(record: IterableChangeRecord_<V>): IterableChangeRecord_<V> {
|
||||
const key = record.trackById;
|
||||
const recordList: _DuplicateItemRecordList = this.map.get(key);
|
||||
const recordList: _DuplicateItemRecordList<V> = this.map.get(key);
|
||||
// Remove the list of duplicates when it gets empty
|
||||
if (recordList.remove(record)) {
|
||||
this.map.delete(key);
|
||||
|
@ -10,62 +10,64 @@ import {StringMapWrapper} from '../../facade/collection';
|
||||
import {isJsObject, looseIdentical, stringify} from '../../facade/lang';
|
||||
import {ChangeDetectorRef} from '../change_detector_ref';
|
||||
|
||||
import {KeyValueDiffer, KeyValueDifferFactory} from './keyvalue_differs';
|
||||
import {KeyValueChangeRecord, KeyValueChanges, KeyValueDiffer, KeyValueDifferFactory} from './keyvalue_differs';
|
||||
|
||||
|
||||
export class DefaultKeyValueDifferFactory implements KeyValueDifferFactory {
|
||||
export class DefaultKeyValueDifferFactory<K, V> implements KeyValueDifferFactory {
|
||||
constructor() {}
|
||||
supports(obj: any): boolean { return obj instanceof Map || isJsObject(obj); }
|
||||
|
||||
create(cdRef: ChangeDetectorRef): KeyValueDiffer { return new DefaultKeyValueDiffer(); }
|
||||
create<K, V>(cdRef: ChangeDetectorRef): KeyValueDiffer<K, V> {
|
||||
return new DefaultKeyValueDiffer<K, V>();
|
||||
}
|
||||
}
|
||||
|
||||
export class DefaultKeyValueDiffer implements KeyValueDiffer {
|
||||
private _records: Map<any, any> = new Map();
|
||||
private _mapHead: KeyValueChangeRecord = null;
|
||||
private _previousMapHead: KeyValueChangeRecord = null;
|
||||
private _changesHead: KeyValueChangeRecord = null;
|
||||
private _changesTail: KeyValueChangeRecord = null;
|
||||
private _additionsHead: KeyValueChangeRecord = null;
|
||||
private _additionsTail: KeyValueChangeRecord = null;
|
||||
private _removalsHead: KeyValueChangeRecord = null;
|
||||
private _removalsTail: KeyValueChangeRecord = null;
|
||||
export class DefaultKeyValueDiffer<K, V> implements KeyValueDiffer<K, V>, KeyValueChanges<K, V> {
|
||||
private _records: Map<K, V> = new Map<K, V>();
|
||||
private _mapHead: KeyValueChangeRecord_<K, V> = null;
|
||||
private _previousMapHead: KeyValueChangeRecord_<K, V> = null;
|
||||
private _changesHead: KeyValueChangeRecord_<K, V> = null;
|
||||
private _changesTail: KeyValueChangeRecord_<K, V> = null;
|
||||
private _additionsHead: KeyValueChangeRecord_<K, V> = null;
|
||||
private _additionsTail: KeyValueChangeRecord_<K, V> = null;
|
||||
private _removalsHead: KeyValueChangeRecord_<K, V> = null;
|
||||
private _removalsTail: KeyValueChangeRecord_<K, V> = null;
|
||||
|
||||
get isDirty(): boolean {
|
||||
return this._additionsHead !== null || this._changesHead !== null ||
|
||||
this._removalsHead !== null;
|
||||
}
|
||||
|
||||
forEachItem(fn: (r: KeyValueChangeRecord) => void) {
|
||||
let record: KeyValueChangeRecord;
|
||||
forEachItem(fn: (r: KeyValueChangeRecord<K, V>) => void) {
|
||||
let record: KeyValueChangeRecord_<K, V>;
|
||||
for (record = this._mapHead; record !== null; record = record._next) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachPreviousItem(fn: (r: KeyValueChangeRecord) => void) {
|
||||
let record: KeyValueChangeRecord;
|
||||
forEachPreviousItem(fn: (r: KeyValueChangeRecord<K, V>) => void) {
|
||||
let record: KeyValueChangeRecord_<K, V>;
|
||||
for (record = this._previousMapHead; record !== null; record = record._nextPrevious) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachChangedItem(fn: (r: KeyValueChangeRecord) => void) {
|
||||
let record: KeyValueChangeRecord;
|
||||
forEachChangedItem(fn: (r: KeyValueChangeRecord<K, V>) => void) {
|
||||
let record: KeyValueChangeRecord_<K, V>;
|
||||
for (record = this._changesHead; record !== null; record = record._nextChanged) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachAddedItem(fn: (r: KeyValueChangeRecord) => void) {
|
||||
let record: KeyValueChangeRecord;
|
||||
forEachAddedItem(fn: (r: KeyValueChangeRecord<K, V>) => void) {
|
||||
let record: KeyValueChangeRecord_<K, V>;
|
||||
for (record = this._additionsHead; record !== null; record = record._nextAdded) {
|
||||
fn(record);
|
||||
}
|
||||
}
|
||||
|
||||
forEachRemovedItem(fn: (r: KeyValueChangeRecord) => void) {
|
||||
let record: KeyValueChangeRecord;
|
||||
forEachRemovedItem(fn: (r: KeyValueChangeRecord<K, V>) => void) {
|
||||
let record: KeyValueChangeRecord_<K, V>;
|
||||
for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
|
||||
fn(record);
|
||||
}
|
||||
@ -86,9 +88,9 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer {
|
||||
check(map: Map<any, any>|{[k: string]: any}): boolean {
|
||||
this._reset();
|
||||
const records = this._records;
|
||||
let oldSeqRecord: KeyValueChangeRecord = this._mapHead;
|
||||
let lastOldSeqRecord: KeyValueChangeRecord = null;
|
||||
let lastNewSeqRecord: KeyValueChangeRecord = null;
|
||||
let oldSeqRecord: KeyValueChangeRecord_<K, V> = this._mapHead;
|
||||
let lastOldSeqRecord: KeyValueChangeRecord_<K, V> = null;
|
||||
let lastNewSeqRecord: KeyValueChangeRecord_<K, V> = null;
|
||||
let seqChanged: boolean = false;
|
||||
|
||||
this._forEach(map, (value: any, key: any) => {
|
||||
@ -106,7 +108,7 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer {
|
||||
newSeqRecord = records.get(key);
|
||||
this._maybeAddToChanges(newSeqRecord, value);
|
||||
} else {
|
||||
newSeqRecord = new KeyValueChangeRecord(key);
|
||||
newSeqRecord = new KeyValueChangeRecord_<K, V>(key);
|
||||
records.set(key, newSeqRecord);
|
||||
newSeqRecord.currentValue = value;
|
||||
this._addToAdditions(newSeqRecord);
|
||||
@ -134,7 +136,7 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer {
|
||||
/** @internal */
|
||||
_reset() {
|
||||
if (this.isDirty) {
|
||||
let record: KeyValueChangeRecord;
|
||||
let record: KeyValueChangeRecord_<K, V>;
|
||||
// Record the state of the mapping
|
||||
for (record = this._previousMapHead = this._mapHead; record !== null; record = record._next) {
|
||||
record._nextPrevious = record._next;
|
||||
@ -154,8 +156,7 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer {
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_truncate(lastRecord: KeyValueChangeRecord, record: KeyValueChangeRecord) {
|
||||
private _truncate(lastRecord: KeyValueChangeRecord_<K, V>, record: KeyValueChangeRecord_<K, V>) {
|
||||
while (record !== null) {
|
||||
if (lastRecord === null) {
|
||||
this._mapHead = null;
|
||||
@ -168,14 +169,15 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer {
|
||||
record = nextRecord;
|
||||
}
|
||||
|
||||
for (let rec: KeyValueChangeRecord = this._removalsHead; rec !== null; rec = rec._nextRemoved) {
|
||||
for (let rec: KeyValueChangeRecord_<K, V> = this._removalsHead; rec !== null;
|
||||
rec = rec._nextRemoved) {
|
||||
rec.previousValue = rec.currentValue;
|
||||
rec.currentValue = null;
|
||||
this._records.delete(rec.key);
|
||||
}
|
||||
}
|
||||
|
||||
private _maybeAddToChanges(record: KeyValueChangeRecord, newValue: any): void {
|
||||
private _maybeAddToChanges(record: KeyValueChangeRecord_<K, V>, newValue: any): void {
|
||||
if (!looseIdentical(newValue, record.currentValue)) {
|
||||
record.previousValue = record.currentValue;
|
||||
record.currentValue = newValue;
|
||||
@ -183,14 +185,12 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer {
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_isInRemovals(record: KeyValueChangeRecord) {
|
||||
private _isInRemovals(record: KeyValueChangeRecord_<K, V>) {
|
||||
return record === this._removalsHead || record._nextRemoved !== null ||
|
||||
record._prevRemoved !== null;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_addToRemovals(record: KeyValueChangeRecord) {
|
||||
private _addToRemovals(record: KeyValueChangeRecord_<K, V>) {
|
||||
if (this._removalsHead === null) {
|
||||
this._removalsHead = this._removalsTail = record;
|
||||
} else {
|
||||
@ -200,8 +200,7 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer {
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_removeFromSeq(prev: KeyValueChangeRecord, record: KeyValueChangeRecord) {
|
||||
private _removeFromSeq(prev: KeyValueChangeRecord_<K, V>, record: KeyValueChangeRecord_<K, V>) {
|
||||
const next = record._next;
|
||||
if (prev === null) {
|
||||
this._mapHead = next;
|
||||
@ -211,8 +210,7 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer {
|
||||
record._next = null;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_removeFromRemovals(record: KeyValueChangeRecord) {
|
||||
private _removeFromRemovals(record: KeyValueChangeRecord_<K, V>) {
|
||||
const prev = record._prevRemoved;
|
||||
const next = record._nextRemoved;
|
||||
if (prev === null) {
|
||||
@ -228,8 +226,7 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer {
|
||||
record._prevRemoved = record._nextRemoved = null;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_addToAdditions(record: KeyValueChangeRecord) {
|
||||
private _addToAdditions(record: KeyValueChangeRecord_<K, V>) {
|
||||
if (this._additionsHead === null) {
|
||||
this._additionsHead = this._additionsTail = record;
|
||||
} else {
|
||||
@ -238,8 +235,7 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer {
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_addToChanges(record: KeyValueChangeRecord) {
|
||||
private _addToChanges(record: KeyValueChangeRecord_<K, V>) {
|
||||
if (this._changesHead === null) {
|
||||
this._changesHead = this._changesTail = record;
|
||||
} else {
|
||||
@ -254,7 +250,7 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer {
|
||||
const changes: any[] = [];
|
||||
const additions: any[] = [];
|
||||
const removals: any[] = [];
|
||||
let record: KeyValueChangeRecord;
|
||||
let record: KeyValueChangeRecord_<K, V>;
|
||||
|
||||
for (record = this._mapHead; record !== null; record = record._next) {
|
||||
items.push(stringify(record));
|
||||
@ -293,24 +289,24 @@ export class DefaultKeyValueDiffer implements KeyValueDiffer {
|
||||
/**
|
||||
* @stable
|
||||
*/
|
||||
export class KeyValueChangeRecord {
|
||||
previousValue: any = null;
|
||||
currentValue: any = null;
|
||||
class KeyValueChangeRecord_<K, V> implements KeyValueChangeRecord<K, V> {
|
||||
previousValue: V = null;
|
||||
currentValue: V = null;
|
||||
|
||||
/** @internal */
|
||||
_nextPrevious: KeyValueChangeRecord = null;
|
||||
_nextPrevious: KeyValueChangeRecord_<K, V> = null;
|
||||
/** @internal */
|
||||
_next: KeyValueChangeRecord = null;
|
||||
_next: KeyValueChangeRecord_<K, V> = null;
|
||||
/** @internal */
|
||||
_nextAdded: KeyValueChangeRecord = null;
|
||||
_nextAdded: KeyValueChangeRecord_<K, V> = null;
|
||||
/** @internal */
|
||||
_nextRemoved: KeyValueChangeRecord = null;
|
||||
_nextRemoved: KeyValueChangeRecord_<K, V> = null;
|
||||
/** @internal */
|
||||
_prevRemoved: KeyValueChangeRecord = null;
|
||||
_prevRemoved: KeyValueChangeRecord_<K, V> = null;
|
||||
/** @internal */
|
||||
_nextChanged: KeyValueChangeRecord = null;
|
||||
_nextChanged: KeyValueChangeRecord_<K, V> = null;
|
||||
|
||||
constructor(public key: any) {}
|
||||
constructor(public key: K) {}
|
||||
|
||||
toString(): string {
|
||||
return looseIdentical(this.previousValue, this.currentValue) ?
|
||||
|
@ -12,16 +12,105 @@ import {ChangeDetectorRef} from '../change_detector_ref';
|
||||
|
||||
|
||||
/**
|
||||
* A strategy for tracking changes over time to an iterable. Used for {@link NgFor} to
|
||||
* A strategy for tracking changes over time to an iterable. Used by {@link NgFor} to
|
||||
* respond to changes in an iterable by effecting equivalent changes in the DOM.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export interface IterableDiffer {
|
||||
diff(object: any): any;
|
||||
onDestroy(): any /** TODO #9100 */;
|
||||
export interface IterableDiffer<V> {
|
||||
/**
|
||||
* Compute a difference between the previous state and the new `object` state.
|
||||
*
|
||||
* @param object containing the new value.
|
||||
* @returns an object describing the difference. The return value is only valid until the next
|
||||
* `diff()` invocation.
|
||||
*/
|
||||
diff(object: V[]|Set<V>|any /* |Iterable<V> */): IterableChanges<V>;
|
||||
// TODO(misko): We can't use Iterable as it would break the .d.ts file for ES5 users.
|
||||
// Iterable is not apart of @types/es6-collections since it requires Symbol.
|
||||
}
|
||||
|
||||
/**
|
||||
* An object describing the changes in the `Iterable` collection since last time
|
||||
* `IterableDiffer#diff()` was invoked.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export interface IterableChanges<V> {
|
||||
/**
|
||||
* Iterate over all changes. `IterableChangeRecord` will contain information about changes
|
||||
* to each item.
|
||||
*/
|
||||
forEachItem(fn: (record: IterableChangeRecord<V>) => void): void;
|
||||
|
||||
/**
|
||||
* Iterate over a set of operations which when applied to the original `Iterable` will produce the
|
||||
* new `Iterable`.
|
||||
*
|
||||
* NOTE: These are not necessarily the actual operations which were applied to the original
|
||||
* `Iterable`, rather these are a set of computed operations which may not be the same as the
|
||||
* ones applied.
|
||||
*
|
||||
* @param record A change which needs to be applied
|
||||
* @param previousIndex The `IterableChangeRecord#previousIndex` of the `record` refers to the
|
||||
* original `Iterable` location, where as `previousIndex` refers to the transient location
|
||||
* of the item, after applying the operations up to this point.
|
||||
* @param currentIndex The `IterableChangeRecord#currentIndex` of the `record` refers to the
|
||||
* original `Iterable` location, where as `currentIndex` refers to the transient location
|
||||
* of the item, after applying the operations up to this point.
|
||||
*/
|
||||
forEachOperation(
|
||||
fn: (record: IterableChangeRecord<V>, previousIndex: number, currentIndex: number) => void):
|
||||
void;
|
||||
|
||||
/**
|
||||
* Iterate over changes in the order of original `Iterable` showing where the original items
|
||||
* have moved.
|
||||
*/
|
||||
forEachPreviousItem(fn: (record: IterableChangeRecord<V>) => void): void;
|
||||
|
||||
/** Iterate over all added items. */
|
||||
forEachAddedItem(fn: (record: IterableChangeRecord<V>) => void): void;
|
||||
|
||||
/** Iterate over all moved items. */
|
||||
forEachMovedItem(fn: (record: IterableChangeRecord<V>) => void): void;
|
||||
|
||||
/** Iterate over all removed items. */
|
||||
forEachRemovedItem(fn: (record: IterableChangeRecord<V>) => void): void;
|
||||
|
||||
/** Iterate over all items which had their identity (as computed by the `trackByFn`) changed. */
|
||||
forEachIdentityChange(fn: (record: IterableChangeRecord<V>) => void): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record representing the item change information.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export interface IterableChangeRecord<V> {
|
||||
/** Current index of the item in `Iterable` or null if removed. */
|
||||
// TODO(TS2.1): make readonly once we move to TS v2.1
|
||||
/* readonly */ currentIndex: number;
|
||||
|
||||
/** Previous index of the item in `Iterable` or null if added. */
|
||||
// TODO(TS2.1): make readonly once we move to TS v2.1
|
||||
/* readonly */ previousIndex: number;
|
||||
|
||||
/** The item. */
|
||||
// TODO(TS2.1): make readonly once we move to TS v2.1
|
||||
/* readonly */ item: V;
|
||||
|
||||
/** Track by identity as computed by the `trackByFn`. */
|
||||
// TODO(TS2.1): make readonly once we move to TS v2.1
|
||||
/* readonly */ trackById: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated v4.0.0 - Use IterableChangeRecord instead.
|
||||
*/
|
||||
export interface CollectionChangeRecord<V> extends IterableChangeRecord<V> {}
|
||||
|
||||
|
||||
/**
|
||||
* An optional function passed into {@link NgFor} that defines how to track
|
||||
* items in an iterable (e.g. by index or id)
|
||||
@ -38,7 +127,7 @@ export interface TrackByFn { (index: number, item: any): any; }
|
||||
*/
|
||||
export interface IterableDifferFactory {
|
||||
supports(objects: any): boolean;
|
||||
create(cdRef: ChangeDetectorRef, trackByFn?: TrackByFn): IterableDiffer;
|
||||
create<V>(cdRef: ChangeDetectorRef, trackByFn?: TrackByFn): IterableDiffer<V>;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,7 +135,11 @@ export interface IterableDifferFactory {
|
||||
* @stable
|
||||
*/
|
||||
export class IterableDiffers {
|
||||
constructor(public factories: IterableDifferFactory[]) {}
|
||||
/**
|
||||
* @deprecated v4.0.0 - Should be private
|
||||
*/
|
||||
factories: IterableDifferFactory[];
|
||||
constructor(factories: IterableDifferFactory[]) { this.factories = factories; }
|
||||
|
||||
static create(factories: IterableDifferFactory[], parent?: IterableDiffers): IterableDiffers {
|
||||
if (isPresent(parent)) {
|
||||
@ -64,8 +157,8 @@ export class IterableDiffers {
|
||||
* {@link IterableDiffers} instance.
|
||||
*
|
||||
* The following example shows how to extend an existing list of factories,
|
||||
* which will only be applied to the injector for this component and its children.
|
||||
* This step is all that's required to make a new {@link IterableDiffer} available.
|
||||
* which will only be applied to the injector for this component and its children.
|
||||
* This step is all that's required to make a new {@link IterableDiffer} available.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
|
@ -7,8 +7,6 @@
|
||||
*/
|
||||
|
||||
import {Optional, Provider, SkipSelf} from '../../di';
|
||||
import {ListWrapper} from '../../facade/collection';
|
||||
import {isPresent} from '../../facade/lang';
|
||||
import {ChangeDetectorRef} from '../change_detector_ref';
|
||||
|
||||
|
||||
@ -17,9 +15,83 @@ import {ChangeDetectorRef} from '../change_detector_ref';
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export interface KeyValueDiffer {
|
||||
diff(object: any): any /** TODO #9100 */;
|
||||
onDestroy(): any /** TODO #9100 */;
|
||||
export interface KeyValueDiffer<K, V> {
|
||||
/**
|
||||
* Compute a difference between the previous state and the new `object` state.
|
||||
*
|
||||
* @param object containing the new value.
|
||||
* @returns an object describing the difference. The return value is only valid until the next
|
||||
* `diff()` invocation.
|
||||
*/
|
||||
diff(object: Map<K, V>): KeyValueChanges<K, V>;
|
||||
|
||||
/**
|
||||
* Compute a difference between the previous state and the new `object` state.
|
||||
*
|
||||
* @param object containing the new value.
|
||||
* @returns an object describing the difference. The return value is only valid until the next
|
||||
* `diff()` invocation.
|
||||
*/
|
||||
diff(object: {[key: string]: V}): KeyValueChanges<string, V>;
|
||||
// TODO(TS2.1): diff<KP extends string>(this: KeyValueDiffer<KP, V>, object: Record<KP, V>):
|
||||
// KeyValueDiffer<KP, V>;
|
||||
}
|
||||
|
||||
/**
|
||||
* An object describing the changes in the `Map` or `{[k:string]: string}` since last time
|
||||
* `KeyValueDiffer#diff()` was invoked.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export interface KeyValueChanges<K, V> {
|
||||
/**
|
||||
* Iterate over all changes. `KeyValueChangeRecord` will contain information about changes
|
||||
* to each item.
|
||||
*/
|
||||
forEachItem(fn: (r: KeyValueChangeRecord<K, V>) => void): void;
|
||||
|
||||
/**
|
||||
* Iterate over changes in the order of original Map showing where the original items
|
||||
* have moved.
|
||||
*/
|
||||
forEachPreviousItem(fn: (r: KeyValueChangeRecord<K, V>) => void): void;
|
||||
|
||||
/**
|
||||
* Iterate over all keys for which values have changed.
|
||||
*/
|
||||
forEachChangedItem(fn: (r: KeyValueChangeRecord<K, V>) => void): void;
|
||||
|
||||
/**
|
||||
* Iterate over all added items.
|
||||
*/
|
||||
forEachAddedItem(fn: (r: KeyValueChangeRecord<K, V>) => void): void;
|
||||
|
||||
/**
|
||||
* Iterate over all removed items.
|
||||
*/
|
||||
forEachRemovedItem(fn: (r: KeyValueChangeRecord<K, V>) => void): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record representing the item change information.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export interface KeyValueChangeRecord<K, V> {
|
||||
/**
|
||||
* Current key in the Map.
|
||||
*/
|
||||
/* readonly */ key: K;
|
||||
|
||||
/**
|
||||
* Current value for the key or `undefined` if removed.
|
||||
*/
|
||||
/* readonly */ currentValue: V;
|
||||
|
||||
/**
|
||||
* Previous value for the key or `undefined` if added.
|
||||
*/
|
||||
/* readonly */ previousValue: V;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -28,8 +100,15 @@ export interface KeyValueDiffer {
|
||||
* @stable
|
||||
*/
|
||||
export interface KeyValueDifferFactory {
|
||||
/**
|
||||
* Test to see if the differ knows how to diff this kind of object.
|
||||
*/
|
||||
supports(objects: any): boolean;
|
||||
create(cdRef: ChangeDetectorRef): KeyValueDiffer;
|
||||
|
||||
/**
|
||||
* Create a `KeyValueDiffer`.
|
||||
*/
|
||||
create<K, V>(cdRef: ChangeDetectorRef): KeyValueDiffer<K, V>;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,16 +116,19 @@ export interface KeyValueDifferFactory {
|
||||
* @stable
|
||||
*/
|
||||
export class KeyValueDiffers {
|
||||
constructor(public factories: KeyValueDifferFactory[]) {}
|
||||
/**
|
||||
* @deprecated v4.0.0 - Should be private.
|
||||
*/
|
||||
factories: KeyValueDifferFactory[];
|
||||
|
||||
static create(factories: KeyValueDifferFactory[], parent?: KeyValueDiffers): KeyValueDiffers {
|
||||
if (isPresent(parent)) {
|
||||
constructor(factories: KeyValueDifferFactory[]) { this.factories = factories; }
|
||||
|
||||
static create<S>(factories: KeyValueDifferFactory[], parent?: KeyValueDiffers): KeyValueDiffers {
|
||||
if (parent) {
|
||||
const copied = parent.factories.slice();
|
||||
factories = factories.concat(copied);
|
||||
return new KeyValueDiffers(factories);
|
||||
} else {
|
||||
return new KeyValueDiffers(factories);
|
||||
}
|
||||
return new KeyValueDiffers(factories);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -68,14 +150,13 @@ export class KeyValueDiffers {
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
static extend(factories: KeyValueDifferFactory[]): Provider {
|
||||
static extend<S>(factories: KeyValueDifferFactory[]): Provider {
|
||||
return {
|
||||
provide: KeyValueDiffers,
|
||||
useFactory: (parent: KeyValueDiffers) => {
|
||||
if (!parent) {
|
||||
// Typically would occur when calling KeyValueDiffers.extend inside of dependencies passed
|
||||
// to
|
||||
// bootstrap(), which would override default pipes instead of extending them.
|
||||
// to bootstrap(), which would override default pipes instead of extending them.
|
||||
throw new Error('Cannot extend KeyValueDiffers without a parent injector');
|
||||
}
|
||||
return KeyValueDiffers.create(factories, parent);
|
||||
@ -85,12 +166,11 @@ export class KeyValueDiffers {
|
||||
};
|
||||
}
|
||||
|
||||
find(kv: Object): KeyValueDifferFactory {
|
||||
find(kv: any): KeyValueDifferFactory {
|
||||
const factory = this.factories.find(f => f.supports(kv));
|
||||
if (isPresent(factory)) {
|
||||
if (factory) {
|
||||
return factory;
|
||||
} else {
|
||||
throw new Error(`Cannot find a differ supporting object '${kv}'`);
|
||||
}
|
||||
throw new Error(`Cannot find a differ supporting object '${kv}'`);
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,6 @@ export const __core_private__: {
|
||||
StaticNodeDebugInfo: typeof debug_context.StaticNodeDebugInfo,
|
||||
_StaticNodeDebugInfo?: debug_context.StaticNodeDebugInfo,
|
||||
devModeEqual: typeof change_detection_util.devModeEqual,
|
||||
UNINITIALIZED: typeof change_detection_util.UNINITIALIZED,
|
||||
ValueUnwrapper: typeof change_detection_util.ValueUnwrapper,
|
||||
_ValueUnwrapper?: change_detection_util.ValueUnwrapper,
|
||||
RenderDebugInfo: typeof api.RenderDebugInfo,
|
||||
@ -130,7 +129,6 @@ export const __core_private__: {
|
||||
DebugContext: debug_context.DebugContext,
|
||||
StaticNodeDebugInfo: debug_context.StaticNodeDebugInfo,
|
||||
devModeEqual: change_detection_util.devModeEqual,
|
||||
UNINITIALIZED: change_detection_util.UNINITIALIZED,
|
||||
ValueUnwrapper: change_detection_util.ValueUnwrapper,
|
||||
RenderDebugInfo: api.RenderDebugInfo,
|
||||
TemplateRef_: template_ref.TemplateRef_,
|
||||
|
@ -6,8 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Injectable} from './metadata';
|
||||
|
||||
/**
|
||||
* Creates a token that can be used in a DI Provider.
|
||||
*
|
||||
@ -30,7 +28,6 @@ import {Injectable} from './metadata';
|
||||
* error messages.
|
||||
* @stable
|
||||
*/
|
||||
@Injectable() // so that metadata is gathered for this class
|
||||
export class OpaqueToken {
|
||||
constructor(private _desc: string) {}
|
||||
|
||||
|
@ -89,9 +89,12 @@ export class ComponentRef_<C> extends ComponentRef<C> {
|
||||
* @stable
|
||||
*/
|
||||
export class ComponentFactory<C> {
|
||||
/** @internal */
|
||||
_viewClass: Type<AppView<any>>;
|
||||
constructor(
|
||||
public selector: string, private _viewClass: Type<AppView<any>>,
|
||||
private _componentType: Type<any>) {}
|
||||
public selector: string, _viewClass: Type<AppView<any>>, private _componentType: Type<any>) {
|
||||
this._viewClass = _viewClass;
|
||||
}
|
||||
|
||||
get componentType(): Type<any> { return this._componentType; }
|
||||
|
||||
|
@ -6,7 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {UNINITIALIZED} from '../change_detection/change_detection_util';
|
||||
import {BaseError, WrappedError} from '../facade/errors';
|
||||
|
||||
import {DebugContext} from './debug_context';
|
||||
@ -45,10 +44,10 @@ import {DebugContext} from './debug_context';
|
||||
* @stable
|
||||
*/
|
||||
export class ExpressionChangedAfterItHasBeenCheckedError extends BaseError {
|
||||
constructor(oldValue: any, currValue: any) {
|
||||
constructor(oldValue: any, currValue: any, isFirstCheck: boolean) {
|
||||
let msg =
|
||||
`Expression has changed after it was checked. Previous value: '${oldValue}'. Current value: '${currValue}'.`;
|
||||
if (oldValue === UNINITIALIZED) {
|
||||
if (isFirstCheck) {
|
||||
msg +=
|
||||
` It seems like the view has been created after its parent and its children have been dirty checked.` +
|
||||
` Has it been created in a change detection hook ?`;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user