Compare commits

..

81 Commits

Author SHA1 Message Date
d07c288e2e docs: update the release schedule 2017-12-20 14:21:30 -08:00
abca7c0243 docs: add changelog for 5.2.0-beta.1 2017-12-20 13:42:24 -08:00
1f5256e745 release: cut the 5.1.0-beta.1 release 2017-12-20 13:39:58 -08:00
6990354047 docs: add changelog for 5.1.2 2017-12-20 13:39:37 -08:00
135ead6c97 ci(router): update the public API guard for the router
This fixes a badly applied revert earlier.
2017-12-20 11:54:22 -08:00
33c0ee3441 fix(compiler): report an error for recursive module references
Modules that directly or indirectly export or imported themselves
reports an error instead of generating a stack fault.

Fixes: #19979
2017-12-20 10:54:45 -08:00
5efea2f6a0 feat(router): add "paramsInheritanceStrategy" router configuration option
Previously, the router would merge path and matrix params, as well as
data/resolve, with special rules (only merging down when the route has
an empty path, or is component-less). This change adds an extra option
"paramsInheritanceStrategy" which, when set to 'always', makes child
routes unconditionally inherit params from parent routes.

Closes #20572.
2017-12-20 10:06:24 -08:00
5f23a1223f fix(compiler-cli): do not force type checking on .js files
The compiler host would force any file that is in node_modules
into the list of files that needed to be type checked which
captures .js files if `allowJs` is set to `true`. This should
have only forced .d.ts files into the project to enable
generation of factories.

Fixes: #19757
2017-12-20 10:01:10 -08:00
30208759cd fix(compiler-cli): do not emit invalid .metadata.json files
If no metadata is collected the `ngc` would generate file
that contained `[null]` instead of eliding the `.metadata.json`
file.

Fixes: #20479
2017-12-20 09:58:36 -08:00
e4c53f8529 Revert "feat(router): add a function set up router sync when used with downgradeModule"
This reverts commit f5bb999319. The commit
does not include proper tests.
2017-12-20 09:15:15 -08:00
b61e3e9d20 Revert "fix(router): replaceURL when reacting to a change coming from angularjs"
This reverts commit 0b2d636b75. The commit
does not include proper tests.
2017-12-20 09:14:27 -08:00
f593552cfe docs(aio): Rename service worker files, update examples, move service worker under Techniques 2017-12-19 11:07:57 -08:00
8458647232 docs(aio): fix inconsistency in lifecycle hooks table 2017-12-19 10:48:29 -08:00
a693c5614c ci: add router/testing to public API guard 2017-12-19 10:45:33 -08:00
8ceffd8b48 fix(aio): improve transitions between pages
- Avoid unnecessary animations, style transitions, repositioning on
  initial rendering.
- Better handle transitioning from/to Home page (which is the only page
  with transparent top-menu).
- Better coordinate sidenav and hamburger animations with page
  transitions.
- Improve fade-in/out animations.

Fixes #20996
2017-12-19 10:45:19 -08:00
2986e25abb refactor(aio): clean up top-menu CSS
- Clean-up and re-organize top-menu styles.
- Clean-up and merge hamburger styles into top-menu styles.
2017-12-19 10:45:19 -08:00
f8fe53aeb0 feat(aio): support disabling DocViewer animations via class 2017-12-19 10:45:19 -08:00
74e3115686 build(aio): make zipper work correctly with CLI projects 2017-12-19 10:44:56 -08:00
3846f19f22 docs(core): move core examples into examples/core/ directory
This allows examples to be found during aio's `yarn serve-and-sync`, which only
looks for examples in `packages/examples/<packageName>/**/*`, where
`packageName` is the name of the package that the modified file belonged to;
e.g. `core`, `common`, etc.).
2017-12-18 12:10:29 -08:00
057513536b fix(upgrade): replaces get/setAngularLib with get/setAngularJSGlobal
The current names are confusing because Angular should refer to the latest version of the framework.
2017-12-18 12:10:01 -08:00
82bcd83566 feat(compiler): allow ngIf to use the ngIf expression directly as a guard
Allows a directive to use the expression passed directly to a property
as a guard instead of filtering the type through a type expression.

This more accurately matches the intent of the ngIf usage of its template
enabling better type inference.

Moved NgIf to using this type of guard instead of a function guard.

Closes: #20967
2017-12-18 12:09:21 -08:00
e48f477477 ci: temporarily remove wardbell from pullapprove (2FA) 2017-12-18 08:41:44 -08:00
20e1cc049f fix(service-worker): check for updates on navigation
Currently the Service Worker checks for updates only on SW startup,
an event which happens frequently but also nondeterministically. This
makes it hard for developers to observe the update process or reason
about how updates will be delivered to users. This problem is
exacerbated by the DevTools behavior of keeping the SW alive
indefinitely while opened, effectively preventing the page from
updating at all.

This change causes the SW to additionally check for updates on
navigation requests (app page reloads). This creates deterministic
update behavior, and is much easier for developers to reason about.
It does leave the old update-on-SW-startup behavior in place, as
removing that would be a breaking change.

Fixes #20877
2017-12-15 15:19:20 -08:00
0b2d636b75 fix(router): replaceURL when reacting to a change coming from angularjs
Closes: #20549
2017-12-15 07:59:56 -08:00
f5bb999319 feat(router): add a function set up router sync when used with downgradeModule 2017-12-15 07:59:56 -08:00
a4742763b9 test(aio): correct usage of fakeAsync and inject together in test
Corrects a test which wrapped the fakeAsync call in an inject call.  This caused
the test to not actually run.
2017-12-15 07:55:09 -08:00
05ff6c09ca fix(compiler): make tsx file aot compatible
fixes #20555
2017-12-15 07:53:46 -08:00
d91ff17adc fix(compiler): generate the correct imports for summary type-check
Summaries should be ignored when importing the types used in a
type-check block.
2017-12-15 07:53:11 -08:00
d213a20dfc fix(forms): avoid producing an error with hostBindingTypeCheck
Using the default value accessor no longer produces errors when
used in combination with fullTemplateTypeCheck and hostBindingTypeCheck.

Fixes: #19905
2017-12-15 07:52:52 -08:00
2e7e935b02 fix(common): fix a Closure compilation issue.
Closure Compiler cannot infer that the swtich statement is exhaustive,
which causes it to complain that the method does not always return a
value.

Work around the problem by throwing an exception in the default case,
and using the `: never` type to ensure the code is unreachable.
2017-12-15 07:51:30 -08:00
b89e7c2cb7 ci(aio): move e2e tests to non-optional job
This essentially reverts #20178, since the flakes should be gone after
pinning ChromeDriver and Chrome versions to 2.32 and 59 respectively.
2017-12-14 08:48:52 -08:00
b4db2e25d6 ci: downgrade Chromium to a version that does not cause flakes
There seems to be some issue that causes Chrome/ChromeDriver to
unexpectedly reload during the aio e2e tests, causing flakes. It is not
clear what exactly is causing the reloading, but to the best of my
knowledge it is something inside Chrome or ChromeDriver.

Pinning Chrome to r494239 (between 62.0.3185.0 and 62.0.3186.0) fixes
the flakes.

