Compare commits

...

76 Commits

Author SHA1 Message Date
5c89d6bffa feat(core): support metadata reflection for native class types (#22356)
closes #21731

PR Close #22356
2018-02-21 16:09:27 -08:00
3e6a86fb0a fix(forms): set state before emitting a value from ngModelChange (#21514)
Closes #21513.

PR Close #21514
2018-02-21 15:59:33 -08:00
a7ebf5aadd fix(core): properly handle function without prototype in reflector (#22284)
closes #19978

PR Close #22284
2018-02-21 14:52:04 -08:00
b42921bbd2 docs(aio): updates directive event hooks real capabilities (#16654)
Minor documentation update to include event hooks that were assumed to only work on components.

Closes angular/angular#10221

PR Close #16654
2018-02-21 14:51:04 -08:00
722dec11b0 docs(aio): Wrong code example. Form status field was added later in the guide. (#21275)
PR Close #21275
2018-02-21 11:06:47 -08:00
9e6268ba59 docs(http): fix a typo in code comment (#22327)
PR Close #22327
2018-02-21 11:06:06 -08:00
435f6eecd2 build: make git revert messages valid (#22339)
`git revert` default message is "Revert <original message>" (no semi-colon)

PR Close #22339
2018-02-21 11:05:35 -08:00
1c1cbba04b docs: add ngStyle to cheat sheet (#22070)
PR Close #22070
2018-02-20 16:08:15 -08:00
3b692a55a7 docs(aio): fix incorrect quote mark usage (#22335)
PR Close #22335
2018-02-20 15:42:55 -08:00
69a0578e00 docs(aio): fix the css of the heroes component's buttons (#22333)
Fixes #22222

PR Close #22333
2018-02-20 15:41:56 -08:00
b5ca275590 docs(aio): Fix name of component (#22332)
PR Close #22332
2018-02-20 15:41:35 -08:00
519f022b02 docs(aio): update installed mobile tool list (#22331)
PR Close #22331
2018-02-20 15:41:15 -08:00
236a9320df build: update tsickle dep from compiler-cli (#22295)
PR Close #22295
2018-02-20 15:40:44 -08:00
28ac24444f fix(compiler-cli): add missing entry point to package, update tsickle (#22295)
PR Close #22295
2018-02-20 15:40:44 -08:00
99909bbf2c feat(ivy): generate pipe references and definitions (#22034)
PR Close #22034
2018-02-20 13:58:03 -08:00
ee60bb5b36 fix(ivy): pureFunction8 should update the right bindings (#22313)
PR Close #22313
2018-02-20 11:36:50 -08:00
f6120c09e7 docs(aio): add Nx and Angular Enterprise Playbook to resources (#22321)
PR Close #22321
2018-02-20 10:09:33 -08:00
e2bdef4cf6 test(language-service): fix minor typos (#21372)
PR Close #21372
2018-02-20 10:08:55 -08:00
8115edc82f fix(common): then and else template might be set to null (#22298)
PR Close #22298
2018-02-18 19:25:28 -08:00
a8b5465e24 fix(ivy): update master with renamings (#22268)
PR Close #22268
2018-02-18 18:48:41 -08:00
9ce495b3d8 refactor(ivy): simplify interpolation instructions (#22268)
PR Close #22268
2018-02-18 18:48:41 -08:00
d40263447d refactor(ivy): move get functions next to their underlying variable (#22268)
PR Close #22268
2018-02-18 18:48:41 -08:00
31c5c1060a docs(ivy): update the API docs instructions to add details about removing attributes (#22268)
PR Close #22268
2018-02-18 18:48:41 -08:00
c9ebd60435 refactor(ivy): interpolatiom instructions do not support NO_CHANGE at input. (#22268)
PR Close #22268
2018-02-18 18:48:41 -08:00
5a14e2238f refactor(ivy): split the memory instruction into store and load (#22268)
PR Close #22268
2018-02-18 18:48:41 -08:00
3ceee99e22 feat(compiler-cli): Check unvalidated combination of ngc and TypeScript (#22293)
closes #20669

PR Close #22293
2018-02-18 15:12:46 -08:00
28b23f954c docs(aio): add angular-buch to resources (#22163)
adds a link to the website of our book. second version of the text. thanks!

PR Close #22163
2018-02-18 15:12:13 -08:00
ad17e5e791 docs(aio): add angular-buch to resources (#22163)
adds a link to the website of our book. many thanks for reviewing this

PR Close #22163
2018-02-18 15:12:13 -08:00
c30d329faa fix(ivy): fix merge errors (master is broken) (#22291)
PR Close #22291
2018-02-18 15:03:39 -08:00
991300b86c feat(platform-browser): do not throw error when Hammer.js not loaded (#22257)
closes #16992

PR Close #22257
2018-02-18 13:29:14 -08:00
7c45db3a19 docs: correct grammar mistakes in CONTRIBUTING.md (#22285)
Various grammar mistakes were present in the contribution guidelines
This commit corrects some of them

PR Close #22285
2018-02-18 13:27:23 -08:00
67cf11d071 feat(common): better error message when non-template element used in NgIf (#22274)
closes #16410

PR Close #22274
2018-02-18 13:26:50 -08:00
49082d7ab2 feat(ivy): support host attributes (#22213)
PR Close #22213
2018-02-18 13:22:38 -08:00
6b627f67db test(ivy): add missing host listener and host attribute binding tests (#22213)
PR Close #22213
2018-02-18 13:22:38 -08:00
5c320b4c2a test(ivy): add missing host binding and query tests (#22213)
PR Close #22213
2018-02-18 13:22:34 -08:00
ac2b04a5ab test(ivy): Add small_app spec for sprint #3 (#22018)
PR Close #22018
2018-02-18 13:18:54 -08:00
a63b764b54 test(ivy): move compiler canonical specs into a single directory (#22018)
PR Close #22018
2018-02-18 13:18:54 -08:00
2654357c72 docs: update BAZEL.md crosstool error instructions (#22018)
PR Close #22018
2018-02-18 13:18:54 -08:00
4ec40c6ab2 fix(aio): improve announcement-bar layout with wide logos (#22272)
PR Close #22272
2018-02-18 13:16:30 -08:00
80d424798e docs(aio): added ngconf announcement (#22272)
PR Close #22272
2018-02-18 13:16:30 -08:00
7fa2d4b503 fix: merge-pr script (#22290)
PR Close #22290
2018-02-18 13:13:29 -08:00
f4845fae12 build: use authenticated mode for the merge script (#22269)
`TOKEN` is the name with use for other GH scripts

PR Close #22269
2018-02-18 12:44:42 -08:00
f693be3996 feat(ivy): add pureFunction0 instruction (#22214)
PR Close #22214
2018-02-16 18:03:55 -08:00
a73d5308e0 refactor(ivy): rename objectLiteral to pureFn to prep for pipes (#22214)
PR Close #22214
2018-02-16 18:03:55 -08:00
e1bf067090 fix(animations): report correct totalTime value even during noOp animations (#22225)
This patch ensures that if the NoopAnimationsModule is used then it will
correctly report the associated `totalTime` property within the emitted
AnimationEvent instance when an animation event trigger is fired.

BREAKING CHANGE: When animation is trigged within a disabled zone, the
associated event (which an instance of AnimationEvent) will no longer
report the totalTime as 0 (it will emit the actual time of the
animation). To detect if an animation event is reporting a disabled
animation then the `event.disabled` property can be used instead.

PR Close #22225
2018-02-16 18:03:31 -08:00
884de18cba docs: replace plnkr with StackBlitz (#20365)
PR Close #20365
2018-02-16 15:12:10 -08:00
dfa2fb95d5 test(ivy): clean the imported renderer2 (#22255)
PR Close #22255
2018-02-16 15:11:23 -08:00
2639b4bffb fix(common): correct mapping of Observable methods (#20518)
fixes #20516
PR Close #20518
2018-02-16 15:10:31 -08:00
978f97cc59 test(aio): increase docs-test timeouts to prevent flakes on Travis (#22261)
PR Close #22261
2018-02-16 14:46:23 -08:00
f1a063298e feat(core): set preserveWhitespaces to false by default (#22046)
Fixes #22027

PR Close #22046
2018-02-16 09:06:14 -08:00
d241532488 docs(ivy): add a note about exporting top level variables (#22234)
PR Close #22234
2018-02-15 16:16:40 -08:00
f755db78dc fix(core): require factory to be provided for shakeable InjectionToken (#22207)
InjectionToken can be created with an ngInjectableDef, and previously
this allowed the full expressiveness of @Injectable. However, this
requires a runtime reflection system in order to generate factories
from expressed provider declarations.

Instead, this change requires scoped InjectionTokens to provide the
factory directly (likely using inject() for the arguments), bypassing
the need for a reflection system.

Fixes #22205

PR Close #22207
2018-02-15 16:16:16 -08:00
5dd2b5135d refactor(ivy): rename bindX() functions to interpolationX() (#22229)
rationale: remove the confusion with `bind()` and `bind0()`

PR Close #22229
2018-02-15 14:20:53 -08:00
7ac34e42a0 feat: allow direct scoping of @Injectables to the root injector (#22185)
@Injectable() supports a scope parameter which specifies the target module.
However, it's still difficult to specify that a particular service belongs
in the root injector. A developer attempting to ensure that must either
also provide a module intended for placement in the root injector or target
a module known to already be in the root injector (e.g. BrowserModule).
Both of these strategies are cumbersome and brittle.

Instead, this commit adds a token APP_ROOT_SCOPE which provides a
straightforward way of targeting the root injector directly, without
requiring special knowledge of modules within it.

PR Close #22185
2018-02-15 14:20:27 -08:00
029dbf0e18 feat(bazel): ng_module produces bundle index (#22176)
It creates the bundle index .d.ts and .metadata.json files.
The names are based on the ng_module target.

PR Close #22176
2018-02-15 14:08:53 -08:00
bba65e0f41 feat(bazel): introduce a binary stamping feature (#22176)
This grabs version control metadata and makes it available in the build, eg. to put in the version field for released artifacts

PR Close #22176
2018-02-15 14:08:53 -08:00
a069e08354 refactor(bazel): convert most ts_library to ng_module (#22176)
This is necessary so we can produce ng metadata for our packages that are published as libraries

PR Close #22176
2018-02-15 14:08:53 -08:00
03d93c96a3 Revert: "build: merge-pr new checks that all requested changes have been addressed (#21817)"
This reverts commit 4a4d749710.
2018-02-15 11:00:52 -08:00
020338230f style: fix typos boostrap to bootstrap (#21917)
PR Close #21917
2018-02-15 09:54:00 -08:00
a1d86daa71 refactor(ivy): assertion (#22189)
Encourage the use of message to explain the assertion

PR Close #22189
2018-02-15 09:53:05 -08:00
7078fbffb4 fix(aio): improve printing styles (#19651)
printfix

PR Close #19651
2018-02-15 09:52:32 -08:00
0aa9b46b79 Revert "build: allow bazel build ... (#22168)"
This reverts commit 265ac8a106.
2018-02-15 03:28:35 -08:00
831592c381 Revert "style: fix typos boostrap to bootstrap (#21917)"
This reverts commit 363498b6b4.
2018-02-14 22:56:44 -05:00
f628797d91 Revert "refactor(ivy): assertion (#22189)"
This reverts commit 0b683123d2.
2018-02-14 22:56:11 -05:00
47f51c2ead Revert "Revert "build: merge-pr new checks that all requested changes have been addressed (#21817)""
This reverts commit 5b8eb9c5c7.
2018-02-14 22:55:56 -05:00
ba9cd5bbc4 Revert "fix(aio): improve printing styles (#19651)"
This reverts commit b54ad053f9.
2018-02-14 22:55:24 -05:00
b54ad053f9 fix(aio): improve printing styles (#19651)
printfix

PR Close #19651
2018-02-14 18:49:58 -05:00
5b8eb9c5c7 Revert "build: merge-pr new checks that all requested changes have been addressed (#21817)"
This reverts commit 4a4d749710.
2018-02-14 18:48:45 -05:00
0b683123d2 refactor(ivy): assertion (#22189)
Encourage the use of message to explain the assertion

PR Close #22189
2018-02-14 18:42:04 -05:00
363498b6b4 style: fix typos boostrap to bootstrap (#21917)
PR Close #21917
2018-02-14 18:21:52 -05:00
a1bb56f739 docs: fix changelog errors (#22226)
PR Close #22226
2018-02-14 18:18:22 -05:00
5bb9fcad3e build: comment-out chromium version checking code temporarily (#22232)
Related #22231

PR Close #22232
2018-02-14 17:26:43 -05:00
Pat
f4697f351e docs: typo - components should be possessive (#22172)
PR Close #22172
2018-02-14 15:06:52 -05:00
1d571b299d feat(platform-browser): fix #19604, can config hammerOptions (#21979)
PR Close #21979
2018-02-14 15:02:58 -05:00
3a0b5a928c docs(aio): fix extraneous divs (#22069)
PR Close #22069
2018-02-14 15:02:36 -05:00
265ac8a106 build: allow bazel build ... (#22168)
Note, the reason this commit removes `firebase-tools` is:

1) firebase-tools has an optional dependency on
https://www.npmjs.com/package/@google-cloud/functions-emulator
2) yarn's `--ignore-optional` doesn't work for transitive deps, so
there's no way to yarn install without getting that functions-emulator
package
3) functions-emulator has a transitive dep on `grpc`
4) the version of `grpc` we get has `BUILD` files and no `WORKSPACE`
file so it always breaks `bazel build ...`

It could be solved by any of:
1) remove firebase-tools - this is what I did
2) fix yarn so you can omit optional deps of a transitive dep
3) make functions-emulator depend transitively on a more recent `grpc`
version
4) patch `grpc` after install by doing an `rm` command in our
postinstall or something

In its place we must install protobufjs. This is needed by the
ngc-wrapped test, which needs jasmine as well as bazel's worker mode
dependencies, and therefore cannot simply rely on
node_modules =
"@build_bazel_rules_typescript_tsc_wrapped_deps//:node_modules"

PR Close #22168
2018-02-14 15:01:41 -05:00
192 changed files with 4348 additions and 2116 deletions

View File

@ -25,7 +25,7 @@ ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION.
## Minimal reproduction of the problem with instructions
<!--
For bug reports please provide the *STEPS TO REPRODUCE* and if possible a *MINIMAL DEMO* of the problem via
https://plnkr.co or similar (you can use this template as a starting point: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5).
https://stackblitz.com or similar (you can use this template as a starting point: https://stackblitz.com/fork/angular-gitter).
-->
## What is the motivation / use case for changing the behavior?

View File

@ -7,12 +7,11 @@
* **bazel:** allow TS to read ambient typings ([#21876](https://github.com/angular/angular/issues/21876)) ([b081dfe](https://github.com/angular/angular/commit/b081dfe)), closes [#21872](https://github.com/angular/angular/issues/21872)
* **bazel:** improve error message for missing assets ([#22096](https://github.com/angular/angular/issues/22096)) ([dcf64a0](https://github.com/angular/angular/commit/dcf64a0)), closes [#22095](https://github.com/angular/angular/issues/22095)
* **common:** add locale currency values ([#21783](https://github.com/angular/angular/issues/21783)) ([420cc7a](https://github.com/angular/angular/commit/420cc7a)), closes [#20385](https://github.com/angular/angular/issues/20385)
* **common:** regenerate i18n locale data files ([#21783](https://github.com/angular/angular/issues/21783)) ([0b2f7d1](https://github.com/angular/angular/commit/0b2f7d1))
* **common:** round currencies based on decimal digits in `CurrencyPipe` ([#21783](https://github.com/angular/angular/issues/21783)) ([44154e7](https://github.com/angular/angular/commit/44154e7)), closes [#10189](https://github.com/angular/angular/issues/10189)
* **common:** weaken AsyncPipe transform signature ([#22169](https://github.com/angular/angular/issues/22169)) ([be59c3a](https://github.com/angular/angular/commit/be59c3a))
* **compiler:** make unary plus operator consistent to JavaScript ([#22154](https://github.com/angular/angular/issues/22154)) ([72f8abd](https://github.com/angular/angular/commit/72f8abd)), closes [#22089](https://github.com/angular/angular/issues/22089)
* **core:** add stacktrace in log when error during cleanup component in TestBed ([#22162](https://github.com/angular/angular/issues/22162)) ([16d1700](https://github.com/angular/angular/commit/16d1700))
* **core:** ensure initial value of QueryList length ([#21980](https://github.com/angular/angular/issues/21980)) ([#21982](https://github.com/angular/angular/issues/21982)) ([e56de10](https://github.com/angular/angular/commit/e56de10)), closes [/github.com/angular/angular/commit/e54474215629aa6a0e0497fe61bfc896cea532c9#diff-a85dbe0991a7577ea24b49374e9ae90](https://github.com//github.com/angular/angular/commit/e54474215629aa6a0e0497fe61bfc896cea532c9/issues/diff-a85dbe0991a7577ea24b49374e9ae90)
* **core:** ensure initial value of QueryList length ([#21980](https://github.com/angular/angular/issues/21980)) ([#21982](https://github.com/angular/angular/issues/21982)) ([e56de10](https://github.com/angular/angular/commit/e56de10)), closes [#21980](https://github.com/angular/angular/issues/21980)
* **core:** use appropriate inert document strategy for Firefox & Safari ([#17019](https://github.com/angular/angular/issues/17019)) ([a751649](https://github.com/angular/angular/commit/a751649))
* **forms:** make Validators.email support optional controls ([#20869](https://github.com/angular/angular/issues/20869)) ([140e7c0](https://github.com/angular/angular/commit/140e7c0))
* **forms:** prevent event emission on enable/disable when emitEvent is false ([#12366](https://github.com/angular/angular/issues/12366)) ([#21018](https://github.com/angular/angular/issues/21018)) ([0bcfae7](https://github.com/angular/angular/commit/0bcfae7))
@ -37,13 +36,13 @@
### Bug Fixes
(https://github.com/angular/angular/commit/15ff7ba)), closes [#21377](https://github.com/angular/angular/issues/21377)
* **aio:** update Firebase redirects and SW routes ([#21763](https://github.com/angular/angular/pull/21763)) ([#22104](https://github.com/angular/angular/pull/22104)) ([15ff7ba](https://github.com/angular/angular/commit/15ff7ba)), closes [#21377](https://github.com/angular/angular/issues/21377)
* **bazel:** allow TS to read ambient typings ([#21876](https://github.com/angular/angular/issues/21876)) ([d57fd0b](https://github.com/angular/angular/commit/d57fd0b)), closes [#21872](https://github.com/angular/angular/issues/21872)
* **bazel:** improve error message for missing assets ([#22096](https://github.com/angular/angular/issues/22096)) ([c5ec8d9](https://github.com/angular/angular/commit/c5ec8d9)), closes [#22095](https://github.com/angular/angular/issues/22095)
* **common:** weaken AsyncPipe transform signature ([#22169](https://github.com/angular/angular/issues/22169)) ([c6bdc83](https://github.com/angular/angular/commit/c6bdc83))
* **compiler:** make unary plus operator consistent to JavaScript ([#22154](https://github.com/angular/angular/issues/22154)) ([1b8ea10](https://github.com/angular/angular/commit/1b8ea10)), closes [#22089](https://github.com/angular/angular/issues/22089)
* **core:** add stacktrace in log when error during cleanup component in TestBed ([#22162](https://github.com/angular/angular/issues/22162)) ([c4f841f](https://github.com/angular/angular/commit/c4f841f))
* **core:** ensure initial value of QueryList length ([#21980](https://github.com/angular/angular/issues/21980)) ([#21982](https://github.com/angular/angular/issues/21982)) ([47b73fd](https://github.com/angular/angular/commit/47b73fd)), closes [/github.com/angular/angular/commit/e54474215629aa6a0e0497fe61bfc896cea532c9#diff-a85dbe0991a7577ea24b49374e9ae90](https://github.com//github.com/angular/angular/commit/e54474215629aa6a0e0497fe61bfc896cea532c9/issues/diff-a85dbe0991a7577ea24b49374e9ae90)
* **core:** ensure initial value of QueryList length ([#21980](https://github.com/angular/angular/issues/21980)) ([#21982](https://github.com/angular/angular/issues/21982)) ([47b73fd](https://github.com/angular/angular/commit/47b73fd)), closes [#21980](https://github.com/angular/angular/issues/21980)
* **core:** use appropriate inert document strategy for Firefox & Safari ([#17019](https://github.com/angular/angular/issues/17019)) ([47b71d9](https://github.com/angular/angular/commit/47b71d9))
* **forms:** prevent event emission on enable/disable when emitEvent is false ([#12366](https://github.com/angular/angular/issues/12366)) ([#21018](https://github.com/angular/angular/issues/21018)) ([56b9591](https://github.com/angular/angular/commit/56b9591))
* **language-service:** correct instructions to install the language service ([#22000](https://github.com/angular/angular/issues/22000)) ([0b23573](https://github.com/angular/angular/commit/0b23573))

View File

@ -51,7 +51,7 @@ and help you to craft the change so that it is successfully accepted into the pr
Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion might inform you of workarounds readily available.
We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs we will systematically ask you to provide a minimal reproduction scenario using http://plnkr.co. Having a live, reproducible scenario gives us wealth of important information without going back & forth to you with additional questions like:
We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs, we will systematically ask you to provide a minimal reproduction scenario using http://plnkr.co. Having a live, reproducible scenario gives us a wealth of important information without going back & forth to you with additional questions like:
- version of Angular used
- 3rd-party libraries and their versions
@ -61,7 +61,7 @@ A minimal reproduce scenario using http://plnkr.co/ allows us to quickly confirm
We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal plunk. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it.
Unfortunately we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you we are going to close an issue that don't have enough info to be reproduced.
Unfortunately, we are not able to investigate / fix bugs without a minimal reproduction, so if we don't hear back from you we are going to close an issue that doesn't have enough info to be reproduced.
You can file new issues by filling out our [new issue form](https://github.com/angular/angular/issues/new).
@ -173,12 +173,12 @@ The **header** is mandatory and the **scope** of the header is optional.
Any line of the commit message cannot be longer 100 characters! This allows the message to be easier
to read on GitHub as well as in various git tools.
Footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.
The footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.
Samples: (even more [samples](https://github.com/angular/angular/commits/master))
```
docs(changelog): update change log to beta.5
docs(changelog): update changelog to beta.5
```
```
fix(release): need to depend on latest rxjs and zone.js
@ -203,7 +203,7 @@ Must be one of the following:
* **test**: Adding missing tests or correcting existing tests
### Scope
The scope should be the name of the npm package affected (as perceived by person reading changelog generated from commit messages.
The scope should be the name of the npm package affected (as perceived by the person reading the changelog generated from commit messages.
The following is the list of supported scopes:
@ -232,10 +232,10 @@ There are currently a few exceptions to the "use package name" rule:
* none/empty string: useful for `style`, `test` and `refactor` changes that are done across all packages (e.g. `style: add missing semicolons`)
### Subject
The subject contains succinct description of the change:
The subject contains a succinct description of the change:
* use the imperative, present tense: "change" not "changed" nor "changes"
* don't capitalize first letter
* don't capitalize the first letter
* no dot (.) at the end
### Body
@ -266,7 +266,7 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise
* https://help.github.com/articles/setting-your-commit-email-address-in-git/
* https://stackoverflow.com/questions/37245303/what-does-usera-committed-with-userb-13-days-ago-on-github-mean
* https://help.github.com/articles/about-commit-email-addresses/
* https://help.github.com/articles/blocking-command-line-pushes-that-expose-your-personal-email-address/
* https://help.github.com/articles/blocking-command-line-pushes-that-expose-your-personal-email-address/
Note that if you have more than one Git identity, it is important to verify that you are logged in with the same ID with which you signed the CLA, before you commit changes. If not, your PR will fail the CLA check.

View File

@ -1,10 +1,14 @@
workspace(name = "angular")
# Using a pre-release snapshot to pick up a commit that makes all nodejs_binary
# programs produce source-mapped stack traces.
RULES_NODEJS_VERSION = "926349cea4cd360afcd5647ccdd09d2d2fb471aa"
http_archive(
name = "build_bazel_rules_nodejs",
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.4.1.zip",
strip_prefix = "rules_nodejs-0.4.1",
sha256 = "e9bc013417272b17f302dc169ad597f05561bb277451f010043f4da493417607",
url = "https://github.com/bazelbuild/rules_nodejs/archive/%s.zip" % RULES_NODEJS_VERSION,
strip_prefix = "rules_nodejs-%s" % RULES_NODEJS_VERSION,
sha256 = "5ba3c8c209078c2e3f0c6aa4abd01a1a561f92a5bfda04e25604af5f4734d69d",
)
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories")
@ -12,10 +16,12 @@ load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_reposi
check_bazel_version("0.9.0")
node_repositories(package_json = ["//:package.json"])
RULES_TYPESCRIPT_VERSION = "0.10.1"
http_archive(
name = "build_bazel_rules_typescript",
url = "https://github.com/bazelbuild/rules_typescript/archive/0.10.1.zip",
strip_prefix = "rules_typescript-0.10.1",
url = "https://github.com/bazelbuild/rules_typescript/archive/%s.zip" % RULES_TYPESCRIPT_VERSION,
strip_prefix = "rules_typescript-%s" % RULES_TYPESCRIPT_VERSION,
sha256 = "a2c81776a4a492ff9f878f9705639f5647bef345f7f3e1da09c9eeb8dec80485",
)

View File

@ -3,6 +3,6 @@
<h2>Messages</h2>
<button class="clear"
(click)="messageService.clear()">clear</button>
<div *ngFor='let message of messageService.messages'> {{message}} </div>
<div *ngFor="let message of messageService.messages"> {{message}} </div>
</div>

View File

@ -190,6 +190,29 @@ describe('Tutorial part 6', () => {
const maxId = heroesBefore[heroesBefore.length - 1].id;
expect(heroesAfter[numHeroes]).toEqual({id: maxId + 1, name: newHeroName});
});
it('displays correctly styled buttons', async () => {
element.all(by.buttonText('x')).then(buttons => {
for (const button of buttons) {
// Inherited styles from styles.css
expect(button.getCssValue('font-family')).toBe('Arial');
expect(button.getCssValue('border')).toContain('none');
expect(button.getCssValue('padding')).toBe('5px 10px');
expect(button.getCssValue('border-radius')).toBe('4px');
// Styles defined in heroes.component.css
expect(button.getCssValue('left')).toBe('194px');
expect(button.getCssValue('top')).toBe('-32px');
}
});
const addButton = element(by.buttonText('add'));
// Inherited styles from styles.css
expect(addButton.getCssValue('font-family')).toBe('Arial');
expect(addButton.getCssValue('border')).toContain('none');
expect(addButton.getCssValue('padding')).toBe('5px 10px');
expect(addButton.getCssValue('border-radius')).toBe('4px');
});
});
describe('Progressive hero search', () => {

View File

@ -51,7 +51,7 @@
}
/* #docregion additions */
.button {
button {
background-color: #eee;
border: none;
padding: 5px 10px;

View File

@ -92,7 +92,7 @@ You can control your app compilation by providing template compiler options in t
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"preserveWhitespaces": false,
"preserveWhitespaces": true,
...
}
}
@ -234,9 +234,7 @@ done manually.
### *preserveWhitespaces*
This option tells the compiler whether to remove blank text nodes from compiled templates.
This option is `true` by default.
*Note*: It is recommended to set this explicitly to `false` as it emits smaller template factory modules and might be set to `false` by default in the future.
As of v6, this option is `false` by default, which results in smaller emitted template factory modules.
### *allowEmptyCodegenFiles*

View File

@ -140,6 +140,11 @@ is available to <code>declarations</code> of this module.</p>
<td><p>Binds the presence of CSS classes on the element to the truthiness of the associated map values. The right-hand expression should return {class-name: true/false} map.</p>
</td>
</tr>
<tr>
<td><code>&lt;div <b>[ngStyle]</b>="{'property': 'value'}"&gt;</code><br><code>&lt;div <b>[ngStyle]</b>="dynamicStyles()"&gt;</code></td>
<td><p>Allows you to assign styles to an HTML element using CSS. You can use CSS directly, as in the first example, or you can call a method from the component.</p>
</td>
</tr>
</tbody></table>
<table class="is-full-width is-fixed-layout">

View File

@ -512,10 +512,6 @@ You rarely access Angular feature modules directly. You usually import them from
## NgModule
<div class="l-sub-section">
Helps you organize an application into cohesive blocks of functionality.
An NgModule identifies the components, directives, and pipes that the application uses along with the list of external NgModules that the application needs, such as `FormsModule`.
@ -526,7 +522,7 @@ For details and examples, see [NgModules](guide/ngmodules) and the
related files in that section.
</div>
{@a O}
@ -631,13 +627,10 @@ For more information, see the [Routing & Navigation](guide/router) page.
## Router module
<div class="l-sub-section">
A separate [NgModule](guide/glossary#ngmodule) that provides the necessary service providers and directives for navigating through application views.
For more information, see the [Routing & Navigation](guide/router) page.
</div>
## Routing component

View File

@ -8,7 +8,7 @@ checks it when its data-bound properties change, and destroys it before removing
Angular offers **lifecycle hooks**
that provide visibility into these key life moments and the ability to act when they occur.
A directive has the same set of lifecycle hooks, minus the hooks that are specific to component content and views.
A directive has the same set of lifecycle hooks.
{@a hooks-overview}
@ -25,7 +25,7 @@ that Angular calls shortly after creating the component:
<code-example path="lifecycle-hooks/src/app/peek-a-boo.component.ts" region="ngOnInit" title="peek-a-boo.component.ts (excerpt)" linenums="false"></code-example>
No directive or component will implement all of the lifecycle hooks and some of the hooks only make sense for components.
No directive or component will implement all of the lifecycle hooks.
Angular only calls a directive/component hook method *if it is defined*.
{@a hooks-purpose-timing}
@ -86,12 +86,10 @@ calls the lifecycle hook methods in the following sequence at specific moments:
</td>
<td>
Respond after Angular projects external content into the component's view.
Respond after Angular projects external content into the component's view / the view that a directive is in.
Called _once_ after the first `ngDoCheck()`.
_A component-only hook_.
</td>
</tr>
<tr style='vertical-align:top'>
@ -100,12 +98,10 @@ calls the lifecycle hook methods in the following sequence at specific moments:
</td>
<td>
Respond after Angular checks the content projected into the component.
Respond after Angular checks the content projected into the directive/component.
Called after the `ngAfterContentInit()` and every subsequent `ngDoCheck()`.
_A component-only hook_.
</td>
</tr>
<tr style='vertical-align:top'>
@ -114,12 +110,10 @@ calls the lifecycle hook methods in the following sequence at specific moments:
</td>
<td>
Respond after Angular initializes the component's views and child views.
Respond after Angular initializes the component's views and child views / the view that a directive is in.
Called _once_ after the first `ngAfterContentChecked()`.
_A component-only hook_.
</td>
</tr>
<tr style='vertical-align:top'>
@ -128,12 +122,10 @@ calls the lifecycle hook methods in the following sequence at specific moments:
</td>
<td>
Respond after Angular checks the component's views and child views.
Respond after Angular checks the component's views and child views / the view that a directive is in.
Called after the `ngAfterViewInit` and every subsequent `ngAfterContentChecked()`.
_A component-only hook_.
</td>
</tr>
<tr style='vertical-align:top'>

View File

@ -418,7 +418,7 @@ To see the form model, add the following line after the
closing `form` tag in the `hero-detail.component.html`:
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-3.component.html" region="form-value-json" title="src/app/hero-detail/hero-detail.component.html" linenums="false">
<code-example path="reactive-forms/src/app/hero-detail/hero-detail-2.component.html" region="form-value-json" title="src/app/hero-detail/hero-detail.component.html" linenums="false">
</code-example>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -1,9 +1,9 @@
[
{
"startDate": "2018-01-01",
"endDate": "2018-02-02",
"message": "Join us in Atlanta for ngATL<br/>Jan 30 - Feb 2, 2018",
"imageUrl": "generated/images/marketing/home/ng-atl.png",
"linkUrl": "http://ng-atl.org/"
"startDate": "2018-02-14",
"endDate": "2018-04-22",
"message": "Join us for ng-conf<br/>Apr 18th-20th, 2018",
"imageUrl": "generated/images/marketing/home/ng-conf.png",
"linkUrl": "http://ng-conf.org/"
}
]
]

View File

@ -20,7 +20,7 @@
<div class="feature">
<div class="feature-title">Native</div>
<p class="text-body">Build native mobile apps with strategies from Ionic Framework, NativeScript, and React Native.</p>
<p class="text-body">Build native mobile apps with strategies from Cordova, Ionic, or NativeScript.</p>
</div>
<div class="feature">

View File

@ -247,6 +247,13 @@
"rev": true,
"title": "Angular Playground",
"url": "http://www.angularplayground.it/"
},
"nx": {
"desc": "Nx (Nrwl Extensions for Angular) is an open source toolkit built on top of Angular CLI to help enterprise teams develop Angular at scale.",
"rev": true,
"title": "Nx",
"logo": "https://nrwl.io/assets/nx-logo.png",
"url": "https://nrwl.io/nx"
}
}
},
@ -444,6 +451,13 @@
"rev": true,
"title": "Essential Angular",
"url": "https://gumroad.com/l/essential_angular"
},
"angular-buch": {
"desc": "The first German book about Angular. It gives you a detailed practical overview of the key concepts of the platform. In each chapter a sample application is built upon with a new Angular topic. All sources are available on GitHub.",
"logo": "https://angular-buch.com/assets/img/brand.svg",
"rev": true,
"title": "Angular-Buch (German)",
"url": "https://angular-buch.com/"
}
}
},
@ -488,6 +502,13 @@
"title": "CodeSchool: Accelerating Through Angular",
"url": "https://www.codeschool.com/courses/accelerating-through-angular-2"
},
"angular-playbook": {
"desc": "Learn advanced Angular best practices for enterprise teams, created by Nrwl.io.",
"logo": "https://nrwl.io/assets/logo_footer_2x.png",
"rev": true,
"title": "Angular Enterprise Playbook",
"url": "https://angularplaybook.com"
},
"a2b": {
"desc": "Hundreds of Angular courses for all skill levels",
"logo": "",

View File

@ -29,7 +29,7 @@ and annotate the component class with `@Component`.
The CLI generated three metadata properties:
1. `selector`&mdash; the components CSS element selector
1. `selector`&mdash; the component's CSS element selector
1. `templateUrl`&mdash; the location of the component's template file.
1. `styleUrls`&mdash; the location of the component's private CSS styles.

View File

@ -96,7 +96,7 @@ Bind the `HeroesComponent.selectedHero` to the element's `hero` property like th
`[hero]="selectedHero"` is an Angular [property binding](guide/template-syntax#property-binding).
It's a _one way_ data binding from
the `selectedHero` property of the `HeroComponent` to the `hero` property of the target element, which maps to the `hero` property of the `HeroDetailComponent`.
the `selectedHero` property of the `HeroesComponent` to the `hero` property of the target element, which maps to the `hero` property of the `HeroDetailComponent`.
Now when the user clicks a hero in the list, the `selectedHero` changes.
When the `selectedHero` changes, the _property binding_ updates `hero`

View File

@ -4,7 +4,7 @@
<mat-progress-bar mode="indeterminate" color="warn"></mat-progress-bar>
</div>
<mat-toolbar color="primary" class="app-toolbar" [class.transitioning]="isTransitioning">
<mat-toolbar color="primary" class="app-toolbar no-print" [class.transitioning]="isTransitioning">
<mat-toolbar-row class="notification-container">
<aio-notification
icon="insert_comment"
@ -29,6 +29,7 @@
<aio-search-box class="search-container" #searchBox (onSearch)="doSearch($event)" (onFocus)="doSearch($event)"></aio-search-box>
</mat-toolbar-row>
</mat-toolbar>
<aio-search-results #searchResultsView *ngIf="showSearchResults" [searchResults]="searchResults | async" (resultSelected)="hideSearchResults()"></aio-search-results>
<mat-sidenav-container class="sidenav-container" [class.starting]="isStarting" [class.has-floating-toc]="hasFloatingToc" role="main">
@ -56,11 +57,11 @@
</mat-sidenav-container>
<div *ngIf="hasFloatingToc" class="toc-container" [style.max-height.px]="tocMaxHeight" (mousewheel)="restrainScrolling($event)">
<div *ngIf="hasFloatingToc" class="toc-container no-print" [style.max-height.px]="tocMaxHeight" (mousewheel)="restrainScrolling($event)">
<aio-toc></aio-toc>
</div>
<footer>
<footer class="no-print">
<aio-footer [nodes]="footerNodes" [versionInfo]="versionInfo" ></aio-footer>
</footer>

View File

@ -29,7 +29,7 @@ const defaultLineNumsCount = 10; // by default, show linenums over this number
selector: 'aio-code',
template: `
<pre class="prettyprint lang-{{language}}">
<button *ngIf="!hideCopy" class="material-icons copy-button"
<button *ngIf="!hideCopy" class="material-icons copy-button no-print"
title="Copy code snippet"
[attr.aria-label]="ariaLabel"
(click)="doCopy()">

View File

@ -1,4 +1,4 @@
<div *ngIf="type !== 'None'" class="toc-inner" [class.collapsed]="isCollapsed">
<div *ngIf="type !== 'None'" class="toc-inner no-print" [class.collapsed]="isCollapsed">
<div *ngIf="type === 'EmbeddedSimple'" class="toc-heading embedded">
Contents

View File

@ -12,3 +12,4 @@
@import 'sidenav';
@import 'table-of-contents';
@import 'top-menu';
@import 'print-layout';

View File

@ -125,6 +125,7 @@ section#intro {
.announcement-bar {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-around;
align-items: center;
max-width: 50vw;

View File

@ -0,0 +1,110 @@
@media print {
// General Adjustments
* {
box-shadow: none !important;
}
h1 {
height: 40px !important;
color: $darkgray !important;
}
h1, h2, h3, h4, h5, h6 {
page-break-after: avoid;
}
ul, ol, img, code-example, table, tr, .alert, .l-subsection, .feature {
page-break-inside: avoid;
}
table tbody tr:last-child td {
border-bottom: 1px solid $lightgray !important;
}
img {
max-width: 100% !important;
}
p {
widows: 4;
}
p > code, li > code, table code {
color: $blue !important;
}
// No Print Class
.no-print {
display: none !important;
}
// Custom No Print for Sidenav Menu
mat-sidenav.sidenav.mat-sidenav {
display: none !important;
}
// Custom No Print Element Adjustments
.mat-sidenav-content {
margin: 0 !important;
}
mat-sidenav-container.sidenav-container {
min-width: 100vw;
}
.sidenav-content {
overflow: visible;
}
.filetree {
max-width: 100%;
}
aio-code code {
border: none !important;
}
code-example {
pre.lang-bash code span {
color: $mediumgray !important;
}
pre.lang-sh code span {
color: $darkgray !important;
}
header {
border: 0.5px solid $lightgray;
color: $darkgray;
}
}
.content code {
border: 0.5px solid $lightgray;
}
.mat-tab-labels {
div.mat-tab-label {
&:not(.mat-tab-label-active) span {
font-style: italic;
}
&.mat-tab-label-active span {
font-weight: bold;
}
}
}
.api-header label {
color: $darkgray !important;
font-weight: bold !important;
margin: 2px !important;
padding: 0 !important;
display: block !important;
}
.feature-section img {
max-width: 70px !important;
}
}

View File

@ -85,7 +85,6 @@ aio-code pre {
.copy-button {
display: inline-block;
position: absolute;
top: -8px;
right: -32px;

View File

@ -5,8 +5,15 @@ const {generateDocs} = require('./index.js');
const { DOCS_OUTPUT_PATH } = require('../config');
describe('authors-package (integration tests)', () => {
let originalJasmineTimeout;
let files;
beforeAll(() => {
originalJasmineTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
});
afterAll(() => jasmine.DEFAULT_TIMEOUT_INTERVAL = originalJasmineTimeout);
beforeEach(() => {
files = [];
spyOn(fs, 'writeFile').and.callFake((file, content, callback) => {
@ -24,7 +31,7 @@ describe('authors-package (integration tests)', () => {
expect(files).toContain(resolve(DOCS_OUTPUT_PATH, '../resources.json'));
done();
});
}, 4000);
});
it('should generate tutorial docs if the "fileChanged" is a tutorial doc', (done) => {
generateDocs('aio/content/tutorial/toh-pt5.md', { silent: true }).then(() => {
@ -32,7 +39,7 @@ describe('authors-package (integration tests)', () => {
expect(files).toContain(resolve(DOCS_OUTPUT_PATH, 'tutorial/toh-pt5.json'));
done();
});
}, 4000);
});
it('should generate tutorial docs if the "fileChanged" is the tutorial index', (done) => {
generateDocs('aio/content/tutorial/index.md', { silent: true }).then(() => {
@ -40,7 +47,7 @@ describe('authors-package (integration tests)', () => {
expect(files).toContain(resolve(DOCS_OUTPUT_PATH, 'tutorial.json'));
done();
});
}, 4000);
});
it('should generate tutorial docs if the "fileChanged" is a tutorial example', (done) => {
generateDocs('aio/content/examples/toh-pt3/app/app.component.1.html', { silent: true }).then(() => {
@ -48,7 +55,7 @@ describe('authors-package (integration tests)', () => {
expect(files).toContain(resolve(DOCS_OUTPUT_PATH, 'tutorial/toh-pt3.json'));
done();
});
}, 4000);
});
it('should generate guide doc if the "fileChanged" is a guide doc', (done) => {
generateDocs('aio/content/guide/architecture.md', { silent: true }).then(() => {
@ -56,7 +63,7 @@ describe('authors-package (integration tests)', () => {
expect(files).toContain(resolve(DOCS_OUTPUT_PATH, 'guide/architecture.json'));
done();
});
}, 4000);
});
it('should generate guide doc if the "fileChanged" is a guide example', (done) => {
generateDocs('aio/content/examples/architecture/src/app/app.module.ts', { silent: true }).then(() => {
@ -64,7 +71,7 @@ describe('authors-package (integration tests)', () => {
expect(files).toContain(resolve(DOCS_OUTPUT_PATH, 'guide/architecture.json'));
done();
});
}, 4000);
});
it('should generate API doc if the "fileChanged" is an API doc', (done) => {
generateDocs('packages/forms/src/form_builder.ts', { silent: true }).then(() => {
@ -81,4 +88,4 @@ describe('authors-package (integration tests)', () => {
done();
});
}, 16000);
});
});

View File

@ -17,6 +17,14 @@ you run the first build.
[install]: https://bazel.build/versions/master/docs/install.html
### Installation of ibazel
Install interactive bazel runner / fs watcher via:
```
yarn global add @bazel/ibazel
```
## Configuration
The `WORKSPACE` file indicates that our root directory is a
@ -112,6 +120,33 @@ Apple+Shift+D on Mac) and click on the green play icon next to the configuration
- Open chrome at: [http://localhost:9876/debug.html](http://localhost:9876/debug.html)
- Open chrome inspector
### Debugging Bazel rules
Open `external` directory which contains everything that bazel downloaded while executing the workspace file:
```sh
open $(bazel info output_base)/external
```
See subcommands that bazel executes (helpful for debugging):
```sh
bazel build //packages/core:package -s
```
To debug nodejs_binary executable paths uncomment `find . -name rollup 1>&2` (~ line 96) in
```sh
open $(bazel info output_base)/external/build_bazel_rules_nodejs/internal/node_launcher.sh
```
## Stamping
Bazel supports the ability to include non-hermetic information from the version control system in built artifacts. This is called stamping.
You can see an overview at https://www.kchodorow.com/blog/2017/03/27/stamping-your-builds/
In our repo, here is how it's configured:
1) In `tools/bazel_stamp_vars.sh` we run the `git` commands to generate our versioning info.
1) In `tools/bazel.rc` we register this script as the value for the `workspace_status_command` flag. Bazel will run the script when it needs to stamp a binary.
1) In `tools/BUILD.bazel` we have a target `stamp_data` with the special `stamp=1` attribute, which requests that Bazel run the `workspace_status_command`. The result is written to a text file that can be used as an input to other rules.
## Remote cache
Bazel supports fetching action results from a cache, allowing a clean build to pick up artifacts from prior builds.
@ -159,10 +194,13 @@ source: https://github.com/bazelbuild/bazel/issues/4603
If VSCode is not the root cause, you might try:
- Quit VSCode (make sure no VSCode is running).
```
bazel clean --expunge
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -license
bazel build //packages/core # Run a build outside VSCode to pre-build the xcode; then safe to run VSCode
```
Source: https://stackoverflow.com/questions/45276830/xcode-version-must-be-specified-to-use-an-apple-crosstool

View File

@ -5,25 +5,25 @@ The following are canned responses that the Angular team should use to close iss
Since GitHub currently doesn't allow us to have a repository-wide or organization-wide list of [saved replies](https://help.github.com/articles/working-with-saved-replies/), these replies need to be maintained by individual team members. Since the responses can be modified in the future, all responses are versioned to simplify the process of keeping the responses up to date.
## Angular: Already Fixed (v1)
## Angular: Already Fixed (v2)
```
Thanks for reporting this issue. Luckily it has already been fixed in one of the recent releases. Please update to the most recent version to resolve the problem.
If after upgrade the problem still exists in your application please open a new issue and provide a plunker reproducing the problem and describing the difference between the expected and current behavior. You can use this plunker template: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5?p=catalogue
If after upgrade the problem still exists in your application please open a new issue and provide a StackBlitz reproducing the problem and describing the difference between the expected and current behavior. You can use this StackBlitz template: https://stackblitz.com/fork/angular-gitter
```
## Angular: Don't Understand (v1)
## Angular: Don't Understand (v2)
```
I'm sorry but we don't understand the problem you are reporting.
If the problem still exists please open a new issue and provide a plunker reproducing the problem and describing the difference between the expected and current behavior. You can use this plunker template: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5?p=catalogue
If the problem still exists please open a new issue and provide a StackBlitz reproducing the problem and describing the difference between the expected and current behavior. You can use this StackBlitz template: https://stackblitz.com/fork/angular-gitter
```
## Angular: Plunker Needed (v1)
## Angular: StackBlitz Needed (v1)
```
I'm sorry but reported issues require a plunker reproducing the problem.
I'm sorry but reported issues require a StackBlitz reproducing the problem.
If this issue persists, please create a plunker using this template and describe the difference between the expected and current behavior and create a new issue: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5?p=catalogue
If this issue persists, please create a StackBlitz using this template and describe the difference between the expected and current behavior and create a new issue: https://stackblitz.com/fork/angular-gitter
```
## Angular: Duplicate (v1)

View File

@ -3,7 +3,7 @@
"master": {
"uncompressed": {
"inline": 1447,
"main": 159944,
"main": 155112,
"polyfills": 59179
}
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ɵC as C, ɵE as E, ɵT as T, ɵV as V, ɵb as b, ɵb1 as b1, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as _detectChanges, ɵe as e, ɵp as p, ɵr as r, ɵs as s, ɵt as t, ɵv as v} from '@angular/core';
import {ɵC as C, ɵE as E, ɵT as T, ɵV as V, ɵb as b, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as _detectChanges, ɵe as e, ɵi1 as i1, ɵp as p, ɵr as r, ɵs as s, ɵt as t, ɵv as v} from '@angular/core';
import {ComponentDef} from '@angular/core/src/render3/interfaces/definition';
import {TreeNode, buildTree, emptyTree} from '../util';
@ -47,7 +47,7 @@ export class TreeComponent {
C(3);
}
s(0, 'background-color', b(ctx.data.depth % 2 ? '' : 'grey'));
t(1, b1(' ', ctx.data.value, ' '));
t(1, i1(' ', ctx.data.value, ' '));
cR(2);
{
if (ctx.data.left != null) {
@ -117,7 +117,7 @@ export function TreeTpl(ctx: TreeNode, cm: boolean) {
e();
}
s(1, 'background-color', b(ctx.depth % 2 ? '' : 'grey'));
t(2, b1(' ', ctx.value, ' '));
t(2, i1(' ', ctx.value, ' '));
cR(3);
{
if (ctx.left != null) {

View File

@ -11,3 +11,8 @@ ts_library(
name = "types",
srcs = glob(["*.ts"]),
)
exports_files([
"license-banner.txt",
"README.md",
])

View File

@ -1,8 +1,8 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ts_library")
load("//tools:defaults.bzl", "ng_module")
ts_library(
ng_module(
name = "animations",
srcs = glob(
[

View File

@ -1,8 +1,8 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ts_library")
load("//tools:defaults.bzl", "ng_module")
ts_library(
ng_module(
name = "browser",
srcs = glob(
[

View File

@ -59,10 +59,13 @@ export class AnimationTransitionFactory {
driver, element, this.ast.animation, enterClassName, leaveClassName, currentStateStyles,
nextStateStyles, animationOptions, subInstructions, errors);
let totalTime = 0;
timelines.forEach(tl => { totalTime = Math.max(tl.duration + tl.delay, totalTime); });
if (errors.length) {
return createTransitionInstruction(
element, this._triggerName, currentState, nextState, isRemoval, currentStateStyles,
nextStateStyles, [], [], preStyleMap, postStyleMap, errors);
nextStateStyles, [], [], preStyleMap, postStyleMap, totalTime, errors);
}
timelines.forEach(tl => {
@ -81,7 +84,7 @@ export class AnimationTransitionFactory {
const queriedElementsList = iteratorToArray(queriedElements.values());
return createTransitionInstruction(
element, this._triggerName, currentState, nextState, isRemoval, currentStateStyles,
nextStateStyles, timelines, queriedElementsList, preStyleMap, postStyleMap);
nextStateStyles, timelines, queriedElementsList, preStyleMap, postStyleMap, totalTime);
}
}

View File

@ -21,6 +21,7 @@ export interface AnimationTransitionInstruction extends AnimationEngineInstructi
queriedElements: any[];
preStyleProps: Map<any, {[prop: string]: boolean}>;
postStyleProps: Map<any, {[prop: string]: boolean}>;
totalTime: number;
errors?: any[];
}
@ -29,7 +30,7 @@ export function createTransitionInstruction(
isRemovalTransition: boolean, fromStyles: ɵStyleData, toStyles: ɵStyleData,
timelines: AnimationTimelineInstruction[], queriedElements: any[],
preStyleProps: Map<any, {[prop: string]: boolean}>,
postStyleProps: Map<any, {[prop: string]: boolean}>,
postStyleProps: Map<any, {[prop: string]: boolean}>, totalTime: number,
errors?: any[]): AnimationTransitionInstruction {
return {
type: AnimationTransitionInstructionType.TransitionAnimation,
@ -44,6 +45,7 @@ export function createTransitionInstruction(
queriedElements,
preStyleProps,
postStyleProps,
totalTime,
errors
};
}

View File

@ -34,7 +34,7 @@ export class NoopAnimationDriver implements AnimationDriver {
animate(
element: any, keyframes: {[key: string]: string | number}[], duration: number, delay: number,
easing: string, previousPlayers: any[] = []): AnimationPlayer {
return new NoopAnimationPlayer();
return new NoopAnimationPlayer(duration, delay);
}
}

View File

@ -75,23 +75,24 @@ export function listenOnPlayer(
callback: (event: any) => any) {
switch (eventName) {
case 'start':
player.onStart(() => callback(event && copyAnimationEvent(event, 'start', player.totalTime)));
player.onStart(() => callback(event && copyAnimationEvent(event, 'start', player)));
break;
case 'done':
player.onDone(() => callback(event && copyAnimationEvent(event, 'done', player.totalTime)));
player.onDone(() => callback(event && copyAnimationEvent(event, 'done', player)));
break;
case 'destroy':
player.onDestroy(
() => callback(event && copyAnimationEvent(event, 'destroy', player.totalTime)));
player.onDestroy(() => callback(event && copyAnimationEvent(event, 'destroy', player)));
break;
}
}
export function copyAnimationEvent(
e: AnimationEvent, phaseName?: string, totalTime?: number): AnimationEvent {
e: AnimationEvent, phaseName: string, player: AnimationPlayer): AnimationEvent {
const totalTime = player.totalTime;
const disabled = (player as any).disabled ? true : false;
const event = makeAnimationEvent(
e.element, e.triggerName, e.fromState, e.toState, phaseName || e.phaseName,
totalTime == undefined ? e.totalTime : totalTime);
totalTime == undefined ? e.totalTime : totalTime, disabled);
const data = (e as any)['_data'];
if (data != null) {
(event as any)['_data'] = data;
@ -101,8 +102,8 @@ export function copyAnimationEvent(
export function makeAnimationEvent(
element: any, triggerName: string, fromState: string, toState: string, phaseName: string = '',
totalTime: number = 0): AnimationEvent {
return {element, triggerName, fromState, toState, phaseName, totalTime};
totalTime: number = 0, disabled?: boolean): AnimationEvent {
return {element, triggerName, fromState, toState, phaseName, totalTime, disabled: !!disabled};
}
export function getOrSetAsInMap(

View File

@ -1081,6 +1081,8 @@ export class TransitionAnimationEngine {
if (subTimelines.has(element)) {
if (disabledElementsSet.has(element)) {
player.onDestroy(() => setStyles(element, instruction.toStyles));
player.disabled = true;
player.overrideTotalTime(instruction.totalTime);
skippedPlayers.push(player);
return;
}
@ -1311,7 +1313,8 @@ export class TransitionAnimationEngine {
// FIXME (matsko): make sure to-be-removed animations are removed properly
const details = element[REMOVAL_FLAG];
if (details && details.removedBeforeQueried) return new NoopAnimationPlayer();
if (details && details.removedBeforeQueried)
return new NoopAnimationPlayer(timelineInstruction.duration, timelineInstruction.delay);
const isQueriedElement = element !== rootElement;
const previousPlayers =
@ -1379,7 +1382,7 @@ export class TransitionAnimationEngine {
// special case for when an empty transition|definition is provided
// ... there is no point in rendering an empty animation
return new NoopAnimationPlayer();
return new NoopAnimationPlayer(instruction.duration, instruction.delay);
}
}
@ -1392,8 +1395,10 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
public parentPlayer: AnimationPlayer;
public markedForDestroy: boolean = false;
public disabled = false;
readonly queued: boolean = true;
public readonly totalTime: number = 0;
constructor(public namespaceId: string, public triggerName: string, public element: any) {}
@ -1407,15 +1412,18 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
});
this._queuedCallbacks = {};
this._containsRealPlayer = true;
this.overrideTotalTime(player.totalTime);
(this as{queued: boolean}).queued = false;
}
getRealPlayer() { return this._player; }
overrideTotalTime(totalTime: number) { (this as any).totalTime = totalTime; }
syncPlayerEvents(player: AnimationPlayer) {
const p = this._player as any;
if (p.triggerCallback) {
player.onStart(() => p.triggerCallback('start'));
player.onStart(() => p.triggerCallback !('start'));
}
player.onDone(() => this.finish());
player.onDestroy(() => this.destroy());
@ -1473,8 +1481,6 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
getPosition(): number { return this.queued ? 0 : this._player.getPosition(); }
get totalTime(): number { return this._player.totalTime; }
/* @internal */
triggerCallback(phaseName: string): void {
const p = this._player as any;

View File

@ -299,7 +299,8 @@ const DEFAULT_NAMESPACE_ID = 'id';
phaseName: 'start',
fromState: '123',
toState: '456',
totalTime: 1234
totalTime: 1234,
disabled: false
});
capture = null !;
@ -313,7 +314,8 @@ const DEFAULT_NAMESPACE_ID = 'id';
phaseName: 'done',
fromState: '123',
toState: '456',
totalTime: 1234
totalTime: 1234,
disabled: false
});
});
});

View File

@ -1,8 +1,8 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ts_library")
load("//tools:defaults.bzl", "ng_module")
ts_library(
ng_module(
name = "testing",
testonly = 1,
srcs = glob(["**/*.ts"]),

View File

@ -58,7 +58,7 @@ export class MockAnimationPlayer extends NoopAnimationPlayer {
public element: any, public keyframes: {[key: string]: string | number}[],
public duration: number, public delay: number, public easing: string,
public previousPlayers: any[]) {
super();
super(duration, delay);
if (allowPreviousPlayerStylesMerge(duration, delay)) {
previousPlayers.forEach(player => {
@ -68,8 +68,6 @@ export class MockAnimationPlayer extends NoopAnimationPlayer {
}
});
}
this.totalTime = delay + duration;
}
/* @internal */

View File

@ -44,4 +44,5 @@ export interface AnimationEvent {
phaseName: string;
element: any;
triggerName: string;
disabled: boolean;
}

View File

@ -352,6 +352,14 @@ export interface AnimationStaggerMetadata extends AnimationMetadata {
elements located in disabled areas of the template and still animate them as it sees fit. This is
also the case for when a sub animation is queried by a parent and then later animated using {@link
animateChild animateChild}.
* ### Detecting when an animation is disabled
* If a region of the DOM (or the entire application) has its animations disabled, then animation
* trigger callbacks will still fire just as normal (only for zero seconds).
*
* When a trigger callback fires it will provide an instance of an {@link AnimationEvent}. If
animations
* are disabled then the `.disabled` flag on the event will be true.
*
* @experimental Animation support is experimental.
*/

View File

@ -33,6 +33,8 @@ export interface AnimationPlayer {
beforeDestroy?: () => any;
/* @internal */
triggerCallback?: (phaseName: string) => void;
/* @internal */
disabled?: boolean;
}
/**
@ -46,8 +48,8 @@ export class NoopAnimationPlayer implements AnimationPlayer {
private _destroyed = false;
private _finished = false;
public parentPlayer: AnimationPlayer|null = null;
public totalTime = 0;
constructor() {}
public readonly totalTime: number;
constructor(duration: number = 0, delay: number = 0) { this.totalTime = duration + delay; }
private _onFinish() {
if (!this._finished) {
this._finished = true;
@ -100,4 +102,4 @@ export class NoopAnimationPlayer implements AnimationPlayer {
methods.forEach(fn => fn());
methods.length = 0;
}
}
}

View File

@ -7,6 +7,6 @@
Users should not load files under "/src"
"""
load("//src:ng_module.bzl", _ng_module = "ng_module")
load("//packages/bazel/src:ng_module.bzl", _ng_module = "ng_module")
ng_module = _ng_module

View File

@ -1,3 +1,5 @@
package(default_visibility = ["//visibility:public"])
load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary")
nodejs_binary(
@ -8,12 +10,22 @@ nodejs_binary(
# additional npm dependencies when we run rollup or uglify.
entry_point = "build_bazel_rules_nodejs_rollup_deps/node_modules/rollup/bin/rollup",
node_modules = "@build_bazel_rules_nodejs_rollup_deps//:node_modules",
visibility = ["//visibility:public"],
)
nodejs_binary(
name = "modify_tsconfig",
data = ["modify_tsconfig.js"],
entry_point = "angular/packages/bazel/src/modify_tsconfig.js",
visibility = ["//visibility:public"],
)
nodejs_binary(
name = "index_bundler",
data = [
# BEGIN-INTERNAL
# Only needed when compiling within the Angular repo.
# Users will get this dependency from node_modules.
"//packages/compiler-cli",
# END-INTERNAL
],
entry_point = "@angular/compiler-cli/src/metadata/bundle_index_main.js",
)

View File

@ -12,6 +12,7 @@ load(":rules_typescript.bzl",
"compile_ts",
"DEPS_ASPECTS",
"ts_providers_dict_to_struct",
"json_marshal",
)
def _basename_of(ctx, file):
@ -90,8 +91,7 @@ def _ngc_tsconfig(ctx, files, srcs, **kwargs):
"enableSummariesForJit": True,
"fullTemplateTypeCheck": ctx.attr.type_check,
# FIXME: wrong place to de-dupe
"expectedOut": depset([o.path for o in expected_outs]).to_list(),
"preserveWhitespaces": False,
"expectedOut": depset([o.path for o in expected_outs]).to_list()
}
})
@ -236,6 +236,43 @@ def _ts_expected_outs(ctx, label):
_ignored = [label]
return _expected_outs(ctx)
def _write_bundle_index(ctx):
basename = "_%s.bundle_index" % ctx.label.name
tsconfig_file = ctx.actions.declare_file("%s.tsconfig.json" % basename)
metadata_file = ctx.actions.declare_file("%s.metadata.json" % basename)
tstyping_file = ctx.actions.declare_file("%s.d.ts" % basename)
tsconfig = dict(tsc_wrapped_tsconfig(ctx, ctx.files.srcs, ctx.files.srcs), **{
"angularCompilerOptions": {
"flatModuleOutFile": basename,
},
})
if ctx.attr.module_name:
tsconfig["angularCompilerOptions"]["flatModuleId"] = ctx.attr.module_name
# createBundleIndexHost in bundle_index_host.ts will throw if the "files" has more than one entry.
# We don't want to fail() here, however, because not all ng_module's will have the bundle index written.
# So we make the assumption that the index.ts file in the highest parent directory is the entry point.
index_file = None
for f in tsconfig["files"]:
if f.endswith("/index.ts"):
if not index_file or len(f) < len(index_file):
index_file = f
tsconfig["files"] = [index_file]
ctx.actions.write(tsconfig_file, json_marshal(tsconfig))
outputs = [metadata_file, tstyping_file]
ctx.action(
progress_message = "Producing metadata for bundle %s" % ctx.label.name,
executable = ctx.executable._index_bundler,
inputs = ctx.files.srcs + [tsconfig_file],
outputs = outputs,
arguments = ["-p", tsconfig_file.path],
)
return outputs
def ng_module_impl(ctx, ts_compile_actions):
"""Implementation function for the ng_module rule.
@ -263,6 +300,13 @@ def ng_module_impl(ctx, ts_compile_actions):
}
providers["ngc_messages"] = outs.i18n_messages
# Only produces the flattened "index bundle" metadata when requested by some other rule
# and only under Bazel
if hasattr(ctx.executable, "_index_bundler"):
bundle_index_metadata = _write_bundle_index(ctx)
# note, not recursive
providers["angular"]["flat_module_metadata"] = depset(bundle_index_metadata)
return providers
def _ng_module_impl(ctx):
@ -315,6 +359,10 @@ ng_module = rule(
"node_modules": attr.label(
default = Label("@//:node_modules")
),
"_index_bundler": attr.label(
executable = True,
cfg = "host",
default = Label("//packages/bazel/src:index_bundler")),
}),
outputs = COMMON_OUTPUTS,
)

View File

@ -12,8 +12,8 @@ ts_library(
visibility = ["//packages/bazel/test/ngc-wrapped:__subpackages__"],
deps = [
# BEGIN-INTERNAL
# Only needed when compiling Angular from sources.
# Users with an npm depnedency will get this dependency from node_modules.
# Only needed when compiling within the Angular repo.
# Users will get this dependency from node_modules.
"//packages/compiler-cli",
# END-INTERNAL
"@build_bazel_rules_typescript//internal/tsc_wrapped",

View File

@ -10,12 +10,8 @@ ts_library(
],
tsconfig = ":tsconfig.json",
deps = [
# BEGIN-INTERNAL
# Only needed when compiling within the Angular repo.
# Users will get this dependency from node_modules.
"//packages/compiler-cli",
# END-INTERNAL
"//packages/bazel/src/ngc-wrapped:ngc_lib",
"//packages/compiler-cli",
],
)

View File

@ -14,7 +14,7 @@ const globals = {
'rxjs/Observer': 'Rx',
'rxjs/Subject': 'Rx',
'rxjs/observable/of': 'Rx.Observable.prototype',
'rxjs/observable/of': 'Rx.Observable',
'rxjs/operator/concatMap': 'Rx.Observable.prototype',
'rxjs/operator/filter': 'Rx.Observable.prototype',

View File

@ -156,7 +156,7 @@ export class JsonpClientBackend implements HttpBackend {
statusText: 'OK', url,
}));
// Complete the stream, the resposne is over.
// Complete the stream, the response is over.
observer.complete();
};

View File

@ -1,10 +1,9 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ts_library")
load("//tools:defaults.bzl", "ng_module")
ts_library(
ng_module(
name = "testing",
testonly = 1,
srcs = glob(
[
"*.ts",

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef} from '@angular/core';
import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef, ɵstringify as stringify} from '@angular/core';
/**
@ -117,14 +117,16 @@ export class NgIf {
}
@Input()
set ngIfThen(templateRef: TemplateRef<NgIfContext>) {
set ngIfThen(templateRef: TemplateRef<NgIfContext>|null) {
assertTemplate('ngIfThen', templateRef);
this._thenTemplateRef = templateRef;
this._thenViewRef = null; // clear previous view if any.
this._updateView();
}
@Input()
set ngIfElse(templateRef: TemplateRef<NgIfContext>) {
set ngIfElse(templateRef: TemplateRef<NgIfContext>|null) {
assertTemplate('ngIfElse', templateRef);
this._elseTemplateRef = templateRef;
this._elseViewRef = null; // clear previous view if any.
this._updateView();
@ -163,3 +165,10 @@ export class NgIfContext {
public $implicit: any = null;
public ngIf: any = null;
}
function assertTemplate(property: string, templateRef: TemplateRef<any>| null): void {
const isTemplateRefOrNull = !!(!templateRef || templateRef.createEmbeddedView);
if (!isTemplateRefOrNull) {
throw new Error(`${property} must be a TemplateRef, but received '${stringify(templateRef)}'.`);
}
}

View File

@ -138,7 +138,7 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
expect(fixture.nativeElement).toHaveText('hello');
}));
describe('else', () => {
describe('then/else templates', () => {
it('should support else', async(() => {
const template = '<span *ngIf="booleanCondition; else elseBlock">TRUE</span>' +
'<ng-template #elseBlock>FALSE</ng-template>';
@ -169,6 +169,37 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
expect(fixture.nativeElement).toHaveText('ELSE');
}));
it('should support removing the then/else templates', () => {
const template = `<span *ngIf="booleanCondition;
then nestedBooleanCondition ? tplRef : null;
else nestedBooleanCondition ? tplRef : null"></span>
<ng-template #tplRef>Template</ng-template>`;
fixture = createTestComponent(template);
const comp = fixture.componentInstance;
// then template
comp.booleanCondition = true;
comp.nestedBooleanCondition = true;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('Template');
comp.nestedBooleanCondition = false;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
// else template
comp.booleanCondition = true;
comp.nestedBooleanCondition = true;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('Template');
comp.nestedBooleanCondition = false;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
});
it('should support dynamic else', async(() => {
const template =
'<span *ngIf="booleanCondition; else nestedBooleanCondition ? b1 : b2">TRUE</span>' +
@ -217,6 +248,28 @@ import {expect} from '@angular/platform-browser/testing/src/matchers';
expect(fixture.nativeElement).toHaveText('false');
}));
});
describe('Type guarding', () => {
it('should throw when then block is not template', async(() => {
const template = '<span *ngIf="booleanCondition; then thenBlock">IGNORE</span>' +
'<div #thenBlock>THEN</div>';
fixture = createTestComponent(template);
expect(() => fixture.detectChanges())
.toThrowError(/ngIfThen must be a TemplateRef, but received/);
}));
it('should throw when else block is not template', async(() => {
const template = '<span *ngIf="booleanCondition; else elseBlock">IGNORE</span>' +
'<div #elseBlock>ELSE</div>';
fixture = createTestComponent(template);
expect(() => fixture.detectChanges())
.toThrowError(/ngIfElse must be a TemplateRef, but received/);
}));
});
});
}

View File

@ -1,10 +1,9 @@
package(default_visibility = ["//visibility:public"])
load("//tools:defaults.bzl", "ts_library")
load("//tools:defaults.bzl", "ng_module")
ts_library(
ng_module(
name = "testing",
testonly = 1,
srcs = glob(["**/*.ts"]),
module_name = "@angular/common/testing",
deps = [

View File

@ -17,11 +17,11 @@ ts_library(
],
exclude = [
"src/extract_i18n.ts",
"src/main.ts",
"src/integrationtest/**/*.ts",
],
),
module_name = "@angular/compiler-cli",
node_modules = "@//:node_modules",
tsconfig = ":tsconfig",
deps = [
"//packages/compiler",

View File

@ -16,6 +16,7 @@ ng_module(
"//packages/core",
"//packages/platform-browser",
"//packages/platform-server",
"//packages/router",
"@rxjs",
],
)

View File

@ -0,0 +1,43 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {APP_ROOT_SCOPE, Component, Injectable, NgModule, Optional, Self} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {ServerModule} from '@angular/platform-server';
import {RouterModule} from '@angular/router';
import {LazyModuleNgFactory} from './root_lazy.ngfactory';
@Component({
selector: 'root-app',
template: '<router-outlet></router-outlet>',
})
export class AppComponent {
}
export function children(): any {
console.error('children', LazyModuleNgFactory);
return LazyModuleNgFactory;
}
@NgModule({
imports: [
BrowserModule.withServerTransition({appId: 'id-app'}),
ServerModule,
RouterModule.forRoot(
[
{path: '', pathMatch: 'prefix', loadChildren: children},
],
{initialNavigation: 'enabled'}),
],
declarations: [AppComponent],
bootstrap: [AppComponent],
})
export class RootAppModule {
}

View File

@ -0,0 +1,35 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Component, NgModule, Optional, Self} from '@angular/core';
import {RouterModule} from '@angular/router';
import {Service} from './root_service';
@Component({
selector: 'lazy-route',
template: '{{service}}:{{serviceInLazyInjector}}',
})
export class RouteComponent {
service: boolean;
serviceInLazyInjector: boolean;
constructor(@Optional() service: Service, @Optional() @Self() lazyService: Service) {
this.service = !!service;
this.serviceInLazyInjector = !!lazyService;
}
}
@NgModule({
declarations: [RouteComponent],
imports: [
RouterModule.forChild([
{path: '', pathMatch: 'prefix', component: RouteComponent},
]),
],
})
export class LazyModule {
}

View File

@ -0,0 +1,15 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {APP_ROOT_SCOPE, Injectable} from '@angular/core';
@Injectable({
scope: APP_ROOT_SCOPE,
})
export class Service {
}

View File

@ -12,6 +12,16 @@ import {ServerModule} from '@angular/platform-server';
export interface IService { readonly data: string; }
@NgModule({})
export class TokenModule {
}
export const TOKEN = new InjectionToken('test', {
scope: TokenModule,
factory: () => new Service(),
});
@Component({
selector: 'token-app',
template: '{{data}}',
@ -25,18 +35,12 @@ export class AppComponent {
imports: [
BrowserModule.withServerTransition({appId: 'id-app'}),
ServerModule,
TokenModule,
],
declarations: [AppComponent],
bootstrap: [AppComponent],
providers: [{provide: forwardRef(() => TOKEN), useClass: forwardRef(() => Service)}]
})
export class TokenAppModule {
}
export class Service { readonly data = 'fromToken'; }
export const TOKEN = new InjectionToken('test', {
scope: TokenAppModule,
useClass: Service,
deps: [],
});
export class Service { readonly data = 'fromToken'; }

View File

@ -10,6 +10,7 @@ import {enableProdMode} from '@angular/core';
import {renderModuleFactory} from '@angular/platform-server';
import {BasicAppModuleNgFactory} from 'app_built/src/basic.ngfactory';
import {HierarchyAppModuleNgFactory} from 'app_built/src/hierarchy.ngfactory';
import {RootAppModuleNgFactory} from 'app_built/src/root.ngfactory';
import {SelfAppModuleNgFactory} from 'app_built/src/self.ngfactory';
import {TokenAppModuleNgFactory} from 'app_built/src/token.ngfactory';
@ -55,4 +56,14 @@ describe('ngInjectableDef Bazel Integration', () => {
done();
});
});
it('APP_ROOT_SCOPE works', done => {
renderModuleFactory(RootAppModuleNgFactory, {
document: '<root-app></root-app>',
url: '/',
}).then(html => {
expect(html).toMatch(/>true:false<\//);
done();
});
});
});

View File

@ -0,0 +1,23 @@
load("//packages/bazel:index.bzl", "ng_module")
ng_module(
name = "test_module",
srcs = glob(["*.ts"]),
module_name = "some_npm_module",
deps = ["//packages/core"],
)
load(":extract_flat_module_index.bzl", "extract_flat_module_index")
extract_flat_module_index(
name = "flat_module_index",
deps = [":test_module"],
)
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
jasmine_node_test(
name = "test",
srcs = ["spec.js"],
data = [":flat_module_index"],
)

View File

@ -0,0 +1,14 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {NgModule} from '@angular/core';
import {Parent} from './parent';
@NgModule({imports: [Parent]})
export class Child {
}

View File

@ -0,0 +1,20 @@
# Copyright Google Inc. All Rights Reserved.
#
# Use of this source code is governed by an MIT-style license that can be
# found in the LICENSE file at https://angular.io/license
"""Test utility to extract the "flat_module_metadata" from transitive Angular deps.
"""
def _extract_flat_module_index(ctx):
return [DefaultInfo(files = depset(transitive = [
dep.angular.flat_module_metadata
for dep in ctx.attr.deps
if hasattr(dep, "angular")
]))]
extract_flat_module_index = rule(
implementation = _extract_flat_module_index,
attrs = {
"deps": attr.label_list(),
},
)

View File

@ -0,0 +1,9 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export * from './child';

View File

@ -0,0 +1,13 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {NgModule} from '@angular/core';
@NgModule({})
export class Parent {
}

View File

@ -0,0 +1,31 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
const fs = require('fs');
const PKG = 'angular/packages/compiler-cli/integrationtest/bazel/ng_module';
describe('flat module index', () => {
describe('child metadata', () => {
it('should have contents', () => {
const metadata = fs.readFileSync(
require.resolve(`${PKG}/_test_module.bundle_index.metadata.json`), {encoding: 'utf-8'});
expect(metadata).toContain('"__symbolic":"module"');
expect(metadata).toContain('"__symbolic":"reference","module":"@angular/core"');
expect(metadata).toContain('"origins":{"Child":"./child","ɵa":"./parent"}');
expect(metadata).toContain('"importAs":"some_npm_module"');
});
});
describe('child typings', () => {
it('should have contents', () => {
const dts = fs.readFileSync(
require.resolve(`${PKG}/_test_module.bundle_index.d.ts`), {encoding: 'utf-8'});
expect(dts).toContain('export * from \'./index\';');
expect(dts).toContain('export { Parent as ɵa } from \'./parent\';');
});
});
});

View File

@ -11,7 +11,7 @@
"dependencies": {
"reflect-metadata": "^0.1.2",
"minimist": "^1.2.0",
"tsickle": "^0.26.0",
"tsickle": "^0.27.2",
"chokidar": "^1.4.2"
},
"peerDependencies": {

View File

@ -0,0 +1,43 @@
#!/usr/bin/env node
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
// Must be imported first, because Angular decorators throw on load.
import 'reflect-metadata';
import * as ts from 'typescript';
import * as path from 'path';
import {readCommandLineAndConfiguration} from '../main';
import {createBundleIndexHost} from './bundle_index_host';
import * as ng from '../transformers/entry_points';
export function main(args: string[], consoleError: (s: string) => void = console.error): number {
const {options, rootNames} = readCommandLineAndConfiguration(args);
const host = ng.createCompilerHost({options});
const {host: bundleHost, indexName, errors} = createBundleIndexHost(options, rootNames, host);
if (!indexName) {
console.error('Did not find an index.ts in the top-level of the package.');
return 1;
}
// The index file is synthetic, so we have to add it to the program after parsing the tsconfig
rootNames.push(indexName);
const program = ts.createProgram(rootNames, options, bundleHost);
const indexSourceFile = program.getSourceFile(indexName);
if (!indexSourceFile) {
console.error(`${indexSourceFile} is not in the program. Please file a bug.`);
return 1;
}
program.emit(indexSourceFile);
return 0;
}
// CLI entry point
if (require.main === module) {
const args = process.argv.slice(2);
process.exitCode = main(args);
}

View File

@ -597,6 +597,7 @@ export class CompilerHostAdapter implements MetadataBundlerHost {
constructor(private host: ts.CompilerHost) {}
getMetadataFor(fileName: string): ModuleMetadata|undefined {
if (!this.host.fileExists(fileName + '.ts')) return undefined;
const sourceFile = this.host.getSourceFile(fileName + '.ts', ts.ScriptTarget.Latest);
return sourceFile && this.collector.getMetadata(sourceFile);
}

View File

@ -381,9 +381,6 @@ export class Evaluator {
case ts.SyntaxKind.NewExpression:
const newExpression = <ts.NewExpression>node;
const newArgs = arrayOrEmpty(newExpression.arguments).map(arg => this.evaluateNode(arg));
if (!this.options.verboseInvalidExpression && newArgs.some(isMetadataError)) {
return recordEntry(newArgs.find(isMetadataError), node);
}
const newTarget = this.evaluateNode(newExpression.expression);
if (isMetadataError(newTarget)) {
return recordEntry(newTarget, node);

View File

@ -60,6 +60,7 @@ export interface CompilerOptions extends ts.CompilerOptions {
i18nInFile?: string;
i18nInMissingTranslations?: 'error'|'warning'|'ignore';
preserveWhitespaces?: boolean;
disableTypeScriptVersionCheck?: boolean;
}
export interface CompilerHost extends ts.CompilerHost {

View File

@ -132,6 +132,9 @@ export interface CompilerOptions extends ts.CompilerOptions {
// position.
disableExpressionLowering?: boolean;
// Disable TypeScript Version Check.
disableTypeScriptVersionCheck?: boolean;
// Locale of the application
i18nOutLocale?: string;
// Export format (xlf, xlf2 or xmb)
@ -148,8 +151,8 @@ export interface CompilerOptions extends ts.CompilerOptions {
// How to handle missing messages
i18nInMissingTranslations?: 'error'|'warning'|'ignore';
// Whether to remove blank text nodes from compiled templates. It is `true` by default
// in Angular 5 and will be re-visited in Angular 6.
// Whether to remove blank text nodes from compiled templates. It is `false` by default starting
// from Angular 6.
preserveWhitespaces?: boolean;
/** generate all possible generated files */

View File

@ -71,10 +71,11 @@ class AngularCompilerProgram implements Program {
rootNames: ReadonlyArray<string>, private options: CompilerOptions,
private host: CompilerHost, oldProgram?: Program) {
this.rootNames = [...rootNames];
const [major, minor] = ts.version.split('.');
Number(major) > 2 || (Number(major) === 2 && Number(minor) >= 4) ||
userError('The Angular Compiler requires TypeScript >= 2.4.');
if (ts.version < '2.4.2' || (ts.version >= '2.7.0' && !options.disableTypeScriptVersionCheck)) {
throw new Error(
`The Angular Compiler requires TypeScript >=2.4.2 and <2.7 but ${ts.version} was found instead.`);
}
this.oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined;
if (oldProgram) {

View File

@ -2094,5 +2094,24 @@ describe('ngc transformer command-line', () => {
`);
expect(source).toMatch(/ngInjectableDef.*return ..\(..\.inject\(Existing, undefined, 1\)/);
});
it('compiles a service that depends on a token', () => {
const source = compileService(`
import {Inject, Injectable, InjectionToken} from '@angular/core';
import {Module} from './module';
export const TOKEN = new InjectionToken('desc', {scope: Module, factory: () => true});
@Injectable({
scope: Module,
})
export class Service {
constructor(@Inject(TOKEN) value: boolean) {}
}
`);
expect(source).toMatch(/ngInjectableDef = .+\.defineInjectable\(/);
expect(source).toMatch(/ngInjectableDef.*token: Service/);
expect(source).toMatch(/ngInjectableDef.*scope: .+\.Module/);
});
});
});

View File

@ -12,6 +12,9 @@
},
"outDir": "../../dist/packages/compiler-cli"
},
"bazelOptions": {
"suppressTsconfigOverrideWarnings": true
},
"exclude": [
"integrationtest"
@ -21,6 +24,7 @@
"index.ts",
"ngtools2.ts",
"src/main.ts",
"src/metadata/bundle_index_main.ts",
"src/extract_i18n.ts",
"src/language_services.ts",
"../../node_modules/@types/node/index.d.ts",

View File

@ -20,6 +20,7 @@ import {NgModuleCompiler} from '../ng_module_compiler';
import {OutputEmitter} from '../output/abstract_emitter';
import * as o from '../output/output_ast';
import {ParseError} from '../parse_util';
import {compilePipe as compileIvyPipe} from '../render3/r3_pipe_compiler';
import {compileComponent as compileIvyComponent, compileDirective as compileIvyDirective} from '../render3/r3_view_compiler';
import {CompiledStylesheet, StyleCompiler} from '../style_compiler';
import {SummaryResolver} from '../summary_resolver';
@ -356,14 +357,22 @@ export class AotCompiler {
error(
`Cannot determine the module for component '${identifierName(directiveMetadata.type)}'`);
const {template: parsedTemplate} =
const {template: parsedTemplate, pipes: parsedPipes} =
this._parseTemplate(directiveMetadata, module, module.transitiveModule.directives);
compileIvyComponent(context, directiveMetadata, parsedTemplate, this._reflector);
compileIvyComponent(
context, directiveMetadata, parsedPipes, parsedTemplate, this._reflector);
} else {
compileIvyDirective(context, directiveMetadata, this._reflector);
}
});
pipes.forEach(pipeType => {
const pipeMetadata = this._metadataResolver.getPipeMetadata(pipeType);
if (pipeMetadata) {
compileIvyPipe(context, pipeMetadata, this._reflector);
}
});
injectables.forEach(injectable => this._injectableCompiler.compile(injectable, context));
if (context.statements && context.statements.length > 0) {

View File

@ -46,6 +46,6 @@ export class CompilerConfig {
}
export function preserveWhitespacesDefault(
preserveWhitespacesOption: boolean | null, defaultSetting = true): boolean {
preserveWhitespacesOption: boolean | null, defaultSetting = false): boolean {
return preserveWhitespacesOption === null ? defaultSetting : preserveWhitespacesOption;
}

View File

@ -11,7 +11,7 @@ import {OutputContext, error} from './util';
const CONSTANT_PREFIX = '_c';
export const enum DefinitionKind {Injector, Directive, Component}
export const enum DefinitionKind {Injector, Directive, Component, Pipe}
/**
* A node that is a place-holder that allows the node to be replaced when the actual
@ -51,6 +51,7 @@ export class ConstantPool {
private injectorDefinitions = new Map<any, FixupExpression>();
private directiveDefinitions = new Map<any, FixupExpression>();
private componentDefinitions = new Map<any, FixupExpression>();
private pipeDefinitions = new Map<any, FixupExpression>();
private nextNameIndex = 0;
@ -75,18 +76,19 @@ export class ConstantPool {
return fixup;
}
getDefinition(type: any, kind: DefinitionKind, ctx: OutputContext): o.Expression {
const declarations = kind == DefinitionKind.Component ?
this.componentDefinitions :
kind == DefinitionKind.Directive ? this.directiveDefinitions : this.injectorDefinitions;
let fixup = declarations.get(type);
getDefinition(type: any, kind: DefinitionKind, ctx: OutputContext, forceShared: boolean = false):
o.Expression {
const definitions = this.definitionsOf(kind);
let fixup = definitions.get(type);
let newValue = false;
if (!fixup) {
const property = kind == DefinitionKind.Component ?
'ngComponentDef' :
kind == DefinitionKind.Directive ? 'ngDirectiveDef' : 'ngInjectorDef';
const property = this.propertyNameOf(kind);
fixup = new FixupExpression(ctx.importExpr(type).prop(property));
declarations.set(type, fixup);
} else if (!fixup.shared) {
definitions.set(type, fixup);
newValue = true;
}
if ((!newValue && !fixup.shared) || (newValue && forceShared)) {
const name = this.freshName();
this.statements.push(
o.variable(name).set(fixup.resolved).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
@ -104,6 +106,36 @@ export class ConstantPool {
*/
uniqueName(prefix: string): string { return `${prefix}${this.nextNameIndex++}`; }
private definitionsOf(kind: DefinitionKind): Map<any, FixupExpression> {
switch (kind) {
case DefinitionKind.Component:
return this.componentDefinitions;
case DefinitionKind.Directive:
return this.directiveDefinitions;
case DefinitionKind.Injector:
return this.injectorDefinitions;
case DefinitionKind.Pipe:
return this.pipeDefinitions;
}
error(`Unknown definition kind ${kind}`);
return this.componentDefinitions;
}
public propertyNameOf(kind: DefinitionKind): string {
switch (kind) {
case DefinitionKind.Component:
return 'ngComponentDef';
case DefinitionKind.Directive:
return 'ngDirectiveDef';
case DefinitionKind.Injector:
return 'ngInjectorDef';
case DefinitionKind.Pipe:
return 'ngPipeDef';
}
error(`Unknown definition kind ${kind}`);
return '<unknown>';
}
private freshName(): string { return this.uniqueName(CONSTANT_PREFIX); }
private keyOf(expression: o.Expression) {

View File

@ -435,6 +435,173 @@ export class AstTransformer implements AstVisitor {
}
}
// A transformer that only creates new nodes if the transformer makes a change or
// a change is made a child node.
export class AstMemoryEfficientTransformer implements AstVisitor {
visitImplicitReceiver(ast: ImplicitReceiver, context: any): AST { return ast; }
visitInterpolation(ast: Interpolation, context: any): Interpolation {
const expressions = this.visitAll(ast.expressions);
if (expressions !== ast.expressions)
return new Interpolation(ast.span, ast.strings, expressions);
return ast;
}
visitLiteralPrimitive(ast: LiteralPrimitive, context: any): AST { return ast; }
visitPropertyRead(ast: PropertyRead, context: any): AST {
const receiver = ast.receiver.visit(this);
if (receiver !== ast.receiver) {
return new PropertyRead(ast.span, receiver, ast.name);
}
return ast;
}
visitPropertyWrite(ast: PropertyWrite, context: any): AST {
const receiver = ast.receiver.visit(this);
const value = ast.value.visit(this);
if (receiver !== ast.receiver || value !== ast.value) {
return new PropertyWrite(ast.span, receiver, ast.name, value);
}
return ast;
}
visitSafePropertyRead(ast: SafePropertyRead, context: any): AST {
const receiver = ast.receiver.visit(this);
if (receiver !== ast.receiver) {
return new SafePropertyRead(ast.span, receiver, ast.name);
}
return ast;
}
visitMethodCall(ast: MethodCall, context: any): AST {
const receiver = ast.receiver.visit(this);
if (receiver !== ast.receiver) {
return new MethodCall(ast.span, receiver, ast.name, this.visitAll(ast.args));
}
return ast;
}
visitSafeMethodCall(ast: SafeMethodCall, context: any): AST {
const receiver = ast.receiver.visit(this);
const args = this.visitAll(ast.args);
if (receiver !== ast.receiver || args !== ast.args) {
return new SafeMethodCall(ast.span, receiver, ast.name, args);
}
return ast;
}
visitFunctionCall(ast: FunctionCall, context: any): AST {
const target = ast.target && ast.target.visit(this);
const args = this.visitAll(ast.args);
if (target !== ast.target || args !== ast.args) {
return new FunctionCall(ast.span, target, args);
}
return ast;
}
visitLiteralArray(ast: LiteralArray, context: any): AST {
const expressions = this.visitAll(ast.expressions);
if (expressions !== ast.expressions) {
return new LiteralArray(ast.span, expressions);
}
return ast;
}
visitLiteralMap(ast: LiteralMap, context: any): AST {
const values = this.visitAll(ast.values);
if (values !== ast.values) {
return new LiteralMap(ast.span, ast.keys, values);
}
return ast;
}
visitBinary(ast: Binary, context: any): AST {
const left = ast.left.visit(this);
const right = ast.right.visit(this);
if (left !== ast.left || right !== ast.right) {
return new Binary(ast.span, ast.operation, left, right);
}
return ast;
}
visitPrefixNot(ast: PrefixNot, context: any): AST {
const expression = ast.expression.visit(this);
if (expression !== ast.expression) {
return new PrefixNot(ast.span, expression);
}
return ast;
}
visitNonNullAssert(ast: NonNullAssert, context: any): AST {
const expression = ast.expression.visit(this);
if (expression !== ast.expression) {
return new NonNullAssert(ast.span, expression);
}
return ast;
}
visitConditional(ast: Conditional, context: any): AST {
const condition = ast.condition.visit(this);
const trueExp = ast.trueExp.visit(this);
const falseExp = ast.falseExp.visit(this);
if (condition !== ast.condition || trueExp !== ast.trueExp || falseExp !== falseExp) {
return new Conditional(ast.span, condition, trueExp, falseExp);
}
return ast;
}
visitPipe(ast: BindingPipe, context: any): AST {
const exp = ast.exp.visit(this);
const args = this.visitAll(ast.args);
if (exp !== ast.exp || args !== ast.args) {
return new BindingPipe(ast.span, exp, ast.name, args);
}
return ast;
}
visitKeyedRead(ast: KeyedRead, context: any): AST {
const obj = ast.obj.visit(this);
const key = ast.key.visit(this);
if (obj !== ast.obj || key !== ast.key) {
return new KeyedRead(ast.span, obj, key);
}
return ast;
}
visitKeyedWrite(ast: KeyedWrite, context: any): AST {
const obj = ast.obj.visit(this);
const key = ast.key.visit(this);
const value = ast.value.visit(this);
if (obj !== ast.obj || key !== ast.key || value !== ast.value) {
return new KeyedWrite(ast.span, obj, key, value);
}
return ast;
}
visitAll(asts: any[]): any[] {
const res = new Array(asts.length);
let modified = false;
for (let i = 0; i < asts.length; ++i) {
const original = asts[i];
const value = original.visit(this);
res[i] = value;
modified = modified || value !== original;
}
return modified ? res : asts;
}
visitChain(ast: Chain, context: any): AST {
const expressions = this.visitAll(ast.expressions);
if (expressions !== ast.expressions) {
return new Chain(ast.span, expressions);
}
return ast;
}
visitQuote(ast: Quote, context: any): AST { return ast; }
}
export function visitAstChildren(ast: AST, visitor: AstVisitor, context?: any) {
function visit(ast: AST) {
visitor.visit && visitor.visit(ast, context) || ast.visit(visitor, context);

View File

@ -48,12 +48,14 @@ export class InjectableCompiler {
} else if (v.ngMetadataName === 'Self') {
flags |= InjectFlags.Self;
} else if (v.ngMetadataName === 'Inject') {
throw new Error('@Inject() is not implemented');
token = v.token;
} else {
token = v;
}
}
}
}
if (flags !== InjectFlags.Default || defaultValue !== undefined) {
args = [ctx.importExpr(token), o.literal(defaultValue), o.literal(flags)];
} else {
args = [ctx.importExpr(token)];

View File

@ -21,6 +21,7 @@ export class Identifiers {
/* Methods */
static NEW_METHOD = 'n';
static HOST_BINDING_METHOD = 'h';
static TRANSFORM_METHOD = 'transform';
/* Instructions */
static createElement: o.ExternalReference = {name: 'ɵE', moduleName: CORE};
@ -53,17 +54,25 @@ export class Identifiers {
static bind: o.ExternalReference = {name: 'ɵb', moduleName: CORE};
static bind1: o.ExternalReference = {name: b1', moduleName: CORE};
static bind2: o.ExternalReference = {name: b2', moduleName: CORE};
static bind3: o.ExternalReference = {name: b3', moduleName: CORE};
static bind4: o.ExternalReference = {name: b4', moduleName: CORE};
static bind5: o.ExternalReference = {name: b5', moduleName: CORE};
static bind6: o.ExternalReference = {name: b6', moduleName: CORE};
static bind7: o.ExternalReference = {name: b7', moduleName: CORE};
static bind8: o.ExternalReference = {name: b8', moduleName: CORE};
static bindV: o.ExternalReference = {name: bV', moduleName: CORE};
static interpolation1: o.ExternalReference = {name: i1', moduleName: CORE};
static interpolation2: o.ExternalReference = {name: i2', moduleName: CORE};
static interpolation3: o.ExternalReference = {name: i3', moduleName: CORE};
static interpolation4: o.ExternalReference = {name: i4', moduleName: CORE};
static interpolation5: o.ExternalReference = {name: i5', moduleName: CORE};
static interpolation6: o.ExternalReference = {name: i6', moduleName: CORE};
static interpolation7: o.ExternalReference = {name: i7', moduleName: CORE};
static interpolation8: o.ExternalReference = {name: i8', moduleName: CORE};
static interpolationV: o.ExternalReference = {name: iV', moduleName: CORE};
static memory: o.ExternalReference = {name: m', moduleName: CORE};
static pipeBind1: o.ExternalReference = {name: pb1', moduleName: CORE};
static pipeBind2: o.ExternalReference = {name: 'ɵpb2', moduleName: CORE};
static pipeBind3: o.ExternalReference = {name: 'ɵpb3', moduleName: CORE};
static pipeBind4: o.ExternalReference = {name: 'ɵpb4', moduleName: CORE};
static pipeBindV: o.ExternalReference = {name: 'ɵpbV', moduleName: CORE};
static load: o.ExternalReference = {name: 'ɵld', moduleName: CORE};
static pipe: o.ExternalReference = {name: 'ɵPp', moduleName: CORE};
static projection: o.ExternalReference = {name: 'ɵP', moduleName: CORE};
static projectionDef: o.ExternalReference = {name: 'ɵpD', moduleName: CORE};
@ -88,5 +97,7 @@ export class Identifiers {
moduleName: CORE,
};
static definePipe: o.ExternalReference = {name: 'ɵdefinePipe', moduleName: CORE};
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE};
}

View File

@ -0,0 +1,50 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {CompileDirectiveMetadata, CompilePipeMetadata, identifierName} from '../compile_metadata';
import {CompileReflector} from '../compile_reflector';
import {DefinitionKind} from '../constant_pool';
import * as o from '../output/output_ast';
import {OutputContext, error} from '../util';
import {Identifiers as R3} from './r3_identifiers';
import {createFactory} from './r3_view_compiler';
export function compilePipe(
outputCtx: OutputContext, pipe: CompilePipeMetadata, reflector: CompileReflector) {
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
// e.g. 'type: MyPipe`
definitionMapValues.push(
{key: 'type', value: outputCtx.importExpr(pipe.type.reference), quoted: false});
// e.g. factory: function MyPipe_Factory() { return new MyPipe(); },
const templateFactory = createFactory(pipe.type, outputCtx, reflector);
definitionMapValues.push({key: 'factory', value: templateFactory, quoted: false});
// e.g. pure: true
if (pipe.pure) {
definitionMapValues.push({key: 'pure', value: o.literal(true), quoted: false});
}
const className = identifierName(pipe.type) !;
className || error(`Cannot resolve the name of ${pipe.type}`);
outputCtx.statements.push(new o.ClassStmt(
/* name */ className,
/* parent */ null,
/* fields */[new o.ClassField(
/* name */ outputCtx.constantPool.propertyNameOf(DefinitionKind.Pipe),
/* type */ o.INFERRED_TYPE,
/* modifiers */[o.StmtModifier.Static],
/* initializer */ o.importExpr(R3.definePipe).callFn([o.literalMap(
definitionMapValues)]))],
/* getters */[],
/* constructorMethod */ new o.ClassMethod(null, [], []),
/* methods */[]));
}

View File

@ -8,9 +8,9 @@
import {CompileDirectiveMetadata, CompilePipeSummary, CompileTokenMetadata, CompileTypeMetadata, flatten, identifierName, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata';
import {CompileReflector} from '../compile_reflector';
import {BindingForm, BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
import {BindingForm, BuiltinConverter, ConvertPropertyBindingResult, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
import {ConstantPool, DefinitionKind} from '../constant_pool';
import {AST} from '../expression_parser/ast';
import {AST, AstMemoryEfficientTransformer, AstTransformer, BindingPipe, FunctionCall, ImplicitReceiver, LiteralPrimitive, MethodCall, ParseSpan, PropertyRead} from '../expression_parser/ast';
import {Identifiers} from '../identifiers';
import {LifecycleHooks} from '../lifecycle_reflector';
import * as o from '../output/output_ast';
@ -21,6 +21,7 @@ import {OutputContext, error} from '../util';
import {Identifiers as R3} from './r3_identifiers';
/** Name of the context parameter passed into a template function */
const CONTEXT_NAME = 'ctx';
@ -62,7 +63,7 @@ export function compileDirective(
/* name */ className,
/* parent */ null,
/* fields */[new o.ClassField(
/* name */ 'ngDirectiveDef',
/* name */ outputCtx.constantPool.propertyNameOf(DefinitionKind.Directive),
/* type */ o.INFERRED_TYPE,
/* modifiers */[o.StmtModifier.Static],
/* initializer */ o.importExpr(R3.defineDirective).callFn([o.literalMap(
@ -73,8 +74,8 @@ export function compileDirective(
}
export function compileComponent(
outputCtx: OutputContext, component: CompileDirectiveMetadata, template: TemplateAst[],
reflector: CompileReflector) {
outputCtx: OutputContext, component: CompileDirectiveMetadata, pipes: CompilePipeSummary[],
template: TemplateAst[], reflector: CompileReflector) {
const definitionMapValues: {key: string, quoted: boolean, value: o.Expression}[] = [];
// e.g. `type: MyApp`
@ -112,10 +113,11 @@ export function compileComponent(
// e.g. `template: function MyComponent_Template(_ctx, _cm) {...}`
const templateTypeName = component.type.reference.name;
const templateName = templateTypeName ? `${templateTypeName}_Template` : null;
const pipeMap = new Map(pipes.map<[string, CompilePipeSummary]>(pipe => [pipe.name, pipe]));
const templateFunctionExpression =
new TemplateDefinitionBuilder(
outputCtx, outputCtx.constantPool, reflector, CONTEXT_NAME, ROOT_SCOPE.nestedScope(), 0,
component.template !.ngContentSelectors, templateTypeName, templateName)
component.template !.ngContentSelectors, templateTypeName, templateName, pipeMap)
.buildTemplateFunction(template, []);
definitionMapValues.push({key: 'template', value: templateFunctionExpression, quoted: false});
@ -143,7 +145,7 @@ export function compileComponent(
/* name */ className,
/* parent */ null,
/* fields */[new o.ClassField(
/* name */ 'ngComponentDef',
/* name */ outputCtx.constantPool.propertyNameOf(DefinitionKind.Component),
/* type */ o.INFERRED_TYPE,
/* modifiers */[o.StmtModifier.Static],
/* initializer */ o.importExpr(R3.defineComponent).callFn([o.literalMap(
@ -178,25 +180,41 @@ function interpolate(args: o.Expression[]): o.Expression {
args = args.slice(1); // Ignore the length prefix added for render2
switch (args.length) {
case 3:
return o.importExpr(R3.bind1).callFn(args);
return o.importExpr(R3.interpolation1).callFn(args);
case 5:
return o.importExpr(R3.bind2).callFn(args);
return o.importExpr(R3.interpolation2).callFn(args);
case 7:
return o.importExpr(R3.bind3).callFn(args);
return o.importExpr(R3.interpolation3).callFn(args);
case 9:
return o.importExpr(R3.bind4).callFn(args);
return o.importExpr(R3.interpolation4).callFn(args);
case 11:
return o.importExpr(R3.bind5).callFn(args);
return o.importExpr(R3.interpolation5).callFn(args);
case 13:
return o.importExpr(R3.bind6).callFn(args);
return o.importExpr(R3.interpolation6).callFn(args);
case 15:
return o.importExpr(R3.bind7).callFn(args);
return o.importExpr(R3.interpolation7).callFn(args);
case 17:
return o.importExpr(R3.bind8).callFn(args);
return o.importExpr(R3.interpolation8).callFn(args);
}
(args.length >= 19 && args.length % 2 == 1) ||
error(`Invalid interpolation argument length ${args.length}`);
return o.importExpr(R3.bindV).callFn([o.literalArr(args)]);
return o.importExpr(R3.interpolationV).callFn([o.literalArr(args)]);
}
function pipeBinding(args: o.Expression[]): o.ExternalReference {
switch (args.length) {
case 0:
// The first parameter to pipeBind is always the value to be transformed followed
// by arg.length arguments so the total number of arguments to pipeBind are
// arg.length + 1.
return R3.pipeBind1;
case 1:
return R3.pipeBind2;
case 2:
return R3.pipeBind3;
default:
return R3.pipeBindV;
}
}
class BindingScope {
@ -219,10 +237,10 @@ class BindingScope {
return null;
}
set(name: string, variableName: string): BindingScope {
set(name: string, value: o.Expression): BindingScope {
!this.map.has(name) ||
error(`The name ${name} is already defined in scope to be ${this.map.get(name)}`);
this.map.set(name, o.variable(variableName));
this.map.set(name, value);
return this;
}
@ -236,7 +254,7 @@ class BindingScope {
}
}
const ROOT_SCOPE = new BindingScope(null).set('$event', '$event');
const ROOT_SCOPE = new BindingScope(null).set('$event', o.variable('$event'));
class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
private _dataIndex = 0;
@ -251,6 +269,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
private _postfix: o.Statement[] = [];
private _contentProjections: Map<NgContentAst, NgContentInfo>;
private _projectionDefinitionIndex = 0;
private _pipeConverter: PipeConverter;
private unsupported = unsupported;
private invalid = invalid;
@ -258,7 +277,23 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
private outputCtx: OutputContext, private constantPool: ConstantPool,
private reflector: CompileReflector, private contextParameter: string,
private bindingScope: BindingScope, private level = 0, private ngContentSelectors: string[],
private contextName: string|null, private templateName: string|null) {}
private contextName: string|null, private templateName: string|null,
private pipes: Map<string, CompilePipeSummary>) {
this._pipeConverter =
new PipeConverter(() => this.allocateDataSlot(), (name, localName, slot, value) => {
bindingScope.set(localName, value);
const pipe = pipes.get(name) !;
pipe || error(`Could not find pipe ${name}`);
const pipeDefinition = constantPool.getDefinition(
pipe.type.reference, DefinitionKind.Pipe, outputCtx, /* forceShared */ true);
this._creationMode.push(
o.importExpr(R3.pipe)
.callFn([
o.literal(slot), pipeDefinition, pipeDefinition.callMethod(R3.NEW_METHOD, [])
])
.toStmt());
});
}
buildTemplateFunction(asts: TemplateAst[], variables: VariableAst[]): o.FunctionExpr {
// Create variable bindings
@ -272,7 +307,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
]);
// Add the reference to the local scope.
this.bindingScope.set(variableName, scopedName);
this.bindingScope.set(variableName, o.variable(scopedName));
// Declare the local variable in binding mode
this._bindingMode.push(declaration);
@ -332,8 +367,10 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
o.INFERRED_TYPE, null, this.templateName);
}
// LocalResolver
getLocal(name: string): o.Expression|null { return this.bindingScope.get(name); }
// TemplateAstVisitor
visitNgContent(ast: NgContentAst) {
const info = this._contentProjections.get(ast) !;
info || error(`Expected ${ast.sourceSpan} to be included in content projection collection`);
@ -361,6 +398,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
};
}
// TemplateAstVisitor
visitElement(ast: ElementAst) {
let bindingCount = 0;
const elementIndex = this.allocateDataSlot();
@ -408,9 +446,9 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
// Generate the update temporary.
const variableName = this.bindingScope.freshReferenceName();
this._bindingMode.push(o.variable(variableName, o.INFERRED_TYPE)
.set(o.importExpr(R3.memory).callFn([o.literal(slot)]))
.set(o.importExpr(R3.load).callFn([o.literal(slot)]))
.toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
this.bindingScope.set(reference.name, variableName);
this.bindingScope.set(reference.name, o.variable(variableName));
return [reference.name, reference.originalValue];
})).map(value => o.literal(value));
parameters.push(
@ -434,18 +472,14 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
if (input.isAnimation) {
this.unsupported('animations');
}
// TODO(chuckj): Built-in transform?
const convertedBinding = convertPropertyBinding(
this, implicit, input.value, this.bindingContext(), BindingForm.TrySimple, interpolate);
this._bindingMode.push(...convertedBinding.stmts);
const parameters =
[o.literal(elementIndex), o.literal(input.name), convertedBinding.currValExpr];
const convertedBinding = this.convertPropertyBinding(implicit, input.value);
const parameters = [o.literal(elementIndex), o.literal(input.name), convertedBinding];
const instruction = BINDING_INSTRUCTION_MAP[input.type];
if (instruction) {
// TODO(chuckj): runtime: security context?
this.instruction(
this._bindingMode, input.sourceSpan, instruction, o.literal(elementIndex),
o.literal(input.name), convertedBinding.currValExpr);
o.literal(input.name), convertedBinding);
} else {
this.unsupported(`binding ${PropertyBindingType[input.type]}`);
}
@ -479,13 +513,10 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
// Bindings
for (const input of directive.inputs) {
const convertedBinding = convertPropertyBinding(
this, implicit, input.value, this.bindingContext(), BindingForm.TrySimple, interpolate);
this._bindingMode.push(...convertedBinding.stmts);
const convertedBinding = this.convertPropertyBinding(implicit, input.value);
this.instruction(
this._bindingMode, directive.sourceSpan, R3.elementProperty, o.literal(nodeIndex),
o.literal(input.templateName),
o.importExpr(R3.bind).callFn([convertedBinding.currValExpr]));
o.literal(input.templateName), o.importExpr(R3.bind).callFn([convertedBinding]));
}
// e.g. MyDirective.ngDirectiveDef.h(0, 0);
@ -501,6 +532,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
}
}
// TemplateAstVisitor
visitEmbeddedTemplate(ast: EmbeddedTemplateAst) {
const templateIndex = this.allocateDataSlot();
@ -539,7 +571,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
const templateVisitor = new TemplateDefinitionBuilder(
this.outputCtx, this.constantPool, this.reflector, templateContext,
this.bindingScope.nestedScope(), this.level + 1, this.ngContentSelectors, contextName,
templateName);
templateName, this.pipes);
const templateFunctionExpr = templateVisitor.buildTemplateFunction(ast.children, ast.variables);
this._postfix.push(templateFunctionExpr.toDeclStmt(templateName, null));
}
@ -551,6 +583,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
readonly visitElementProperty = invalid;
readonly visitAttr = invalid;
// TemplateAstVisitor
visitBoundText(ast: BoundTextAst) {
const nodeIndex = this.allocateDataSlot();
@ -563,6 +596,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
this.bind(o.variable(CONTEXT_NAME), ast.value, ast.sourceSpan));
}
// TemplateAstVisitor
visitText(ast: TextAst) {
// Text is defined in creation mode only.
this.instruction(
@ -600,8 +634,10 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
}
private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression {
const pipesConvertedValue = value.visit(this._pipeConverter);
const convertedPropertyBinding = convertPropertyBinding(
this, implicit, value, this.bindingContext(), BindingForm.TrySimple, interpolate);
this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple,
interpolate);
this._refreshMode.push(...convertedPropertyBinding.stmts);
return convertedPropertyBinding.currValExpr;
}
@ -611,7 +647,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
}
}
function createFactory(
export function createFactory(
type: CompileTypeMetadata, outputCtx: OutputContext,
reflector: CompileReflector): o.FunctionExpr {
let args: o.Expression[] = [];
@ -652,6 +688,35 @@ function createFactory(
o.INFERRED_TYPE, null, type.reference.name ? `${type.reference.name}_Factory` : null);
}
class PipeConverter extends AstMemoryEfficientTransformer {
private pipeSlots = new Map<string, number>();
constructor(
private allocateSlot: () => number,
private definePipe:
(name: string, localName: string, slot: number, value: o.Expression) => void) {
super();
}
// AstMemoryEfficientTransformer
visitPipe(ast: BindingPipe, context: any): AST {
// Allocate a slot to create the pipe
let slot = this.pipeSlots.get(ast.name);
if (slot == null) {
slot = this.allocateSlot();
this.pipeSlots.set(ast.name, slot);
}
const slotPseudoLocal = `PIPE:${slot}`;
const target = new PropertyRead(ast.span, new ImplicitReceiver(ast.span), slotPseudoLocal);
const bindingId = pipeBinding(ast.args);
this.definePipe(ast.name, slotPseudoLocal, slot, o.importExpr(bindingId));
const value = ast.exp.visit(this);
const args = this.visitAll(ast.args);
return new FunctionCall(
ast.span, target, [new LiteralPrimitive(ast.span, slot), value, ...args]);
}
}
function invalid<T>(arg: o.Expression | o.Statement | TemplateAst): never {
throw new Error(
`Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`);

View File

@ -7,7 +7,7 @@
*/
import {MissingTranslationStrategy} from '@angular/core';
import {CompilerConfig} from '../src/config';
import {CompilerConfig, preserveWhitespacesDefault} from '../src/config';
{
describe('compiler config', () => {
@ -16,4 +16,13 @@ import {CompilerConfig} from '../src/config';
expect(config.missingTranslation).toEqual(MissingTranslationStrategy.Error);
});
});
describe('preserveWhitespacesDefault', () => {
it('should return the default `false` setting when no preserveWhitespacesOption are provided',
() => { expect(preserveWhitespacesDefault(null)).toEqual(false); });
it('should return the preserveWhitespacesOption when provided as a parameter', () => {
expect(preserveWhitespacesDefault(true)).toEqual(true);
expect(preserveWhitespacesDefault(false)).toEqual(false);
});
});
}

View File

@ -6,12 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AotCompilerHost, AotCompilerOptions, AotSummaryResolver, CompileDirectiveMetadata, CompileMetadataResolver, CompilerConfig, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, Lexer, NgModuleResolver, Parser, PipeResolver, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, TypeScriptEmitter, analyzeNgModules, createAotUrlResolver} from '@angular/compiler';
import {AotCompilerHost, AotCompilerOptions, AotSummaryResolver, CompileDirectiveMetadata, CompileMetadataResolver, CompilePipeSummary, CompilerConfig, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, I18NHtmlParser, Lexer, NgModuleResolver, Parser, PipeResolver, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser, TypeScriptEmitter, analyzeNgModules, createAotUrlResolver} from '@angular/compiler';
import {ViewEncapsulation} from '@angular/core';
import * as ts from 'typescript';
import {ConstantPool} from '../../src/constant_pool';
import * as o from '../../src/output/output_ast';
import {compilePipe} from '../../src/render3/r3_pipe_compiler';
import {compileComponent, compileDirective} from '../../src/render3/r3_view_compiler';
import {OutputContext} from '../../src/util';
import {MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, arrayToMockDir, expectNoDiagnostics, settings, setup, toMockFileArray} from '../aot/test_util';
@ -173,7 +174,7 @@ export function compile(
// to generate a template definition.
const analyzedModules = analyzeNgModules(sourceFiles, compilerHost, symbolResolver, resolver);
const directives = Array.from(analyzedModules.ngModuleByPipeOrDirective.keys());
const pipesOrDirectives = Array.from(analyzedModules.ngModuleByPipeOrDirective.keys());
const fakeOutputContext: OutputContext = {
genFilePath: 'fakeFactory.ts',
@ -193,20 +194,20 @@ export function compile(
constantPool: new ConstantPool()
};
// Load All directives
for (const directive of directives) {
const module = analyzedModules.ngModuleByPipeOrDirective.get(directive) !;
// Load all directives and pipes
for (const pipeOrDirective of pipesOrDirectives) {
const module = analyzedModules.ngModuleByPipeOrDirective.get(pipeOrDirective) !;
resolver.loadNgModuleDirectiveAndPipeMetadata(module.type.reference, true);
}
// Compile the directives.
for (const directive of directives) {
const module = analyzedModules.ngModuleByPipeOrDirective.get(directive);
for (const pipeOrDirective of pipesOrDirectives) {
const module = analyzedModules.ngModuleByPipeOrDirective.get(pipeOrDirective);
if (!module || !module.type.reference.filePath.startsWith('/app')) {
continue;
}
if (resolver.isDirective(directive)) {
const metadata = resolver.getDirectiveMetadata(directive);
if (resolver.isDirective(pipeOrDirective)) {
const metadata = resolver.getDirectiveMetadata(pipeOrDirective);
if (metadata.isComponent) {
const fakeUrl = 'ng://fake-template-url.html';
const htmlAst = htmlParser.parse(metadata.template !.template !, fakeUrl);
@ -217,11 +218,16 @@ export function compile(
module.transitiveModule.pipes.map(pipe => resolver.getPipeSummary(pipe.reference));
const parsedTemplate = templateParser.parse(
metadata, htmlAst, directives, pipes, module.schemas, fakeUrl, false);
compileComponent(fakeOutputContext, metadata, parsedTemplate.template, staticReflector);
compileComponent(
fakeOutputContext, metadata, pipes, parsedTemplate.template, staticReflector);
} else {
compileDirective(fakeOutputContext, metadata, staticReflector);
}
} else if (resolver.isPipe(pipeOrDirective)) {
const metadata = resolver.getPipeMetadata(pipeOrDirective);
if (metadata) {
compilePipe(fakeOutputContext, metadata, staticReflector);
}
}
}

View File

@ -0,0 +1,716 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {MockDirectory, setup} from '../aot/test_util';
import {compile, expectEmit} from './mock_compile';
/* These tests are codified version of the tests in compiler_canonical_spec.ts. Every
* test in compiler_canonical_spec.ts should have a corresponding test here.
*/
describe('compiler compliance', () => {
const angularFiles = setup({
compileAngular: true,
compileAnimations: false,
compileCommon: true,
});
describe('elements', () => {
it('should translate DOM structure', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`<div class="my-app" title="Hello">Hello <b>World</b>!</div>\`
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
// The factory should look like this:
const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }';
// The template should look like this (where IDENT is a wild card for an identifier):
const template = `
const $c1$ = ['class', 'my-app', 'title', 'Hello'];
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'div', $c1$);
$r3$.ɵT(1, 'Hello ');
$r3$.ɵE(2, 'b');
$r3$.ɵT(3, 'World');
$r3$.ɵe();
$r3$.ɵT(4, '!');
$r3$.ɵe();
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, factory, 'Incorrect factory');
expectEmit(result.source, template, 'Incorrect template');
});
});
describe('components & directives', () => {
it('should instantiate directives', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule} from '@angular/core';
@Component({selector: 'child', template: 'child-view'})
export class ChildComponent {}
@Directive({selector: '[some-directive]'})
export class SomeDirective {}
@Component({selector: 'my-component', template: '<child some-directive></child>!'})
export class MyComponent {}
@NgModule({declarations: [ChildComponent, SomeDirective, MyComponent]})
export class MyModule{}
`
}
};
// ChildComponent definition should be:
const ChildComponentDefinition = `
static ngComponentDef = $r3$.ɵdefineComponent({
type: ChildComponent,
tag: 'child',
factory: function ChildComponent_Factory() { return new ChildComponent(); },
template: function ChildComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵT(0, 'child-view');
}
}
});`;
// SomeDirective definition should be:
const SomeDirectiveDefinition = `
static ngDirectiveDef = $r3$.ɵdefineDirective({
type: SomeDirective,
factory: function SomeDirective_Factory() {return new SomeDirective(); }
});
`;
// MyComponent definition should be:
const MyComponentDefinition = `
const $c1$ = ['some-directive', ''];
const $c2$ = [SomeDirective];
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, ChildComponent, IDENT, IDENT);
$r3$.ɵe();
$r3$.ɵT(3, '!');
}
ChildComponent.ngComponentDef.h(1, 0);
SomeDirective.ngDirectiveDef.h(2, 0);
$r3$.ɵr(1, 0);
$r3$.ɵr(2, 0);
}
});
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, ChildComponentDefinition, 'Incorrect ChildComponent.ngComponentDef');
expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ngDirectiveDef');
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponentDefinition.ngComponentDef');
});
it('should support structural directives', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
@Directive({selector: '[if]'})
export class IfDirective {
constructor(template: TemplateRef<any>) { }
}
@Component({
selector: 'my-component',
template: '<ul #foo><li *if>{{salutation}} {{foo}}</li></ul>'
})
export class MyComponent {
salutation = 'Hello';
}
@NgModule({declarations: [IfDirective, MyComponent]})
export class MyModule {}
`
}
};
const IfDirectiveDefinition = `
static ngDirectiveDef = $r3$.ɵdefineDirective({
type: IfDirective,
factory: function IfDirective_Factory() { return new IfDirective($r3$.ɵinjectTemplateRef()); }
});`;
const MyComponentDefinition = `
const $c1$ = ['foo', ''];
const $c2$ = [IfDirective];
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'ul', null, null, $c1$);
$r3$.ɵC(2, $c2$, MyComponent_IfDirective_Template_2);
$r3$.ɵe();
}
const $foo$ = $r3$.ɵld(1);
IfDirective.ngDirectiveDef.h(3,2);
$r3$.ɵcR(2);
$r3$.ɵr(3,2);
$r3$.ɵcr();
function MyComponent_IfDirective_Template_2(ctx0: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'li');
$r3$.ɵT(1);
$r3$.ɵe();
}
$r3$.ɵt(1, $r3$.ɵi2('', ctx.salutation, ' ', $foo$, ''));
}
}
});`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, IfDirectiveDefinition, 'Incorrect IfDirective.ngDirectiveDef');
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
});
it('should support content projection', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
@Component({selector: 'simple', template: '<div><ng-content></ng-content></div>'})
export class SimpleComponent {}
@Component({
selector: 'complex',
template: \`
<div id="first"><ng-content select="span[title=toFirst]"></ng-content></div>
<div id="second"><ng-content select="span[title=toSecond]"></ng-content></div>\`
})
export class ComplexComponent { }
@NgModule({declarations: [SimpleComponent, ComplexComponent]})
export class MyModule {}
@Component({
selector: 'my-app',
template: '<simple>content</simple> <complex></complex>'
})
export class MyApp {}
`
}
};
const SimpleComponentDefinition = `
static ngComponentDef = $r3$.ɵdefineComponent({
type: SimpleComponent,
tag: 'simple',
factory: function SimpleComponent_Factory() { return new SimpleComponent(); },
template: function SimpleComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵpD(0);
$r3$.ɵE(1, 'div');
$r3$.ɵP(2, 0);
$r3$.ɵe();
}
}
});`;
const ComplexComponentDefinition = `
const $c1$ = [[[['span', 'title', 'tofirst'], null]], [[['span', 'title', 'tosecond'], null]]];
const $c2$ = ['id','first'];
const $c3$ = ['id','second'];
static ngComponentDef = $r3$.ɵdefineComponent({
type: ComplexComponent,
tag: 'complex',
factory: function ComplexComponent_Factory() { return new ComplexComponent(); },
template: function ComplexComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵpD(0, $c1$);
$r3$.ɵE(1, 'div', $c2$);
$r3$.ɵP(2, 0, 1);
$r3$.ɵe();
$r3$.ɵE(3, 'div', $c3$);
$r3$.ɵP(4, 0, 2);
$r3$.ɵe();
}
}
});
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition');
expectEmit(
result.source, ComplexComponentDefinition, 'Incorrect ComplexComponent definition');
});
describe('pipes', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule, Pipe, PipeTransform, OnDestroy} from '@angular/core';
@Pipe({
name: 'myPipe',
pure: false
})
export class MyPipe implements PipeTransform,
OnDestroy {
transform(value: any, ...args: any[]) { return value; }
ngOnDestroy(): void { }
}
@Pipe({
name: 'myPurePipe',
pure: true,
})
export class MyPurePipe implements PipeTransform {
transform(value: any, ...args: any[]) { return value; }
}
@Component({selector: 'my-app', template: '{{name | myPipe:size | myPurePipe:size }}'})
export class MyApp {
name = 'World';
size = 0;
}
@NgModule({declarations:[MyPipe, MyPurePipe, MyApp]})
export class MyModule {}
`
}
};
it('should render pipes', () => {
const MyPipeDefinition = `
static ngPipeDef = $r3$.ɵdefinePipe(
{type: MyPipe, factory: function MyPipe_Factory() { return new MyPipe(); }});
`;
const MyPurePipeDefinition = `
static ngPipeDef = $r3$.ɵdefinePipe({
type: MyPurePipe,
factory: function MyPurePipe_Factory() { return new MyPurePipe(); },
pure: true
});`;
const MyAppDefinition = `
const $MyPurePipe_ngPipeDef$ = MyPurePipe.ngPipeDef;
const $MyPipe_ngPipeDef$ = MyPipe.ngPipeDef;
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyApp,
tag: 'my-app',
factory: function MyApp_Factory() { return new MyApp(); },
template: function MyApp_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵT(0);
$r3$.ɵPp(1, $MyPurePipe_ngPipeDef$, $MyPurePipe_ngPipeDef$.n());
$r3$.ɵPp(2, $MyPipe_ngPipeDef$, $MyPipe_ngPipeDef$.n());
}
$r3$.ɵt(0, $r3$.ɵi1('', $r3$.ɵpb2(1, $r3$.ɵpb2(2,ctx.name, ctx.size), ctx.size), ''));
}
});`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyPipeDefinition, 'Invalid pipe definition');
expectEmit(source, MyPurePipeDefinition, 'Invalid pure pipe definition');
expectEmit(source, MyAppDefinition, 'Invalid MyApp definition');
});
});
it('local reference', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({selector: 'my-component', template: '<input #user>Hello {{user.value}}!'})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const MyComponentDefinition = `
const $c1$ = ['user', ''];
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'input', null, null, $c1$);
$r3$.ɵe();
$r3$.ɵT(2);
}
const $user$ = $r3$.ɵld(1);
$r3$.ɵt(2, $r3$.ɵi1('Hello ', $user$.value, '!'));
}
});
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
});
describe('lifecycle hooks', () => {
const files = {
app: {
'spec.ts': `
import {Component, Input, NgModule} from '@angular/core';
let events: string[] = [];
@Component({selector: 'lifecycle-comp', template: ''})
export class LifecycleComp {
@Input('name') nameMin: string;
ngOnChanges() { events.push('changes' + this.nameMin); }
ngOnInit() { events.push('init' + this.nameMin); }
ngDoCheck() { events.push('check' + this.nameMin); }
ngAfterContentInit() { events.push('content init' + this.nameMin); }
ngAfterContentChecked() { events.push('content check' + this.nameMin); }
ngAfterViewInit() { events.push('view init' + this.nameMin); }
ngAfterViewChecked() { events.push('view check' + this.nameMin); }
ngOnDestroy() { events.push(this.nameMin); }
}
@Component({
selector: 'simple-layout',
template: \`
<lifecycle-comp [name]="name1"></lifecycle-comp>
<lifecycle-comp [name]="name2"></lifecycle-comp>
\`
})
export class SimpleLayout {
name1 = '1';
name2 = '2';
}
@NgModule({declarations: [LifecycleComp, SimpleLayout]})
export class LifecycleModule {}
`
}
};
it('should gen hooks with a few simple components', () => {
const LifecycleCompDefinition = `
static ngComponentDef = $r3$.ɵdefineComponent({
type: LifecycleComp,
tag: 'lifecycle-comp',
factory: function LifecycleComp_Factory() { return new LifecycleComp(); },
template: function LifecycleComp_Template(ctx: IDENT, cm: IDENT) {},
inputs: {nameMin: 'name'},
features: [$r3$.ɵNgOnChangesFeature(LifecycleComp)]
});`;
const SimpleLayoutDefinition = `
const $c1$ = LifecycleComp.ngComponentDef;
static ngComponentDef = $r3$.ɵdefineComponent({
type: SimpleLayout,
tag: 'simple-layout',
factory: function SimpleLayout_Factory() { return new SimpleLayout(); },
template: function SimpleLayout_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, LifecycleComp);
$r3$.ɵe();
$r3$.ɵE(2, LifecycleComp);
$r3$.ɵe();
}
$r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name1));
$r3$.ɵp(2, 'name', $r3$.ɵb(ctx.name2));
$c1$.h(1, 0);
$c1$.h(3, 2);
$r3$.ɵr(1, 0);
$r3$.ɵr(3, 2);
}
});`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, LifecycleCompDefinition, 'Invalid LifecycleComp definition');
expectEmit(source, SimpleLayoutDefinition, 'Invalid SimpleLayout definition');
});
});
describe('template variables', () => {
const shared = {
shared: {
'for_of.ts': `
import {Directive, Input, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
export interface ForOfContext {
$implicit: any;
index: number;
even: boolean;
odd: boolean;
}
@Directive({selector: '[forOf]'})
export class ForOfDirective {
private previous: any[];
constructor(private view: ViewContainerRef, private template: TemplateRef<any>) {}
@Input() forOf: any[];
ngOnChanges(simpleChanges: SimpleChanges) {
if ('forOf' in simpleChanges) {
this.update();
}
}
ngDoCheck(): void {
const previous = this.previous;
const current = this.forOf;
if (!previous || previous.length != current.length ||
previous.some((value: any, index: number) => current[index] !== previous[index])) {
this.update();
}
}
private update() {
// TODO(chuckj): Not implemented yet
// this.view.clear();
if (this.forOf) {
const current = this.forOf;
for (let i = 0; i < current.length; i++) {
const context = {$implicit: current[i], index: i, even: i % 2 == 0, odd: i % 2 == 1};
// TODO(chuckj): Not implemented yet
// this.view.createEmbeddedView(this.template, context);
}
this.previous = [...this.forOf];
}
}
}
`
}
};
it('should support a let variable and reference', () => {
const files = {
app: {
...shared,
'spec.ts': `
import {Component, NgModule} from '@angular/core';
import {ForOfDirective} from './shared/for_of';
@Component({
selector: 'my-component',
template: \`<ul><li *for="let item of items">{{item.name}}</li></ul>\`
})
export class MyComponent {
items = [{name: 'one'}, {name: 'two'}];
}
@NgModule({
declarations: [MyComponent, ForOfDirective]
})
export class MyModule {}
`
}
};
// TODO(chuckj): Enforce this when the directives are specified
const ForDirectiveDefinition = `
static ngDirectiveDef = $r3$.ɵdefineDirective({
type: ForOfDirective,
factory: function ForOfDirective_Factory() {
return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef());
},
features: [$r3$.ɵNgOnChangesFeature(NgForOf)],
inputs: {forOf: 'forOf'}
});
`;
const MyComponentDefinition = `
const $c1$ = [ForOfDirective];
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'ul');
$r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1);
$r3$.ɵe();
}
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
ForOfDirective.ngDirectiveDef.h(2, 1);
$r3$.ɵcR(1);
$r3$.ɵr(2, 1);
$r3$.ɵcr();
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'li');
$r3$.ɵT(1);
$r3$.ɵe();
}
const $item$ = ctx0.$implicit;
$r3$.ɵt(1, $r3$.ɵi1('', $item$.name, ''));
}
}
});
`;
const result = compile(files, angularFiles);
const source = result.source;
// TODO(chuckj): Enforce this when the directives are specified
// expectEmit(source, ForDirectiveDefinition, 'Invalid directive definition');
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
});
it('should support accessing parent template variables', () => {
const files = {
app: {
...shared,
'spec.ts': `
import {Component, NgModule} from '@angular/core';
import {ForOfDirective} from './shared/for_of';
@Component({
selector: 'my-component',
template: \`
<ul>
<li *for="let item of items">
<div>{{item.name}}</div>
<ul>
<li *for="let info of item.infos">
{{item.name}}: {{info.description}}
</li>
</ul>
</li>
</ul>\`
})
export class MyComponent {
items = [
{name: 'one', infos: [{description: '11'}, {description: '12'}]},
{name: 'two', infos: [{description: '21'}, {description: '22'}]}
];
}
@NgModule({
declarations: [MyComponent, ForOfDirective]
})
export class MyModule {}
`
}
};
const MyComponentDefinition = `
const $c1$ = [ForOfDirective];
const $c2$ = ForOfDirective.ngDirectiveDef;
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'ul');
$r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1);
$r3$.ɵe();
}
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
$c2$.h(2,1);
$r3$.ɵcR(1);
$r3$.ɵr(2, 1);
$r3$.ɵcr();
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'li');
$r3$.ɵE(1, 'div');
$r3$.ɵT(2);
$r3$.ɵe();
$r3$.ɵE(3, 'ul');
$r3$.ɵC(4, $c1$, MyComponent_ForOfDirective_ForOfDirective_Template_4);
$r3$.ɵe();
$r3$.ɵe();
}
const $item$ = ctx0.$implicit;
$r3$.ɵp(4, 'forOf', $r3$.ɵb(IDENT.infos));
$c2$.h(5,4);
$r3$.ɵt(2, $r3$.ɵi1('', IDENT.name, ''));
$r3$.ɵcR(4);
$r3$.ɵr(5, 4);
$r3$.ɵcr();
function MyComponent_ForOfDirective_ForOfDirective_Template_4(
ctx1: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'li');
$r3$.ɵT(1);
$r3$.ɵe();
}
const $info$ = ctx1.$implicit;
$r3$.ɵt(1, $r3$.ɵi2(' ', $item$.name, ': ', $info$.description, ' '));
}
}
}
});`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
});
});
});
});

View File

@ -114,7 +114,7 @@ describe('r3_view_compiler', () => {
}
};
const bV_call = `$r3$bV([' ',ctx.list[0],' ',ctx.list[1],' ',ctx.list[2],' ',ctx.list[3],
const bV_call = `$r3$iV([' ',ctx.list[0],' ',ctx.list[1],' ',ctx.list[2],' ',ctx.list[3],
' ',ctx.list[4],' ',ctx.list[5],' ',ctx.list[6],' ',ctx.list[7],' ',ctx.list[8],
' '])`;
const result = compile(files, angularFiles);
@ -122,626 +122,4 @@ describe('r3_view_compiler', () => {
});
});
/* These tests are codified version of the tests in compiler_canonical_spec.ts. Every
* test in compiler_canonical_spec.ts should have a corresponding test here.
*/
describe('compiler conformance', () => {
describe('elements', () => {
it('should translate DOM structure', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`<div class="my-app" title="Hello">Hello <b>World</b>!</div>\`
})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
// The factory should look like this:
const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }';
// The template should look like this (where IDENT is a wild card for an identifier):
const template = `
const $c1$ = ['class', 'my-app', 'title', 'Hello'];
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'div', $c1$);
$r3$.ɵT(1, 'Hello ');
$r3$.ɵE(2, 'b');
$r3$.ɵT(3, 'World');
$r3$.ɵe();
$r3$.ɵT(4, '!');
$r3$.ɵe();
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, factory, 'Incorrect factory');
expectEmit(result.source, template, 'Incorrect template');
});
});
});
describe('components & directives', () => {
it('should instantiate directives', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule} from '@angular/core';
@Component({selector: 'child', template: 'child-view'})
export class ChildComponent {}
@Directive({selector: '[some-directive]'})
export class SomeDirective {}
@Component({selector: 'my-component', template: '<child some-directive></child>!'})
export class MyComponent {}
@NgModule({declarations: [ChildComponent, SomeDirective, MyComponent]})
export class MyModule{}
`
}
};
// ChildComponent definition should be:
const ChildComponentDefinition = `
static ngComponentDef = $r3$.ɵdefineComponent({
type: ChildComponent,
tag: 'child',
factory: function ChildComponent_Factory() { return new ChildComponent(); },
template: function ChildComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵT(0, 'child-view');
}
}
});`;
// SomeDirective definition should be:
const SomeDirectiveDefinition = `
static ngDirectiveDef = $r3$.ɵdefineDirective({
type: SomeDirective,
factory: function SomeDirective_Factory() {return new SomeDirective(); }
});
`;
// MyComponent definition should be:
const MyComponentDefinition = `
const $c1$ = ['some-directive', ''];
const $c2$ = [SomeDirective];
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, ChildComponent, IDENT, IDENT);
$r3$.ɵe();
$r3$.ɵT(3, '!');
}
ChildComponent.ngComponentDef.h(1, 0);
SomeDirective.ngDirectiveDef.h(2, 0);
$r3$.ɵr(1, 0);
$r3$.ɵr(2, 0);
}
});
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, ChildComponentDefinition, 'Incorrect ChildComponent.ngComponentDef');
expectEmit(source, SomeDirectiveDefinition, 'Incorrect SomeDirective.ngDirectiveDef');
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponentDefinition.ngComponentDef');
});
it('should support structural directives', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
@Directive({selector: '[if]'})
export class IfDirective {
constructor(template: TemplateRef<any>) { }
}
@Component({
selector: 'my-component',
template: '<ul #foo><li *if>{{salutation}} {{foo}}</li></ul>'
})
export class MyComponent {
salutation = 'Hello';
}
@NgModule({declarations: [IfDirective, MyComponent]})
export class MyModule {}
`
}
};
const IfDirectiveDefinition = `
static ngDirectiveDef = $r3$.ɵdefineDirective({
type: IfDirective,
factory: function IfDirective_Factory() { return new IfDirective($r3$.ɵinjectTemplateRef()); }
});`;
const MyComponentDefinition = `
const $c1$ = ['foo', ''];
const $c2$ = [IfDirective];
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'ul', null, null, $c1$);
$r3$.ɵC(2, $c2$, MyComponent_IfDirective_Template_2);
$r3$.ɵe();
}
const $foo$ = $r3$.ɵm(1);
IfDirective.ngDirectiveDef.h(3,2);
$r3$.ɵcR(2);
$r3$.ɵr(3,2);
$r3$.ɵcr();
function MyComponent_IfDirective_Template_2(ctx0: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'li');
$r3$.ɵT(1);
$r3$.ɵe();
}
$r3$.ɵt(1, $r3$.ɵb2('', ctx.salutation, ' ', $foo$, ''));
}
}
});`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, IfDirectiveDefinition, 'Incorrect IfDirective.ngDirectiveDef');
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
});
it('should support content projection', () => {
const files = {
app: {
'spec.ts': `
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
@Component({selector: 'simple', template: '<div><ng-content></ng-content></div>'})
export class SimpleComponent {}
@Component({
selector: 'complex',
template: \`
<div id="first"><ng-content select="span[title=toFirst]"></ng-content></div>
<div id="second"><ng-content select="span[title=toSecond]"></ng-content></div>\`
})
export class ComplexComponent { }
@NgModule({declarations: [SimpleComponent, ComplexComponent]})
export class MyModule {}
@Component({
selector: 'my-app',
template: '<simple>content</simple> <complex></complex>'
})
export class MyApp {}
`
}
};
const SimpleComponentDefinition = `
static ngComponentDef = $r3$.ɵdefineComponent({
type: SimpleComponent,
tag: 'simple',
factory: function SimpleComponent_Factory() { return new SimpleComponent(); },
template: function SimpleComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵpD(0);
$r3$.ɵE(1, 'div');
$r3$.ɵP(2, 0);
$r3$.ɵe();
}
}
});`;
const ComplexComponentDefinition = `
const $c1$ = [[[['span', 'title', 'tofirst'], null]], [[['span', 'title', 'tosecond'], null]]];
const $c2$ = ['id','first'];
const $c3$ = ['id','second'];
static ngComponentDef = $r3$.ɵdefineComponent({
type: ComplexComponent,
tag: 'complex',
factory: function ComplexComponent_Factory() { return new ComplexComponent(); },
template: function ComplexComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵpD(0, $c1$);
$r3$.ɵE(1, 'div', $c2$);
$r3$.ɵP(2, 0, 1);
$r3$.ɵe();
$r3$.ɵE(3, 'div', $c3$);
$r3$.ɵP(4, 0, 2);
$r3$.ɵe();
}
}
});
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(result.source, SimpleComponentDefinition, 'Incorrect SimpleComponent definition');
expectEmit(
result.source, ComplexComponentDefinition, 'Incorrect ComplexComponent definition');
});
it('local reference', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({selector: 'my-component', template: '<input #user>Hello {{user.value}}!'})
export class MyComponent {}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const MyComponentDefinition = `
const $c1$ = ['user', ''];
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'input', null, null, $c1$);
$r3$.ɵe();
$r3$.ɵT(2);
}
const $user$ = $r3$.ɵm(1);
$r3$.ɵt(2, $r3$.ɵb1('Hello ', $user$.value, '!'));
}
});
`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
});
describe('lifecycle hooks', () => {
const files = {
app: {
'spec.ts': `
import {Component, Input, NgModule} from '@angular/core';
let events: string[] = [];
@Component({selector: 'lifecycle-comp', template: ''})
export class LifecycleComp {
@Input('name') nameMin: string;
ngOnChanges() { events.push('changes' + this.nameMin); }
ngOnInit() { events.push('init' + this.nameMin); }
ngDoCheck() { events.push('check' + this.nameMin); }
ngAfterContentInit() { events.push('content init' + this.nameMin); }
ngAfterContentChecked() { events.push('content check' + this.nameMin); }
ngAfterViewInit() { events.push('view init' + this.nameMin); }
ngAfterViewChecked() { events.push('view check' + this.nameMin); }
ngOnDestroy() { events.push(this.nameMin); }
}
@Component({
selector: 'simple-layout',
template: \`
<lifecycle-comp [name]="name1"></lifecycle-comp>
<lifecycle-comp [name]="name2"></lifecycle-comp>
\`
})
export class SimpleLayout {
name1 = '1';
name2 = '2';
}
@NgModule({declarations: [LifecycleComp, SimpleLayout]})
export class LifecycleModule {}
`
}
};
it('should gen hooks with a few simple components', () => {
const LifecycleCompDefinition = `
static ngComponentDef = $r3$.ɵdefineComponent({
type: LifecycleComp,
tag: 'lifecycle-comp',
factory: function LifecycleComp_Factory() { return new LifecycleComp(); },
template: function LifecycleComp_Template(ctx: IDENT, cm: IDENT) {},
inputs: {nameMin: 'name'},
features: [$r3$.ɵNgOnChangesFeature(LifecycleComp)]
});`;
const SimpleLayoutDefinition = `
const $c1$ = LifecycleComp.ngComponentDef;
static ngComponentDef = $r3$.ɵdefineComponent({
type: SimpleLayout,
tag: 'simple-layout',
factory: function SimpleLayout_Factory() { return new SimpleLayout(); },
template: function SimpleLayout_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, LifecycleComp);
$r3$.ɵe();
$r3$.ɵE(2, LifecycleComp);
$r3$.ɵe();
}
$r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name1));
$r3$.ɵp(2, 'name', $r3$.ɵb(ctx.name2));
$c1$.h(1, 0);
$c1$.h(3, 2);
$r3$.ɵr(1, 0);
$r3$.ɵr(3, 2);
}
});`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, LifecycleCompDefinition, 'Invalid LifecycleComp definition');
expectEmit(source, SimpleLayoutDefinition, 'Invalid SimpleLayout definition');
});
});
describe('template variables', () => {
const shared = {
shared: {
'for_of.ts': `
import {Directive, Input, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
export interface ForOfContext {
$implicit: any;
index: number;
even: boolean;
odd: boolean;
}
@Directive({selector: '[forOf]'})
export class ForOfDirective {
private previous: any[];
constructor(private view: ViewContainerRef, private template: TemplateRef<any>) {}
@Input() forOf: any[];
ngOnChanges(simpleChanges: SimpleChanges) {
if ('forOf' in simpleChanges) {
this.update();
}
}
ngDoCheck(): void {
const previous = this.previous;
const current = this.forOf;
if (!previous || previous.length != current.length ||
previous.some((value: any, index: number) => current[index] !== previous[index])) {
this.update();
}
}
private update() {
// TODO(chuckj): Not implemented yet
// this.view.clear();
if (this.forOf) {
const current = this.forOf;
for (let i = 0; i < current.length; i++) {
const context = {$implicit: current[i], index: i, even: i % 2 == 0, odd: i % 2 == 1};
// TODO(chuckj): Not implemented yet
// this.view.createEmbeddedView(this.template, context);
}
this.previous = [...this.forOf];
}
}
}
`
}
};
it('should support a let variable and reference', () => {
const files = {
app: {
...shared,
'spec.ts': `
import {Component, NgModule} from '@angular/core';
import {ForOfDirective} from './shared/for_of';
@Component({
selector: 'my-component',
template: \`<ul><li *for="let item of items">{{item.name}}</li></ul>\`
})
export class MyComponent {
items = [{name: 'one'}, {name: 'two'}];
}
@NgModule({
declarations: [MyComponent, ForOfDirective]
})
export class MyModule {}
`
}
};
// TODO(chuckj): Enforce this when the directives are specified
const ForDirectiveDefinition = `
static ngDirectiveDef = $r3$.ɵdefineDirective({
type: ForOfDirective,
factory: function ForOfDirective_Factory() {
return new ForOfDirective($r3$.ɵinjectViewContainerRef(), $r3$.ɵinjectTemplateRef());
},
features: [$r3$.ɵNgOnChangesFeature(NgForOf)],
inputs: {forOf: 'forOf'}
});
`;
const MyComponentDefinition = `
const $c1$ = [ForOfDirective];
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'ul');
$r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1);
$r3$.ɵe();
}
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
ForOfDirective.ngDirectiveDef.h(2, 1);
$r3$.ɵcR(1);
$r3$.ɵr(2, 1);
$r3$.ɵcr();
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'li');
$r3$.ɵT(1);
$r3$.ɵe();
}
const $item$ = ctx0.$implicit;
$r3$.ɵt(1, $r3$.ɵb1('', $item$.name, ''));
}
}
});
`;
const result = compile(files, angularFiles);
const source = result.source;
// TODO(chuckj): Enforce this when the directives are specified
// expectEmit(source, ForDirectiveDefinition, 'Invalid directive definition');
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
});
it('should support accessing parent template variables', () => {
const files = {
app: {
...shared,
'spec.ts': `
import {Component, NgModule} from '@angular/core';
import {ForOfDirective} from './shared/for_of';
@Component({
selector: 'my-component',
template: \`
<ul>
<li *for="let item of items">
<div>{{item.name}}</div>
<ul>
<li *for="let info of item.infos">
{{item.name}}: {{info.description}}
</li>
</ul>
</li>
</ul>\`
})
export class MyComponent {
items = [
{name: 'one', infos: [{description: '11'}, {description: '12'}]},
{name: 'two', infos: [{description: '21'}, {description: '22'}]}
];
}
@NgModule({
declarations: [MyComponent, ForOfDirective]
})
export class MyModule {}
`
}
};
const MyComponentDefinition = `
const $c1$ = [ForOfDirective];
const $c2$ = ForOfDirective.ngDirectiveDef;
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
tag: 'my-component',
factory: function MyComponent_Factory() { return new MyComponent(); },
template: function MyComponent_Template(ctx: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'ul');
$r3$.ɵC(1, $c1$, MyComponent_ForOfDirective_Template_1);
$r3$.ɵe();
}
$r3$.ɵp(1, 'forOf', $r3$.ɵb(ctx.items));
$c2$.h(2,1);
$r3$.ɵcR(1);
$r3$.ɵr(2, 1);
$r3$.ɵcr();
function MyComponent_ForOfDirective_Template_1(ctx0: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'li');
$r3$.ɵE(1, 'div');
$r3$.ɵT(2);
$r3$.ɵe();
$r3$.ɵE(3, 'ul');
$r3$.ɵC(4, $c1$, MyComponent_ForOfDirective_ForOfDirective_Template_4);
$r3$.ɵe();
$r3$.ɵe();
}
const $item$ = ctx0.$implicit;
$r3$.ɵp(4, 'forOf', $r3$.ɵb(IDENT.infos));
$c2$.h(5,4);
$r3$.ɵt(2, $r3$.ɵb1('', IDENT.name, ''));
$r3$.ɵcR(4);
$r3$.ɵr(5, 4);
$r3$.ɵcr();
function MyComponent_ForOfDirective_ForOfDirective_Template_4(
ctx1: IDENT, cm: IDENT) {
if (cm) {
$r3$.ɵE(0, 'li');
$r3$.ɵT(1);
$r3$.ɵe();
}
const $info$ = ctx1.$implicit;
$r3$.ɵt(1, $r3$.ɵb2(' ', $item$.name, ': ', $info$.description, ' '));
}
}
}
});`;
const result = compile(files, angularFiles);
const source = result.source;
expectEmit(source, MyComponentDefinition, 'Invalid component definition');
});
});
});
});

View File

@ -21,6 +21,7 @@ export {
PublicFeature as ɵPublicFeature,
NgOnChangesFeature as ɵNgOnChangesFeature,
CssSelector as ɵCssSelector,
NC as ɵNC,
C as ɵC,
E as ɵE,
L as ɵL,
@ -29,40 +30,43 @@ export {
Q as ɵQ,
P as ɵP,
b as ɵb,
b1 as ɵb1,
b2 as ɵb2,
b3 as ɵb3,
b4 as ɵb4,
b5 as ɵb5,
b6 as ɵb6,
b7 as ɵb7,
b8 as ɵb8,
bV as ɵbV,
i1 as ɵi1,
i2 as ɵi2,
i3 as ɵi3,
i4 as ɵi4,
i5 as ɵi5,
i6 as ɵi6,
i7 as ɵi7,
i8 as ɵi8,
iV as ɵiV,
pb1 as ɵpb1,
pb2 as ɵpb2,
pb3 as ɵpb3,
pb4 as ɵpb4,
pbV as ɵpbV,
o1 as ɵo1,
o2 as ɵo2,
o3 as ɵo3,
o4 as ɵo4,
o5 as ɵo5,
o6 as ɵo6,
o7 as ɵo7,
o8 as ɵo8,
oV as ɵoV,
f0 as ɵf0,
f1 as ɵf1,
f2 as ɵf2,
f3 as ɵf3,
f4 as ɵf4,
f5 as ɵf5,
f6 as ɵf6,
f7 as ɵf7,
f8 as ɵf8,
fV as ɵfV,
cR as ɵcR,
cr as ɵcr,
qR as ɵqR,
e as ɵe,
p as ɵp,
pD as ɵpD,
a as ɵa,
s as ɵs,
t as ɵt,
v as ɵv,
r as ɵr,
m as ɵm,
st as ɵst,
ld as ɵld,
Pp as ɵPp,
} from './render3/index';
// clang-format on

View File

@ -23,3 +23,4 @@ export {StaticProvider, ValueProvider, ExistingProvider, FactoryProvider, Provid
export {ResolvedReflectiveFactory, ResolvedReflectiveProvider} from './di/reflective_provider';
export {ReflectiveKey} from './di/reflective_key';
export {InjectionToken} from './di/injection_token';
export {APP_ROOT_SCOPE} from './di/scope';

View File

@ -8,11 +8,7 @@
import {Type} from '../type';
import {Injectable, convertInjectableProviderToFactory, defineInjectable} from './injectable';
import {ClassSansProvider, ExistingSansProvider, FactorySansProvider, StaticClassSansProvider, ValueSansProvider} from './provider';
export type InjectionTokenProvider = ValueSansProvider | ExistingSansProvider |
FactorySansProvider | ClassSansProvider | StaticClassSansProvider;
import {Injectable, defineInjectable} from './injectable';
/**
* Creates a token that can be used in a DI Provider.
@ -42,11 +38,11 @@ export class InjectionToken<T> {
readonly ngInjectableDef: Injectable|undefined;
constructor(protected _desc: string, options?: {scope: Type<any>}&InjectionTokenProvider) {
constructor(protected _desc: string, options?: {scope: Type<any>, factory: () => T}) {
if (options !== undefined) {
this.ngInjectableDef = defineInjectable({
scope: options.scope,
factory: convertInjectableProviderToFactory(this as any, options),
factory: options.factory,
});
} else {
this.ngInjectableDef = undefined;

View File

@ -0,0 +1,27 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Type} from '../type';
import {InjectionToken} from './injection_token';
// APP_ROOT_SCOPE is cast as a Type to allow for its usage as the scope parameter of @Injectable().
/**
* A scope which targets the root injector.
*
* When specified as the `scope` parameter to `@Injectable` or `InjectionToken`, this special
* scope indicates the provider for the service or token being configured belongs in the root
* injector. This is loosely equivalent to the convention of having a `forRoot()` static
* function within a module that configures the provider, and expecting users to only import that
* module via its `forRoot()` function in the root injector.
*
* @experimental
*/
export const APP_ROOT_SCOPE: Type<any> = new InjectionToken<boolean>(
'The presence of this token marks an injector as being the root injector.') as any;

View File

@ -693,13 +693,13 @@ export interface Component extends Directive {
* - text nodes are left as-is inside HTML tags where whitespaces are significant (ex. `<pre>`,
* `<textarea>`).
*
* Described transformations can (potentially) influence DOM nodes layout so the
* `preserveWhitespaces` option is `true` be default (no whitespace removal).
* In Angular 5 you need to opt-in for whitespace removal (but we might revisit the default
* setting in Angular 6 or later). If you want to change the default setting for all components
* in your application you can use the `preserveWhitespaces` option of the AOT compiler.
* Described transformations may (potentially) influence DOM nodes layout. However, the impact
* should so be minimal. That's why starting from Angular 6, the
* `preserveWhitespaces` option is `false` by default (whitespace removal).
* If you want to change the default setting for all components in your application you can use
* the `preserveWhitespaces` option of the AOT compiler.
*
* Even if you decide to opt-in for whitespace removal there are ways of preserving whitespaces
* Even with the default behavior of whitespace removal, there are ways of preserving whitespaces
* in certain fragments of a template. You can either exclude entire DOM sub-tree by using the
* `ngPreserveWhitespaces` attribute, ex.:
*

View File

@ -15,9 +15,12 @@ import {GetterFn, MethodFn, SetterFn} from './types';
/**
* Attention: This regex has to hold even if the code is minified!
* Attention: These regex has to hold even if the code is minified!
*/
export const DELEGATE_CTOR = /^function\s+\S+\(\)\s*{[\s\S]+\.apply\(this,\s*arguments\)/;
export const INHERITED_CLASS = /^class\s+[A-Za-z\d$_]*\s*extends\s+[A-Za-z\d$_]+\s*{/;
export const INHERITED_CLASS_WITH_CTOR =
/^class\s+[A-Za-z\d$_]*\s*extends\s+[A-Za-z\d$_]+\s*{[\s\S]*constructor\s*\(/;
export class ReflectionCapabilities implements PlatformReflectionCapabilities {
private _reflect: any;
@ -57,6 +60,7 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
}
private _ownParameters(type: Type<any>, parentCtor: any): any[][]|null {
const typeStr = type.toString();
// If we have no decorators, we only have function.length as metadata.
// In that case, to detect whether a child class declared an own constructor or not,
// we need to look inside of that constructor to check whether it is
@ -64,7 +68,8 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
// This also helps to work around for https://github.com/Microsoft/TypeScript/issues/12439
// that sets 'design:paramtypes' to []
// if a class inherits from another class but has no ctor declared itself.
if (DELEGATE_CTOR.exec(type.toString())) {
if (DELEGATE_CTOR.exec(typeStr) ||
(INHERITED_CLASS.exec(typeStr) && !INHERITED_CLASS_WITH_CTOR.exec(typeStr))) {
return null;
}
@ -252,7 +257,7 @@ function convertTsickleDecoratorIntoMetadata(decoratorInvocations: any[]): any[]
}
function getParentCtor(ctor: Function): Type<any> {
const parentProto = Object.getPrototypeOf(ctor.prototype);
const parentProto = ctor.prototype ? Object.getPrototypeOf(ctor.prototype) : null;
const parentCtor = parentProto ? parentProto.constructor : null;
// Note: We always use `Object` as the null value
// to simplify checking later on.

View File

@ -0,0 +1,53 @@
## General Notes
Each Array costs 70 bytes and is composed of `Array` and `(array)` object
* `Array` javascript visible object: 32 bytes
* `(array)` VM object where the array is actually stored in: 38 bytes
Each Object cost is 24 bytes plus 8 bytes per property.
For small arrays, it is more efficient to store the data as a linked list
of items rather than small arrays. However, the array access is faster as
shown here: https://jsperf.com/small-arrays-vs-linked-objects
## Monomorphic vs Megamorphic code
Great read: [What's up with monomorphism?](http://mrale.ph/blog/2015/01/11/whats-up-with-monomorphism.html)
1) Monomorphic prop access is 100 times faster then megamorphic.
2) Monomorphic call is 4 times faster the megamorphic call.
See benchmark [here](https://jsperf.com/mono-vs-megamorphic-property-access).
## Exporting top level variables
Exporting top level variables should be avoided where possible where performance
and code size matters:
```
// Typescript
export let exported = 0;
let notExported = 0;
notExported = exported;
// Would be compiled to
exports.exported = 0;
var notExported = 0;
notExported = exports.exported;
```
Most minifiers do not rename properties (closure is an exception here).
What could be done instead is:
```
let exported = 0;
export function getExported() { return exported; }
export function setExported(v) { exported = v; }
```
Also writing to a property of `exports` might change its hidden class resulting in megamorphic access.

View File

@ -10,58 +10,48 @@
// about state in an instruction are correct before implementing any logic.
// They are meant only to be called in dev mode as sanity checks.
/**
* Stringifies values such that strings are wrapped in explicit quotation marks and
* other types are stringified normally. Used in error messages (e.g. assertThrow)
* to make it clear that certain values are of the string type when comparing.
*
* e.g. `expected "3" to be 3` is easier to understand than `expected 3 to be 3`.
*
* @param value The value to be stringified
* @returns The stringified value
*/
function stringifyValueForError(value: any): string {
if (value && value.native && value.native.outerHTML) {
return value.native.outerHTML;
export function assertNumber(actual: any, msg: string) {
if (typeof actual != 'number') {
throwError(msg);
}
return typeof value === 'string' ? `"${value}"` : value;
}
export function assertNumber(actual: any, name: string) {
(typeof actual != 'number') && assertThrow(actual, 'number', name, 'typeof ==');
export function assertEqual<T>(actual: T, expected: T, msg: string) {
if (actual != expected) {
throwError(msg);
}
}
export function assertEqual<T>(
actual: T, expected: T, name: string, serializer?: ((v: T) => string)) {
(actual != expected) && assertThrow(actual, expected, name, '==', serializer);
export function assertNotEqual<T>(actual: T, expected: T, msg: string) {
if (actual == expected) {
throwError(msg);
}
}
export function assertLessThan<T>(actual: T, expected: T, name: string) {
(actual >= expected) && assertThrow(actual, expected, name, '<');
export function assertSame<T>(actual: T, expected: T, msg: string) {
if (actual !== expected) {
throwError(msg);
}
}
export function assertNotNull<T>(actual: T, name: string) {
assertNotEqual(actual, null, name);
export function assertLessThan<T>(actual: T, expected: T, msg: string) {
if (actual >= expected) {
throwError(msg);
}
}
export function assertNotEqual<T>(actual: T, expected: T, name: string) {
(actual == expected) && assertThrow(actual, expected, name, '!=');
export function assertNull<T>(actual: T, msg: string) {
if (actual != null) {
throwError(msg);
}
}
/**
* Throws an error with a message constructed from the arguments.
*
* @param actual The actual value (e.g. 3)
* @param expected The expected value (e.g. 5)
* @param name The name of the value being checked (e.g. attrs.length)
* @param operator The comparison operator (e.g. <, >, ==)
* @param serializer Function that maps a value to its display value
*/
export function assertThrow<T>(
actual: T, expected: T, name: string, operator: string,
serializer: ((v: T) => string) = stringifyValueForError): never {
const error =
`ASSERT: expected ${name} ${operator} ${serializer(expected)} but was ${serializer(actual)}!`;
debugger; // leave `debugger` here to aid in debugging.
throw new Error(error);
export function assertNotNull<T>(actual: T, msg: string) {
if (actual == null) {
throwError(msg);
}
}
function throwError(msg: string): never {
throw new Error(`ASSERTION ERROR: ${msg}`);
}

View File

@ -17,6 +17,7 @@ import {NG_HOST_SYMBOL, createError, createLView, createTView, directiveCreate,
import {ComponentDef, ComponentType} from './interfaces/definition';
import {LElementNode} from './interfaces/node';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
import {RootContext} from './interfaces/view';
import {notImplemented, stringify} from './util';
@ -43,6 +44,19 @@ export interface CreateComponentOptions {
* Example: PublicFeature is a function that makes the component public to the DI system.
*/
features?: (<T>(component: T, componentDef: ComponentDef<T>) => void)[];
/**
* A function which is used to schedule change detection work in the future.
*
* When marking components as dirty, it is necessary to schedule the work of
* change detection in the future. This is done to coalesce multiple
* {@link markDirty} calls into a single changed detection processing.
*
* The default value of the scheduler is the `requestAnimationFrame` function.
*
* It is also useful to override this function for testing purposes.
*/
scheduler?: (work: () => void) => void;
}
@ -155,11 +169,22 @@ export const NULL_INJECTOR: Injector = {
}
};
/**
* A permanent marker promise which signifies that the current CD tree is
* clean.
*/
const CLEAN_PROMISE = Promise.resolve(null);
/**
* Bootstraps a Component into an existing host element and returns an instance
* of the component.
*
* Use this function to bootstrap a component into the DOM tree. Each invocation
* of this function will create a separate tree of components, injectors and
* change detection cycles and lifetimes. To dynamically insert a new component
* into an existing tree such that it shares the same injection, change detection
* and object lifetime, use {@link ViewContainer#createComponent}.
*
* @param componentType Component to bootstrap
* @param options Optional parameters which control bootstrapping
*/
@ -170,15 +195,23 @@ export function renderComponent<T>(
if (componentDef.type != componentType) componentDef.type = componentType;
let component: T;
const hostNode = locateHostElement(rendererFactory, opts.host || componentDef.tag);
const rootContext: RootContext = {
// Incomplete initialization due to circular reference.
component: null !,
scheduler: opts.scheduler || requestAnimationFrame,
clean: CLEAN_PROMISE,
};
const oldView = enterView(
createLView(
-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), createTView()),
-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), createTView(),
null, rootContext),
null !);
try {
// Create element node at index 0 in data array
hostElement(hostNode, componentDef);
// Create directive instance with n() and store at index 1 in data array (el is 0)
component = getDirectiveInstance(directiveCreate(1, componentDef.n(), componentDef));
component = rootContext.component =
getDirectiveInstance(directiveCreate(1, componentDef.n(), componentDef));
} finally {
leaveView(oldView);
}
@ -188,27 +221,120 @@ export function renderComponent<T>(
return component;
}
export function detectChanges<T>(component: T) {
ngDevMode && assertNotNull(component, 'component');
const hostNode = (component as any)[NG_HOST_SYMBOL] as LElementNode;
if (ngDevMode && !hostNode) {
createError('Not a directive instance', component);
}
ngDevMode && assertNotNull(hostNode.data, 'hostNode.data');
/**
* Synchronously perform change detection on a component (and possibly its sub-components).
*
* This function triggers change detection in a synchronous way on a component. There should
* be very little reason to call this function directly since a preferred way to do change
* detection is to {@link markDirty} the component and wait for the scheduler to call this method
* at some future point in time. This is because a single user action often results in many
* components being invalidated and calling change detection on each component synchronously
* would be inefficient. It is better to wait until all components are marked as dirty and
* then perform single change detection across all of the components
*
* @param component The component which the change detection should be performed on.
*/
export function detectChanges<T>(component: T): void {
const hostNode = _getComponentHostLElementNode(component);
ngDevMode && assertNotNull(hostNode.data, 'Component host node should be attached to an LView');
renderComponentOrTemplate(hostNode, hostNode.view, component);
isDirty = false;
}
let isDirty = false;
export function markDirty<T>(
component: T, scheduler: (fn: () => void) => void = requestAnimationFrame) {
ngDevMode && assertNotNull(component, 'component');
if (!isDirty) {
isDirty = true;
scheduler(() => detectChanges(component));
/**
* Mark the component as dirty (needing change detection).
*
* Marking a component dirty will schedule a change detection on this
* component at some point in the future. Marking an already dirty
* component as dirty is a noop. Only one outstanding change detection
* can be scheduled per component tree. (Two components bootstrapped with
* separate `renderComponent` will have separate schedulers)
*
* When the root component is bootstrapped with `renderComponent` a scheduler
* can be provided.
*
* @param component Component to mark as dirty.
*/
export function markDirty<T>(component: T) {
const rootContext = getRootContext(component);
if (rootContext.clean == CLEAN_PROMISE) {
let res: null|((val: null) => void);
rootContext.clean = new Promise<null>((r) => res = r);
rootContext.scheduler(() => {
detectChanges(rootContext.component);
res !(null);
rootContext.clean = CLEAN_PROMISE;
});
}
}
export function getHostElement<T>(component: T): RElement {
return ((component as any)[NG_HOST_SYMBOL] as LElementNode).native;
/**
* Retrieve the root component of any component by walking the parent `LView` until
* reaching the root `LView`.
*
* @param component any component
*/
function getRootContext(component: any): RootContext {
ngDevMode && assertNotNull(component, 'component');
const lElementNode = _getComponentHostLElementNode(component);
let lView = lElementNode.view;
while (lView.parent) {
lView = lView.parent;
}
const rootContext = lView.context as RootContext;
ngDevMode && assertNotNull(rootContext, 'rootContext');
return rootContext;
}
function _getComponentHostLElementNode<T>(component: T): LElementNode {
ngDevMode && assertNotNull(component, 'expecting component got null');
const lElementNode = (component as any)[NG_HOST_SYMBOL] as LElementNode;
ngDevMode && assertNotNull(component, 'object is not a component');
return lElementNode;
}
/**
* Retrieve the host element of the component.
*
* Use this function to retrieve the host element of the component. The host
* element is the element which the component is associated with.
*
* @param component Component for which the host element should be retrieved.
*/
export function getHostElement<T>(component: T): HTMLElement {
return _getComponentHostLElementNode(component).native as any;
}
/**
* Retrieves the rendered text for a given component.
*
* This function retrieves the host element of a component and
* and then returns the `textContent` for that element. This implies
* that the text returned will include re-projected content of
* the component as well.
*
* @param component The component to return the content text for.
*/
export function getRenderedText(component: any): string {
const hostElement = getHostElement(component);
return hostElement.textContent || '';
}
/**
* Wait on component until it is rendered.
*
* This function returns a `Promise` which is resolved when the component's
* change detection is executed. This is determined by finding the scheduler
* associated with the `component`'s render tree and waiting until the scheduler
* flushes. If nothing is scheduled, the function returns a resolved promise.
*
* Example:
* ```
* await whenRendered(myComponent);
* ```
*
* @param component Component to wait upon
* @returns Promise which resolves when the component is rendered.
*/
export function whenRendered(component: any): Promise<null> {
return getRootContext(component).clean;
}

View File

@ -42,6 +42,7 @@ export function defineComponent<T>(componentDefinition: ComponentDefArgs<T>): Co
tag: (componentDefinition as ComponentDefArgs<T>).tag || null !,
template: (componentDefinition as ComponentDefArgs<T>).template || null !,
h: componentDefinition.hostBindings || noop,
attributes: componentDefinition.attributes || null,
inputs: invertObject(componentDefinition.inputs),
outputs: invertObject(componentDefinition.outputs),
methods: invertObject(componentDefinition.methods),
@ -177,4 +178,4 @@ export function definePipe<T>(
{type, factory, pure}: {type: Type<T>, factory: () => PipeTransform, pure?: boolean}):
PipeDef<T> {
throw new Error('TODO: implement!');
}
}

View File

@ -6,12 +6,14 @@
* found in the LICENSE file at https://angular.io/license
*/
import {createComponentRef, detectChanges, getHostElement, markDirty, renderComponent} from './component';
import {createComponentRef, detectChanges, getHostElement, getRenderedText, markDirty, renderComponent, whenRendered} from './component';
import {NgOnChangesFeature, PublicFeature, defineComponent, defineDirective, definePipe} from './definition';
import {InjectFlags} from './di';
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType} from './interfaces/definition';
export {InjectFlags, QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, inject, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di';
export {CssSelector} from './interfaces/projection';
// Naming scheme:
// - Capital letters are for creating things: T(Text), E(Element), D(Directive), V(View),
@ -27,15 +29,15 @@ export {
NO_CHANGE as NC,
bind as b,
bind1 as b1,
bind2 as b2,
bind3 as b3,
bind4 as b4,
bind5 as b5,
bind6 as b6,
bind7 as b7,
bind8 as b8,
bindV as bV,
interpolation1 as i1,
interpolation2 as i2,
interpolation3 as i3,
interpolation4 as i4,
interpolation5 as i5,
interpolation6 as i6,
interpolation7 as i7,
interpolation8 as i8,
interpolationV as iV,
componentRefresh as r,
@ -51,7 +53,8 @@ export {
elementStyle as s,
listener as L,
memory as m,
store as st,
load as ld,
projection as P,
projectionDef as pD,
@ -79,16 +82,17 @@ export {
queryRefresh as qR,
} from './query';
export {
objectLiteral1 as o1,
objectLiteral2 as o2,
objectLiteral3 as o3,
objectLiteral4 as o4,
objectLiteral5 as o5,
objectLiteral6 as o6,
objectLiteral7 as o7,
objectLiteral8 as o8,
objectLiteralV as oV,
} from './object_literal';
pureFunction0 as f0,
pureFunction1 as f1,
pureFunction2 as f2,
pureFunction3 as f3,
pureFunction4 as f4,
pureFunction5 as f5,
pureFunction6 as f6,
pureFunction7 as f7,
pureFunction8 as f8,
pureFunctionV as fV,
} from './pure_function';
// clang-format on
@ -105,6 +109,11 @@ export {
defineComponent,
defineDirective,
definePipe,
detectChanges,
createComponentRef,
getHostElement,
getRenderedText,
markDirty,
renderComponent,
whenRendered,
};
export {createComponentRef, detectChanges, getHostElement, markDirty, renderComponent};
export {CssSelector} from './interfaces/projection';

Some files were not shown because too many files have changed in this diff Show More