Fixes #20159
2017-12-14 08:48:52 -08:00
a33eaf6e07 test(aio): disable DocViewer animations during e2e tests 2017-12-14 08:48:52 -08:00
0d47c39609 test(aio): fix and clean up e2e tests 2017-12-14 08:48:52 -08:00
cbe7e39bbe build(common): don't generate .d.ts & .metadata.json files for i18n locales
Fixes #20880
2017-12-14 08:29:36 -08:00
6d57cb04f6 ci: parallelize bazel build and test
The current setup can cause a de-optimization where unit tests can't start running until the slowest build target completes.
2017-12-14 08:28:52 -08:00
6e2a8a2ba4 docs: add changelog for 5.2.0-beta.0 2017-12-13 11:43:12 -08:00
7874697b6c release: cut the 5.2.0-beta.0 release 2017-12-13 11:39:44 -08:00
767141761a docs: add changelog for 5.1.1 2017-12-13 11:36:25 -08:00
b3eb1db6dd build: update node version number in .nvmrc (#20832)
PR Close #20832
2017-12-12 11:56:18 -08:00
ee0dab025b docs: update DEVELOPER.md with the node and yarn info (#20832)
I intentionally removed version numbers so that we don't need to update them in this file -
because we usually forget.

PR Close #20832
2017-12-12 11:56:18 -08:00
b7738e1fe5 feat(core): add source to StaticInjectorError message (#20817)
Closes #19302
PR Close #20817
2017-12-12 11:56:06 -08:00
634d33f5dd fix(compiler): support referencing enums in namespaces (#20947)
Due to an overly agressive assert the compiler would generate
an internal error when referencing an enum declared in
namspace.

Fixes #18170

PR Close #20947
2017-12-12 11:55:55 -08:00
3401283399 ci: clean up circleci/config.yml (#20954)
PR Close #20954
2017-12-12 11:55:44 -08:00
981947d104 ci: allow me to approve circleCI changes (#20957)
Removes the root group from the pullapprove settings for .circleci/*
PR Close #20957
2017-12-12 11:55:35 -08:00
8c52088346 fix(compiler-cli): merge @fileoverview comments. (#20870)
Previously, this code would unconditionally add a @fileoverview
comment to generated files, and only if the contained any code at all.

However often existing fileoverview comments should be copied from the
file the generated file was originally based off of. This allows users
to e.g. include Closure Compiler directives in their original
`component.ts` file, which will then automaticallly also apply to code
generated from it.

This special cases `@license` comments, as Closure disregards directives
in comments containing `@license`.

PR Close #20870
2017-12-12 11:37:55 -08:00
add3589451 ci: use container version in cache key (#20952)
PR Close #20952
2017-12-11 16:07:28 -08:00
81d497ce1f build: pin ChromeDriver version (#20940)
Since our version of Chromium is also pinned, a new ChromeDriver (that
drops support for our Chromium version) can cause random (and unrelated
to the corresponding changes) errors on CI.
This commit pins the version of ChromeDriver and it should now be
manually upgraded to a vrsion that is compatible with th currently used
Chromium version.

PR Close #20940
2017-12-11 15:53:04 -08:00
70cd124ede feat(compiler): add a pseudo $any() function to disable type checking (#20876)
`$any()` can now be used in a binding expression to disable type
checking for the rest of the expression. This similar to `as any` in
TypeScript and allows expression that work at runtime but do not
type-check.

PR Close #20876
2017-12-11 14:34:38 -08:00
7363b3d4b5 build: remove bazel option --noshow_results (#20943)
I originally added this when I was trying to build `//packages/core`, which is not what users will do often.
This makes it harder for team members to understand what Bazel is doing. I find myself suggesting to turn it off, so it's better to just remove it.
PR Close #20943
2017-12-11 11:16:59 -08:00
f05937db4d fix(bazel): don't equate moduleName with fileName (#20895)
Fixes broken material build.
/cc @jelbourn
PR Close #20895
2017-12-11 11:16:49 -08:00
d684f55423 build: require bazel 0.8 (#20897)
Users should get an error if they are running an older version of Bazel than we have on CI.
PR Close #20897
2017-12-11 11:16:41 -08:00
db06cb170f build: new docker image, faster to boot on circleci 2017-12-11 11:16:29 -08:00
77a1f9f2e8 ci: print the buildifier command when BUILD lint fails (#20882)
PR Close #20882
2017-12-11 11:15:47 -08:00
13e663c232 fix(animations): ensure multi-level route leave animations are queryable (#20787)
Closes #19807

PR Close #20787
2017-12-08 13:44:01 -08:00
d098cf5a8b ci: remove obsolete chromedriverpatch (#18515)
Dart(ium) is not used anymore in Angular so this file should be obsolete

PR Close #18515
2017-12-08 13:43:55 -08:00
3ce3b4d2af refactor(common): update i18n locale data to CLDR v32 (#20830)
List of changes between v31.0.1 and v32: http://cldr.unicode.org/index/downloads/cldr-32
PR Close #20830
2017-12-08 10:24:33 -08:00
e7d9cb3e4c feat(compiler): narrow types of expressions used in *ngIf (#20702)
Structural directives can now specify a type guard that describes
what types can be inferred for an input expression inside the
directive's template.

NgIf was modified to declare an input guard on ngIf.

After this change, `fullTemplateTypeCheck` will infer that
usage of `ngIf` expression inside it's template is truthy.

For example, if a component has a property `person?: Person`
and a template of `<div *ngIf="person"> {{person.name}} </div>`
the compiler will no longer report that `person` might be null or
undefined.

The template compiler will generate code similar to,

```
  if (NgIf.ngIfTypeGuard(instance.person)) {
    instance.person.name
  }
```

to validate the template's use of the interpolation expression.
Calling the type guard in this fashion allows TypeScript to infer
that `person` is non-null.

Fixes: #19756?

PR Close #20702
2017-12-08 10:24:26 -08:00
e544742156 refactor(core): Removed readonly getters and changed to readonly (#19842)
variables

PR Close #19842
2017-12-08 10:24:19 -08:00
c9ad529afc docs(common): fix mistakes in number pipe example (#20788)
PR Close #20788
2017-12-08 10:24:10 -08:00
75e468494c ci: add IgorMinar to bazel pullapprove group (#20843)
PR Close #20843
2017-12-08 10:24:00 -08:00
ddada6e2be build(aio): upgrade to latest @angular/cli (#18428)
PR Close #18428
2017-12-08 10:11:16 -08:00
22ae17bb0b build(aio): upgrade to latest @angular/material and @angular/cdk (#18428)
PR Close #18428
2017-12-08 10:11:15 -08:00
d546be48e1 build(aio): upgrade to latest @angular/* (#18428)
PR Close #18428
2017-12-08 10:11:15 -08:00
753a130aaa build(aio): upgrade to latest rxjs (#18428)
PR Close #18428
2017-12-08 10:11:15 -08:00
94e2ea7361 fix(aio): fix embedded ToC and improve ToC, destroying components and scroll timing (#18428)
- Fix embedded ToC:
  Previously, the element was added too late and was never instantiated.

- Improve ToC update timing:
  Previously, the ToC was updated after the entering animation was over, which
  resulted in the ToC being outdated for the duration of the animation.

- Improve destroying components timing:
  Previously, the old embedded components were destroyed as soon as a
  new document was requested. Even if the transition ended up never
  happening (e.g. due to error while preparing the new document), the
  embedded components would have been destroyed and the displayed
  document would not work as expected.
  Now the old embedded components are destroyed only after the new
  document has been fully prepared.

- Improve scroll-to-top timing:
  Previously, the page was scrolled to top after the entering animation was
  over, which resulted in "jumpi-ness". Now the scrolling happens after the
  leaving document has been removed and before the entering document has been
  inserted.

PR Close #18428
2017-12-08 10:11:15 -08:00
1539cd8819 feat(aio): animate the leaving/entering documents (#18428)
This commit adds a simple fade-in/out animation.

Fixes #15629

PR Close #18428
2017-12-08 10:11:15 -08:00
131c8ab6be fix(aio): do not show new document until embedded components are ready (#18428)
Previously, the document was shown as soon as the HTML was received, but before
the embedded components were ready (e.g. downloaded and instantiated). This
caused FOUC (Flash Of Uninstantiated Components).
This commit fixes it by preparing the new document in an off-DOM node and
swapping the nodes when the embedded components are ready.

PR Close #18428
2017-12-08 10:11:15 -08:00
7d81309e11 feat(aio): lazy-load embedded components (#18428)
Fixes #16127

PR Close #18428
2017-12-08 10:11:15 -08:00
225baf4686 docs(aio): fix typo for missing quote (#20888)
PR Close #20888
2017-12-08 10:06:41 -08:00
70b061be2e fix(aio): tsconfig.app.json excludes all testing files (#20779)
Fixes app build error in testing guide which has testing folder at multiple levels,
with files in them referring to files in the root `testing` folder.
Also removed the exclusion of files with `.1` in the name because
all app `.ts` files must be buildable per aio policy.
must build

PR Close #20779
2017-12-08 10:02:43 -08:00
46aa0a1cf6 fix(animations): properly recover and cleanup DOM when CD failures occur (#20719)
Closes #19093

PR Close #20719
2017-12-07 17:16:27 -08:00
661fdcd3e2 refactor(animations): instantiate Set-matching code with values in constructor (#20725)
For some reason, prior to this fix, the boolean set matching
code (within `animation_transition_expr.ts`) failed to remain
the same when compiled with closure. This refactor makes sure
that the code stays in tact.

Reproduction Details:
Passes without `ng build --prod`: https://burger.stackblitz.io/
Fails with `ng build --prod`: http://burger.fxck.cz/

Closes #20374

PR Close #20725
2017-12-07 17:16:21 -08:00
590d93b30d feat(animations): re-introduce support for transition matching functions (#20723)
Closes #18959

PR Close #20723
2017-12-07 17:16:09 -08:00
c26e1bba1d fix(animations): ensure the web-animations driver properly handles empty keyframes (#20648)
Closes #15858

PR Close #20648
2017-12-07 17:16:02 -08:00
10771d0bd8 fix(animations): support webkit-based vendor prefixes for prop validations (#19055)
Closes #18921

PR Close #19055
2017-12-07 17:15:53 -08:00
d8cc09b76c fix(router): NavigatonError and NavigationCancel should be emitted after resetting the URL (#20803)
PR Close #20803
2017-12-07 13:34:20 -08:00
d41d2c460a feat(forms): allow nulls on setAsyncValidators (#20327)
closes #20296

PR Close #20327
2017-12-07 13:34:12 -08:00
4efc32dabf fix(compiler-cli): disable checkTypes in emit. (#20828)
Closure Compiler by default will report diagnostics from type checks in
any JavaScript code, including code emitted by the Angular compiler.
Disabling `checkTypes` substantially reduces warning spam for users, and
allows them to run with stricter compiler flags (e.g. treating actual
diagnostics from user code as errors).

Closure Compiler will still type check the code and use types (where
found and correct) for optimizations.

PR Close #20828
2017-12-07 13:34:05 -08:00
ef534c0cc1 build: upgrade bazel rules to latest (#20768)
Add enough BUILD files to make it possible to
`bazel build packages/core/test`

Also re-format BUILD.bazel files with Buildifier.
Add a CI lint check that they stay formatted.

PR Close #20768
2017-12-07 11:27:50 -08:00
073f485c72 fix(compiler-cli): Fix swallowed Error messages (#20846)
This commit fixes a bug in which non-formatted errors are silently
dropped.

Internal issue: b/67739418

PR Close #20846
2017-12-06 16:49:22 -08:00
61 changed files with 1316 additions and 247 deletions

View File

@ -25,7 +25,6 @@
# tinayuangao - Tina Gao # tinayuangao - Tina Gao
# vicb - Victor Berchet # vicb - Victor Berchet
# vikerman - Vikram Subramanian # vikerman - Vikram Subramanian
# wardbell - Ward Bell
version: 2 version: 2
@ -312,7 +311,6 @@ groups:
- juleskremer #primary - juleskremer #primary
- Foxandxss - Foxandxss
- stephenfluin - stephenfluin
- wardbell
- petebacondarwin - petebacondarwin
- gkalpak - gkalpak
- IgorMinar #fallback - IgorMinar #fallback

View File

@ -1,3 +1,20 @@
<a name="5.2.0-beta.1"></a>
# [5.2.0-beta.1](https://github.com/angular/angular/compare/5.2.0-beta.0...5.2.0-beta.1) (2017-12-20)
### Bug Fixes
* **compiler:** generate the correct imports for summary type-check ([d91ff17](https://github.com/angular/angular/commit/d91ff17))
* **forms:** avoid producing an error with hostBindingTypeCheck ([d213a20](https://github.com/angular/angular/commit/d213a20))
### Features
* **compiler:** allow ngIf to use the ngIf expression directly as a guard ([82bcd83](https://github.com/angular/angular/commit/82bcd83))
* **router:** add "paramsInheritanceStrategy" router configuration option ([5efea2f](https://github.com/angular/angular/commit/5efea2f)), closes [#20572](https://github.com/angular/angular/issues/20572)
<a name="5.1.2"></a> <a name="5.1.2"></a>
## [5.1.2](https://github.com/angular/angular/compare/5.1.1...5.1.2) (2017-12-20) ## [5.1.2](https://github.com/angular/angular/compare/5.1.1...5.1.2) (2017-12-20)
@ -14,6 +31,20 @@
<a name="5.2.0-beta.0"></a>
# [5.2.0-beta.0](https://github.com/angular/angular/compare/5.1.0...5.2.0-beta.0) (2017-12-13)
### Features
* **animations:** re-introduce support for transition matching functions ([#20723](https://github.com/angular/angular/issues/20723)) ([590d93b](https://github.com/angular/angular/commit/590d93b)), closes [#18959](https://github.com/angular/angular/issues/18959)
* **compiler:** add a pseudo $any() function to disable type checking ([#20876](https://github.com/angular/angular/issues/20876)) ([70cd124](https://github.com/angular/angular/commit/70cd124))
* **compiler:** narrow types of expressions used in *ngIf ([#20702](https://github.com/angular/angular/issues/20702)) ([e7d9cb3](https://github.com/angular/angular/commit/e7d9cb3))
* **core:** add source to `StaticInjectorError` message ([#20817](https://github.com/angular/angular/issues/20817)) ([b7738e1](https://github.com/angular/angular/commit/b7738e1)), closes [#19302](https://github.com/angular/angular/issues/19302)
* **forms:** allow nulls on setAsyncValidators ([#20327](https://github.com/angular/angular/issues/20327)) ([d41d2c4](https://github.com/angular/angular/commit/d41d2c4)), closes [#20296](https://github.com/angular/angular/issues/20296)
<a name="5.1.1"></a> <a name="5.1.1"></a>
## [5.1.1](https://github.com/angular/angular/compare/5.1.0...5.1.1) (2017-12-13) ## [5.1.1](https://github.com/angular/angular/compare/5.1.0...5.1.1) (2017-12-13)

View File

@ -17,7 +17,7 @@ In such case, we'll update this document accordingly.
The dates are just a guidance and might be adjusted slightly if necessary. The dates are just a guidance and might be adjusted slightly if necessary.
## Tentative Schedule Until September 2017 ## Tentative Schedule Until December 2017
<!-- <!--
The table below is formatted so that it's easy to read and edit in both markdown and rendered html form. The table below is formatted so that it's easy to read and edit in both markdown and rendered html form.
@ -25,43 +25,54 @@ The table below is formatted so that it's easy to read and edit in both markdown
In order to deal with undesirable line breaks, two special characters are occasionally used: 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 hyphen: "" http://www.fileformat.info/info/unicode/char/2011/index.htm
- non-breaking space: " " http://www.fileformat.info/info/unicode/char/00a0/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. 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 Week Of | Stable Release<br>(@latest npm tag) | Beta/RC Release<br>(@next npm tag) | Note
------------- | ----------------------------------- | ---------------------------------- | --------------------- ------------- | ----------------------------------- | ---------------------------------- | ---------------------
20170501   | 4.1.1                               | 4.2.0beta.0                       | 20170501 | 4.1.1 | 4.2.0beta.0 |
20170508   | 4.1.2                               | 4.2.0beta.1                       | 20170508 | 4.1.2 | 4.2.0beta.1 |
20170515   | 4.1.3                               | 4.2.0rc.0                         | 20170515 | 4.1.3 | 4.2.0rc.0 |
20170526   |                                    | 4.2.0rc.1                         | 20170526 | | 4.2.0rc.1 |
20170601    |                                    | 4.2.0rc.2                         | 20170601 | | 4.2.0rc.2 |
20170605   | 4.2.0                               |                                   | Minor Version Release 20170605 | 4.2.0 | | Minor Version Release
*20170609*  | 4.2.1                               |                        | *Regression Patch Release* *20170609* | 4.2.1 | | *Regression Patch Release*
2017-06-12 | 4.2.2 | | 2017-06-12 | 4.2.2 | |
*2017-06-16*  | 4.2.3 | | *Regression Patch Release* *2017-06-16* | 4.2.3 | | *Regression Patch Release*
20170619   | 4.2.4                               | 4.3.0beta.0                       | 20170619 | 4.2.4 | 4.3.0beta.0 |
20170626   | 4.2.5                               | 4.3.0beta.1                       | 20170626 | 4.2.5 | 4.3.0beta.1 |
20170703   | 4.2.6                               | 4.3.0rc.0                         | 20170703 | 4.2.6 | 4.3.0rc.0 |
20170710   | 4.3.0                               | -                        | Minor Version Release 20170710 | 4.3.0 | - | Minor Version Release
20170717   | 4.3.1                               | 5.0.0beta.0                       | 20170717 | 4.3.1 | 5.0.0beta.0 |
20170724   | 4.3.2                               | 5.0.0beta.1                       | 20170724 | 4.3.2 | 5.0.0beta.1 |
20170731   | 4.3.3                               | 5.0.0beta.2                       | 20170731 | 4.3.3 | 5.0.0beta.2 |
20170807   | 4.3.4                               | 5.0.0beta.3                       | 20170807 | 4.3.4 | 5.0.0beta.3 |
20170814    | 4.3.5                               | 5.0.0beta.4                       | 20170814 | 4.3.5 | 5.0.0beta.4 |
20170821   | 4.3.6                               | - | 20170821 | 4.3.6 | - |
20170828   | -                               | 5.0.0beta.5                       | 20170828 | - | 5.0.0beta.5 |
20170904   | -                                | 5.0.0beta.6                       | 20170904 | - | 5.0.0beta.6 |
20170911   | 4.4.0                               | 5.0.0-beta.7                       | 20170911 | 4.4.0 | 5.0.0-beta.7 |
20170918   | 4.4.1                               | 5.0.0beta.8                       | 20170918 | 4.4.1 | 5.0.0beta.8 |
20170925   | 4.4.2                               | 5.0.0rc.0                         | 20170925 | 4.4.2 | 5.0.0rc.0 |
20171002   | 4.4.3                               | 5.0.0rc.1                         | 20171002 | 4.4.3 | 5.0.0rc.1 |
20171009   | 4.4.4                               | 5.0.0rc.2                         | 20171009 | 4.4.4 | 5.0.0rc.2 |
20171016  | 4.4.5                               | 5.0.0rc.3                         | 20171016 | 4.4.5 | 5.0.0rc.3 |
20171023  | 5.0.0                               |                                   | Major Version Release 201711-01 | 5.0.0 | | Major Version Release
2017-11-08 | 5.0.1 | 5.1.0.Beta.0 | *Regression Patch Release*
2017-11-16 | 5.0.2 | 5.1.0.Beta.1 | *Regression Patch Release*
2017-11-22 | 5.0.3 | 5.1.0.beta.2 | *Regression Patch Release*
2017-11-30 | 5.0.4 | 5.1.0.rc.0 |
2017-12-01 | 5.0.5 | 5.1.0.rc.1 |
2017-12-06 | 5.1.0 | | Minor Version Release
2017-12-13 | 5.1.1 | 5.2.0.beta.0 |
2017-12-20 | 5.1.2 | |
## Tentative Schedule After September 2017
## Tentative Schedule After December 2017
Date | Stable Release | Compatibility`*` Date | Stable Release | Compatibility`*`
---------------------- | -------------- | ---------------- ---------------------- | -------------- | ----------------

View File

@ -1,6 +1,6 @@
{ {
"name": "angular-srcs", "name": "angular-srcs",
"version": "5.1.2", "version": "5.2.0-beta.1",
"private": true, "private": true,
"branchPattern": "2.0.*", "branchPattern": "2.0.*",
"description": "Angular - a web framework for modern web apps", "description": "Angular - a web framework for modern web apps",

View File

@ -1393,9 +1393,9 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
public markedForDestroy: boolean = false; public markedForDestroy: boolean = false;
constructor(public namespaceId: string, public triggerName: string, public element: any) {} readonly queued: boolean = true;
get queued() { return this._containsRealPlayer == false; } constructor(public namespaceId: string, public triggerName: string, public element: any) {}
setRealPlayer(player: AnimationPlayer) { setRealPlayer(player: AnimationPlayer) {
if (this._containsRealPlayer) return; if (this._containsRealPlayer) return;
@ -1407,6 +1407,7 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
}); });
this._queuedCallbacks = {}; this._queuedCallbacks = {};
this._containsRealPlayer = true; this._containsRealPlayer = true;
(this as{queued: boolean}).queued = false;
} }
getRealPlayer() { return this._player; } getRealPlayer() { return this._player; }

View File

@ -115,7 +115,7 @@ export interface AnimationStateMetadata extends AnimationMetadata {
* @experimental Animation support is experimental. * @experimental Animation support is experimental.
*/ */
export interface AnimationTransitionMetadata extends AnimationMetadata { export interface AnimationTransitionMetadata extends AnimationMetadata {
expr: string; expr: string|((fromState: string, toState: string) => boolean);
animation: AnimationMetadata|AnimationMetadata[]; animation: AnimationMetadata|AnimationMetadata[];
options: AnimationOptions|null; options: AnimationOptions|null;
} }
@ -836,7 +836,8 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe
* @experimental Animation support is experimental. * @experimental Animation support is experimental.
*/ */
export function transition( export function transition(
stateChangeExpr: string, steps: AnimationMetadata | AnimationMetadata[], stateChangeExpr: string | ((fromState: string, toState: string) => boolean),
steps: AnimationMetadata | AnimationMetadata[],
options: AnimationOptions | null = null): AnimationTransitionMetadata { options: AnimationOptions | null = null): AnimationTransitionMetadata {
return {type: AnimationMetadataType.Transition, expr: stateChangeExpr, animation: steps, options}; return {type: AnimationMetadataType.Transition, expr: stateChangeExpr, animation: steps, options};
} }

View File

@ -23,13 +23,12 @@ export class MockScriptElement {
export class MockDocument { export class MockDocument {
mock: MockScriptElement|null; mock: MockScriptElement|null;
readonly body: any = this;
createElement(tag: 'script'): HTMLScriptElement { createElement(tag: 'script'): HTMLScriptElement {
return new MockScriptElement() as any as HTMLScriptElement; return new MockScriptElement() as any as HTMLScriptElement;
} }
get body(): any { return this; }
appendChild(node: any): void { this.mock = node; } appendChild(node: any): void { this.mock = node; }
removeNode(node: any): void { removeNode(node: any): void {
@ -41,4 +40,4 @@ export class MockDocument {
mockLoad(): void { this.mock !.listeners.load !(null as any); } mockLoad(): void { this.mock !.listeners.load !(null as any); }
mockError(err: Error) { this.mock !.listeners.error !(err); } mockError(err: Error) { this.mock !.listeners.error !(err); }
} }

View File

@ -151,6 +151,9 @@ export class NgIf {
} }
} }
} }
/** @internal */
public static ngIfUseIfTypeGuard: void;
} }
/** /**

View File

@ -48,11 +48,11 @@ describe('ng type checker', () => {
} }
function reject( function reject(
message: string | RegExp, location: RegExp, files: MockFiles, message: string | RegExp, location: RegExp | null, files: MockFiles,
overrideOptions: ng.CompilerOptions = {}) { overrideOptions: ng.CompilerOptions = {}) {
const diagnostics = compileAndCheck([QUICKSTART, files], overrideOptions); const diagnostics = compileAndCheck([QUICKSTART, files], overrideOptions);
if (!diagnostics || !diagnostics.length) { if (!diagnostics || !diagnostics.length) {
throw new Error('Expected a diagnostic erorr message'); throw new Error('Expected a diagnostic error message');
} else { } else {
const matches: (d: ng.Diagnostic) => boolean = typeof message === 'string' ? const matches: (d: ng.Diagnostic) => boolean = typeof message === 'string' ?
d => ng.isNgDiagnostic(d)&& d.messageText == message : d => ng.isNgDiagnostic(d)&& d.messageText == message :
@ -63,11 +63,13 @@ describe('ng type checker', () => {
`Expected a diagnostics matching ${message}, received\n ${diagnostics.map(d => d.messageText).join('\n ')}`); `Expected a diagnostics matching ${message}, received\n ${diagnostics.map(d => d.messageText).join('\n ')}`);
} }
const span = matchingDiagnostics[0].span; if (location) {
if (!span) { const span = matchingDiagnostics[0].span;
throw new Error('Expected a sourceSpan'); if (!span) {
throw new Error('Expected a sourceSpan');
}
expect(`${span.start.file.url}@${span.start.line}:${span.start.offset}`).toMatch(location);
} }
expect(`${span.start.file.url}@${span.start.line}:${span.start.offset}`).toMatch(location);
} }
} }
@ -81,6 +83,543 @@ describe('ng type checker', () => {
}); });
}); });
describe('type narrowing', () => {
const a = (files: MockFiles, options: object = {}) => {
accept(files, {fullTemplateTypeCheck: true, ...options});
};
it('should narrow an *ngIf like directive', () => {
a({
'src/app.component.ts': '',
'src/lib.ts': '',
'src/app.module.ts': `
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
export interface Person {
name: string;
}
@Component({
selector: 'comp',
template: '<div *myIf="person"> {{person.name}} </div>'
})
export class MainComp {
person?: Person;
}
export class MyIfContext {
public $implicit: any = null;
public myIf: any = null;
}
@Directive({selector: '[myIf]'})
export class MyIf {
constructor(templateRef: TemplateRef<MyIfContext>) {}
@Input()
set myIf(condition: any) {}
static myIfTypeGuard: <T>(v: T | null | undefined | false) => v is T;
}
@NgModule({
declarations: [MainComp, MyIf],
})
export class MainModule {}`
});
});
it('should narrow a renamed *ngIf like directive', () => {
a({
'src/app.component.ts': '',
'src/lib.ts': '',
'src/app.module.ts': `
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
export interface Person {
name: string;
}
@Component({
selector: 'comp',
template: '<div *my-if="person"> {{person.name}} </div>'
})
export class MainComp {
person?: Person;
}
export class MyIfContext {
public $implicit: any = null;
public myIf: any = null;
}
@Directive({selector: '[my-if]'})
export class MyIf {
constructor(templateRef: TemplateRef<MyIfContext>) {}
@Input('my-if')
set myIf(condition: any) {}
static myIfTypeGuard: <T>(v: T | null | undefined | false) => v is T;
}
@NgModule({
declarations: [MainComp, MyIf],
})
export class MainModule {}`
});
});
it('should narrow a type in a nested *ngIf like directive', () => {
a({
'src/app.component.ts': '',
'src/lib.ts': '',
'src/app.module.ts': `
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
export interface Address {
street: string;
}
export interface Person {
name: string;
address?: Address;
}
@Component({
selector: 'comp',
template: '<div *myIf="person"> {{person.name}} <span *myIf="person.address">{{person.address.street}}</span></div>'
})
export class MainComp {
person?: Person;
}
export class MyIfContext {
public $implicit: any = null;
public myIf: any = null;
}
@Directive({selector: '[myIf]'})
export class MyIf {
constructor(templateRef: TemplateRef<MyIfContext>) {}
@Input()
set myIf(condition: any) {}
static myIfTypeGuard: <T>(v: T | null | undefined | false) => v is T;
}
@NgModule({
declarations: [MainComp, MyIf],
})
export class MainModule {}`
});
});
it('should narrow an *ngIf like directive with UseIf', () => {
a({
'src/app.component.ts': '',
'src/lib.ts': '',
'src/app.module.ts': `
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
export interface Person {
name: string;
}
@Component({
selector: 'comp',
template: '<div *myIf="person"> {{person.name}} </div>'
})
export class MainComp {
person?: Person;
}
export class MyIfContext {
public $implicit: any = null;
public myIf: any = null;
}
@Directive({selector: '[myIf]'})
export class MyIf {
constructor(templateRef: TemplateRef<MyIfContext>) {}
@Input()
set myIf(condition: any) {}
static myIfUseIfTypeGuard: void;
}
@NgModule({
declarations: [MainComp, MyIf],
})
export class MainModule {}`
});
});
it('should narrow a renamed *ngIf like directive with UseIf', () => {
a({
'src/app.component.ts': '',
'src/lib.ts': '',
'src/app.module.ts': `
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
export interface Person {
name: string;
}
@Component({
selector: 'comp',
template: '<div *my-if="person"> {{person.name}} </div>'
})
export class MainComp {
person?: Person;
}
export class MyIfContext {
public $implicit: any = null;
public myIf: any = null;
}
@Directive({selector: '[my-if]'})
export class MyIf {
constructor(templateRef: TemplateRef<MyIfContext>) {}
@Input('my-if')
set myIf(condition: any) {}
static myIfUseIfTypeGuard: void;
}
@NgModule({
declarations: [MainComp, MyIf],
})
export class MainModule {}`
});
});
it('should narrow a type in a nested *ngIf like directive with UseIf', () => {
a({
'src/app.component.ts': '',
'src/lib.ts': '',
'src/app.module.ts': `
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
export interface Address {
street: string;
}
export interface Person {
name: string;
address?: Address;
}
@Component({
selector: 'comp',
template: '<div *myIf="person"> {{person.name}} <span *myIf="person.address">{{person.address.street}}</span></div>'
})
export class MainComp {
person?: Person;
}
export class MyIfContext {
public $implicit: any = null;
public myIf: any = null;
}
@Directive({selector: '[myIf]'})
export class MyIf {
constructor(templateRef: TemplateRef<MyIfContext>) {}
@Input()
set myIf(condition: any) {}
static myIfUseIfTypeGuard: void;
}
@NgModule({
declarations: [MainComp, MyIf],
})
export class MainModule {}`
});
});
it('should narrow an *ngIf like directive with UseIf and &&', () => {
a({
'src/app.component.ts': '',
'src/lib.ts': '',
'src/app.module.ts': `
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
export interface Address {
street: string;
}
export interface Person {
name: string;
}
@Component({
selector: 'comp',
template: '<div *myIf="person && address"> {{person.name}} lives at {{address.street}} </div>'
})
export class MainComp {
person?: Person;
address?: Address;
}
export class MyIfContext {
public $implicit: any = null;
public myIf: any = null;
}
@Directive({selector: '[myIf]'})
export class MyIf {
constructor(templateRef: TemplateRef<MyIfContext>) {}
@Input()
set myIf(condition: any) {}
static myIfUseIfTypeGuard: void;
}
@NgModule({
declarations: [MainComp, MyIf],
})
export class MainModule {}`
});
});
it('should narrow an *ngIf like directive with UseIf and !!', () => {
a({
'src/app.component.ts': '',
'src/lib.ts': '',
'src/app.module.ts': `
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
export interface Person {
name: string;
}
@Component({
selector: 'comp',
template: '<div *myIf="!!person"> {{person.name}} </div>'
})
export class MainComp {
person?: Person;
}
export class MyIfContext {
public $implicit: any = null;
public myIf: any = null;
}
@Directive({selector: '[myIf]'})
export class MyIf {
constructor(templateRef: TemplateRef<MyIfContext>) {}
@Input()
set myIf(condition: any) {}
static myIfUseIfTypeGuard: void;
}
@NgModule({
declarations: [MainComp, MyIf],
})
export class MainModule {}`
});
});
it('should narrow an *ngIf like directive with UseIf and != null', () => {
a({
'src/app.component.ts': '',
'src/lib.ts': '',
'src/app.module.ts': `
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
export interface Person {
name: string;
}
@Component({
selector: 'comp',
template: '<div *myIf="person != null"> {{person.name}} </div>'
})
export class MainComp {
person: Person | null = null;
}
export class MyIfContext {
public $implicit: any = null;
public myIf: any = null;
}
@Directive({selector: '[myIf]'})
export class MyIf {
constructor(templateRef: TemplateRef<MyIfContext>) {}
@Input()
set myIf(condition: any) {}
static myIfUseIfTypeGuard: void;
}
@NgModule({
declarations: [MainComp, MyIf],
})
export class MainModule {}`
});
});
it('should narrow an *ngIf like directive with UseIf and != undefined', () => {
a({
'src/app.component.ts': '',
'src/lib.ts': '',
'src/app.module.ts': `
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
export interface Person {
name: string;
}
@Component({
selector: 'comp',
template: '<div *myIf="person != undefined"> {{person.name}} </div>'
})
export class MainComp {
person?: Person;
}
export class MyIfContext {
public $implicit: any = null;
public myIf: any = null;
}
@Directive({selector: '[myIf]'})
export class MyIf {
constructor(templateRef: TemplateRef<MyIfContext>) {}
@Input()
set myIf(condition: any) {}
static myIfUseIfTypeGuard: void;
}
@NgModule({
declarations: [MainComp, MyIf],
})
export class MainModule {}`
});
});
});
describe('casting $any', () => {
const a = (files: MockFiles, options: object = {}) => {
accept(
{'src/app.component.ts': '', 'src/lib.ts': '', ...files},
{fullTemplateTypeCheck: true, ...options});
};
const r =
(message: string | RegExp, location: RegExp | null, files: MockFiles,
options: object = {}) => {
reject(
message, location, {'src/app.component.ts': '', 'src/lib.ts': '', ...files},
{fullTemplateTypeCheck: true, ...options});
};
it('should allow member access of an expression', () => {
a({
'src/app.module.ts': `
import {NgModule, Component} from '@angular/core';
export interface Person {
name: string;
}
@Component({
selector: 'comp',
template: ' {{$any(person).address}}'
})
export class MainComp {
person: Person;
}
@NgModule({
declarations: [MainComp],
})
export class MainModule {
}`
});
});
it('should allow invalid this.member access', () => {
a({
'src/app.module.ts': `
import {NgModule, Component} from '@angular/core';
@Component({
selector: 'comp',
template: ' {{$any(this).missing}}'
})
export class MainComp { }
@NgModule({
declarations: [MainComp],
})
export class MainModule {
}`
});
});
it('should reject too few parameters to $any', () => {
r(/Invalid call to \$any, expected 1 argument but received none/, null, {
'src/app.module.ts': `
import {NgModule, Component} from '@angular/core';
@Component({
selector: 'comp',
template: ' {{$any().missing}}'
})
export class MainComp { }
@NgModule({
declarations: [MainComp],
})
export class MainModule {
}`
});
});
it('should reject too many parameters to $any', () => {
r(/Invalid call to \$any, expected 1 argument but received 2/, null, {
'src/app.module.ts': `
import {NgModule, Component} from '@angular/core';
export interface Person {
name: string;
}
@Component({
selector: 'comp',
template: ' {{$any(person, 12).missing}}'
})
export class MainComp {
person: Person;
}
@NgModule({
declarations: [MainComp],
})
export class MainModule {
}`
});
});
});
describe('regressions ', () => { describe('regressions ', () => {
const a = (files: MockFiles, options: object = {}) => { const a = (files: MockFiles, options: object = {}) => {
accept(files, {fullTemplateTypeCheck: true, ...options}); accept(files, {fullTemplateTypeCheck: true, ...options});

View File

@ -1038,6 +1038,25 @@ describe('Collector', () => {
expect(metadata).toBeUndefined(); expect(metadata).toBeUndefined();
}); });
it('should collect type guards', () => {
const metadata = collectSource(`
import {Directive, Input, TemplateRef} from '@angular/core';
@Directive({selector: '[myIf]'})
export class MyIf {
constructor(private templateRef: TemplateRef) {}
@Input() myIf: any;
static typeGuard: <T>(v: T | null | undefined): v is T;
}
`);
expect((metadata.metadata.MyIf as any).statics.typeGuard)
.not.toBeUndefined('typeGuard was not collected');
});
it('should be able to collect an invalid access expression', () => { it('should be able to collect an invalid access expression', () => {
const source = createSource(` const source = createSource(`
import {Component} from '@angular/core'; import {Component} from '@angular/core';

View File

@ -12,6 +12,7 @@ import * as path from 'path';
import * as ts from 'typescript'; import * as ts from 'typescript';
import * as ng from '../index'; import * as ng from '../index';
// TEST_TMPDIR is set by bazel.
const tmpdir = process.env.TEST_TMPDIR || os.tmpdir(); const tmpdir = process.env.TEST_TMPDIR || os.tmpdir();
function getNgRootDir() { function getNgRootDir() {
@ -21,7 +22,6 @@ function getNgRootDir() {
} }
export function writeTempFile(name: string, contents: string): string { export function writeTempFile(name: string, contents: string): string {
// TEST_TMPDIR is set by bazel.
const id = (Math.random() * 1000000).toFixed(0); const id = (Math.random() * 1000000).toFixed(0);
const fn = path.join(tmpdir, `tmp.${id}.${name}`); const fn = path.join(tmpdir, `tmp.${id}.${name}`);
fs.writeFileSync(fn, contents); fs.writeFileSync(fn, contents);
@ -29,8 +29,12 @@ export function writeTempFile(name: string, contents: string): string {
} }
export function makeTempDir(): string { export function makeTempDir(): string {
const id = (Math.random() * 1000000).toFixed(0); let dir: string;
const dir = path.join(tmpdir, `tmp.${id}`); while (true) {
const id = (Math.random() * 1000000).toFixed(0);
dir = path.join(tmpdir, `tmp.${id}`);
if (!fs.existsSync(dir)) break;
}
fs.mkdirSync(dir); fs.mkdirSync(dir);
return dir; return dir;
} }

View File

@ -218,15 +218,14 @@ export class AotCompiler {
const externalReferenceVars = new Map<any, string>(); const externalReferenceVars = new Map<any, string>();
externalReferences.forEach((ref, typeIndex) => { externalReferences.forEach((ref, typeIndex) => {
if (this._host.isSourceFile(ref.filePath)) { externalReferenceVars.set(ref, `_decl${ngModuleIndex}_${typeIndex}`);
externalReferenceVars.set(ref, `_decl${ngModuleIndex}_${typeIndex}`);
}
}); });
externalReferenceVars.forEach((varName, reference) => { externalReferenceVars.forEach((varName, reference) => {
outputCtx.statements.push( outputCtx.statements.push(
o.variable(varName) o.variable(varName)
.set(o.NULL_EXPR.cast(o.DYNAMIC_TYPE)) .set(o.NULL_EXPR.cast(o.DYNAMIC_TYPE))
.toDeclStmt(o.expressionType(outputCtx.importExpr(reference)))); .toDeclStmt(o.expressionType(outputCtx.importExpr(
reference, /* typeParams */ null, /* useSummaries */ false))));
}); });
if (emitFlags & StubEmitFlags.TypeCheck) { if (emitFlags & StubEmitFlags.TypeCheck) {
@ -271,7 +270,7 @@ export class AotCompiler {
const {template: parsedTemplate, pipes: usedPipes} = const {template: parsedTemplate, pipes: usedPipes} =
this._parseTemplate(compMeta, moduleMeta, directives); this._parseTemplate(compMeta, moduleMeta, directives);
ctx.statements.push(...this._typeCheckCompiler.compileComponent( ctx.statements.push(...this._typeCheckCompiler.compileComponent(
componentId, compMeta, parsedTemplate, usedPipes, externalReferenceVars)); componentId, compMeta, parsedTemplate, usedPipes, externalReferenceVars, ctx));
} }
emitMessageBundle(analyzeResult: NgAnalyzedModules, locale: string|null): MessageBundle { emitMessageBundle(analyzeResult: NgAnalyzedModules, locale: string|null): MessageBundle {
@ -515,35 +514,38 @@ export class AotCompiler {
} }
private _createOutputContext(genFilePath: string): OutputContext { private _createOutputContext(genFilePath: string): OutputContext {
const importExpr = (symbol: StaticSymbol, typeParams: o.Type[] | null = null) => { const importExpr =
if (!(symbol instanceof StaticSymbol)) { (symbol: StaticSymbol, typeParams: o.Type[] | null = null,
throw new Error(`Internal error: unknown identifier ${JSON.stringify(symbol)}`); useSummaries: boolean = true) => {
} if (!(symbol instanceof StaticSymbol)) {
const arity = this._symbolResolver.getTypeArity(symbol) || 0; throw new Error(`Internal error: unknown identifier ${JSON.stringify(symbol)}`);
const {filePath, name, members} = this._symbolResolver.getImportAs(symbol) || symbol; }
const importModule = this._fileNameToModuleName(filePath, genFilePath); const arity = this._symbolResolver.getTypeArity(symbol) || 0;
const {filePath, name, members} =
this._symbolResolver.getImportAs(symbol, useSummaries) || symbol;
const importModule = this._fileNameToModuleName(filePath, genFilePath);
// It should be good enough to compare filePath to genFilePath and if they are equal // It should be good enough to compare filePath to genFilePath and if they are equal
// there is a self reference. However, ngfactory files generate to .ts but their // there is a self reference. However, ngfactory files generate to .ts but their
// symbols have .d.ts so a simple compare is insufficient. They should be canonical // symbols have .d.ts so a simple compare is insufficient. They should be canonical
// and is tracked by #17705. // and is tracked by #17705.
const selfReference = this._fileNameToModuleName(genFilePath, genFilePath); const selfReference = this._fileNameToModuleName(genFilePath, genFilePath);
const moduleName = importModule === selfReference ? null : importModule; const moduleName = importModule === selfReference ? null : importModule;
// If we are in a type expression that refers to a generic type then supply // If we are in a type expression that refers to a generic type then supply
// the required type parameters. If there were not enough type parameters // the required type parameters. If there were not enough type parameters
// supplied, supply any as the type. Outside a type expression the reference // supplied, supply any as the type. Outside a type expression the reference
// should not supply type parameters and be treated as a simple value reference // should not supply type parameters and be treated as a simple value reference
// to the constructor function itself. // to the constructor function itself.
const suppliedTypeParams = typeParams || []; const suppliedTypeParams = typeParams || [];
const missingTypeParamsCount = arity - suppliedTypeParams.length; const missingTypeParamsCount = arity - suppliedTypeParams.length;
const allTypeParams = const allTypeParams =
suppliedTypeParams.concat(new Array(missingTypeParamsCount).fill(o.DYNAMIC_TYPE)); suppliedTypeParams.concat(new Array(missingTypeParamsCount).fill(o.DYNAMIC_TYPE));
return members.reduce( return members.reduce(
(expr, memberName) => expr.prop(memberName), (expr, memberName) => expr.prop(memberName),
<o.Expression>o.importExpr( <o.Expression>o.importExpr(
new o.ExternalReference(moduleName, name, null), allTypeParams)); new o.ExternalReference(moduleName, name, null), allTypeParams));
}; };
return {statements: [], genFilePath, importExpr}; return {statements: [], genFilePath, importExpr};
} }

View File

@ -29,6 +29,8 @@ const IGNORE = {
const USE_VALUE = 'useValue'; const USE_VALUE = 'useValue';
const PROVIDE = 'provide'; const PROVIDE = 'provide';
const REFERENCE_SET = new Set([USE_VALUE, 'useFactory', 'data']); const REFERENCE_SET = new Set([USE_VALUE, 'useFactory', 'data']);
const TYPEGUARD_POSTFIX = 'TypeGuard';
const USE_IF = 'UseIf';
function shouldIgnore(value: any): boolean { function shouldIgnore(value: any): boolean {
return value && value.__symbolic == 'ignore'; return value && value.__symbolic == 'ignore';
@ -43,6 +45,7 @@ export class StaticReflector implements CompileReflector {
private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>(); private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>();
private parameterCache = new Map<StaticSymbol, any[]>(); private parameterCache = new Map<StaticSymbol, any[]>();
private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>(); private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>();
private staticCache = new Map<StaticSymbol, string[]>();
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>(); private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
private injectionToken: StaticSymbol; private injectionToken: StaticSymbol;
private opaqueToken: StaticSymbol; private opaqueToken: StaticSymbol;
@ -251,6 +254,18 @@ export class StaticReflector implements CompileReflector {
return methodNames; return methodNames;
} }
private _staticMembers(type: StaticSymbol): string[] {
let staticMembers = this.staticCache.get(type);
if (!staticMembers) {
const classMetadata = this.getTypeMetadata(type);
const staticMemberData = classMetadata['statics'] || {};
staticMembers = Object.keys(staticMemberData);
this.staticCache.set(type, staticMembers);
}
return staticMembers;
}
private findParentType(type: StaticSymbol, classMetadata: any): StaticSymbol|undefined { private findParentType(type: StaticSymbol, classMetadata: any): StaticSymbol|undefined {
const parentType = this.trySimplify(type, classMetadata['extends']); const parentType = this.trySimplify(type, classMetadata['extends']);
if (parentType instanceof StaticSymbol) { if (parentType instanceof StaticSymbol) {
@ -273,6 +288,30 @@ export class StaticReflector implements CompileReflector {
} }
} }
guards(type: any): {[key: string]: StaticSymbol} {
if (!(type instanceof StaticSymbol)) {
this.reportError(
new Error(`guards received ${JSON.stringify(type)} which is not a StaticSymbol`), type);
return {};
}
const staticMembers = this._staticMembers(type);
const result: {[key: string]: StaticSymbol} = {};
for (let name of staticMembers) {
if (name.endsWith(TYPEGUARD_POSTFIX)) {
let property = name.substr(0, name.length - TYPEGUARD_POSTFIX.length);
let value: any;
if (property.endsWith(USE_IF)) {
property = name.substr(0, property.length - USE_IF.length);
value = USE_IF;
} else {
value = this.getStaticSymbol(type.filePath, type.name, [name]);
}
result[property] = value;
}
}
return result;
}
private _registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void { private _registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void {
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => new ctor(...args)); this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => new ctor(...args));
} }

View File

@ -98,10 +98,10 @@ export class StaticSymbolResolver {
* *
* @param staticSymbol the symbol for which to generate a import symbol * @param staticSymbol the symbol for which to generate a import symbol
*/ */
getImportAs(staticSymbol: StaticSymbol): StaticSymbol|null { getImportAs(staticSymbol: StaticSymbol, useSummaries: boolean = true): StaticSymbol|null {
if (staticSymbol.members.length) { if (staticSymbol.members.length) {
const baseSymbol = this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name); const baseSymbol = this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name);
const baseImportAs = this.getImportAs(baseSymbol); const baseImportAs = this.getImportAs(baseSymbol, useSummaries);
return baseImportAs ? return baseImportAs ?
this.getStaticSymbol(baseImportAs.filePath, baseImportAs.name, staticSymbol.members) : this.getStaticSymbol(baseImportAs.filePath, baseImportAs.name, staticSymbol.members) :
null; null;
@ -111,14 +111,14 @@ export class StaticSymbolResolver {
const summarizedName = stripSummaryForJitNameSuffix(staticSymbol.name); const summarizedName = stripSummaryForJitNameSuffix(staticSymbol.name);
const baseSymbol = const baseSymbol =
this.getStaticSymbol(summarizedFileName, summarizedName, staticSymbol.members); this.getStaticSymbol(summarizedFileName, summarizedName, staticSymbol.members);
const baseImportAs = this.getImportAs(baseSymbol); const baseImportAs = this.getImportAs(baseSymbol, useSummaries);
return baseImportAs ? return baseImportAs ?
this.getStaticSymbol( this.getStaticSymbol(
summaryForJitFileName(baseImportAs.filePath), summaryForJitName(baseImportAs.name), summaryForJitFileName(baseImportAs.filePath), summaryForJitName(baseImportAs.name),
baseSymbol.members) : baseSymbol.members) :
null; null;
} }
let result = this.summaryResolver.getImportAs(staticSymbol); let result = (useSummaries && this.summaryResolver.getImportAs(staticSymbol)) || null;
if (!result) { if (!result) {
result = this.importAs.get(staticSymbol) !; result = this.importAs.get(staticSymbol) !;
} }

View File

@ -254,6 +254,7 @@ export interface CompileDirectiveSummary extends CompileTypeSummary {
providers: CompileProviderMetadata[]; providers: CompileProviderMetadata[];
viewProviders: CompileProviderMetadata[]; viewProviders: CompileProviderMetadata[];
queries: CompileQueryMetadata[]; queries: CompileQueryMetadata[];
guards: {[key: string]: any};
viewQueries: CompileQueryMetadata[]; viewQueries: CompileQueryMetadata[];
entryComponents: CompileEntryComponentMetadata[]; entryComponents: CompileEntryComponentMetadata[];
changeDetection: ChangeDetectionStrategy|null; changeDetection: ChangeDetectionStrategy|null;
@ -268,8 +269,8 @@ export interface CompileDirectiveSummary extends CompileTypeSummary {
*/ */
export class CompileDirectiveMetadata { export class CompileDirectiveMetadata {
static create({isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs, static create({isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs,
host, providers, viewProviders, queries, viewQueries, entryComponents, template, host, providers, viewProviders, queries, guards, viewQueries, entryComponents,
componentViewType, rendererType, componentFactory}: { template, componentViewType, rendererType, componentFactory}: {
isHost: boolean, isHost: boolean,
type: CompileTypeMetadata, type: CompileTypeMetadata,
isComponent: boolean, isComponent: boolean,
@ -282,6 +283,7 @@ export class CompileDirectiveMetadata {
providers: CompileProviderMetadata[], providers: CompileProviderMetadata[],
viewProviders: CompileProviderMetadata[], viewProviders: CompileProviderMetadata[],
queries: CompileQueryMetadata[], queries: CompileQueryMetadata[],
guards: {[key: string]: any};
viewQueries: CompileQueryMetadata[], viewQueries: CompileQueryMetadata[],
entryComponents: CompileEntryComponentMetadata[], entryComponents: CompileEntryComponentMetadata[],
template: CompileTemplateMetadata, template: CompileTemplateMetadata,
@ -336,6 +338,7 @@ export class CompileDirectiveMetadata {
providers, providers,
viewProviders, viewProviders,
queries, queries,
guards,
viewQueries, viewQueries,
entryComponents, entryComponents,
template, template,
@ -358,6 +361,7 @@ export class CompileDirectiveMetadata {
providers: CompileProviderMetadata[]; providers: CompileProviderMetadata[];
viewProviders: CompileProviderMetadata[]; viewProviders: CompileProviderMetadata[];
queries: CompileQueryMetadata[]; queries: CompileQueryMetadata[];
guards: {[key: string]: any};
viewQueries: CompileQueryMetadata[]; viewQueries: CompileQueryMetadata[];
entryComponents: CompileEntryComponentMetadata[]; entryComponents: CompileEntryComponentMetadata[];
@ -367,10 +371,27 @@ export class CompileDirectiveMetadata {
rendererType: StaticSymbol|object|null; rendererType: StaticSymbol|object|null;
componentFactory: StaticSymbol|object|null; componentFactory: StaticSymbol|object|null;
constructor({isHost, type, isComponent, selector, exportAs, constructor({isHost,
changeDetection, inputs, outputs, hostListeners, hostProperties, type,
hostAttributes, providers, viewProviders, queries, viewQueries, isComponent,
entryComponents, template, componentViewType, rendererType, componentFactory}: { selector,
exportAs,
changeDetection,
inputs,
outputs,
hostListeners,
hostProperties,
hostAttributes,
providers,
viewProviders,
queries,
guards,
viewQueries,
entryComponents,
template,
componentViewType,
rendererType,
componentFactory}: {
isHost: boolean, isHost: boolean,
type: CompileTypeMetadata, type: CompileTypeMetadata,
isComponent: boolean, isComponent: boolean,
@ -385,6 +406,7 @@ export class CompileDirectiveMetadata {
providers: CompileProviderMetadata[], providers: CompileProviderMetadata[],
viewProviders: CompileProviderMetadata[], viewProviders: CompileProviderMetadata[],
queries: CompileQueryMetadata[], queries: CompileQueryMetadata[],
guards: {[key: string]: any},
viewQueries: CompileQueryMetadata[], viewQueries: CompileQueryMetadata[],
entryComponents: CompileEntryComponentMetadata[], entryComponents: CompileEntryComponentMetadata[],
template: CompileTemplateMetadata|null, template: CompileTemplateMetadata|null,
@ -406,6 +428,7 @@ export class CompileDirectiveMetadata {
this.providers = _normalizeArray(providers); this.providers = _normalizeArray(providers);
this.viewProviders = _normalizeArray(viewProviders); this.viewProviders = _normalizeArray(viewProviders);
this.queries = _normalizeArray(queries); this.queries = _normalizeArray(queries);
this.guards = guards;
this.viewQueries = _normalizeArray(viewQueries); this.viewQueries = _normalizeArray(viewQueries);
this.entryComponents = _normalizeArray(entryComponents); this.entryComponents = _normalizeArray(entryComponents);
this.template = template; this.template = template;
@ -430,6 +453,7 @@ export class CompileDirectiveMetadata {
providers: this.providers, providers: this.providers,
viewProviders: this.viewProviders, viewProviders: this.viewProviders,
queries: this.queries, queries: this.queries,
guards: this.guards,
viewQueries: this.viewQueries, viewQueries: this.viewQueries,
entryComponents: this.entryComponents, entryComponents: this.entryComponents,
changeDetection: this.changeDetection, changeDetection: this.changeDetection,

View File

@ -17,6 +17,7 @@ export abstract class CompileReflector {
abstract annotations(typeOrFunc: /*Type*/ any): any[]; abstract annotations(typeOrFunc: /*Type*/ any): any[];
abstract propMetadata(typeOrFunc: /*Type*/ any): {[key: string]: any[]}; abstract propMetadata(typeOrFunc: /*Type*/ any): {[key: string]: any[]};
abstract hasLifecycleHook(type: any, lcProperty: string): boolean; abstract hasLifecycleHook(type: any, lcProperty: string): boolean;
abstract guards(typeOrFunc: /* Type */ any): {[key: string]: any};
abstract componentModuleUrl(type: /*Type*/ any, cmpMetadata: Component): string; abstract componentModuleUrl(type: /*Type*/ any, cmpMetadata: Component): string;
abstract resolveExternalReference(ref: o.ExternalReference): any; abstract resolveExternalReference(ref: o.ExternalReference): any;
} }

View File

@ -90,6 +90,14 @@ export class ConvertPropertyBindingResult {
constructor(public stmts: o.Statement[], public currValExpr: o.Expression) {} constructor(public stmts: o.Statement[], public currValExpr: o.Expression) {}
} }
export enum BindingForm {
// The general form of binding expression, supports all expressions.
General,
// Try to generate a simple binding (no temporaries or statements)
// otherise generate a general binding
TrySimple,
}
/** /**
* Converts the given expression AST into an executable output AST, assuming the expression * Converts the given expression AST into an executable output AST, assuming the expression
* is used in property binding. The expression has to be preprocessed via * is used in property binding. The expression has to be preprocessed via
@ -97,7 +105,8 @@ export class ConvertPropertyBindingResult {
*/ */
export function convertPropertyBinding( export function convertPropertyBinding(
localResolver: LocalResolver | null, implicitReceiver: o.Expression, localResolver: LocalResolver | null, implicitReceiver: o.Expression,
expressionWithoutBuiltins: cdAst.AST, bindingId: string): ConvertPropertyBindingResult { expressionWithoutBuiltins: cdAst.AST, bindingId: string,
form: BindingForm): ConvertPropertyBindingResult {
if (!localResolver) { if (!localResolver) {
localResolver = new DefaultLocalResolver(); localResolver = new DefaultLocalResolver();
} }
@ -110,9 +119,11 @@ export function convertPropertyBinding(
for (let i = 0; i < visitor.temporaryCount; i++) { for (let i = 0; i < visitor.temporaryCount; i++) {
stmts.push(temporaryDeclaration(bindingId, i)); stmts.push(temporaryDeclaration(bindingId, i));
} }
} else if (form == BindingForm.TrySimple) {
return new ConvertPropertyBindingResult([], outputExpr);
} }
stmts.push(currValExpr.set(outputExpr).toDeclStmt(null, [o.StmtModifier.Final])); stmts.push(currValExpr.set(outputExpr).toDeclStmt(o.DYNAMIC_TYPE, [o.StmtModifier.Final]));
return new ConvertPropertyBindingResult(stmts, currValExpr); return new ConvertPropertyBindingResult(stmts, currValExpr);
} }
@ -323,12 +334,27 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
} }
visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any { visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any {
return convertToStatementIfNeeded(mode, o.literal(ast.value)); // For literal values of null, undefined, true, or false allow type inteference
// to infer the type.
const type =
ast.value === null || ast.value === undefined || ast.value === true || ast.value === true ?
o.INFERRED_TYPE :
undefined;
return convertToStatementIfNeeded(mode, o.literal(ast.value, type));
} }
private _getLocal(name: string): o.Expression|null { return this._localResolver.getLocal(name); } private _getLocal(name: string): o.Expression|null { return this._localResolver.getLocal(name); }
visitMethodCall(ast: cdAst.MethodCall, mode: _Mode): any { visitMethodCall(ast: cdAst.MethodCall, mode: _Mode): any {
if (ast.receiver instanceof cdAst.ImplicitReceiver && ast.name == '$any') {
const args = this.visitAll(ast.args, _Mode.Expression) as any[];
if (args.length != 1) {
throw new Error(
`Invalid call to $any, expected 1 argument but received ${args.length || 'none'}`);
}
return (args[0] as o.Expression).cast(o.DYNAMIC_TYPE);
}
const leftMostSafe = this.leftMostSafeNode(ast); const leftMostSafe = this.leftMostSafeNode(ast);
if (leftMostSafe) { if (leftMostSafe) {
return this.convertSafeAccess(ast, leftMostSafe, mode); return this.convertSafeAccess(ast, leftMostSafe, mode);

View File

@ -51,6 +51,7 @@ export interface Directive {
providers?: Provider[]; providers?: Provider[];
exportAs?: string; exportAs?: string;
queries?: {[key: string]: any}; queries?: {[key: string]: any};
guards?: {[key: string]: any};
} }
export const createDirective = export const createDirective =
makeMetadataFactory<Directive>('Directive', (dir: Directive = {}) => dir); makeMetadataFactory<Directive>('Directive', (dir: Directive = {}) => dir);

View File

@ -44,7 +44,8 @@ export class DirectiveResolver {
const metadata = findLast(typeMetadata, isDirectiveMetadata); const metadata = findLast(typeMetadata, isDirectiveMetadata);
if (metadata) { if (metadata) {
const propertyMetadata = this._reflector.propMetadata(type); const propertyMetadata = this._reflector.propMetadata(type);
return this._mergeWithPropertyMetadata(metadata, propertyMetadata, type); const guards = this._reflector.guards(type);
return this._mergeWithPropertyMetadata(metadata, propertyMetadata, guards, type);
} }
} }
@ -56,12 +57,12 @@ export class DirectiveResolver {
} }
private _mergeWithPropertyMetadata( private _mergeWithPropertyMetadata(
dm: Directive, propertyMetadata: {[key: string]: any[]}, directiveType: Type): Directive { dm: Directive, propertyMetadata: {[key: string]: any[]}, guards: {[key: string]: any},
directiveType: Type): Directive {
const inputs: string[] = []; const inputs: string[] = [];
const outputs: string[] = []; const outputs: string[] = [];
const host: {[key: string]: string} = {}; const host: {[key: string]: string} = {};
const queries: {[key: string]: any} = {}; const queries: {[key: string]: any} = {};
Object.keys(propertyMetadata).forEach((propName: string) => { Object.keys(propertyMetadata).forEach((propName: string) => {
const input = findLast(propertyMetadata[propName], (a) => createInput.isTypeOf(a)); const input = findLast(propertyMetadata[propName], (a) => createInput.isTypeOf(a));
if (input) { if (input) {
@ -105,18 +106,20 @@ export class DirectiveResolver {
queries[propName] = query; queries[propName] = query;
} }
}); });
return this._merge(dm, inputs, outputs, host, queries, directiveType); return this._merge(dm, inputs, outputs, host, queries, guards, directiveType);
} }
private _extractPublicName(def: string) { return splitAtColon(def, [null !, def])[1].trim(); } private _extractPublicName(def: string) { return splitAtColon(def, [null !, def])[1].trim(); }
private _dedupeBindings(bindings: string[]): string[] { private _dedupeBindings(bindings: string[]): string[] {
const names = new Set<string>(); const names = new Set<string>();
const publicNames = new Set<string>();
const reversedResult: string[] = []; const reversedResult: string[] = [];
// go last to first to allow later entries to overwrite previous entries // go last to first to allow later entries to overwrite previous entries
for (let i = bindings.length - 1; i >= 0; i--) { for (let i = bindings.length - 1; i >= 0; i--) {
const binding = bindings[i]; const binding = bindings[i];
const name = this._extractPublicName(binding); const name = this._extractPublicName(binding);
publicNames.add(name);
if (!names.has(name)) { if (!names.has(name)) {
names.add(name); names.add(name);
reversedResult.push(binding); reversedResult.push(binding);
@ -127,14 +130,13 @@ export class DirectiveResolver {
private _merge( private _merge(
directive: Directive, inputs: string[], outputs: string[], host: {[key: string]: string}, directive: Directive, inputs: string[], outputs: string[], host: {[key: string]: string},
queries: {[key: string]: any}, directiveType: Type): Directive { queries: {[key: string]: any}, guards: {[key: string]: any}, directiveType: Type): Directive {
const mergedInputs = const mergedInputs =
this._dedupeBindings(directive.inputs ? directive.inputs.concat(inputs) : inputs); this._dedupeBindings(directive.inputs ? directive.inputs.concat(inputs) : inputs);
const mergedOutputs = const mergedOutputs =
this._dedupeBindings(directive.outputs ? directive.outputs.concat(outputs) : outputs); this._dedupeBindings(directive.outputs ? directive.outputs.concat(outputs) : outputs);
const mergedHost = directive.host ? {...directive.host, ...host} : host; const mergedHost = directive.host ? {...directive.host, ...host} : host;
const mergedQueries = directive.queries ? {...directive.queries, ...queries} : queries; const mergedQueries = directive.queries ? {...directive.queries, ...queries} : queries;
if (createComponent.isTypeOf(directive)) { if (createComponent.isTypeOf(directive)) {
const comp = directive as Component; const comp = directive as Component;
return createComponent({ return createComponent({
@ -166,7 +168,7 @@ export class DirectiveResolver {
host: mergedHost, host: mergedHost,
exportAs: directive.exportAs, exportAs: directive.exportAs,
queries: mergedQueries, queries: mergedQueries,
providers: directive.providers providers: directive.providers, guards
}); });
} }
} }

View File

@ -208,6 +208,7 @@ export class CompileMetadataResolver {
providers: [], providers: [],
viewProviders: [], viewProviders: [],
queries: [], queries: [],
guards: {},
viewQueries: [], viewQueries: [],
componentViewType: hostViewType, componentViewType: hostViewType,
rendererType: rendererType:
@ -240,6 +241,7 @@ export class CompileMetadataResolver {
providers: metadata.providers, providers: metadata.providers,
viewProviders: metadata.viewProviders, viewProviders: metadata.viewProviders,
queries: metadata.queries, queries: metadata.queries,
guards: metadata.guards,
viewQueries: metadata.viewQueries, viewQueries: metadata.viewQueries,
entryComponents: metadata.entryComponents, entryComponents: metadata.entryComponents,
componentViewType: metadata.componentViewType, componentViewType: metadata.componentViewType,
@ -383,6 +385,7 @@ export class CompileMetadataResolver {
providers: providers || [], providers: providers || [],
viewProviders: viewProviders || [], viewProviders: viewProviders || [],
queries: queries || [], queries: queries || [],
guards: dirMeta.guards || {},
viewQueries: viewQueries || [], viewQueries: viewQueries || [],
entryComponents: entryComponentMetadata, entryComponents: entryComponentMetadata,
componentViewType: nonNormalizedTemplateMetadata ? this.getComponentViewClass(directiveType) : componentViewType: nonNormalizedTemplateMetadata ? this.getComponentViewClass(directiveType) :

View File

@ -152,7 +152,7 @@ export function utf8Encode(str: string): string {
export interface OutputContext { export interface OutputContext {
genFilePath: string; genFilePath: string;
statements: o.Statement[]; statements: o.Statement[];
importExpr(reference: any, typeParams?: o.Type[]|null): o.Expression; importExpr(reference: any, typeParams?: o.Type[]|null, useSummaries?: boolean): o.Expression;
} }
export function stringify(token: any): string { export function stringify(token: any): string {

View File

@ -10,13 +10,15 @@ import {AotCompilerOptions} from '../aot/compiler_options';
import {StaticReflector} from '../aot/static_reflector'; import {StaticReflector} from '../aot/static_reflector';
import {StaticSymbol} from '../aot/static_symbol'; import {StaticSymbol} from '../aot/static_symbol';
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompilePipeSummary} from '../compile_metadata'; import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompilePipeSummary} from '../compile_metadata';
import {BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter'; import {BindingForm, BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast'; import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast';
import {Identifiers} from '../identifiers'; import {Identifiers} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {convertValueToOutputAst} from '../output/value_util'; import {convertValueToOutputAst} from '../output/value_util';
import {ParseSourceSpan} from '../parse_util'; import {ParseSourceSpan} from '../parse_util';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAst, ProviderAstType, QueryMatch, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast'; import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAst, ProviderAstType, QueryMatch, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
import {OutputContext} from '../util';
/** /**
* Generates code that is used to type check templates. * Generates code that is used to type check templates.
@ -34,27 +36,34 @@ export class TypeCheckCompiler {
*/ */
compileComponent( compileComponent(
componentId: string, component: CompileDirectiveMetadata, template: TemplateAst[], componentId: string, component: CompileDirectiveMetadata, template: TemplateAst[],
usedPipes: CompilePipeSummary[], usedPipes: CompilePipeSummary[], externalReferenceVars: Map<StaticSymbol, string>,
externalReferenceVars: Map<StaticSymbol, string>): o.Statement[] { ctx: OutputContext): o.Statement[] {
const pipes = new Map<string, StaticSymbol>(); const pipes = new Map<string, StaticSymbol>();
usedPipes.forEach(p => pipes.set(p.name, p.type.reference)); usedPipes.forEach(p => pipes.set(p.name, p.type.reference));
let embeddedViewCount = 0; let embeddedViewCount = 0;
const viewBuilderFactory = (parent: ViewBuilder | null): ViewBuilder => { const viewBuilderFactory =
const embeddedViewIndex = embeddedViewCount++; (parent: ViewBuilder | null, guards: GuardExpression[]): ViewBuilder => {
return new ViewBuilder( const embeddedViewIndex = embeddedViewCount++;
this.options, this.reflector, externalReferenceVars, parent, component.type.reference, return new ViewBuilder(
component.isHost, embeddedViewIndex, pipes, viewBuilderFactory); this.options, this.reflector, externalReferenceVars, parent, component.type.reference,
}; component.isHost, embeddedViewIndex, pipes, guards, ctx, viewBuilderFactory);
};
const visitor = viewBuilderFactory(null); const visitor = viewBuilderFactory(null, []);
visitor.visitAll([], template); visitor.visitAll([], template);
return visitor.build(componentId); return visitor.build(componentId);
} }
} }
interface GuardExpression {
guard: StaticSymbol;
useIf: boolean;
expression: Expression;
}
interface ViewBuilderFactory { interface ViewBuilderFactory {
(parent: ViewBuilder): ViewBuilder; (parent: ViewBuilder, guards: GuardExpression[]): ViewBuilder;
} }
// Note: This is used as key in Map and should therefore be // Note: This is used as key in Map and should therefore be
@ -94,6 +103,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
private externalReferenceVars: Map<StaticSymbol, string>, private parent: ViewBuilder|null, private externalReferenceVars: Map<StaticSymbol, string>, private parent: ViewBuilder|null,
private component: StaticSymbol, private isHostComponent: boolean, private component: StaticSymbol, private isHostComponent: boolean,
private embeddedViewIndex: number, private pipes: Map<string, StaticSymbol>, private embeddedViewIndex: number, private pipes: Map<string, StaticSymbol>,
private guards: GuardExpression[], private ctx: OutputContext,
private viewBuilderFactory: ViewBuilderFactory) {} private viewBuilderFactory: ViewBuilderFactory) {}
private getOutputVar(type: o.BuiltinTypeName|StaticSymbol): string { private getOutputVar(type: o.BuiltinTypeName|StaticSymbol): string {
@ -112,6 +122,24 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
return varName; return varName;
} }
private getTypeGuardExpressions(ast: EmbeddedTemplateAst): GuardExpression[] {
const result = [...this.guards];
for (let directive of ast.directives) {
for (let input of directive.inputs) {
const guard = directive.directive.guards[input.directiveName];
if (guard) {
const useIf = guard === 'UseIf';
result.push({
guard,
useIf,
expression: {context: this.component, value: input.value} as Expression
});
}
}
}
return result;
}
visitAll(variables: VariableAst[], astNodes: TemplateAst[]) { visitAll(variables: VariableAst[], astNodes: TemplateAst[]) {
this.variables = variables; this.variables = variables;
templateVisitAll(this, astNodes); templateVisitAll(this, astNodes);
@ -119,7 +147,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
build(componentId: string, targetStatements: o.Statement[] = []): o.Statement[] { build(componentId: string, targetStatements: o.Statement[] = []): o.Statement[] {
this.children.forEach((child) => child.build(componentId, targetStatements)); this.children.forEach((child) => child.build(componentId, targetStatements));
const viewStmts: o.Statement[] = let viewStmts: o.Statement[] =
[o.variable(DYNAMIC_VAR_NAME).set(o.NULL_EXPR).toDeclStmt(o.DYNAMIC_TYPE)]; [o.variable(DYNAMIC_VAR_NAME).set(o.NULL_EXPR).toDeclStmt(o.DYNAMIC_TYPE)];
let bindingCount = 0; let bindingCount = 0;
this.updates.forEach((expression) => { this.updates.forEach((expression) => {
@ -127,7 +155,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
const bindingId = `${bindingCount++}`; const bindingId = `${bindingCount++}`;
const nameResolver = context === this.component ? this : defaultResolver; const nameResolver = context === this.component ? this : defaultResolver;
const {stmts, currValExpr} = convertPropertyBinding( const {stmts, currValExpr} = convertPropertyBinding(
nameResolver, o.variable(this.getOutputVar(context)), value, bindingId); nameResolver, o.variable(this.getOutputVar(context)), value, bindingId,
BindingForm.General);
stmts.push(new o.ExpressionStatement(currValExpr)); stmts.push(new o.ExpressionStatement(currValExpr));
viewStmts.push(...stmts.map( viewStmts.push(...stmts.map(
(stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan))); (stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
@ -142,6 +171,28 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
(stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan))); (stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
}); });
if (this.guards.length) {
let guardExpression: o.Expression|undefined = undefined;
for (const guard of this.guards) {
const {context, value} = this.preprocessUpdateExpression(guard.expression);
const bindingId = `${bindingCount++}`;
const nameResolver = context === this.component ? this : defaultResolver;
// We only support support simple expressions and ignore others as they
// are unlikely to affect type narrowing.
const {stmts, currValExpr} = convertPropertyBinding(
nameResolver, o.variable(this.getOutputVar(context)), value, bindingId,
BindingForm.TrySimple);
if (stmts.length == 0) {
const guardClause =
guard.useIf ? currValExpr : this.ctx.importExpr(guard.guard).callFn([currValExpr]);
guardExpression = guardExpression ? guardExpression.and(guardClause) : guardClause;
}
}
if (guardExpression) {
viewStmts = [new o.IfStmt(guardExpression, viewStmts)];
}
}
const viewName = `_View_${componentId}_${this.embeddedViewIndex}`; const viewName = `_View_${componentId}_${this.embeddedViewIndex}`;
const viewFactory = new o.DeclareFunctionStmt(viewName, [], viewStmts); const viewFactory = new o.DeclareFunctionStmt(viewName, [], viewStmts);
targetStatements.push(viewFactory); targetStatements.push(viewFactory);
@ -163,7 +214,12 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
// for the context in any embedded view. // for the context in any embedded view.
// We keep this behaivor behind a flag for now. // We keep this behaivor behind a flag for now.
if (this.options.fullTemplateTypeCheck) { if (this.options.fullTemplateTypeCheck) {
const childVisitor = this.viewBuilderFactory(this); // Find any applicable type guards. For example, NgIf has a type guard on ngIf
// (see NgIf.ngIfTypeGuard) that can be used to indicate that a template is only
// stamped out if ngIf is truthy so any bindings in the template can assume that,
// if a nullable type is used for ngIf, that expression is not null or undefined.
const guards = this.getTypeGuardExpressions(ast);
const childVisitor = this.viewBuilderFactory(this, guards);
this.children.push(childVisitor); this.children.push(childVisitor);
childVisitor.visitAll(ast.variables, ast.children); childVisitor.visitAll(ast.variables, ast.children);
} }

View File

@ -8,7 +8,7 @@
import {CompileDirectiveMetadata, CompilePipeSummary, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata'; import {CompileDirectiveMetadata, CompilePipeSummary, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata';
import {CompileReflector} from '../compile_reflector'; import {CompileReflector} from '../compile_reflector';
import {BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter'; import {BindingForm, BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
import {ArgumentType, BindingFlags, ChangeDetectionStrategy, NodeFlags, QueryBindingType, QueryValueType, ViewFlags} from '../core'; import {ArgumentType, BindingFlags, ChangeDetectionStrategy, NodeFlags, QueryBindingType, QueryValueType, ViewFlags} from '../core';
import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast'; import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast';
import {Identifiers} from '../identifiers'; import {Identifiers} from '../identifiers';
@ -859,7 +859,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
const bindingId = `${updateBindingCount++}`; const bindingId = `${updateBindingCount++}`;
const nameResolver = context === COMP_VAR ? self : null; const nameResolver = context === COMP_VAR ? self : null;
const {stmts, currValExpr} = const {stmts, currValExpr} =
convertPropertyBinding(nameResolver, context, value, bindingId); convertPropertyBinding(nameResolver, context, value, bindingId, BindingForm.General);
updateStmts.push(...stmts.map( updateStmts.push(...stmts.map(
(stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan))); (stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
return o.applySourceSpanToExpressionIfNeeded(currValExpr, sourceSpan); return o.applySourceSpanToExpressionIfNeeded(currValExpr, sourceSpan);

View File

@ -196,6 +196,25 @@ describe('StaticSymbolResolver', () => {
.toBe(symbolCache.get('/test3.d.ts', 'b')); .toBe(symbolCache.get('/test3.d.ts', 'b'));
}); });
it('should ignore summaries for inputAs if requested', () => {
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'), /* useSummaries */ false))
.toBeUndefined();
});
it('should calculate importAs for symbols with members based on importAs for symbols without', it('should calculate importAs for symbols with members based on importAs for symbols without',
() => { () => {
init( init(

View File

@ -126,6 +126,7 @@ export function main() {
outputs: [], outputs: [],
host: {}, host: {},
queries: {}, queries: {},
guards: {},
exportAs: undefined, exportAs: undefined,
providers: undefined providers: undefined
})); }));
@ -154,6 +155,7 @@ export function main() {
outputs: [], outputs: [],
host: {}, host: {},
queries: {}, queries: {},
guards: {},
exportAs: undefined, exportAs: undefined,
providers: undefined providers: undefined
})); }));
@ -164,6 +166,7 @@ export function main() {
outputs: [], outputs: [],
host: {}, host: {},
queries: {}, queries: {},
guards: {},
exportAs: undefined, exportAs: undefined,
providers: undefined providers: undefined
})); }));

View File

@ -38,28 +38,29 @@ function createTypeMeta({reference, diDeps}: {reference: any, diDeps?: any[]}):
return {reference: reference, diDeps: diDeps || [], lifecycleHooks: []}; return {reference: reference, diDeps: diDeps || [], lifecycleHooks: []};
} }
function compileDirectiveMetadataCreate({isHost, type, isComponent, selector, exportAs, function compileDirectiveMetadataCreate(
changeDetection, inputs, outputs, host, providers, {isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs, host,
viewProviders, queries, viewQueries, entryComponents, providers, viewProviders, queries, guards, viewQueries, entryComponents, template,
template, componentViewType, rendererType}: { componentViewType, rendererType}: {
isHost?: boolean, isHost?: boolean,
type?: CompileTypeMetadata, type?: CompileTypeMetadata,
isComponent?: boolean, isComponent?: boolean,
selector?: string | null, selector?: string | null,
exportAs?: string | null, exportAs?: string | null,
changeDetection?: ChangeDetectionStrategy | null, changeDetection?: ChangeDetectionStrategy | null,
inputs?: string[], inputs?: string[],
outputs?: string[], outputs?: string[],
host?: {[key: string]: string}, host?: {[key: string]: string},
providers?: CompileProviderMetadata[] | null, providers?: CompileProviderMetadata[] | null,
viewProviders?: CompileProviderMetadata[] | null, viewProviders?: CompileProviderMetadata[] | null,
queries?: CompileQueryMetadata[] | null, queries?: CompileQueryMetadata[] | null,
viewQueries?: CompileQueryMetadata[], guards?: {[key: string]: any},
entryComponents?: CompileEntryComponentMetadata[], viewQueries?: CompileQueryMetadata[],
template?: CompileTemplateMetadata, entryComponents?: CompileEntryComponentMetadata[],
componentViewType?: StaticSymbol | ProxyClass | null, template?: CompileTemplateMetadata,
rendererType?: StaticSymbol | RendererType2 | null, componentViewType?: StaticSymbol | ProxyClass | null,
}) { rendererType?: StaticSymbol | RendererType2 | null,
}) {
return CompileDirectiveMetadata.create({ return CompileDirectiveMetadata.create({
isHost: !!isHost, isHost: !!isHost,
type: noUndefined(type) !, type: noUndefined(type) !,
@ -73,6 +74,7 @@ function compileDirectiveMetadataCreate({isHost, type, isComponent, selector, ex
providers: providers || [], providers: providers || [],
viewProviders: viewProviders || [], viewProviders: viewProviders || [],
queries: queries || [], queries: queries || [],
guards: guards || {},
viewQueries: viewQueries || [], viewQueries: viewQueries || [],
entryComponents: entryComponents || [], entryComponents: entryComponents || [],
template: noUndefined(template) !, template: noUndefined(template) !,
@ -390,6 +392,7 @@ export function main() {
providers: [], providers: [],
viewProviders: [], viewProviders: [],
queries: [], queries: [],
guards: {},
viewQueries: [], viewQueries: [],
entryComponents: [], entryComponents: [],
componentViewType: null, componentViewType: null,

View File

@ -102,7 +102,8 @@ export function createPlatformFactory(
parentPlatformFactory: ((extraProviders?: StaticProvider[]) => PlatformRef) | null, parentPlatformFactory: ((extraProviders?: StaticProvider[]) => PlatformRef) | null,
name: string, providers: StaticProvider[] = []): (extraProviders?: StaticProvider[]) => name: string, providers: StaticProvider[] = []): (extraProviders?: StaticProvider[]) =>
PlatformRef { PlatformRef {
const marker = new InjectionToken(`Platform: ${name}`); const desc = `Platform: ${name}`;
const marker = new InjectionToken(desc);
return (extraProviders: StaticProvider[] = []) => { return (extraProviders: StaticProvider[] = []) => {
let platform = getPlatform(); let platform = getPlatform();
if (!platform || platform.injector.get(ALLOW_MULTIPLE_PLATFORMS, false)) { if (!platform || platform.injector.get(ALLOW_MULTIPLE_PLATFORMS, false)) {
@ -110,8 +111,9 @@ export function createPlatformFactory(
parentPlatformFactory( parentPlatformFactory(
providers.concat(extraProviders).concat({provide: marker, useValue: true})); providers.concat(extraProviders).concat({provide: marker, useValue: true}));
} else { } else {
createPlatform(Injector.create( const injectedProviders: StaticProvider[] =
providers.concat(extraProviders).concat({provide: marker, useValue: true}))); providers.concat(extraProviders).concat({provide: marker, useValue: true});
createPlatform(Injector.create({providers: injectedProviders, name: desc}));
} }
} }
return assertPlatform(marker); return assertPlatform(marker);
@ -224,10 +226,12 @@ export class PlatformRef {
// pass that as parent to the NgModuleFactory. // pass that as parent to the NgModuleFactory.
const ngZoneOption = options ? options.ngZone : undefined; const ngZoneOption = options ? options.ngZone : undefined;
const ngZone = getNgZone(ngZoneOption); const ngZone = getNgZone(ngZoneOption);
const providers: StaticProvider[] = [{provide: NgZone, useValue: ngZone}];
// Attention: Don't use ApplicationRef.run here, // Attention: Don't use ApplicationRef.run here,
// as we want to be sure that all possible constructor calls are inside `ngZone.run`! // as we want to be sure that all possible constructor calls are inside `ngZone.run`!
return ngZone.run(() => { return ngZone.run(() => {
const ngZoneInjector = Injector.create([{provide: NgZone, useValue: ngZone}], this.injector); const ngZoneInjector = Injector.create(
{providers: providers, parent: this.injector, name: moduleFactory.moduleType.name});
const moduleRef = <InternalNgModuleRef<M>>moduleFactory.create(ngZoneInjector); const moduleRef = <InternalNgModuleRef<M>>moduleFactory.create(ngZoneInjector);
const exceptionHandler: ErrorHandler = moduleRef.injector.get(ErrorHandler, null); const exceptionHandler: ErrorHandler = moduleRef.injector.get(ErrorHandler, null);
if (!exceptionHandler) { if (!exceptionHandler) {

View File

@ -8,12 +8,12 @@
import {Type} from '../type'; import {Type} from '../type';
import {stringify} from '../util'; import {stringify} from '../util';
import {resolveForwardRef} from './forward_ref'; import {resolveForwardRef} from './forward_ref';
import {InjectionToken} from './injection_token'; import {InjectionToken} from './injection_token';
import {Inject, Optional, Self, SkipSelf} from './metadata'; import {Inject, Optional, Self, SkipSelf} from './metadata';
import {ConstructorProvider, ExistingProvider, FactoryProvider, StaticClassProvider, StaticProvider, ValueProvider} from './provider'; import {ConstructorProvider, ExistingProvider, FactoryProvider, StaticClassProvider, StaticProvider, ValueProvider} from './provider';
export const SOURCE = '__source';
const _THROW_IF_NOT_FOUND = new Object(); const _THROW_IF_NOT_FOUND = new Object();
export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND; export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
@ -64,6 +64,13 @@ export abstract class Injector {
*/ */
abstract get(token: any, notFoundValue?: any): any; abstract get(token: any, notFoundValue?: any): any;
/**
* @deprecated from v5 use the new signature Injector.create(options)
*/
static create(providers: StaticProvider[], parent?: Injector): Injector;
static create(options: {providers: StaticProvider[], parent?: Injector, name?: string}): Injector;
/** /**
* Create a new Injector which is configure using `StaticProvider`s. * Create a new Injector which is configure using `StaticProvider`s.
* *
@ -71,8 +78,14 @@ export abstract class Injector {
* *
* {@example core/di/ts/provider_spec.ts region='ConstructorProvider'} * {@example core/di/ts/provider_spec.ts region='ConstructorProvider'}
*/ */
static create(providers: StaticProvider[], parent?: Injector): Injector { static create(
return new StaticInjector(providers, parent); options: StaticProvider[]|{providers: StaticProvider[], parent?: Injector, name?: string},
parent?: Injector): Injector {
if (Array.isArray(options)) {
return new StaticInjector(options, parent);
} else {
return new StaticInjector(options.providers, options.parent, options.name || null);
}
} }
} }
@ -103,11 +116,14 @@ const NO_NEW_LINE = 'ɵ';
export class StaticInjector implements Injector { export class StaticInjector implements Injector {
readonly parent: Injector; readonly parent: Injector;
readonly source: string|null;
private _records: Map<any, Record>; private _records: Map<any, Record>;
constructor(providers: StaticProvider[], parent: Injector = NULL_INJECTOR) { constructor(
providers: StaticProvider[], parent: Injector = NULL_INJECTOR, source: string|null = null) {
this.parent = parent; this.parent = parent;
this.source = source;
const records = this._records = new Map<any, Record>(); const records = this._records = new Map<any, Record>();
records.set( records.set(
Injector, <Record>{token: Injector, fn: IDENT, deps: EMPTY, value: this, useNew: false}); Injector, <Record>{token: Injector, fn: IDENT, deps: EMPTY, value: this, useNew: false});
@ -122,7 +138,10 @@ export class StaticInjector implements Injector {
return tryResolveToken(token, record, this._records, this.parent, notFoundValue); return tryResolveToken(token, record, this._records, this.parent, notFoundValue);
} catch (e) { } catch (e) {
const tokenPath: any[] = e[NG_TEMP_TOKEN_PATH]; const tokenPath: any[] = e[NG_TEMP_TOKEN_PATH];
e.message = formatError('\n' + e.message, tokenPath); if (token[SOURCE]) {
tokenPath.unshift(token[SOURCE]);
}
e.message = formatError('\n' + e.message, tokenPath, this.source);
e[NG_TOKEN_PATH] = tokenPath; e[NG_TOKEN_PATH] = tokenPath;
e[NG_TEMP_TOKEN_PATH] = null; e[NG_TEMP_TOKEN_PATH] = null;
throw e; throw e;
@ -336,7 +355,7 @@ function computeDeps(provider: StaticProvider): DependencyRecord[] {
return deps; return deps;
} }
function formatError(text: string, obj: any): string { function formatError(text: string, obj: any, source: string | null = null): string {
text = text && text.charAt(0) === '\n' && text.charAt(1) == NO_NEW_LINE ? text.substr(2) : text; text = text && text.charAt(0) === '\n' && text.charAt(1) == NO_NEW_LINE ? text.substr(2) : text;
let context = stringify(obj); let context = stringify(obj);
if (obj instanceof Array) { if (obj instanceof Array) {
@ -352,7 +371,7 @@ function formatError(text: string, obj: any): string {
} }
context = `{${parts.join(', ')}}`; context = `{${parts.join(', ')}}`;
} }
return `StaticInjectorError[${context}]: ${text.replace(NEW_LINE, '\n ')}`; return `StaticInjectorError${source ? '(' + source + ')' : ''}[${context}]: ${text.replace(NEW_LINE, '\n ')}`;
} }
function staticError(text: string, obj: any): Error { function staticError(text: string, obj: any): Error {

View File

@ -71,11 +71,13 @@ export interface ResolvedReflectiveProvider {
} }
export class ResolvedReflectiveProvider_ implements ResolvedReflectiveProvider { export class ResolvedReflectiveProvider_ implements ResolvedReflectiveProvider {
readonly resolvedFactory: ResolvedReflectiveFactory;
constructor( constructor(
public key: ReflectiveKey, public resolvedFactories: ResolvedReflectiveFactory[], public key: ReflectiveKey, public resolvedFactories: ResolvedReflectiveFactory[],
public multiProvider: boolean) {} public multiProvider: boolean) {
this.resolvedFactory = this.resolvedFactories[0];
get resolvedFactory(): ResolvedReflectiveFactory { return this.resolvedFactories[0]; } }
} }
/** /**

View File

@ -66,13 +66,20 @@ export class CodegenComponentFactoryResolver implements ComponentFactoryResolver
} }
export class ComponentFactoryBoundToModule<C> extends ComponentFactory<C> { export class ComponentFactoryBoundToModule<C> extends ComponentFactory<C> {
constructor(private factory: ComponentFactory<C>, private ngModule: NgModuleRef<any>) { super(); } readonly selector: string;
readonly componentType: Type<any>;
readonly ngContentSelectors: string[];
readonly inputs: {propName: string, templateName: string}[];
readonly outputs: {propName: string, templateName: string}[];
get selector() { return this.factory.selector; } constructor(private factory: ComponentFactory<C>, private ngModule: NgModuleRef<any>) {
get componentType() { return this.factory.componentType; } super();
get ngContentSelectors() { return this.factory.ngContentSelectors; } this.selector = factory.selector;
get inputs() { return this.factory.inputs; } this.componentType = factory.componentType;
get outputs() { return this.factory.outputs; } this.ngContentSelectors = factory.ngContentSelectors;
this.inputs = factory.inputs;
this.outputs = factory.outputs;
}
create( create(
injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string|any, injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string|any,

View File

@ -41,9 +41,9 @@ export class QueryList<T>/* implements Iterable<T> */ {
private _results: Array<T> = []; private _results: Array<T> = [];
public readonly changes: Observable<any> = new EventEmitter(); public readonly changes: Observable<any> = new EventEmitter();
get length(): number { return this._results.length; } readonly length: number;
get first(): T { return this._results[0]; } readonly first: T;
get last(): T { return this._results[this.length - 1]; } readonly last: T;
/** /**
* See * See
@ -98,6 +98,9 @@ export class QueryList<T>/* implements Iterable<T> */ {
reset(res: Array<T|any[]>): void { reset(res: Array<T|any[]>): void {
this._results = flatten(res); this._results = flatten(res);
(this as{dirty: boolean}).dirty = false; (this as{dirty: boolean}).dirty = false;
(this as{length: number}).length = this._results.length;
(this as{last: T}).last = this._results[this.length - 1];
(this as{first: T}).first = this._results[0];
} }
notifyOnChanges(): void { (this.changes as EventEmitter<any>).emit(this); } notifyOnChanges(): void { (this.changes as EventEmitter<any>).emit(this); }

View File

@ -13,6 +13,7 @@ export interface PlatformReflectionCapabilities {
isReflectionEnabled(): boolean; isReflectionEnabled(): boolean;
factory(type: Type<any>): Function; factory(type: Type<any>): Function;
hasLifecycleHook(type: any, lcProperty: string): boolean; hasLifecycleHook(type: any, lcProperty: string): boolean;
guards(type: any): {[key: string]: any};
/** /**
* Return a list of annotations/types for constructor parameters * Return a list of annotations/types for constructor parameters

View File

@ -207,6 +207,8 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
return type instanceof Type && lcProperty in type.prototype; return type instanceof Type && lcProperty in type.prototype;
} }
guards(type: any): {[key: string]: any} { return {}; }
getter(name: string): GetterFn { return <GetterFn>new Function('o', 'return o.' + name + ';'); } getter(name: string): GetterFn { return <GetterFn>new Function('o', 'return o.' + name + ';'); }
setter(name: string): SetterFn { setter(name: string): SetterFn {

View File

@ -9,6 +9,7 @@
import {resolveForwardRef} from '../di/forward_ref'; import {resolveForwardRef} from '../di/forward_ref';
import {Injector} from '../di/injector'; import {Injector} from '../di/injector';
import {NgModuleRef} from '../linker/ng_module_factory'; import {NgModuleRef} from '../linker/ng_module_factory';
import {stringify} from '../util';
import {DepDef, DepFlags, NgModuleData, NgModuleDefinition, NgModuleProviderDef, NodeFlags} from './types'; import {DepDef, DepFlags, NgModuleData, NgModuleDefinition, NgModuleProviderDef, NodeFlags} from './types';
import {splitDepsDsl, tokenKey} from './util'; import {splitDepsDsl, tokenKey} from './util';
@ -25,7 +26,7 @@ export function moduleProvideDef(
// lowered the expression and then stopped evaluating it, // lowered the expression and then stopped evaluating it,
// i.e. also didn't unwrap it. // i.e. also didn't unwrap it.
value = resolveForwardRef(value); value = resolveForwardRef(value);
const depDefs = splitDepsDsl(deps); const depDefs = splitDepsDsl(deps, stringify(token));
return { return {
// will bet set by the module definition // will bet set by the module definition
index: -1, index: -1,

View File

@ -12,7 +12,7 @@ import {ElementRef} from '../linker/element_ref';
import {TemplateRef} from '../linker/template_ref'; import {TemplateRef} from '../linker/template_ref';
import {ViewContainerRef} from '../linker/view_container_ref'; import {ViewContainerRef} from '../linker/view_container_ref';
import {Renderer as RendererV1, Renderer2} from '../render/api'; import {Renderer as RendererV1, Renderer2} from '../render/api';
import {stringify} from '../util';
import {createChangeDetectorRef, createInjector, createRendererV1} from './refs'; import {createChangeDetectorRef, createInjector, createRendererV1} from './refs';
import {BindingDef, BindingFlags, DepDef, DepFlags, NodeDef, NodeFlags, OutputDef, OutputType, ProviderData, QueryValueType, Services, ViewData, ViewFlags, ViewState, asElementData, asProviderData, shouldCallLifecycleInitHook} from './types'; import {BindingDef, BindingFlags, DepDef, DepFlags, NodeDef, NodeFlags, OutputDef, OutputType, ProviderData, QueryValueType, Services, ViewData, ViewFlags, ViewState, asElementData, asProviderData, shouldCallLifecycleInitHook} from './types';
import {calcBindingFlags, checkBinding, dispatchEvent, isComponentView, splitDepsDsl, splitMatchedQueriesDsl, tokenKey, viewParentEl} from './util'; import {calcBindingFlags, checkBinding, dispatchEvent, isComponentView, splitDepsDsl, splitMatchedQueriesDsl, tokenKey, viewParentEl} from './util';
@ -83,7 +83,7 @@ export function _def(
// i.e. also didn't unwrap it. // i.e. also didn't unwrap it.
value = resolveForwardRef(value); value = resolveForwardRef(value);
const depDefs = splitDepsDsl(deps); const depDefs = splitDepsDsl(deps, stringify(token));
return { return {
// will bet set by the view definition // will bet set by the view definition

View File

@ -481,6 +481,8 @@ class NgModuleRef_ implements NgModuleData, InternalNgModuleRef<any> {
/** @internal */ /** @internal */
_providers: any[]; _providers: any[];
readonly injector: Injector = this;
constructor( constructor(
private _moduleType: Type<any>, public _parent: Injector, private _moduleType: Type<any>, public _parent: Injector,
public _bootstrapComponents: Type<any>[], public _def: NgModuleDefinition) { public _bootstrapComponents: Type<any>[], public _def: NgModuleDefinition) {
@ -496,8 +498,6 @@ class NgModuleRef_ implements NgModuleData, InternalNgModuleRef<any> {
get componentFactoryResolver() { return this.get(ComponentFactoryResolver); } get componentFactoryResolver() { return this.get(ComponentFactoryResolver); }
get injector(): Injector { return this; }
destroy(): void { destroy(): void {
if (this._destroyed) { if (this._destroyed) {
throw new Error( throw new Error(

View File

@ -649,9 +649,8 @@ class DebugRendererFactory2 implements RendererFactory2 {
class DebugRenderer2 implements Renderer2 { class DebugRenderer2 implements Renderer2 {
constructor(private delegate: Renderer2) {} readonly data: {[key: string]: any};
constructor(private delegate: Renderer2) { this.data = this.delegate.data; }
get data() { return this.delegate.data; }
destroyNode(node: any) { destroyNode(node: any) {
removeDebugNodeFromIndex(getDebugNode(node) !); removeDebugNodeFromIndex(getDebugNode(node) !);

View File

@ -7,10 +7,10 @@
*/ */
import {WrappedValue, devModeEqual} from '../change_detection/change_detection'; import {WrappedValue, devModeEqual} from '../change_detection/change_detection';
import {SOURCE} from '../di/injector';
import {ViewEncapsulation} from '../metadata/view'; import {ViewEncapsulation} from '../metadata/view';
import {RendererType2} from '../render/api'; import {RendererType2} from '../render/api';
import {looseIdentical, stringify} from '../util'; import {looseIdentical, stringify} from '../util';
import {expressionChangedAfterItHasBeenCheckedError} from './errors'; import {expressionChangedAfterItHasBeenCheckedError} from './errors';
import {BindingDef, BindingFlags, Definition, DefinitionFactory, DepDef, DepFlags, ElementData, NodeDef, NodeFlags, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asTextData} from './types'; import {BindingDef, BindingFlags, Definition, DefinitionFactory, DepDef, DepFlags, ElementData, NodeDef, NodeFlags, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asTextData} from './types';
@ -209,7 +209,7 @@ export function splitMatchedQueriesDsl(
return {matchedQueries, references, matchedQueryIds}; return {matchedQueries, references, matchedQueryIds};
} }
export function splitDepsDsl(deps: ([DepFlags, any] | any)[]): DepDef[] { export function splitDepsDsl(deps: ([DepFlags, any] | any)[], sourceName?: string): DepDef[] {
return deps.map(value => { return deps.map(value => {
let token: any; let token: any;
let flags: DepFlags; let flags: DepFlags;
@ -219,6 +219,9 @@ export function splitDepsDsl(deps: ([DepFlags, any] | any)[]): DepDef[] {
flags = DepFlags.None; flags = DepFlags.None;
token = value; token = value;
} }
if (token && (typeof token === 'function' || typeof token === 'object') && sourceName) {
Object.defineProperty(token, SOURCE, {value: sourceName, configurable: true});
}
return {flags, token, tokenKey: tokenKey(token)}; return {flags, token, tokenKey: tokenKey(token)};
}); });
} }

View File

@ -255,6 +255,45 @@ export function main() {
]); ]);
}); });
it('should allow a transition to use a function to determine what method to run', () => {
let valueToMatch = '';
const transitionFn =
(fromState: string, toState: string) => { return toState == valueToMatch; };
@Component({
selector: 'if-cmp',
template: '<div [@myAnimation]="exp"></div>',
animations: [
trigger('myAnimation', [transition(
transitionFn,
[style({opacity: 0}), animate(1234, style({opacity: 1}))])]),
]
})
class Cmp {
exp: any = '';
}
TestBed.configureTestingModule({declarations: [Cmp]});
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
valueToMatch = cmp.exp = 'something';
fixture.detectChanges();
let players = getLog();
expect(players.length).toEqual(1);
let [p1] = players;
expect(p1.totalTime).toEqual(1234);
resetLog();
valueToMatch = 'something-else';
cmp.exp = 'this-wont-match';
fixture.detectChanges();
players = getLog();
expect(players.length).toEqual(0);
});
it('should allow a state value to be `0`', () => { it('should allow a state value to be `0`', () => {
@Component({ @Component({
selector: 'if-cmp', selector: 'if-cmp',

View File

@ -147,8 +147,8 @@ export function main() {
expect(() => createAndGetRootNodes(compViewDef(rootElNodes))) expect(() => createAndGetRootNodes(compViewDef(rootElNodes)))
.toThrowError( .toThrowError(
'StaticInjectorError[Dep]: \n' + 'StaticInjectorError(DynamicTestModule)[SomeService -> Dep]: \n' +
' StaticInjectorError[Dep]: \n' + ' StaticInjectorError(Platform: core)[SomeService -> Dep]: \n' +
' NullInjectorError: No provider for Dep!'); ' NullInjectorError: No provider for Dep!');
const nonRootElNodes = [ const nonRootElNodes = [
@ -161,8 +161,8 @@ export function main() {
expect(() => createAndGetRootNodes(compViewDef(nonRootElNodes))) expect(() => createAndGetRootNodes(compViewDef(nonRootElNodes)))
.toThrowError( .toThrowError(
'StaticInjectorError[Dep]: \n' + 'StaticInjectorError(DynamicTestModule)[SomeService -> Dep]: \n' +
' StaticInjectorError[Dep]: \n' + ' StaticInjectorError(Platform: core)[SomeService -> Dep]: \n' +
' NullInjectorError: No provider for Dep!'); ' NullInjectorError: No provider for Dep!');
}); });
@ -186,8 +186,8 @@ export function main() {
directiveDef(1, NodeFlags.None, null, 0, SomeService, ['nonExistingDep']) directiveDef(1, NodeFlags.None, null, 0, SomeService, ['nonExistingDep'])
]))) ])))
.toThrowError( .toThrowError(
'StaticInjectorError[nonExistingDep]: \n' + 'StaticInjectorError(DynamicTestModule)[nonExistingDep]: \n' +
' StaticInjectorError[nonExistingDep]: \n' + ' StaticInjectorError(Platform: core)[nonExistingDep]: \n' +
' NullInjectorError: No provider for nonExistingDep!'); ' NullInjectorError: No provider for nonExistingDep!');
}); });

View File

@ -355,8 +355,12 @@ export class TestBed implements Injector {
} }
const ngZone = new NgZone({enableLongStackTrace: true}); const ngZone = new NgZone({enableLongStackTrace: true});
const ngZoneInjector = const providers: StaticProvider[] = [{provide: NgZone, useValue: ngZone}];
Injector.create([{provide: NgZone, useValue: ngZone}], this.platform.injector); const ngZoneInjector = Injector.create({
providers: providers,
parent: this.platform.injector,
name: this._moduleFactory.moduleType.name
});
this._moduleRef = this._moduleFactory.create(ngZoneInjector); this._moduleRef = this._moduleFactory.create(ngZoneInjector);
// ApplicationInitStatus.runInitializers() is marked @internal to core. So casting to any // ApplicationInitStatus.runInitializers() is marked @internal to core. So casting to any
// before accessing it. // before accessing it.

View File

@ -135,7 +135,7 @@ export function main() {
name = 'square'; name = 'square';
} }
const injector = Injector.create([{provide: Square, deps: []}]); const injector = Injector.create({providers: [{provide: Square, deps: []}]});
const shape: Square = injector.get(Square); const shape: Square = injector.get(Square);
expect(shape.name).toEqual('square'); expect(shape.name).toEqual('square');

View File

@ -49,10 +49,10 @@ export const COMPOSITION_BUFFER_MODE = new InjectionToken<boolean>('CompositionE
// https://github.com/angular/angular/issues/3011 is implemented // https://github.com/angular/angular/issues/3011 is implemented
// selector: '[ngModel],[formControl],[formControlName]', // selector: '[ngModel],[formControl],[formControlName]',
host: { host: {
'(input)': '_handleInput($event.target.value)', '(input)': '$any(this)._handleInput($event.target.value)',
'(blur)': 'onTouched()', '(blur)': 'onTouched()',
'(compositionstart)': '_compositionStart()', '(compositionstart)': '$any(this)._compositionStart()',
'(compositionend)': '_compositionEnd($event.target.value)' '(compositionend)': '$any(this)._compositionEnd($event.target.value)'
}, },
providers: [DEFAULT_VALUE_ACCESSOR] providers: [DEFAULT_VALUE_ACCESSOR]
}) })

View File

@ -253,7 +253,7 @@ export abstract class AbstractControl {
* Sets the async validators that are active on this control. Calling this * Sets the async validators that are active on this control. Calling this
* will overwrite any existing async validators. * will overwrite any existing async validators.
*/ */
setAsyncValidators(newValidator: AsyncValidatorFn|AsyncValidatorFn[]): void { setAsyncValidators(newValidator: AsyncValidatorFn|AsyncValidatorFn[]|null): void {
this.asyncValidator = coerceToAsyncValidator(newValidator); this.asyncValidator = coerceToAsyncValidator(newValidator);
} }

View File

@ -42,6 +42,7 @@ export class JitReflector implements CompileReflector {
hasLifecycleHook(type: any, lcProperty: string): boolean { hasLifecycleHook(type: any, lcProperty: string): boolean {
return this.reflectionCapabilities.hasLifecycleHook(type, lcProperty); return this.reflectionCapabilities.hasLifecycleHook(type, lcProperty);
} }
guards(type: any): {[key: string]: any} { return this.reflectionCapabilities.guards(type); }
resolveExternalReference(ref: ExternalReference): any { resolveExternalReference(ref: ExternalReference): any {
return builtinExternalReferences.get(ref) || ref.runtime; return builtinExternalReferences.get(ref) || ref.runtime;
} }

View File

@ -7,12 +7,12 @@
*/ */
import {isPlatformBrowser} from '@angular/common'; import {isPlatformBrowser} from '@angular/common';
import {APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, Directive, ErrorHandler, Inject, Input, LOCALE_ID, NgModule, OnDestroy, PLATFORM_ID, PLATFORM_INITIALIZER, Pipe, Provider, StaticProvider, VERSION, createPlatformFactory, ɵstringify as stringify} from '@angular/core'; import {APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, Directive, ErrorHandler, Inject, Input, LOCALE_ID, NgModule, OnDestroy, PLATFORM_ID, PLATFORM_INITIALIZER, Pipe, Provider, StaticProvider, Type, VERSION, createPlatformFactory} from '@angular/core';
import {ApplicationRef, destroyPlatform} from '@angular/core/src/application_ref'; import {ApplicationRef, destroyPlatform} from '@angular/core/src/application_ref';
import {Console} from '@angular/core/src/console'; import {Console} from '@angular/core/src/console';
import {ComponentRef} from '@angular/core/src/linker/component_factory'; import {ComponentRef} from '@angular/core/src/linker/component_factory';
import {Testability, TestabilityRegistry} from '@angular/core/src/testability/testability'; import {Testability, TestabilityRegistry} from '@angular/core/src/testability/testability';
import {AsyncTestCompleter, Log, afterEach, beforeEach, beforeEachProviders, ddescribe, describe, iit, inject, it} from '@angular/core/testing/src/testing_internal'; import {AsyncTestCompleter, Log, afterEach, beforeEach, beforeEachProviders, describe, iit, inject, it} from '@angular/core/testing/src/testing_internal';
import {BrowserModule} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
@ -112,10 +112,11 @@ class DummyConsole implements Console {
class TestModule {} class TestModule {}
function bootstrap(cmpType: any, providers: Provider[] = [], platformProviders: StaticProvider[] = [ function bootstrap(
]): Promise<any> { cmpType: any, providers: Provider[] = [], platformProviders: StaticProvider[] = [],
imports: Type<any>[] = []): Promise<any> {
@NgModule({ @NgModule({
imports: [BrowserModule], imports: [BrowserModule, ...imports],
declarations: [cmpType], declarations: [cmpType],
bootstrap: [cmpType], bootstrap: [cmpType],
providers: providers, providers: providers,
@ -183,6 +184,40 @@ export function main() {
}); });
})); }));
it('should throw if no provider', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const logger = new MockConsole();
const errorHandler = new ErrorHandler();
errorHandler._console = logger as any;
class IDontExist {}
@Component({selector: 'cmp', template: 'Cmp'})
class CustomCmp {
constructor(iDontExist: IDontExist) {}
}
@Component({
selector: 'hello-app',
template: '<cmp></cmp>',
})
class RootCmp {
}
@NgModule({declarations: [CustomCmp], exports: [CustomCmp]})
class CustomModule {
}
bootstrap(RootCmp, [{provide: ErrorHandler, useValue: errorHandler}], [], [
CustomModule
]).then(null, (e: Error) => {
expect(e.message).toContain(`StaticInjectorError(TestModule)[CustomCmp -> IDontExist]:
StaticInjectorError(Platform: core)[CustomCmp -> IDontExist]:
NullInjectorError: No provider for IDontExist!`);
async.done();
return null;
});
}));
if (getDOM().supportsDOMEvents()) { if (getDOM().supportsDOMEvents()) {
it('should forward the error to promise when bootstrap fails', it('should forward the error to promise when bootstrap fails',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {

View File

@ -21,13 +21,15 @@ import {reduce} from 'rxjs/operator/reduce';
import {LoadedRouterConfig, ResolveData, RunGuardsAndResolvers} from './config'; import {LoadedRouterConfig, ResolveData, RunGuardsAndResolvers} from './config';
import {ActivationStart, ChildActivationStart, Event} from './events'; import {ActivationStart, ChildActivationStart, Event} from './events';
import {ChildrenOutletContexts, OutletContext} from './router_outlet_context'; import {ChildrenOutletContexts, OutletContext} from './router_outlet_context';
import {ActivatedRouteSnapshot, RouterStateSnapshot, equalParamsAndUrlSegments, inheritedParamsDataResolve} from './router_state'; import {ActivatedRouteSnapshot, ParamsInheritanceStrategy, RouterStateSnapshot, equalParamsAndUrlSegments, inheritedParamsDataResolve} from './router_state';
import {andObservables, forEach, shallowEqual, wrapIntoObservable} from './utils/collection'; import {andObservables, forEach, shallowEqual, wrapIntoObservable} from './utils/collection';
import {TreeNode, nodeChildrenAsMap} from './utils/tree'; import {TreeNode, nodeChildrenAsMap} from './utils/tree';
class CanActivate { class CanActivate {
constructor(public path: ActivatedRouteSnapshot[]) {} readonly route: ActivatedRouteSnapshot;
get route(): ActivatedRouteSnapshot { return this.path[this.path.length - 1]; } constructor(public path: ActivatedRouteSnapshot[]) {
this.route = this.path[this.path.length - 1];
}
} }
class CanDeactivate { class CanDeactivate {
@ -61,11 +63,11 @@ export class PreActivation {
(canDeactivate: boolean) => canDeactivate ? this.runCanActivateChecks() : of (false)); (canDeactivate: boolean) => canDeactivate ? this.runCanActivateChecks() : of (false));
} }
resolveData(): Observable<any> { resolveData(paramsInheritanceStrategy: ParamsInheritanceStrategy): Observable<any> {
if (!this.isActivating()) return of (null); if (!this.isActivating()) return of (null);
const checks$ = from(this.canActivateChecks); const checks$ = from(this.canActivateChecks);
const runningChecks$ = const runningChecks$ = concatMap.call(
concatMap.call(checks$, (check: CanActivate) => this.runResolve(check.route)); checks$, (check: CanActivate) => this.runResolve(check.route, paramsInheritanceStrategy));
return reduce.call(runningChecks$, (_: any, __: any) => _); return reduce.call(runningChecks$, (_: any, __: any) => _);
} }
@ -304,11 +306,14 @@ export class PreActivation {
return every.call(canDeactivate$, (result: any) => result === true); return every.call(canDeactivate$, (result: any) => result === true);
} }
private runResolve(future: ActivatedRouteSnapshot): Observable<any> { private runResolve(
future: ActivatedRouteSnapshot,
paramsInheritanceStrategy: ParamsInheritanceStrategy): Observable<any> {
const resolve = future._resolve; const resolve = future._resolve;
return map.call(this.resolveNode(resolve, future), (resolvedData: any): any => { return map.call(this.resolveNode(resolve, future), (resolvedData: any): any => {
future._resolvedData = resolvedData; future._resolvedData = resolvedData;
future.data = {...future.data, ...inheritedParamsDataResolve(future).resolve}; future.data = {...future.data,
...inheritedParamsDataResolve(future, paramsInheritanceStrategy).resolve};
return null; return null;
}); });
} }

View File

@ -12,7 +12,7 @@ import {Observer} from 'rxjs/Observer';
import {of } from 'rxjs/observable/of'; import {of } from 'rxjs/observable/of';
import {Data, ResolveData, Route, Routes} from './config'; import {Data, ResolveData, Route, Routes} from './config';
import {ActivatedRouteSnapshot, RouterStateSnapshot, inheritedParamsDataResolve} from './router_state'; import {ActivatedRouteSnapshot, ParamsInheritanceStrategy, RouterStateSnapshot, inheritedParamsDataResolve} from './router_state';
import {PRIMARY_OUTLET, defaultUrlMatcher} from './shared'; import {PRIMARY_OUTLET, defaultUrlMatcher} from './shared';
import {UrlSegment, UrlSegmentGroup, UrlTree, mapChildrenIntoArray} from './url_tree'; import {UrlSegment, UrlSegmentGroup, UrlTree, mapChildrenIntoArray} from './url_tree';
import {forEach, last} from './utils/collection'; import {forEach, last} from './utils/collection';
@ -21,15 +21,17 @@ import {TreeNode} from './utils/tree';
class NoMatch {} class NoMatch {}
export function recognize( export function recognize(
rootComponentType: Type<any>| null, config: Routes, urlTree: UrlTree, rootComponentType: Type<any>| null, config: Routes, urlTree: UrlTree, url: string,
url: string): Observable<RouterStateSnapshot> { paramsInheritanceStrategy: ParamsInheritanceStrategy =
return new Recognizer(rootComponentType, config, urlTree, url).recognize(); 'emptyOnly'): Observable<RouterStateSnapshot> {
return new Recognizer(rootComponentType, config, urlTree, url, paramsInheritanceStrategy)
.recognize();
} }
class Recognizer { class Recognizer {
constructor( constructor(
private rootComponentType: Type<any>|null, private config: Routes, private urlTree: UrlTree, private rootComponentType: Type<any>|null, private config: Routes, private urlTree: UrlTree,
private url: string) {} private url: string, private paramsInheritanceStrategy: ParamsInheritanceStrategy) {}
recognize(): Observable<RouterStateSnapshot> { recognize(): Observable<RouterStateSnapshot> {
try { try {
@ -55,7 +57,7 @@ class Recognizer {
inheritParamsAndData(routeNode: TreeNode<ActivatedRouteSnapshot>): void { inheritParamsAndData(routeNode: TreeNode<ActivatedRouteSnapshot>): void {
const route = routeNode.value; const route = routeNode.value;
const i = inheritedParamsDataResolve(route); const i = inheritedParamsDataResolve(route, this.paramsInheritanceStrategy);
route.params = Object.freeze(i.params); route.params = Object.freeze(i.params);
route.data = Object.freeze(i.data); route.data = Object.freeze(i.data);

View File

@ -27,7 +27,7 @@ import {recognize} from './recognize';
import {DefaultRouteReuseStrategy, DetachedRouteHandleInternal, RouteReuseStrategy} from './route_reuse_strategy'; import {DefaultRouteReuseStrategy, DetachedRouteHandleInternal, RouteReuseStrategy} from './route_reuse_strategy';
import {RouterConfigLoader} from './router_config_loader'; import {RouterConfigLoader} from './router_config_loader';
import {ChildrenOutletContexts} from './router_outlet_context'; import {ChildrenOutletContexts} from './router_outlet_context';
import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from './router_state'; import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState, inheritedParamsDataResolve} from './router_state';
import {Params, isNavigationCancelingError} from './shared'; import {Params, isNavigationCancelingError} from './shared';
import {DefaultUrlHandlingStrategy, UrlHandlingStrategy} from './url_handling_strategy'; import {DefaultUrlHandlingStrategy, UrlHandlingStrategy} from './url_handling_strategy';
import {UrlSerializer, UrlTree, containsTree, createEmptyUrlTree} from './url_tree'; import {UrlSerializer, UrlTree, containsTree, createEmptyUrlTree} from './url_tree';
@ -249,6 +249,16 @@ export class Router {
*/ */
onSameUrlNavigation: 'reload'|'ignore' = 'ignore'; onSameUrlNavigation: 'reload'|'ignore' = 'ignore';
/**
* Defines how the router merges params, data and resolved data from parent to child
* routes. Available options are:
*
* - `'emptyOnly'`, the default, only inherits parent params for path-less or component-less
* routes.
* - `'always'`, enables unconditional inheritance of parent params.
*/
paramsInheritanceStrategy: 'emptyOnly'|'always' = 'emptyOnly';
/** /**
* Creates the router service. * Creates the router service.
*/ */
@ -611,7 +621,8 @@ export class Router {
urlAndSnapshot$ = mergeMap.call(redirectsApplied$, (appliedUrl: UrlTree) => { urlAndSnapshot$ = mergeMap.call(redirectsApplied$, (appliedUrl: UrlTree) => {
return map.call( return map.call(
recognize( recognize(
this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl)), this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl),
this.paramsInheritanceStrategy),
(snapshot: any) => { (snapshot: any) => {
(this.events as Subject<Event>) (this.events as Subject<Event>)
@ -667,7 +678,7 @@ export class Router {
if (p.shouldActivate && preActivation.isActivating()) { if (p.shouldActivate && preActivation.isActivating()) {
this.triggerEvent( this.triggerEvent(
new ResolveStart(id, this.serializeUrl(url), p.appliedUrl, p.snapshot)); new ResolveStart(id, this.serializeUrl(url), p.appliedUrl, p.snapshot));
return map.call(preActivation.resolveData(), () => { return map.call(preActivation.resolveData(this.paramsInheritanceStrategy), () => {
this.triggerEvent( this.triggerEvent(
new ResolveEnd(id, this.serializeUrl(url), p.appliedUrl, p.snapshot)); new ResolveEnd(id, this.serializeUrl(url), p.appliedUrl, p.snapshot));
return p; return p;

View File

@ -278,6 +278,16 @@ export interface ExtraOptions {
* current URL. Default is 'ignore'. * current URL. Default is 'ignore'.
*/ */
onSameUrlNavigation?: 'reload'|'ignore'; onSameUrlNavigation?: 'reload'|'ignore';
/**
* Defines how the router merges params, data and resolved data from parent to child
* routes. Available options are:
*
* - `'emptyOnly'`, the default, only inherits parent params for path-less or component-less
* routes.
* - `'always'`, enables unconditional inheritance of parent params.
*/
paramsInheritanceStrategy?: 'emptyOnly'|'always';
} }
export function setupRouter( export function setupRouter(
@ -314,6 +324,10 @@ export function setupRouter(
router.onSameUrlNavigation = opts.onSameUrlNavigation; router.onSameUrlNavigation = opts.onSameUrlNavigation;
} }
if (opts.paramsInheritanceStrategy) {
router.paramsInheritanceStrategy = opts.paramsInheritanceStrategy;
}
return router; return router;
} }

View File

@ -18,6 +18,7 @@ import {shallowEqual, shallowEqualArrays} from './utils/collection';
import {Tree, TreeNode} from './utils/tree'; import {Tree, TreeNode} from './utils/tree';
/** /**
* @whatItDoes Represents the state of the router. * @whatItDoes Represents the state of the router.
* *
@ -174,6 +175,9 @@ export class ActivatedRoute {
} }
} }
/** @internal */
export type ParamsInheritanceStrategy = 'emptyOnly' | 'always';
/** @internal */ /** @internal */
export type Inherited = { export type Inherited = {
params: Params, params: Params,
@ -181,29 +185,43 @@ export type Inherited = {
resolve: Data, resolve: Data,
}; };
/** @internal */ /**
export function inheritedParamsDataResolve(route: ActivatedRouteSnapshot): Inherited { * Returns the inherited params, data, and resolve for a given route.
const pathToRoot = route.pathFromRoot; * By default, this only inherits values up to the nearest path-less or component-less route.
* @internal
*/
export function inheritedParamsDataResolve(
route: ActivatedRouteSnapshot,
paramsInheritanceStrategy: ParamsInheritanceStrategy = 'emptyOnly'): Inherited {
const pathFromRoot = route.pathFromRoot;
let inhertingStartingFrom = pathToRoot.length - 1; let inheritingStartingFrom = 0;
if (paramsInheritanceStrategy !== 'always') {
inheritingStartingFrom = pathFromRoot.length - 1;
while (inhertingStartingFrom >= 1) { while (inheritingStartingFrom >= 1) {
const current = pathToRoot[inhertingStartingFrom]; const current = pathFromRoot[inheritingStartingFrom];
const parent = pathToRoot[inhertingStartingFrom - 1]; const parent = pathFromRoot[inheritingStartingFrom - 1];
// current route is an empty path => inherits its parent's params and data // current route is an empty path => inherits its parent's params and data
if (current.routeConfig && current.routeConfig.path === '') { if (current.routeConfig && current.routeConfig.path === '') {
inhertingStartingFrom--; inheritingStartingFrom--;
// parent is componentless => current route should inherit its params and data // parent is componentless => current route should inherit its params and data
} else if (!parent.component) { } else if (!parent.component) {
inhertingStartingFrom--; inheritingStartingFrom--;
} else { } else {
break; break;
}
} }
} }
return pathToRoot.slice(inhertingStartingFrom).reduce((res, curr) => { return flattenInherited(pathFromRoot.slice(inheritingStartingFrom));
}
/** @internal */
function flattenInherited(pathFromRoot: ActivatedRouteSnapshot[]): Inherited {
return pathFromRoot.reduce((res, curr) => {
const params = {...res.params, ...curr.params}; const params = {...res.params, ...curr.params};
const data = {...res.data, ...curr.data}; const data = {...res.data, ...curr.data};
const resolve = {...res.resolve, ...curr._resolvedData}; const resolve = {...res.resolve, ...curr._resolvedData};
@ -352,7 +370,7 @@ function setRouterState<U, T extends{_routerState: U}>(state: U, node: TreeNode<
} }
function serializeNode(node: TreeNode<ActivatedRouteSnapshot>): string { function serializeNode(node: TreeNode<ActivatedRouteSnapshot>): string {
const c = node.children.length > 0 ? ` { ${node.children.map(serializeNode).join(", ")} } ` : ''; const c = node.children.length > 0 ? ` { ${node.children.map(serializeNode).join(', ')} } ` : '';
return `${node.value}${c}`; return `${node.value}${c}`;
} }

View File

@ -3794,6 +3794,19 @@ describe('Integration', () => {
}); });
}); });
describe('Testing router options', () => {
describe('paramsInheritanceStrategy', () => {
beforeEach(() => {
TestBed.configureTestingModule(
{imports: [RouterTestingModule.withRoutes([], {paramsInheritanceStrategy: 'always'})]});
});
it('should configure the router', fakeAsync(inject([Router], (router: Router) => {
expect(router.paramsInheritanceStrategy).toEqual('always');
})));
});
});
function expectEvents(events: Event[], pairs: any[]) { function expectEvents(events: Event[], pairs: any[]) {
expect(events.length).toEqual(pairs.length); expect(events.length).toEqual(pairs.length);
for (let i = 0; i < events.length; ++i) { for (let i = 0; i < events.length; ++i) {

View File

@ -8,7 +8,7 @@
import {Routes} from '../src/config'; import {Routes} from '../src/config';
import {recognize} from '../src/recognize'; import {recognize} from '../src/recognize';
import {ActivatedRouteSnapshot, RouterStateSnapshot} from '../src/router_state'; import {ActivatedRouteSnapshot, ParamsInheritanceStrategy, RouterStateSnapshot, inheritedParamsDataResolve} from '../src/router_state';
import {PRIMARY_OUTLET, Params} from '../src/shared'; import {PRIMARY_OUTLET, Params} from '../src/shared';
import {DefaultUrlSerializer, UrlTree} from '../src/url_tree'; import {DefaultUrlSerializer, UrlTree} from '../src/url_tree';
@ -201,7 +201,7 @@ describe('recognize', () => {
}); });
}); });
it('should merge componentless route\'s data', () => { it('should inherit componentless route\'s data', () => {
checkRecognize( checkRecognize(
[{ [{
path: 'a', path: 'a',
@ -214,6 +214,34 @@ describe('recognize', () => {
}); });
}); });
it('should not inherit route\'s data if it has component', () => {
checkRecognize(
[{
path: 'a',
component: ComponentA,
data: {one: 1},
children: [{path: 'b', data: {two: 2}, component: ComponentB}]
}],
'a/b', (s: RouterStateSnapshot) => {
const r: ActivatedRouteSnapshot = s.firstChild(<any>s.firstChild(s.root)) !;
expect(r.data).toEqual({two: 2});
});
});
it('should inherit route\'s data if paramsInheritanceStrategy is \'always\'', () => {
checkRecognize(
[{
path: 'a',
component: ComponentA,
data: {one: 1},
children: [{path: 'b', data: {two: 2}, component: ComponentB}]
}],
'a/b', (s: RouterStateSnapshot) => {
const r: ActivatedRouteSnapshot = s.firstChild(<any>s.firstChild(s.root)) !;
expect(r.data).toEqual({one: 1, two: 2});
}, 'always');
});
it('should set resolved data', () => { it('should set resolved data', () => {
checkRecognize( checkRecognize(
[{path: 'a', resolve: {one: 'some-token'}, component: ComponentA}], 'a', [{path: 'a', resolve: {one: 'some-token'}, component: ComponentA}], 'a',
@ -307,7 +335,7 @@ describe('recognize', () => {
}); });
}); });
it('should match (non-termianl) when both primary and secondary and primary has a child', it('should match (non-terminal) when both primary and secondary and primary has a child',
() => { () => {
const config = [{ const config = [{
path: 'parent', path: 'parent',
@ -579,7 +607,7 @@ describe('recognize', () => {
}); });
}); });
it('should merge params until encounters a normal route', () => { it('should inherit params until encounters a normal route', () => {
checkRecognize( checkRecognize(
[{ [{
path: 'p/:id', path: 'p/:id',
@ -606,6 +634,25 @@ describe('recognize', () => {
checkActivatedRoute(c, 'c', {}, ComponentC); checkActivatedRoute(c, 'c', {}, ComponentC);
}); });
}); });
it('should inherit all params if paramsInheritanceStrategy is \'always\'', () => {
checkRecognize(
[{
path: 'p/:id',
children: [{
path: 'a/:name',
children: [{
path: 'b',
component: ComponentB,
children: [{path: 'c', component: ComponentC}]
}]
}]
}],
'p/11/a/victor/b/c', (s: RouterStateSnapshot) => {
const c = s.firstChild(s.firstChild(s.firstChild(s.firstChild(s.root) !) !) !) !;
checkActivatedRoute(c, 'c', {id: '11', name: 'victor'}, ComponentC);
}, 'always');
});
}); });
describe('empty URL leftovers', () => { describe('empty URL leftovers', () => {
@ -722,8 +769,11 @@ describe('recognize', () => {
}); });
}); });
function checkRecognize(config: Routes, url: string, callback: any): void { function checkRecognize(
recognize(RootComponent, config, tree(url), url).subscribe(callback, e => { throw e; }); config: Routes, url: string, callback: any,
paramsInheritanceStrategy?: ParamsInheritanceStrategy): void {
recognize(RootComponent, config, tree(url), url, paramsInheritanceStrategy)
.subscribe(callback, e => { throw e; });
} }
function checkActivatedRoute( function checkActivatedRoute(

View File

@ -498,7 +498,7 @@ function checkResolveData(
future: RouterStateSnapshot, curr: RouterStateSnapshot, injector: any, check: any): void { future: RouterStateSnapshot, curr: RouterStateSnapshot, injector: any, check: any): void {
const p = new PreActivation(future, curr, injector); const p = new PreActivation(future, curr, injector);
p.initialize(new ChildrenOutletContexts()); p.initialize(new ChildrenOutletContexts());
p.resolveData().subscribe(check, (e) => { throw e; }); p.resolveData('emptyOnly').subscribe(check, (e) => { throw e; });
} }
function checkGuards( function checkGuards(

View File

@ -9,7 +9,7 @@
import {Location, LocationStrategy} from '@angular/common'; import {Location, LocationStrategy} from '@angular/common';
import {MockLocationStrategy, SpyLocation} from '@angular/common/testing'; import {MockLocationStrategy, SpyLocation} from '@angular/common/testing';
import {Compiler, Injectable, Injector, ModuleWithProviders, NgModule, NgModuleFactory, NgModuleFactoryLoader, Optional} from '@angular/core'; import {Compiler, Injectable, Injector, ModuleWithProviders, NgModule, NgModuleFactory, NgModuleFactoryLoader, Optional} from '@angular/core';
import {ChildrenOutletContexts, NoPreloading, PreloadingStrategy, ROUTES, Route, Router, RouterModule, Routes, UrlHandlingStrategy, UrlSerializer, provideRoutes, ɵROUTER_PROVIDERS as ROUTER_PROVIDERS, ɵflatten as flatten} from '@angular/router'; import {ChildrenOutletContexts, ExtraOptions, NoPreloading, PreloadingStrategy, ROUTER_CONFIGURATION, ROUTES, Route, Router, RouterModule, Routes, UrlHandlingStrategy, UrlSerializer, provideRoutes, ɵROUTER_PROVIDERS as ROUTER_PROVIDERS, ɵflatten as flatten} from '@angular/router';
@ -76,6 +76,13 @@ export class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader {
} }
} }
function isUrlHandlingStrategy(opts: ExtraOptions | UrlHandlingStrategy):
opts is UrlHandlingStrategy {
// This property check is needed because UrlHandlingStrategy is an interface and doesn't exist at
// runtime.
return 'shouldProcessUrl' in opts;
}
/** /**
* Router setup factory function used for testing. * Router setup factory function used for testing.
* *
@ -84,9 +91,39 @@ export class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader {
export function setupTestingRouter( export function setupTestingRouter(
urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts, location: Location, urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts, location: Location,
loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][], loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][],
urlHandlingStrategy?: UrlHandlingStrategy) { opts?: ExtraOptions, urlHandlingStrategy?: UrlHandlingStrategy): Router;
/**
* Router setup factory function used for testing.
*
* @deprecated As of v5.2. The 2nd-to-last argument should be `ExtraOptions`, not
* `UrlHandlingStrategy`
*/
export function setupTestingRouter(
urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts, location: Location,
loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][],
urlHandlingStrategy?: UrlHandlingStrategy): Router;
/**
* Router setup factory function used for testing.
*
* @stable
*/
export function setupTestingRouter(
urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts, location: Location,
loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][],
opts?: ExtraOptions | UrlHandlingStrategy, urlHandlingStrategy?: UrlHandlingStrategy) {
const router = new Router( const router = new Router(
null !, urlSerializer, contexts, location, injector, loader, compiler, flatten(routes)); null !, urlSerializer, contexts, location, injector, loader, compiler, flatten(routes));
// Handle deprecated argument ordering.
if (opts) {
if (isUrlHandlingStrategy(opts)) {
router.urlHandlingStrategy = opts;
} else if (opts.paramsInheritanceStrategy) {
router.paramsInheritanceStrategy = opts.paramsInheritanceStrategy;
}
}
if (urlHandlingStrategy) { if (urlHandlingStrategy) {
router.urlHandlingStrategy = urlHandlingStrategy; router.urlHandlingStrategy = urlHandlingStrategy;
} }
@ -128,14 +165,20 @@ export function setupTestingRouter(
useFactory: setupTestingRouter, useFactory: setupTestingRouter,
deps: [ deps: [
UrlSerializer, ChildrenOutletContexts, Location, NgModuleFactoryLoader, Compiler, Injector, UrlSerializer, ChildrenOutletContexts, Location, NgModuleFactoryLoader, Compiler, Injector,
ROUTES, [UrlHandlingStrategy, new Optional()] ROUTES, ROUTER_CONFIGURATION, [UrlHandlingStrategy, new Optional()]
] ]
}, },
{provide: PreloadingStrategy, useExisting: NoPreloading}, provideRoutes([]) {provide: PreloadingStrategy, useExisting: NoPreloading}, provideRoutes([])
] ]
}) })
export class RouterTestingModule { export class RouterTestingModule {
static withRoutes(routes: Routes): ModuleWithProviders { static withRoutes(routes: Routes, config?: ExtraOptions): ModuleWithProviders {
return {ngModule: RouterTestingModule, providers: [provideRoutes(routes)]}; return {
ngModule: RouterTestingModule,
providers: [
provideRoutes(routes),
{provide: ROUTER_CONFIGURATION, useValue: config ? config : {}},
]
};
} }
} }

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ApplicationRef, ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injector, OnChanges, SimpleChange, SimpleChanges, Testability, TestabilityRegistry, Type} from '@angular/core'; import {ApplicationRef, ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injector, OnChanges, SimpleChange, SimpleChanges, StaticProvider, Testability, TestabilityRegistry, Type} from '@angular/core';
import * as angular from './angular1'; import * as angular from './angular1';
import {PropertyBinding} from './component_info'; import {PropertyBinding} from './component_info';
@ -54,8 +54,9 @@ export class DowngradeComponentAdapter {
} }
createComponent(projectableNodes: Node[][]) { createComponent(projectableNodes: Node[][]) {
const childInjector = const providers: StaticProvider[] = [{provide: $SCOPE, useValue: this.componentScope}];
Injector.create([{provide: $SCOPE, useValue: this.componentScope}], this.parentInjector); const childInjector = Injector.create(
{providers: providers, parent: this.parentInjector, name: 'DowngradeComponentAdapter'});
this.componentRef = this.componentRef =
this.componentFactory.create(childInjector, projectableNodes, this.element[0]); this.componentFactory.create(childInjector, projectableNodes, this.element[0]);

View File

@ -172,7 +172,7 @@ export interface AnimationStyleMetadata extends AnimationMetadata {
/** @experimental */ /** @experimental */
export interface AnimationTransitionMetadata extends AnimationMetadata { export interface AnimationTransitionMetadata extends AnimationMetadata {
animation: AnimationMetadata | AnimationMetadata[]; animation: AnimationMetadata | AnimationMetadata[];
expr: string; expr: string | ((fromState: string, toState: string) => boolean);
options: AnimationOptions | null; options: AnimationOptions | null;
} }
@ -240,7 +240,7 @@ export declare function style(tokens: '*' | {
}>): AnimationStyleMetadata; }>): AnimationStyleMetadata;
/** @experimental */ /** @experimental */
export declare function transition(stateChangeExpr: string, steps: AnimationMetadata | AnimationMetadata[], options?: AnimationOptions | null): AnimationTransitionMetadata; export declare function transition(stateChangeExpr: string | ((fromState: string, toState: string) => boolean), steps: AnimationMetadata | AnimationMetadata[], options?: AnimationOptions | null): AnimationTransitionMetadata;
/** @experimental */ /** @experimental */
export declare function trigger(name: string, definitions: AnimationMetadata[]): AnimationTriggerMetadata; export declare function trigger(name: string, definitions: AnimationMetadata[]): AnimationTriggerMetadata;

View File

@ -476,7 +476,12 @@ export declare abstract class Injector {
/** @deprecated */ abstract get(token: any, notFoundValue?: any): any; /** @deprecated */ abstract get(token: any, notFoundValue?: any): any;
static NULL: Injector; static NULL: Injector;
static THROW_IF_NOT_FOUND: Object; static THROW_IF_NOT_FOUND: Object;
static create(providers: StaticProvider[], parent?: Injector): Injector; /** @deprecated */ static create(providers: StaticProvider[], parent?: Injector): Injector;
static create(options: {
providers: StaticProvider[];
parent?: Injector;
name?: string;
}): Injector;
} }
/** @stable */ /** @stable */

View File

@ -50,7 +50,7 @@ export declare abstract class AbstractControl {
}): void; }): void;
abstract patchValue(value: any, options?: Object): void; abstract patchValue(value: any, options?: Object): void;
abstract reset(value?: any, options?: Object): void; abstract reset(value?: any, options?: Object): void;
setAsyncValidators(newValidator: AsyncValidatorFn | AsyncValidatorFn[]): void; setAsyncValidators(newValidator: AsyncValidatorFn | AsyncValidatorFn[] | null): void;
setErrors(errors: ValidationErrors | null, opts?: { setErrors(errors: ValidationErrors | null, opts?: {
emitEvent?: boolean; emitEvent?: boolean;
}): void; }): void;

View File

@ -127,6 +127,7 @@ export interface ExtraOptions {
errorHandler?: ErrorHandler; errorHandler?: ErrorHandler;
initialNavigation?: InitialNavigation; initialNavigation?: InitialNavigation;
onSameUrlNavigation?: 'reload' | 'ignore'; onSameUrlNavigation?: 'reload' | 'ignore';
paramsInheritanceStrategy?: 'emptyOnly' | 'always';
preloadingStrategy?: any; preloadingStrategy?: any;
useHash?: boolean; useHash?: boolean;
} }
@ -329,6 +330,7 @@ export declare class Router {
readonly events: Observable<Event>; readonly events: Observable<Event>;
navigated: boolean; navigated: boolean;
onSameUrlNavigation: 'reload' | 'ignore'; onSameUrlNavigation: 'reload' | 'ignore';
paramsInheritanceStrategy: 'emptyOnly' | 'always';
routeReuseStrategy: RouteReuseStrategy; routeReuseStrategy: RouteReuseStrategy;
readonly routerState: RouterState; readonly routerState: RouterState;
readonly url: string; readonly url: string;

View File

@ -1,10 +1,10 @@
/** @stable */ /** @stable */
export declare class RouterTestingModule { export declare class RouterTestingModule {
static withRoutes(routes: Routes): ModuleWithProviders; static withRoutes(routes: Routes, config?: ExtraOptions): ModuleWithProviders;
} }
/** @stable */ /** @stable */
export declare function setupTestingRouter(urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts, location: Location, loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][], urlHandlingStrategy?: UrlHandlingStrategy): Router; export declare function setupTestingRouter(urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts, location: Location, loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][], opts?: ExtraOptions, urlHandlingStrategy?: UrlHandlingStrategy): Router;
/** @stable */ /** @stable */
export declare class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader { export declare class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader {