Compare commits

...

46 Commits

Author SHA1 Message Date
7edc5e96f3 docs: add changelog for 4.1.3 2017-05-17 15:44:25 -07:00
7b802faa96 release: cut the 4.1.3 release 2017-05-17 15:42:14 -07:00
6f039d7a3c build: do not create the bundles when updating the public API
- 1.7x faster on my machine (2.7 vs 4.6 min)
- should solve the memory issue
2017-05-17 15:12:12 -07:00
0a82f7d69f fix(platform-server): wait for async app initializers to complete before removing server side styles (#16712)
This fixes a flicker when transitioning from server rendered page to client rendered page in lazy loaded routes by waiting for the lazy loaded route to finish loading, assuming initialNavigation on the route is set to 'enabled'.

Fixes #15716
2017-05-17 15:12:08 -07:00
b7f85813d5 fix(compiler-cli): import routing module with forRoot (#16438) 2017-05-17 15:10:59 -07:00
a5bdbed306 fix: add typescript 2.3.2 typings test (#16738)
Closes #16663
2017-05-17 15:10:59 -07:00
883ca285de fix(router): Wrap Promise-like instances in native Promises (#16759)
Hybrid apps (mix of Angular and AngularJS) might return AngularJS implementation
of Promises that do not play well with the change detection. Wrapping them in
native Promises fix this issue.

This could be the case when a Resolver returns a `$q` promise.
2017-05-17 15:10:58 -07:00
afb7540067 fix(upgrade): Prevent renaming of $inject property (#16706)
Use bracket notation to access $inject in downgradeInjectable to
support property renaming. Since the return type is any,
Closure compiler renames $inject.
2017-05-17 15:10:58 -07:00
47df3d681f fix(upgrade): use quote to prevent ClossureCompiler obfuscating $event. (#16724)
This is critical for AngularJS to get the $event object in template.
2017-05-17 15:10:57 -07:00
cd7fe31fbc docs: add saved replies (#16726) 2017-05-17 15:10:56 -07:00
4203b1bb1f docs(common): remove h1s from API docs
Only one h1 is allowed per document, and this is provided by the template.
So we cannot have any h1 tags (or `#` markdown shorthand) in any API docs.

Closes #16193
2017-05-17 15:10:56 -07:00
055db5ad6a docs(router): Change CanDeactivate to CanLoad (#16237)
fix mistake in docs. CanDeactivate should be CanLoad
2017-05-17 15:10:55 -07:00
62a8618536 docs: add changelog for 4.1.2 2017-05-10 15:44:28 -07:00
419fe0ca0d release: cut the 4.1.2 release 2017-05-10 15:42:11 -07:00
e46a65f8fd build: update key for pushing to PACKAGE-builds repos (#16667) 2017-05-09 17:34:35 -07:00
f0e1043a1d docs(*) fix dangling links in API docs (#16632)
* docs(animations): fix links to `Component` animations

* docs(core): fix links to `ReflectiveInjector` methods

The `resolve` and other methods were moved from the
`Injector` to the `ReflectiveInjector`.

* docs(core): fix links to `Renderer`

The local links were assuming that that methods were on the
current document (e.g. `RootRenderer`), but they are actually
on the `Renderer` class.

* docs(router): fix links to methods

* docs(forms): fix links to methods

* docs(core): fix links to methods

* docs(router): fix API page links and an internal link
2017-05-09 17:34:35 -07:00
c1938f5af1 docs(core): remove link to non-existent class in InjectableDecorator (#16653)
Closes #16640
2017-05-09 17:34:34 -07:00
915eae50b4 fix(router): fix redirect to a URL with a param having multiple values (#16376)
fixes #16310

PR Close #16376
2017-05-09 17:34:33 -07:00
68675e625a test(router): simplify redirect tests (#16376) 2017-05-09 17:34:32 -07:00
d0e1688514 fix(compiler): avoid a ...null spread in extraction (#16547)
This code only runs in ES5 mode in the test suite, so this is difficult to test.
However `updateFromTemplate` is being called with a spread operator, as
`...updateFromTemplate(...)`. The spread operator should fail on `null` values.
This change avoids the problem by always returning a (possibly empty) array.

PR Close #16547
2017-05-09 17:34:31 -07:00
ee6705af49 fix(core): detach projected views when a parent view is destroyed (#16592)
This also clarifies via a test 
that we only update projected views when the view is created or destroyed,
but not when it is attached/detached/moved.

Fixes #15578

PR Close #16592
2017-05-09 17:34:31 -07:00
9218812011 fix(core): projected views should be dirty checked when the declaring component is dirty checked. (#16592)
Previously a projected view was only dirty checked when the
component in which it was inserted was dirty checked.

This fix changes the behavior so that a view is also dirty checked if
the declaring component is dirty checked.

Note: This does not change the order of change detection,
only the fact whether a projected view is dirty checked or not.

Fixes #14321
2017-05-09 17:34:30 -07:00
ec77bf9fb0 docs(core): EventEmitter docs for isAsync defaults (#15780)
- solves #15758
2017-05-09 17:34:29 -07:00
fee5b2ad56 docs(animations): remove duplicate word (#16508) 2017-05-09 17:34:24 -07:00
9c70a3cfb1 fix(http): flatten metadata for @angular/http/testing
@angular/http/testing used to publish a metadata structure which paralleled
the .d.ts structure. This causes ngc to write incorrect imports for this
bundle when compiling providers using MockBackend and other http testing
classes.

This change restructures the @angular/http/testing build a bit, modeling it
after @angular/platform-browser-animations, and produces a FESM structure
that has flat metadata.

Fixes #15521.
2017-05-09 17:33:59 -07:00
ec3b6e9603 fix(http): introduce encodingHint for text() for better ArrayBuffer support
Currently, if a Response has an ArrayBuffer body and text() is called, Angular
attempts to convert the ArrayBuffer to a string. Doing this requires knowing
the encoding of the bytes in the buffer, which is context that we don't have.

Instead, we assume that the buffer is encoded in UTF-16, and attempt to process
it that way. Unfortunately the approach chosen (interpret buffer as Uint16Array and
create a Javascript string from each entry using String.fromCharCode) is incorrect
as it does not handle UTF-16 surrogate pairs. What Angular actually implements, then,
is UCS-2 decoding, which is equivalent to UTF-16 with characters restricted to the
base plane.

No standard way of decoding UTF-8 or UTF-16 exists in the browser today. APIs like
TextDecoder are only supported in a few browsers, and although hacks like using the
FileReader API with a Blob to force browsers to do content encoding detection and
decoding exist, they're slow and not compatible with the synchronous text() API.

Thus, this bug is fixed by introducing an encodingHint parameter to text(). The
default value of this parameter is 'legacy', indicating that the existing broken
behavior should be used - this prevents breaking existing apps. The only other
possible value of the hint is 'iso-8859' which interprets each byte of the buffer
with String.fromCharCode. UTF-8 and UTF-16 are not supported - it is up to the
consumer to get the ArrayBuffer and decode it themselves.

The parameter is a hint, as it's not always used (for example, if the conversion
to text doesn't involve an ArrayBuffer source). Additionally, this leaves the door
open for future implementations to perform more sophisticated encoding detection
and ignore the user-provided value if it can be proven to be incorrect.

Fixes #15932.

PR Close #16420
2017-05-09 17:33:59 -07:00
63066f7697 fix(http): honor RequestArgs.search and RequestArgs.params map type
Currently `new Request({search: ...})` is not honored, and
`new Request({params: {'x': 'y'}) doesn't work either, as this object would have
toString() called. This change allows both of these cases to work, as proved by
the 2 new tests.

Fixes #15761

PR Close #16392
2017-05-09 17:33:58 -07:00
5f6d0f2340 revert: fix: public API golden files (#16414)
This reverts commit dcaa11a88b.
2017-05-04 15:00:09 -07:00
4a0e93b03b revert: test(compiler-cli): add test for missingTranslation parameter
This reverts commit 109229246c.
2017-05-04 14:51:30 -07:00
1f5dce2128 docs: add changelog for 4.1.1 2017-05-04 14:22:43 -07:00
14e7e43ad8 release: cut the 4.1.1 release 2017-05-04 14:18:24 -07:00
54d4b893fd test(compiler-cli): add test for missingTranslation parameter 2017-05-04 14:05:17 -07:00
4670cf51cc test: cleanup rxjs custom build
The latest rxjs release works with closure compiler out of the box.
We no longer need to compile our own.

Also put closure options into a file rather than using a shell script.
2017-05-04 14:05:16 -07:00
dd4e501999 fix(upgrade): initialize all inputs in time for ngOnChanges()
Previously, non-bracketed inputs (e.g. `xyz="foo"`) on downgraded components
were initialized using `attrs.$observe()` (which uses `$evalAsync()` under the
hood), while bracketed inputs (e.g. `[xyz]="'foo'"`) were initialized using
`$watch()`. If the downgraded component was created during a `$digest` (e.g. by
an `ng-if` watcher), the non-bracketed inputs were not initialized in time for
the initial call to `ngOnChanges()` and `ngOnInit()`.

This commit fixes it by using `$watch()` to initialize all inputs. `$observe()`
is still used for subsequent updates on non-bracketed inputs, because it is more
performant.

Fixes #16212
2017-05-04 14:05:16 -07:00
9124994849 build: update concurrently to latest version
The regression in `concurrently` v3.2.0 (which made us roll back to v3.1.0
in #14378) has been fixed in v3.3.0 (see kimmobrunfeldt/concurrently#89).
2017-05-04 14:05:16 -07:00
4fbc61469f docs: fix links in api docs 2017-05-04 14:05:16 -07:00
8a883f24f6 refactor(compiler): simplify AOT tests 2017-05-04 14:05:15 -07:00
07cef367ac fix(core): don’t stop change detection because of errors
- prevents unsubscribing from the zone on error
- prevents unsubscribing from directive `EventEmitter`s on error
- prevents detaching views in dev mode if there on error
- ensures that `ngOnInit` is only called 1x (also in prod mode)

Fixes #9531
Fixes #2413
Fixes #15925
2017-05-04 14:05:15 -07:00
c060110695 fix(language-service): remove asserts for non-null expressions (#16422)
Reworked some of the code so asserts are no longer necessary.
Added additional and potentially redundant checks
Added checks where the null checker found real problems.

PR Close #16422
2017-05-04 14:05:15 -07:00
93ff3166ab docs(common): fix API docs for NgComponentOutlet (#16411)
fixes #16373

PR Close #16411
2017-05-04 14:05:14 -07:00
85a1b54c6e fix(core): don’t set ng-version for dynamically created components (#16394)
Angular uses the `ng-version` attribute to indicate which elements
were used to bootstrap an application. However, after 4.0 we also
added this attribute for all dynamically created components.

Fixes #15880

PR Close #16394
2017-05-04 14:05:14 -07:00
acf83b90bc fix(core): allow to detach OnPush components (#16394)
Fixes #9720
2017-05-04 14:05:14 -07:00
f66e59ebe4 fix(core): allow directives to inject the component’s ChangeDetectorRef. (#16394)
When a directive lives on the same element as a component
(e.g. `<my-comp myDir>`), the directive was not able to get hold
of the `ChangeDetectorRef` of the component on that element. However,
as directives are supposed to decorate components, this is incorrect.

This commit enables this use case.

Closes #12816
2017-05-04 14:05:14 -07:00
d932e724ab ci(language-service): update ci tests to official 2.3 build (#16415)
PR Close #16415
2017-05-04 14:05:13 -07:00
dcaa11a88b fix: public API golden files (#16414) 2017-05-04 14:05:13 -07:00
427d63a422 fix: strictNullCheck support. (#16389) (#16389)
Fix #16357

Workaround for https://github.com/Microsoft/TypeScript/issues/10078

Closes #16389

PR Close #16389
2017-05-04 14:05:13 -07:00
119 changed files with 1951 additions and 1093 deletions

View File

@ -32,7 +32,7 @@ env:
global:
# GITHUB_TOKEN_ANGULAR=<github token, a personal access token of the angular-builds account, account access in valentine>
# This is needed for the e2e Travis matrix task to publish packages to github for continuous packages delivery.
- secure: "rNqXoy2gqjbF5tBXlRBy+oiYntO3BtzcxZuEtlLMzNaTNzC4dyMOFub0GkzIPWwOzkARoEU9Kv+bC97fDVbCBUKeyzzEqxqddUKhzRxeaYjsefJ6XeTvBvDxwo7wDwyxZSuWdBeGAe4eARVHm7ypsd+AlvqxtzjyS27TK2BzdL4="
- secure: "aCdHveZuY8AT4Jr1JoJB4LxZsnGWRe/KseZh1YXYe5UtufFCtTVHvUcLn0j2aLBF0KpdyS+hWf0i4np9jthKu2xPKriefoPgCMpisYeC0MFkwbmv+XlgkUbgkgVZMGiVyX7DCYXVahxIoOUjVMEDCbNiHTIrfEuyq24U3ok2tHc="
# FIREBASE_TOKEN
# This is needed for publishing builds to the "aio-staging" firebase site.
# TODO(i): the token was generated using the iminar@google account, we should switch to a shared/role-base account.

View File

@ -1,3 +1,50 @@
<a name="4.1.3"></a>
## [4.1.3](https://github.com/angular/angular/compare/4.1.2...4.1.3) (2017-05-17)
### Bug Fixes
* add typescript 2.3.2 typings test ([#16738](https://github.com/angular/angular/issues/16738)) ([a5bdbed](https://github.com/angular/angular/commit/a5bdbed)), closes [#16663](https://github.com/angular/angular/issues/16663)
* **compiler-cli:** import routing module with forRoot ([#16438](https://github.com/angular/angular/issues/16438)) ([b7f8581](https://github.com/angular/angular/commit/b7f8581))
* **platform-server:** wait for async app initializers to complete before removing server side styles ([#16712](https://github.com/angular/angular/issues/16712)) ([0a82f7d](https://github.com/angular/angular/commit/0a82f7d)), closes [#15716](https://github.com/angular/angular/issues/15716)
* **router:** Wrap Promise-like instances in native Promises ([#16759](https://github.com/angular/angular/issues/16759)) ([883ca28](https://github.com/angular/angular/commit/883ca28))
* **upgrade:** Prevent renaming of $inject property ([#16706](https://github.com/angular/angular/issues/16706)) ([afb7540](https://github.com/angular/angular/commit/afb7540))
* **upgrade:** use quote to prevent ClossureCompiler obfuscating $event. ([#16724](https://github.com/angular/angular/issues/16724)) ([47df3d6](https://github.com/angular/angular/commit/47df3d6))
<a name="4.1.2"></a>
## [4.1.2](https://github.com/angular/angular/compare/4.1.1...4.1.2) (2017-05-10)
### Bug Fixes
* **compiler:** avoid a `...null` spread in extraction ([#16547](https://github.com/angular/angular/issues/16547)) ([d0e1688](https://github.com/angular/angular/commit/d0e1688))
* **core:** detach projected views when a parent view is destroyed ([#16592](https://github.com/angular/angular/issues/16592)) ([ee6705a](https://github.com/angular/angular/commit/ee6705a)), closes [#15578](https://github.com/angular/angular/issues/15578)
* **core:** projected views should be dirty checked when the declaring component is dirty checked. ([#16592](https://github.com/angular/angular/issues/16592)) ([9218812](https://github.com/angular/angular/commit/9218812)), closes [#14321](https://github.com/angular/angular/issues/14321)
* **http:** flatten metadata for [@angular](https://github.com/angular)/http/testing ([9c70a3c](https://github.com/angular/angular/commit/9c70a3c)), closes [#15521](https://github.com/angular/angular/issues/15521)
* **http:** honor RequestArgs.search and RequestArgs.params map type ([63066f7](https://github.com/angular/angular/commit/63066f7)), closes [#15761](https://github.com/angular/angular/issues/15761) [#16392](https://github.com/angular/angular/issues/16392)
* **http:** introduce encodingHint for text() for better ArrayBuffer support ([ec3b6e9](https://github.com/angular/angular/commit/ec3b6e9)), closes [#15932](https://github.com/angular/angular/issues/15932) [#16420](https://github.com/angular/angular/issues/16420)
* **router:** fix redirect to a URL with a param having multiple values ([#16376](https://github.com/angular/angular/issues/16376)) ([915eae5](https://github.com/angular/angular/commit/915eae5)), closes [#16310](https://github.com/angular/angular/issues/16310)
<a name="4.1.1"></a>
## [4.1.1](https://github.com/angular/angular/compare/4.1.0...4.1.1) (2017-05-04)
### Bug Fixes
* **core**: strictNullCheck support. ([#16389](https://github.com/angular/angular/issues/16389)) ([#16389](https://github.com/angular/angular/issues/16389)) ([427d63a](https://github.com/angular/angular/commit/427d63a)), closes [#16357](https://github.com/angular/angular/issues/16357)
* **core:** allow directives to inject the components `ChangeDetectorRef`. ([#16394](https://github.com/angular/angular/issues/16394)) ([f66e59e](https://github.com/angular/angular/commit/f66e59e)), closes [#12816](https://github.com/angular/angular/issues/12816)
* **core:** allow to detach `OnPush` components ([#16394](https://github.com/angular/angular/issues/16394)) ([acf83b9](https://github.com/angular/angular/commit/acf83b9)), closes [#9720](https://github.com/angular/angular/issues/9720)
* **core:** dont set `ng-version` for dynamically created components ([#16394](https://github.com/angular/angular/issues/16394)) ([85a1b54](https://github.com/angular/angular/commit/85a1b54)), closes [#15880](https://github.com/angular/angular/issues/15880)
* **core:** dont stop change detection because of errors ([07cef36](https://github.com/angular/angular/commit/07cef36)), closes [#9531](https://github.com/angular/angular/issues/9531) [#2413](https://github.com/angular/angular/issues/2413) [#15925](https://github.com/angular/angular/issues/15925)
* **language-service:** remove asserts for non-null expressions ([#16422](https://github.com/angular/angular/issues/16422)) ([c060110](https://github.com/angular/angular/commit/c060110))
* **upgrade:** initialize all inputs in time for `ngOnChanges()` ([dd4e501](https://github.com/angular/angular/commit/dd4e501)), closes [#16212](https://github.com/angular/angular/issues/16212)
<a name="4.1.0"></a>
# [4.1.0](https://github.com/angular/angular/compare/4.1.0-rc.0...4.1.0) (2017-04-26)

View File

@ -501,7 +501,7 @@ those callbacks like this:
The callbacks receive an `AnimationEvent` that contains contains useful properties such as
The callbacks receive an `AnimationEvent` that contains useful properties such as
`fromState`, `toState` and `totalTime`.
Those callbacks will fire whether or not an animation is picked up.

View File

@ -19,6 +19,12 @@ 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
```
## Angular: Plunker Needed (v1)
```
I'm sorry but reported issues require a plunker 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
```
## Angular: Duplicate (v1)
```
@ -38,6 +44,10 @@ If the problem still persists, please file a new issue and ensure you provide al
I'm sorry but this issue is not caused by Angular. Please contact the author(s) of project <PROJECT NAME> or file issue on their issue tracker.
```
## Angular: Behaving as Expected (v1)
```
It appears this behaves as expected. If you still feel there is an issue, please provide further details in a new issue.
```
## Angular: Non-reproducible (v1)
```

View File

@ -29,9 +29,10 @@ function loadTask(fileName, taskName) {
gulp.task('format:enforce', loadTask('format', 'enforce'));
gulp.task('format', loadTask('format', 'format'));
gulp.task('build.sh', loadTask('build'));
gulp.task('build.sh', loadTask('build', 'all'));
gulp.task('build.sh:no-bundle', loadTask('build', 'no-bundle'));
gulp.task('public-api:enforce', loadTask('public-api', 'enforce'));
gulp.task('public-api:update', ['build.sh'], loadTask('public-api', 'update'));
gulp.task('public-api:update', ['build.sh:no-bundle'], loadTask('public-api', 'update'));
gulp.task('lint', ['format:enforce', 'validate-commit-messages', 'tslint']);
gulp.task('tslint', ['tools:build'], loadTask('lint'));
gulp.task('validate-commit-messages', loadTask('validate-commit-message'));

View File

@ -1,4 +1,3 @@
/rxjs/
built/
dist/
vendor/

View File

@ -32,13 +32,7 @@ Angular's `node_modules` is installed.
## Running integration tests
The first time you run the tests, you'll need some setup:
```shell
$ ./integration/build_rxjs_es6.sh
```
Now you can iterate on the tests by keeping the dist folder up-to-date.
You can iterate on the tests by keeping the dist folder up-to-date.
See the `package.json` of the test(s) you're debugging, to see which dist/ folders they install from.
Then run the right `tsc --watch` command to keep those dist folders up-to-date, for example:

View File

@ -1,19 +0,0 @@
#!/usr/bin/env bash
# This script builds rxjs from source, with an ES6 target, and with tsickle turned on.
# We need to do this until we work with the RxJS team to get a distribution that works
# with Closure Compiler.
# Note that in nodejs, we still run the standard RxJS distribution. This one is only
# used for the bundle that targets a browser runtime.
# TODO(alexeagle): discuss with Jay Phelps once we have a recommendation
set -e -o pipefail
cd `dirname $0`
rm -rf rxjs
git clone https://github.com/ReactiveX/rxjs.git --depth=200
git -C rxjs/ checkout 5.0.3
cp rxjs.tsconfig.json rxjs/
TSC="node --max-old-space-size=3000 ../dist/tools/@angular/tsc-wrapped/src/main"
$TSC -p rxjs/rxjs.tsconfig.json

View File

@ -1,53 +0,0 @@
#!/usr/bin/env bash
# Build a folder using angular ES6 and the closure compiler
set -e -o pipefail
# The ES6 distro we built for rxjs works only in the browser, not in nodejs.
# Since we installed rxjs in node_modules for ngc to use, we have to point
# to the alternate distro when compiling with closure.
rm -rf vendor
mkdir vendor
cp -pr ../rxjs/dist/es6 vendor/rxjs
CLOSURE_ARGS=(
"--language_in=ES6_STRICT"
"--language_out=ES5"
"--compilation_level=ADVANCED_OPTIMIZATIONS"
"--warning_level=QUIET"
"--js_output_file=dist/bundle.js"
"--create_source_map=%outname%.map"
"--variable_renaming_report=dist/variable_renaming_report"
"--property_renaming_report=dist/property_renaming_report"
# Don't include ES6 polyfills
"--rewrite_polyfills=false"
# List of path prefixes to be removed from ES6 & CommonJS modules.
"--js_module_root=node_modules/@angular/core"
"--js_module_root=node_modules/@angular/common"
"--js_module_root=node_modules/@angular/compiler"
"--js_module_root=node_modules/@angular/platform-browser"
"--js_module_root=vendor"
# Uncomment for easier debugging
# "--formatting=PRETTY_PRINT"
node_modules/@angular/core/src/testability/testability.externs.js
node_modules/zone.js/dist/zone.js
$(find -L vendor/rxjs -name *.js)
node_modules/@angular/core/@angular/core.js
node_modules/@angular/common/@angular/common.js
node_modules/@angular/compiler/@angular/compiler.js
node_modules/@angular/platform-browser/@angular/platform-browser.js
"built/src/*.js"
"--entry_point=./built/src/main"
)
java -jar node_modules/google-closure-compiler/compiler.jar $(echo ${CLOSURE_ARGS[*]})
# gzip on Travis doesn't have --keep option so copy the original file first
cp dist/bundle.js dist/bundle.tmp
gzip -f dist/bundle.js
mv dist/bundle.tmp dist/bundle.js
ls -alH dist/bundle*

View File

@ -0,0 +1,30 @@
--compilation_level=ADVANCED_OPTIMIZATIONS
--language_out=ES5
--js_output_file=dist/bundle.js
--output_manifest=dist/manifest.MF
--variable_renaming_report=dist/variable_renaming_report
--property_renaming_report=dist/property_renaming_report
--create_source_map=%outname%.map
--warning_level=QUIET
--dependency_mode=STRICT
--rewrite_polyfills=false
node_modules/zone.js/dist/zone_externs.js
--js node_modules/rxjs/**.js
--process_common_js_modules
--module_resolution=node
node_modules/@angular/core/@angular/core.js
--js_module_root=node_modules/@angular/core
node_modules/@angular/core/src/testability/testability.externs.js
node_modules/@angular/common/@angular/common.js
--js_module_root=node_modules/@angular/common
node_modules/@angular/platform-browser/@angular/platform-browser.js
--js_module_root=node_modules/@angular/platform-browser
--js built/**.js
--entry_point=built/src/main

View File

@ -1,14 +1,15 @@
{
"open": false,
"logLevel": "silent",
"logLevel": "silent",
"port": 8080,
"server": {
"baseDir": "src",
"routes": {
"/dist": "dist"
"/dist": "dist",
"/node_modules": "node_modules"
},
"middleware": {
"0": null
}
}
}
}

View File

@ -11,21 +11,22 @@
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
"@angular/platform-server": "file:../../dist/packages-dist/platform-server",
"@angular/tsc-wrapped": "file:../../dist/tools/@angular/tsc-wrapped",
"google-closure-compiler": "20161201.0.0",
"rxjs": "file:../../node_modules/rxjs",
"google-closure-compiler": "20170409.0.0",
"rxjs": "5.3.1",
"typescript": "2.1.6",
"zone.js": "0.7.6"
"zone.js": "0.8.6"
},
"devDependencies": {
"@types/jasmine": "2.5.41",
"concurrently": "3.1.0",
"concurrently": "3.4.0",
"lite-server": "2.2.2",
"protractor": "file:../../node_modules/protractor"
},
"scripts": {
"test": "ngc && ./bundle.sh && concurrently \"yarn run serve\" \"yarn run protractor\" --kill-others --success first",
"closure": "java -jar node_modules/google-closure-compiler/compiler.jar --flagfile closure.conf",
"test": "ngc && yarn run closure && concurrently \"yarn run serve\" \"yarn run protractor\" --kill-others --success first",
"serve": "lite-server -c e2e/browser.config.json",
"preprotractor": "tsc -p e2e",
"protractor": "protractor e2e/protractor.config.js"
}
}
}

View File

@ -1,14 +1,18 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Hello World</title>
<base href="/">
</head>
<body>
<hello-world-app>Loading...</hello-world-app>
<script src="dist/bundle.js"></script>
<body>
<hello-world-app>Loading...</hello-world-app>
<script src="node_modules/zone.js/dist/zone.min.js"></script>
<script src="dist/bundle.js"></script>
</body>
</html>
</html>

View File

@ -24,7 +24,7 @@
},
"devDependencies": {
"@types/jasmine": "2.5.41",
"concurrently": "3.1.0",
"concurrently": "3.4.0",
"lite-server": "2.2.2",
"protractor": "file:../../node_modules/protractor"
}

View File

@ -10,6 +10,6 @@
"author": "",
"license": "ISC",
"dependencies": {
"typescript": "2.3.0-dev.20170223"
"typescript": "2.3.0"
}
}

View File

@ -2,6 +2,6 @@
# yarn lockfile v1
typescript@2.3.0-dev.20170223:
version "2.3.0-dev.20170223"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.3.0-dev.20170223.tgz#286494c36625ea2eb26f963ed205cd9ca5c41447"
typescript@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.3.0.tgz#2e63e09284392bc8158a2444c33e2093795c0418"

View File

@ -4,12 +4,6 @@ set -e -o pipefail
cd `dirname $0`
if [ ! -d "rxjs/dist/es6" ]; then
echo "You must run build the ES2015 version of RxJS for some tests:"
echo "./integration/build_rxjs_es6.sh"
exit 1
fi
# Workaround https://github.com/yarnpkg/yarn/issues/2165
# Yarn will cache file://dist URIs and not update Angular code
readonly cache=.yarn_local_cache
@ -20,7 +14,7 @@ rm_cache
mkdir $cache
trap rm_cache EXIT
for testDir in $(ls | grep -v rxjs | grep -v node_modules) ; do
for testDir in $(ls | grep -v node_modules) ; do
[[ -d "$testDir" ]] || continue
echo "#################################"
echo "Running integration test $testDir"

View File

@ -1,42 +0,0 @@
{
"angularCompilerOptions": {
"skipMetadataEmit": true,
"annotationsAs": "static fields",
"annotateForClosureCompiler": true
},
"compilerOptions": {
"types": [],
/**
* Remaining options are copied from
* https://github.com/ReactiveX/rxjs/blob/cba74135810a8e6bbe0b3c7732e8544b0869589e/tsconfig.json
* TODO(alexeagle): use "extends" instead when Angular is on TS 2.1
*/
"removeComments": false,
"preserveConstEnums": true,
"sourceMap": true,
"declaration": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"suppressImplicitAnyIndexErrors": true,
"moduleResolution": "node",
"module": "es2015",
"target": "es6",
"outDir": "dist/es6",
"lib": [
"es5",
"es2015.iterable",
"es2015.collection",
"es2015.promise",
"dom"
]
},
"formatCodeOptions": {
"indentSize": 2,
"tabSize": 2
},
"files": [
"src/Rx.ts"
]
}

View File

@ -8,17 +8,18 @@
import * as compiler from '@angular/compiler';
import * as compilerTesting from '@angular/compiler/testing';
import * as coreTesting from '@angular/core';
import * as core from '@angular/core/testing';
import * as httpTesting from '@angular/http';
import * as http from '@angular/http/testing';
import * as platformBrowserTesting from '@angular/platform-browser';
import * as core from '@angular/core';
import * as coreTesting from '@angular/core/testing';
import * as forms from '@angular/forms';
import * as http from '@angular/http';
import * as httpTesting from '@angular/http/testing';
import * as platformBrowserDynamic from '@angular/platform-browser-dynamic';
import * as platformBrowser from '@angular/platform-browser/testing';
import * as platformServerTesting from '@angular/platform-server';
import * as platformServer from '@angular/platform-server/testing';
import * as routerTesting from '@angular/router';
import * as router from '@angular/router/testing';
import * as platformBrowser from '@angular/platform-browser';
import * as platformBrowserTesting from '@angular/platform-browser/testing';
import * as platformServer from '@angular/platform-server';
import * as platformServerTesting from '@angular/platform-server/testing';
import * as router from '@angular/router';
import * as routerTesting from '@angular/router/testing';
import * as upgrade from '@angular/upgrade';
export default {
@ -26,6 +27,7 @@ export default {
compilerTesting,
core,
coreTesting,
forms,
http,
httpTesting,
platformBrowser,

View File

@ -9,6 +9,7 @@
"@angular/compiler": "file:../../dist/packages-dist/compiler",
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
"@angular/core": "file:../../dist/packages-dist/core",
"@angular/forms": "file:../../dist/packages-dist/forms",
"@angular/http": "file:../../dist/packages-dist/http",
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",

View File

@ -9,8 +9,7 @@
"target": "es5",
"lib": ["es5", "dom", "es2015.collection", "es2015.iterable", "es2015.promise"],
"types": [],
// TODO(i): strictNullChecks should turned on but are temporarily disabled due to #15432
"strictNullChecks": false
"strictNullChecks": true
},
"files": [
"include-all.ts",

View File

@ -8,17 +8,18 @@
import * as compiler from '@angular/compiler';
import * as compilerTesting from '@angular/compiler/testing';
import * as coreTesting from '@angular/core';
import * as core from '@angular/core/testing';
import * as httpTesting from '@angular/http';
import * as http from '@angular/http/testing';
import * as platformBrowserTesting from '@angular/platform-browser';
import * as core from '@angular/core';
import * as coreTesting from '@angular/core/testing';
import * as forms from '@angular/forms';
import * as http from '@angular/http';
import * as httpTesting from '@angular/http/testing';
import * as platformBrowserDynamic from '@angular/platform-browser-dynamic';
import * as platformBrowser from '@angular/platform-browser/testing';
import * as platformServerTesting from '@angular/platform-server';
import * as platformServer from '@angular/platform-server/testing';
import * as routerTesting from '@angular/router';
import * as router from '@angular/router/testing';
import * as platformBrowser from '@angular/platform-browser';
import * as platformBrowserTesting from '@angular/platform-browser/testing';
import * as platformServer from '@angular/platform-server';
import * as platformServerTesting from '@angular/platform-server/testing';
import * as router from '@angular/router';
import * as routerTesting from '@angular/router/testing';
import * as upgrade from '@angular/upgrade';
export default {
@ -26,6 +27,7 @@ export default {
compilerTesting,
core,
coreTesting,
forms,
http,
httpTesting,
platformBrowser,

View File

@ -9,6 +9,7 @@
"@angular/compiler": "file:../../dist/packages-dist/compiler",
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
"@angular/core": "file:../../dist/packages-dist/core",
"@angular/forms": "file:../../dist/packages-dist/forms",
"@angular/http": "file:../../dist/packages-dist/http",
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",

View File

@ -15,11 +15,10 @@
"es2015.promise"
],
"types": [],
// TODO(i): strictNullChecks should turned on but are temporarily disabled due to #15432
"strictNullChecks": false
"strictNullChecks": true
},
"files": [
"include-all.ts",
"node_modules/@types/jasmine/index.d.ts"
]
}
}

View File

@ -0,0 +1,41 @@
/**
* @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 * as compiler from '@angular/compiler';
import * as compilerTesting from '@angular/compiler/testing';
import * as core from '@angular/core';
import * as coreTesting from '@angular/core/testing';
import * as forms from '@angular/forms';
import * as http from '@angular/http';
import * as httpTesting from '@angular/http/testing';
import * as platformBrowserDynamic from '@angular/platform-browser-dynamic';
import * as platformBrowser from '@angular/platform-browser';
import * as platformBrowserTesting from '@angular/platform-browser/testing';
import * as platformServer from '@angular/platform-server';
import * as platformServerTesting from '@angular/platform-server/testing';
import * as router from '@angular/router';
import * as routerTesting from '@angular/router/testing';
import * as upgrade from '@angular/upgrade';
export default {
compiler,
compilerTesting,
core,
coreTesting,
forms,
http,
httpTesting,
platformBrowser,
platformBrowserTesting,
platformBrowserDynamic,
platformServer,
platformServerTesting,
router,
routerTesting,
upgrade
};

View File

@ -0,0 +1,28 @@
{
"name": "angular-integration",
"description": "Assert that users with TypeScript 2.2 can type-check an Angular application",
"version": "0.0.0",
"license": "MIT",
"dependencies": {
"@angular/animations": "file:../../dist/packages-dist/animations",
"@angular/common": "file:../../dist/packages-dist/common",
"@angular/compiler": "file:../../dist/packages-dist/compiler",
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
"@angular/core": "file:../../dist/packages-dist/core",
"@angular/forms": "file:../../dist/packages-dist/forms",
"@angular/http": "file:../../dist/packages-dist/http",
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",
"@angular/platform-server": "file:../../dist/packages-dist/platform-server",
"@angular/router": "file:../../dist/packages-dist/router",
"@angular/tsc-wrapped": "file:../../dist/tools/@angular/tsc-wrapped",
"@angular/upgrade": "file:../../dist/packages-dist/upgrade",
"@types/jasmine": "2.5.41",
"rxjs": "file:../../node_modules/rxjs",
"typescript": "2.3.2",
"zone.js": "0.7.6"
},
"scripts": {
"test": "tsc"
}
}

View File

@ -0,0 +1,24 @@
{
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"module": "commonjs",
"moduleResolution": "node",
"outDir": "../../dist/typing-test/",
"rootDir": ".",
"target": "es5",
"lib": [
"es5",
"dom",
"es2015.collection",
"es2015.iterable",
"es2015.promise"
],
"types": [],
"strictNullChecks": true
},
"files": [
"include-all.ts",
"node_modules/@types/jasmine/index.d.ts"
]
}

View File

@ -7,7 +7,7 @@
*/
import {NgIf} from '@angular/common';
import {ComponentFactory, ComponentFactoryResolver, ComponentRef, Injector, NgModuleRef, RendererFactory2, RootRenderer, Sanitizer, TemplateRef, ViewContainerRef} from '@angular/core';
import {ComponentFactory, ComponentFactoryResolver, ComponentRef, ErrorHandler, Injector, NgModuleRef, RendererFactory2, RootRenderer, Sanitizer, TemplateRef, ViewContainerRef} from '@angular/core';
import {ArgumentType, BindingFlags, NodeFlags, ViewDefinition, ViewFlags, anchorDef, createComponentFactory, directiveDef, elementDef, initServicesIfNeeded, textDef, viewDef} from '@angular/core/src/view/index';
import {DomRendererFactory2} from '@angular/platform-browser/src/dom/dom_renderer';
import {DomSanitizerImpl, SafeStyle} from '@angular/platform-browser/src/security/dom_sanitization_service';
@ -108,6 +108,7 @@ export class AppModule implements Injector, NgModuleRef<any> {
case Sanitizer:
return this.sanitizer;
case RootRenderer:
case ErrorHandler:
return null;
case NgModuleRef:
return this;

View File

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

View File

@ -120,7 +120,7 @@ export interface AnimationGroupMetadata extends AnimationMetadata { steps: Anima
/**
* `trigger` is an animation-specific function that is designed to be used inside of Angular's
animation DSL language. If this information is new, please navigate to the {@link
Component#animations-anchor component animations metadata page} to gain a better understanding of
Component#animations component animations metadata page} to gain a better understanding of
how animations in Angular are used.
*
* `trigger` Creates an animation trigger which will a list of {@link state state} and {@link
@ -128,7 +128,7 @@ export interface AnimationGroupMetadata extends AnimationMetadata { steps: Anima
changes.
*
* Triggers are registered within the component annotation data under the {@link
Component#animations-anchor animations section}. An animation trigger can be placed on an element
Component#animations animations section}. An animation trigger can be placed on an element
within a template by referencing the name of the trigger followed by the expression value that the
trigger is bound to (in the form of `[@triggerName]="expression"`.
*
@ -175,7 +175,7 @@ export function trigger(name: string, definitions: AnimationMetadata[]): Animati
/**
* `animate` is an animation-specific function that is designed to be used inside of Angular's
* animation DSL language. If this information is new, please navigate to the {@link
* Component#animations-anchor component animations metadata page} to gain a better understanding of
* Component#animations component animations metadata page} to gain a better understanding of
* how animations in Angular are used.
*
* `animate` specifies an animation step that will apply the provided `styles` data for a given
@ -226,7 +226,7 @@ export function animate(
/**
* `group` is an animation-specific function that is designed to be used inside of Angular's
* animation DSL language. If this information is new, please navigate to the {@link
* Component#animations-anchor component animations metadata page} to gain a better understanding of
* Component#animations component animations metadata page} to gain a better understanding of
* how animations in Angular are used.
*
* `group` specifies a list of animation steps that are all run in parallel. Grouped animations are
@ -261,7 +261,7 @@ export function group(steps: AnimationMetadata[]): AnimationGroupMetadata {
/**
* `sequence` is an animation-specific function that is designed to be used inside of Angular's
* animation DSL language. If this information is new, please navigate to the {@link
* Component#animations-anchor component animations metadata page} to gain a better understanding of
* Component#animations component animations metadata page} to gain a better understanding of
* how animations in Angular are used.
*
* `sequence` Specifies a list of animation steps that are run one by one. (`sequence` is used by
@ -299,7 +299,7 @@ export function sequence(steps: AnimationMetadata[]): AnimationSequenceMetadata
/**
* `style` is an animation-specific function that is designed to be used inside of Angular's
* animation DSL language. If this information is new, please navigate to the {@link
* Component#animations-anchor component animations metadata page} to gain a better understanding of
* Component#animations component animations metadata page} to gain a better understanding of
* how animations in Angular are used.
*
* `style` declares a key/value object containing CSS properties/styles that can then be used for
@ -347,7 +347,7 @@ export function style(
/**
* `state` is an animation-specific function that is designed to be used inside of Angular's
* animation DSL language. If this information is new, please navigate to the {@link
* Component#animations-anchor component animations metadata page} to gain a better understanding of
* Component#animations component animations metadata page} to gain a better understanding of
* how animations in Angular are used.
*
* `state` declares an animation state within the given trigger. When a state is active within a
@ -399,7 +399,7 @@ export function state(name: string, styles: AnimationStyleMetadata): AnimationSt
/**
* `keyframes` is an animation-specific function that is designed to be used inside of Angular's
* animation DSL language. If this information is new, please navigate to the {@link
* Component#animations-anchor component animations metadata page} to gain a better understanding of
* Component#animations component animations metadata page} to gain a better understanding of
* how animations in Angular are used.
*
* `keyframes` specifies a collection of {@link style style} entries each optionally characterized
@ -448,7 +448,7 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe
/**
* `transition` is an animation-specific function that is designed to be used inside of Angular's
* animation DSL language. If this information is new, please navigate to the {@link
* Component#animations-anchor component animations metadata page} to gain a better understanding of
* Component#animations component animations metadata page} to gain a better understanding of
* how animations in Angular are used.
*
* `transition` declares the {@link sequence sequence of animation steps} that will be run when the

View File

@ -22,9 +22,6 @@ import {ComponentFactoryResolver, ComponentRef, Directive, Injector, Input, NgMo
* * `ngComponentOutletInjector`: Optional custom {@link Injector} that will be used as parent for
* the Component. Defaults to the injector of the current view container.
*
* * `ngComponentOutletProviders`: Optional injectable objects ({@link Provider}) that are visible
* to the component.
*
* * `ngComponentOutletContent`: Optional list of projectable nodes to insert into the content
* section of the component, if exists.
*
@ -52,7 +49,7 @@ import {ComponentFactoryResolver, ComponentRef, Directive, Injector, Input, NgMo
* ngModuleFactory: moduleFactory;">
* </ng-container>
* ```
* # Example
* ## Example
*
* {@example common/ngComponentOutlet/ts/module.ts region='SimpleExample'}
*

View File

@ -17,13 +17,13 @@ import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef} from '
* - `then` template is the inline template of `ngIf` unless bound to a different value.
* - `else` template is blank unless it is bound.
*
* # Most common usage
* ## Most common usage
*
* The most common usage of the `ngIf` directive is to conditionally show the inline template as
* seen in this example:
* {@example common/ngIf/ts/module.ts region='NgIfSimple'}
*
* # Showing an alternative template using `else`
* ## Showing an alternative template using `else`
*
* If it is necessary to display a template when the `expression` is falsy use the `else` template
* binding as shown. Note that the `else` binding points to a `<ng-template>` labeled `#elseBlock`.
@ -32,7 +32,7 @@ import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef} from '
*
* {@example common/ngIf/ts/module.ts region='NgIfElse'}
*
* # Using non-inlined `then` template
* ## Using non-inlined `then` template
*
* Usually the `then` template is the inlined template of the `ngIf`, but it can be changed using
* a binding (just like `else`). Because `then` and `else` are bindings, the template references can
@ -40,7 +40,7 @@ import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef} from '
*
* {@example common/ngIf/ts/module.ts region='NgIfThenElse'}
*
* # Storing conditional result in a variable
* ## Storing conditional result in a variable
*
* A common pattern is that we need to show a set of properties from the same object. If the
* object is undefined, then we have to use the safe-traversal-operator `?.` to guard against

View File

@ -26,7 +26,7 @@ import {Directive, EmbeddedViewRef, Input, OnChanges, SimpleChanges, TemplateRef
*
* Note: using the key `$implicit` in the context object will set it's value as default.
*
* # Example
* ## Example
*
* {@example common/ngTemplateOutlet/ts/module.ts region='NgTemplateOutlet'}
*

View File

@ -14,22 +14,18 @@ import {InjectionToken} from '@angular/core';
* `PlatformLocation` encapsulates all calls to DOM apis, which allows the Router to be platform
* agnostic.
* This means that we can have different implementation of `PlatformLocation` for the different
* platforms
* that angular supports. For example, the default `PlatformLocation` is {@link
* BrowserPlatformLocation},
* however when you run your app in a WebWorker you use {@link WebWorkerPlatformLocation}.
* platforms that angular supports. For example, `@angular/platform-browser` provides an
* implementation specific to the browser environment, while `@angular/platform-webworker` provides
* one suitable for use with web workers.
*
* The `PlatformLocation` class is used directly by all implementations of {@link LocationStrategy}
* when
* they need to interact with the DOM apis like pushState, popState, etc...
* when they need to interact with the DOM apis like pushState, popState, etc...
*
* {@link LocationStrategy} in turn is used by the {@link Location} service which is used directly
* by
* the {@link Router} in order to navigate between routes. Since all interactions between {@link
* by the {@link Router} in order to navigate between routes. Since all interactions between {@link
* Router} /
* {@link Location} / {@link LocationStrategy} and DOM apis flow through the `PlatformLocation`
* class
* they are all platform independent.
* class they are all platform independent.
*
* @stable
*/

View File

@ -9,7 +9,7 @@
"ng-xi18n": "./src/extract_i18n.js"
},
"dependencies": {
"@angular/tsc-wrapped": "4.1.0",
"@angular/tsc-wrapped": "4.1.3",
"reflect-metadata": "^0.1.2",
"minimist": "^1.2.0"
},

View File

@ -149,8 +149,13 @@ function _extractLazyRoutesFromStaticModule(
return acc;
}, []);
const importedSymbols = ((moduleMetadata.imports || []) as any[])
.filter(i => i instanceof StaticSymbol) as StaticSymbol[];
const importedSymbols =
((moduleMetadata.imports || []) as any[])
.filter(i => i instanceof StaticSymbol || i.ngModule instanceof StaticSymbol)
.map(i => {
if (i instanceof StaticSymbol) return i;
return i.ngModule;
}) as StaticSymbol[];
return importedSymbols
.reduce(

View File

@ -26,7 +26,7 @@ export class MessageBundle {
private _implicitAttrs: {[k: string]: string[]}, private _locale: string|null = null) {}
updateFromTemplate(html: string, url: string, interpolationConfig: InterpolationConfig):
ParseError[]|null {
ParseError[] {
const htmlParserResult = this._htmlParser.parse(html, url, true, interpolationConfig);
if (htmlParserResult.errors.length) {
@ -41,7 +41,7 @@ export class MessageBundle {
}
this._messages.push(...i18nParserResult.messages);
return null;
return [];
}
// Return the message in the internal format

View File

@ -6,8 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AotCompiler, AotCompilerHost, AotCompilerOptions, GeneratedFile, createAotCompiler} from '@angular/compiler';
import {RenderComponentType, ɵReflectionCapabilities as ReflectionCapabilities, ɵreflector as reflector} from '@angular/core';
import {GeneratedFile} from '@angular/compiler';
import {NodeFlags} from '@angular/core/src/view/index';
import {async} from '@angular/core/testing';
import {MetadataBundler, MetadataCollector, ModuleMetadata, privateEntriesToIndex} from '@angular/tsc-wrapped';
@ -15,60 +14,16 @@ import * as ts from 'typescript';
import {extractSourceMap, originalPositionFor} from '../output/source_map_util';
import {EmittingCompilerHost, MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, MockMetadataBundlerHost, settings} from './test_util';
const DTS = /\.d\.ts$/;
const minCoreIndex = `
export * from './src/application_module';
export * from './src/change_detection';
export * from './src/metadata';
export * from './src/di/metadata';
export * from './src/di/injector';
export * from './src/di/injection_token';
export * from './src/linker';
export * from './src/render';
export * from './src/codegen_private_exports';
`;
import {EmittingCompilerHost, MockData, MockDirectory, MockMetadataBundlerHost, arrayToMockDir, arrayToMockMap, compile, settings, setup, toMockFileArray} from './test_util';
describe('compiler (unbundled Angular)', () => {
let angularFiles: Map<string, string>;
beforeAll(() => {
const emittingHost = new EmittingCompilerHost([], {emitMetadata: true});
emittingHost.addScript('@angular/core/index.ts', minCoreIndex);
const emittingProgram = ts.createProgram(emittingHost.scripts, settings, emittingHost);
emittingProgram.emit();
angularFiles = emittingHost.written;
});
// Restore reflector since AoT compiler will update it with a new static reflector
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
let angularFiles = setup();
describe('Quickstart', () => {
let host: MockCompilerHost;
let aotHost: MockAotCompilerHost;
beforeEach(() => {
host = new MockCompilerHost(QUICKSTART, FILES, angularFiles);
aotHost = new MockAotCompilerHost(host);
});
it('should compile',
async(() => compile(host, aotHost, expectNoDiagnostics).then(generatedFiles => {
expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
.toBeDefined();
expect(generatedFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl)))
.toBeDefined();
})));
it('should compile using summaries',
async(() => summaryCompile(host, aotHost).then(generatedFiles => {
expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
.toBeDefined();
expect(generatedFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl)))
it('should compile', async(() => compile([QUICKSTART, angularFiles]).then(({genFiles}) => {
expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
.toBeDefined();
expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
})));
});
@ -97,17 +52,11 @@ describe('compiler (unbundled Angular)', () => {
});
function compileApp(): Promise<GeneratedFile> {
return new Promise((resolve, reject) => {
const host = new MockCompilerHost(['/app/app.module.ts'], rootDir, angularFiles);
const aotHost = new MockAotCompilerHost(host);
let result: GeneratedFile[];
let error: Error;
resolve(compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics)
.then(
(files) => files.find(
genFile => genFile.srcFileUrl === componentPath &&
genFile.genFileUrl.endsWith('.ts'))));
});
return compile([rootDir, angularFiles])
.then(
({genFiles}) => {return genFiles.find(
genFile =>
genFile.srcFileUrl === componentPath && genFile.genFileUrl.endsWith('.ts'))});
}
function findLineAndColumn(
@ -247,7 +196,7 @@ describe('compiler (unbundled Angular)', () => {
describe('errors', () => {
it('should only warn if not all arguments of an @Injectable class can be resolved',
async(() => {
const FILES: MockData = {
const FILES: MockDirectory = {
app: {
'app.ts': `
import {Injectable} from '@angular/core';
@ -259,10 +208,8 @@ describe('compiler (unbundled Angular)', () => {
`
}
};
const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles);
const aotHost = new MockAotCompilerHost(host);
const warnSpy = spyOn(console, 'warn');
compile(host, aotHost, expectNoDiagnostics).then(() => {
compile([FILES, angularFiles]).then(() => {
expect(warnSpy).toHaveBeenCalledWith(
`Warning: Can't resolve all parameters for MyService in /app/app.ts: (?). This will become an error in Angular v5.x`);
});
@ -271,7 +218,7 @@ describe('compiler (unbundled Angular)', () => {
});
it('should add the preamble to generated files', async(() => {
const FILES: MockData = {
const FILES: MockDirectory = {
app: {
'app.ts': `
import { NgModule, Component } from '@angular/core';
@ -284,22 +231,19 @@ describe('compiler (unbundled Angular)', () => {
`
}
};
const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles);
const aotHost = new MockAotCompilerHost(host);
const genFilePreamble = '/* Hello world! */';
compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics, {genFilePreamble})
.then((generatedFiles) => {
const genFile = generatedFiles.find(
gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
expect(genFile.source.startsWith(genFilePreamble)).toBe(true);
});
compile([FILES, angularFiles], {genFilePreamble}).then(({genFiles}) => {
const genFile =
genFiles.find(gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
expect(genFile.source.startsWith(genFilePreamble)).toBe(true);
});
}));
describe('ComponentFactories', () => {
it('should include inputs, outputs and ng-content selectors in the component factory',
async(() => {
const FILES: MockData = {
const FILES: MockDirectory = {
app: {
'app.ts': `
import {Component, NgModule, Input, Output} from '@angular/core';
@ -323,11 +267,8 @@ describe('compiler (unbundled Angular)', () => {
`
}
};
const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles);
const aotHost = new MockAotCompilerHost(host);
let generatedFiles: GeneratedFile[];
compile(host, aotHost, expectNoDiagnostics).then((generatedFiles) => {
const genFile = generatedFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts');
compile([FILES, angularFiles]).then(({genFiles}) => {
const genFile = genFiles.find(genFile => genFile.srcFileUrl === '/app/app.ts');
const createComponentFactoryCall =
/ɵccf\([^)]*\)/m.exec(genFile.source) ![0].replace(/\s*/g, '');
// selector
@ -345,7 +286,7 @@ describe('compiler (unbundled Angular)', () => {
describe('generated templates', () => {
it('should not call `check` for directives without bindings nor ngDoCheck/ngOnInit',
async(() => {
const FILES: MockData = {
const FILES: MockDirectory = {
app: {
'app.ts': `
import { NgModule, Component } from '@angular/core';
@ -358,37 +299,16 @@ describe('compiler (unbundled Angular)', () => {
`
}
};
const host = new MockCompilerHost(['/app/app.ts'], FILES, angularFiles);
const aotHost = new MockAotCompilerHost(host);
const genFilePreamble = '/* Hello world! */';
compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics, {genFilePreamble})
.then((generatedFiles) => {
const genFile = generatedFiles.find(
gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
expect(genFile.source).not.toContain('check(');
});
compile([FILES, angularFiles]).then(({genFiles}) => {
const genFile = genFiles.find(
gf => gf.srcFileUrl === '/app/app.ts' && gf.genFileUrl.endsWith('.ts'));
expect(genFile.source).not.toContain('check(');
});
}));
});
describe('inheritance with summaries', () => {
function compileWithSummaries(
libInput: MockData, appInput: MockData): Promise<GeneratedFile[]> {
const libHost = new MockCompilerHost(['/lib/base.ts'], libInput, angularFiles);
const libAotHost = new MockAotCompilerHost(libHost);
libAotHost.tsFilesOnly();
const appHost = new MockCompilerHost(['/app/main.ts'], appInput, angularFiles);
const appAotHost = new MockAotCompilerHost(appHost);
appAotHost.tsFilesOnly();
return compile(libHost, libAotHost, expectNoDiagnostics, expectNoDiagnosticsAndEmit)
.then(() => {
libHost.writtenFiles.forEach((value, key) => appHost.writeFile(key, value, false));
libHost.overrides.forEach((value, key) => appHost.override(key, value));
return compile(appHost, appAotHost, expectNoDiagnostics, expectNoDiagnosticsAndEmit);
});
}
function compileParentAndChild(
{parentClassDecorator, parentModuleDecorator, childClassDecorator, childModuleDecorator}: {
parentClassDecorator: string,
@ -396,7 +316,7 @@ describe('compiler (unbundled Angular)', () => {
childClassDecorator: string,
childModuleDecorator: string
}) {
const libInput: MockData = {
const libInput: MockDirectory = {
'lib': {
'base.ts': `
import {Injectable, Pipe, Directive, Component, NgModule} from '@angular/core';
@ -409,7 +329,7 @@ describe('compiler (unbundled Angular)', () => {
`
}
};
const appInput: MockData = {
const appInput: MockDirectory = {
'app': {
'main.ts': `
import {Injectable, Pipe, Directive, Component, NgModule} from '@angular/core';
@ -424,13 +344,14 @@ describe('compiler (unbundled Angular)', () => {
}
};
return compileWithSummaries(libInput, appInput)
.then((generatedFiles) => generatedFiles.find(gf => gf.srcFileUrl === '/app/main.ts'));
return compile([libInput, angularFiles], {useSummaries: true})
.then(({outDir}) => compile([outDir, appInput, angularFiles], {useSummaries: true}))
.then(({genFiles}) => genFiles.find(gf => gf.srcFileUrl === '/app/main.ts'));
}
it('should inherit ctor and lifecycle hooks from classes in other compilation units',
async(() => {
const libInput: MockData = {
const libInput: MockDirectory = {
'lib': {
'base.ts': `
export class AParam {}
@ -442,7 +363,7 @@ describe('compiler (unbundled Angular)', () => {
`
}
};
const appInput: MockData = {
const appInput: MockDirectory = {
'app': {
'main.ts': `
import {NgModule, Component} from '@angular/core';
@ -459,17 +380,19 @@ describe('compiler (unbundled Angular)', () => {
}
};
compileWithSummaries(libInput, appInput).then((generatedFiles) => {
const mainNgFactory = generatedFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
expect(mainNgFactory.source)
.toContain(`${flags},(null as any),0,import1.Extends,[import2.AParam]`);
});
compile([libInput, angularFiles], {useSummaries: true})
.then(({outDir}) => compile([outDir, appInput, angularFiles], {useSummaries: true}))
.then(({genFiles}) => {
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
expect(mainNgFactory.source)
.toContain(`${flags},(null as any),0,import1.Extends,[import2.AParam]`);
});
}));
it('should inherit ctor and lifecycle hooks from classes in other compilation units over 2 levels',
async(() => {
const lib1Input: MockData = {
const lib1Input: MockDirectory = {
'lib1': {
'base.ts': `
export class AParam {}
@ -482,7 +405,7 @@ describe('compiler (unbundled Angular)', () => {
}
};
const lib2Input: MockData = {
const lib2Input: MockDirectory = {
'lib2': {
'middle.ts': `
import {Base} from '../lib1/base';
@ -492,7 +415,7 @@ describe('compiler (unbundled Angular)', () => {
};
const appInput: MockData = {
const appInput: MockDirectory = {
'app': {
'main.ts': `
import {NgModule, Component} from '@angular/core';
@ -508,29 +431,11 @@ describe('compiler (unbundled Angular)', () => {
`
}
};
const lib1Host = new MockCompilerHost(['/lib1/base.ts'], lib1Input, angularFiles);
const lib1AotHost = new MockAotCompilerHost(lib1Host);
lib1AotHost.tsFilesOnly();
const lib2Host = new MockCompilerHost(['/lib2/middle.ts'], lib2Input, angularFiles);
const lib2AotHost = new MockAotCompilerHost(lib2Host);
lib2AotHost.tsFilesOnly();
const appHost = new MockCompilerHost(['/app/main.ts'], appInput, angularFiles);
const appAotHost = new MockAotCompilerHost(appHost);
appAotHost.tsFilesOnly();
compile(lib1Host, lib1AotHost, expectNoDiagnostics, expectNoDiagnosticsAndEmit)
.then(() => {
lib1Host.writtenFiles.forEach((value, key) => lib2Host.writeFile(key, value, false));
lib1Host.overrides.forEach((value, key) => lib2Host.override(key, value));
return compile(
lib2Host, lib2AotHost, expectNoDiagnostics, expectNoDiagnosticsAndEmit);
})
.then(() => {
lib2Host.writtenFiles.forEach((value, key) => appHost.writeFile(key, value, false));
lib2Host.overrides.forEach((value, key) => appHost.override(key, value));
return compile(appHost, appAotHost, expectNoDiagnostics, expectNoDiagnosticsAndEmit);
})
.then((generatedFiles) => {
const mainNgFactory = generatedFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
compile([lib1Input, angularFiles], {useSummaries: true})
.then(({outDir}) => compile([outDir, lib2Input, angularFiles], {useSummaries: true}))
.then(({outDir}) => compile([outDir, appInput, angularFiles], {useSummaries: true}))
.then(({genFiles}) => {
const mainNgFactory = genFiles.find(gf => gf.srcFileUrl === '/app/main.ts');
const flags = NodeFlags.TypeDirective | NodeFlags.Component | NodeFlags.OnDestroy;
expect(mainNgFactory.source)
.toContain(`${flags},(null as any),0,import1.Extends,[import2.AParam_2]`);
@ -660,6 +565,8 @@ describe('compiler (unbundled Angular)', () => {
});
describe('compiler (bundled Angular)', () => {
setup({compileAngular: false});
let angularFiles: Map<string, string>;
beforeAll(() => {
@ -681,34 +588,19 @@ describe('compiler (bundled Angular)', () => {
const bundleIndexName = emittingHost.effectiveName('@angular/core/bundle_index.ts');
const emittingProgram = ts.createProgram([bundleIndexName], settings, emittingHost);
emittingProgram.emit();
angularFiles = emittingHost.written;
angularFiles = emittingHost.writtenAngularFiles();
});
describe('Quickstart', () => {
let host: MockCompilerHost;
let aotHost: MockAotCompilerHost;
beforeEach(() => {
host = new MockCompilerHost(QUICKSTART, FILES, angularFiles);
aotHost = new MockAotCompilerHost(host);
});
// Restore reflector since AoT compiler will update it with a new static reflector
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
it('should compile',
async(() => compile(host, aotHost, expectNoDiagnostics).then(generatedFiles => {
expect(generatedFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
.toBeDefined();
expect(generatedFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl)))
it('should compile', async(() => compile([QUICKSTART, angularFiles]).then(({genFiles}) => {
expect(genFiles.find(f => /app\.component\.ngfactory\.ts/.test(f.genFileUrl)))
.toBeDefined();
expect(genFiles.find(f => /app\.module\.ngfactory\.ts/.test(f.genFileUrl))).toBeDefined();
})));
});
describe('Bundled library', () => {
let host: MockCompilerHost;
let aotHost: MockAotCompilerHost;
let libraryFiles: Map<string, string>;
let libraryFiles: MockDirectory;
beforeAll(() => {
// Emit the library bundle
@ -728,135 +620,22 @@ describe('compiler (bundled Angular)', () => {
// Emit the sources
const emittingProgram = ts.createProgram(['/bolder/index.ts'], settings, emittingHost);
emittingProgram.emit();
libraryFiles = emittingHost.written;
const libFiles = emittingHost.written;
// Copy the .html file
const htmlFileName = '/bolder/src/bolder.component.html';
libraryFiles.set(htmlFileName, emittingHost.readFile(htmlFileName));
libFiles.set(htmlFileName, emittingHost.readFile(htmlFileName));
libraryFiles = arrayToMockDir(toMockFileArray(libFiles).map(
({fileName, content}) => ({fileName: `/node_modules${fileName}`, content})));
});
beforeEach(() => {
host = new MockCompilerHost(
LIBRARY_USING_APP_MODULE, LIBRARY_USING_APP, angularFiles, [libraryFiles]);
aotHost = new MockAotCompilerHost(host);
});
it('should compile',
async(() => compile(host, aotHost, expectNoDiagnostics, expectNoDiagnostics)));
// Restore reflector since AoT compiler will update it with a new static reflector
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
it('should compile', async(() => compile([LIBRARY_USING_APP, libraryFiles, angularFiles])));
});
});
function expectNoDiagnostics(program: ts.Program) {
function fileInfo(diagnostic: ts.Diagnostic): string {
if (diagnostic.file) {
return `${diagnostic.file.fileName}(${diagnostic.start}): `;
}
return '';
}
function chars(len: number, ch: string): string { return new Array(len).fill(ch).join(''); }
function lineNoOf(offset: number, text: string): number {
let result = 1;
for (let i = 0; i < offset; i++) {
if (text[i] == '\n') result++;
}
return result;
}
function lineInfo(diagnostic: ts.Diagnostic): string {
if (diagnostic.file) {
const start = diagnostic.start;
let end = diagnostic.start + diagnostic.length;
const source = diagnostic.file.text;
let lineStart = start;
let lineEnd = end;
while (lineStart > 0 && source[lineStart] != '\n') lineStart--;
if (lineStart < start) lineStart++;
while (lineEnd < source.length && source[lineEnd] != '\n') lineEnd++;
let line = source.substring(lineStart, lineEnd);
const lineIndex = line.indexOf('/n');
if (lineIndex > 0) {
line = line.substr(0, lineIndex);
end = start + lineIndex;
}
const lineNo = lineNoOf(start, source) + ': ';
return '\n' + lineNo + line + '\n' + chars(start - lineStart + lineNo.length, ' ') +
chars(end - start, '^');
}
return '';
}
function expectNoDiagnostics(diagnostics: ts.Diagnostic[]) {
if (diagnostics && diagnostics.length) {
throw new Error(
'Errors from TypeScript:\n' +
diagnostics.map(d => `${fileInfo(d)}${d.messageText}${lineInfo(d)}`).join(' \n'));
}
}
expectNoDiagnostics(program.getOptionsDiagnostics());
expectNoDiagnostics(program.getSyntacticDiagnostics());
expectNoDiagnostics(program.getSemanticDiagnostics());
}
function expectNoDiagnosticsAndEmit(program: ts.Program) {
expectNoDiagnostics(program);
program.emit();
}
function isDTS(fileName: string): boolean {
return /\.d\.ts$/.test(fileName);
}
function isSource(fileName: string): boolean {
return /\.ts$/.test(fileName);
}
function isFactory(fileName: string): boolean {
return /\.ngfactory\./.test(fileName);
}
function summaryCompile(
host: MockCompilerHost, aotHost: MockAotCompilerHost,
preCompile?: (program: ts.Program) => void) {
// First compile the program to generate the summary files.
return compile(host, aotHost).then(generatedFiles => {
// Remove generated files that were not generated from a DTS file
host.remove(generatedFiles.filter(f => !isDTS(f.srcFileUrl)).map(f => f.genFileUrl));
// Next compile the program shrowding metadata and only treating .ts files as source.
aotHost.hideMetadata();
aotHost.tsFilesOnly();
return compile(host, aotHost);
});
}
function compile(
host: MockCompilerHost, aotHost: AotCompilerHost, preCompile?: (program: ts.Program) => void,
postCompile: (program: ts.Program) => void = expectNoDiagnostics,
options: AotCompilerOptions = {}): Promise<GeneratedFile[]> {
const scripts = host.scriptNames.slice(0);
const program = ts.createProgram(scripts, settings, host);
if (preCompile) preCompile(program);
const {compiler, reflector} = createAotCompiler(aotHost, options);
return compiler.compileAll(program.getSourceFiles().map(sf => sf.fileName))
.then(generatedFiles => {
generatedFiles.forEach(
file => isSource(file.genFileUrl) ? host.addScript(file.genFileUrl, file.source) :
host.override(file.genFileUrl, file.source));
const scripts = host.scriptNames.slice(0);
const newProgram = ts.createProgram(scripts, settings, host);
if (postCompile) postCompile(newProgram);
return generatedFiles;
});
}
const QUICKSTART = ['/quickstart/app/app.module.ts'];
const FILES: MockData = {
const QUICKSTART: MockDirectory = {
quickstart: {
app: {
'app.component.ts': `
@ -891,7 +670,7 @@ const FILES: MockData = {
}
};
const LIBRARY: MockData = {
const LIBRARY: MockDirectory = {
bolder: {
'public-api.ts': `
export * from './src/bolder.component';
@ -927,7 +706,7 @@ const LIBRARY: MockData = {
};
const LIBRARY_USING_APP_MODULE = ['/lib-user/app/app.module.ts'];
const LIBRARY_USING_APP: MockData = {
const LIBRARY_USING_APP: MockDirectory = {
'lib-user': {
app: {
'app.component.ts': `

View File

@ -6,19 +6,26 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AotCompilerHost} from '@angular/compiler';
import {AotCompilerHost, AotCompilerOptions, GeneratedFile, createAotCompiler} from '@angular/compiler';
import {ɵReflectionCapabilities as ReflectionCapabilities, ɵreflector as reflector} from '@angular/core';
import {MetadataBundlerHost, MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
export type MockData = string | MockDirectory;
let nodeModulesPath: string;
let angularSourcePath: string;
let rootPath: string;
calcPathsOnDisc();
export type MockFileOrDirectory = string | MockDirectory;
export type MockDirectory = {
[name: string]: MockData | undefined;
[name: string]: MockFileOrDirectory | undefined;
};
export function isDirectory(data: MockData | undefined): data is MockDirectory {
export function isDirectory(data: MockFileOrDirectory | undefined): data is MockDirectory {
return typeof data !== 'string';
}
@ -43,12 +50,21 @@ export const settings: ts.CompilerOptions = {
export interface EmitterOptions {
emitMetadata: boolean;
mockData?: MockData;
mockData?: MockDirectory;
}
function calcPathsOnDisc() {
const moduleFilename = module.filename.replace(/\\/g, '/');
const distIndex = moduleFilename.indexOf('/dist/all');
if (distIndex >= 0) {
rootPath = moduleFilename.substr(0, distIndex);
nodeModulesPath = path.join(rootPath, 'node_modules');
angularSourcePath = path.join(rootPath, 'packages');
}
}
export class EmittingCompilerHost implements ts.CompilerHost {
private angularSourcePath: string|undefined;
private nodeModulesPath: string|undefined;
private addedFiles = new Map<string, string>();
private writtenFiles = new Map<string, string>();
private scriptNames: string[];
@ -56,19 +72,18 @@ export class EmittingCompilerHost implements ts.CompilerHost {
private collector = new MetadataCollector();
constructor(scriptNames: string[], private options: EmitterOptions) {
const moduleFilename = module.filename.replace(/\\/g, '/');
const distIndex = moduleFilename.indexOf('/dist/all');
if (distIndex >= 0) {
const root = moduleFilename.substr(0, distIndex);
this.nodeModulesPath = path.join(root, 'node_modules');
this.angularSourcePath = path.join(root, 'packages');
// Rewrite references to scripts with '@angular' to its corresponding location in
// the source tree.
this.scriptNames = scriptNames.map(f => this.effectiveName(f));
this.root = rootPath;
}
// Rewrite references to scripts with '@angular' to its corresponding location in
// the source tree.
this.scriptNames = scriptNames.map(f => this.effectiveName(f));
this.root = root;
}
public writtenAngularFiles(target = new Map<string, string>()): Map<string, string> {
this.written.forEach((value, key) => {
const path = `/node_modules/@angular${key.substring(angularSourcePath.length)}`;
target.set(path, value);
});
return target;
}
public addScript(fileName: string, content: string) {
@ -97,7 +112,7 @@ export class EmittingCompilerHost implements ts.CompilerHost {
public effectiveName(fileName: string): string {
const prefix = '@angular/';
return fileName.startsWith('@angular/') ?
path.join(this.angularSourcePath, fileName.substr(prefix.length)) :
path.join(angularSourcePath, fileName.substr(prefix.length)) :
fileName;
}
@ -171,31 +186,17 @@ export class EmittingCompilerHost implements ts.CompilerHost {
getNewLine(): string { return '\n'; }
}
const MOCK_NODEMODULES_PREFIX = '/node_modules/';
export class MockCompilerHost implements ts.CompilerHost {
scriptNames: string[];
private angularSourcePath: string|undefined;
private nodeModulesPath: string|undefined;
public overrides = new Map<string, string>();
public writtenFiles = new Map<string, string>();
private sourceFiles = new Map<string, ts.SourceFile>();
private assumeExists = new Set<string>();
private traces: string[] = [];
constructor(
scriptNames: string[], private data: MockData, private angular: Map<string, string>,
private libraries?: Map<string, string>[]) {
constructor(scriptNames: string[], private data: MockDirectory) {
this.scriptNames = scriptNames.slice(0);
const moduleFilename = module.filename.replace(/\\/g, '/');
let angularIndex = moduleFilename.indexOf('@angular');
let distIndex = moduleFilename.indexOf('/dist/all');
if (distIndex >= 0) {
const root = moduleFilename.substr(0, distIndex);
this.nodeModulesPath = path.join(root, 'node_modules');
this.angularSourcePath = path.join(root, 'packages');
}
}
// Test API
@ -234,22 +235,13 @@ export class MockCompilerHost implements ts.CompilerHost {
const effectiveName = this.getEffectiveName(fileName);
if (effectiveName == fileName) {
let result = open(fileName, this.data) != null;
if (!result && fileName.startsWith(MOCK_NODEMODULES_PREFIX)) {
const libraryPath = fileName.substr(MOCK_NODEMODULES_PREFIX.length - 1);
for (const library of this.libraries !) {
if (library.has(libraryPath)) {
return true;
}
}
}
return result;
} else {
if (fileName.match(rxjs)) {
let result = fs.existsSync(effectiveName);
return result;
}
const result = this.angular.has(effectiveName);
return result;
return false;
}
}
@ -315,12 +307,6 @@ export class MockCompilerHost implements ts.CompilerHost {
let effectiveName = this.getEffectiveName(fileName);
if (effectiveName === fileName) {
const result = open(fileName, this.data);
if (!result && fileName.startsWith(MOCK_NODEMODULES_PREFIX)) {
const libraryPath = fileName.substr(MOCK_NODEMODULES_PREFIX.length - 1);
for (const library of this.libraries !) {
if (library.has(libraryPath)) return library.get(libraryPath);
}
}
return result;
} else {
if (fileName.match(rxjs)) {
@ -328,22 +314,16 @@ export class MockCompilerHost implements ts.CompilerHost {
return fs.readFileSync(fileName, 'utf8');
}
}
return this.angular.get(effectiveName);
}
}
}
private getEffectiveName(name: string): string {
const node_modules = 'node_modules';
const at_angular = '/@angular';
const rxjs = '/rxjs';
if (name.startsWith('/' + node_modules)) {
if (this.angularSourcePath && name.startsWith('/' + node_modules + at_angular)) {
return path.join(
this.angularSourcePath, name.substr(node_modules.length + at_angular.length + 1));
}
if (this.nodeModulesPath && name.startsWith('/' + node_modules + rxjs)) {
return path.join(this.nodeModulesPath, name.substr(node_modules.length + 1));
if (nodeModulesPath && name.startsWith('/' + node_modules + rxjs)) {
return path.join(nodeModulesPath, name.substr(node_modules.length + 1));
}
}
return name;
@ -439,11 +419,12 @@ export class MockMetadataBundlerHost implements MetadataBundlerHost {
}
}
function find(fileName: string, data: MockData | undefined): MockData|undefined {
function find(fileName: string, data: MockFileOrDirectory | undefined): MockFileOrDirectory|
undefined {
if (!data) return undefined;
let names = fileName.split('/');
if (names.length && !names[0].length) names.shift();
let current: MockData|undefined = data;
let current: MockFileOrDirectory|undefined = data;
for (let name of names) {
if (typeof current === 'string')
return undefined;
@ -454,7 +435,7 @@ function find(fileName: string, data: MockData | undefined): MockData|undefined
return current;
}
function open(fileName: string, data: MockData | undefined): string|undefined {
function open(fileName: string, data: MockFileOrDirectory | undefined): string|undefined {
let result = find(fileName, data);
if (typeof result === 'string') {
return result;
@ -462,7 +443,204 @@ function open(fileName: string, data: MockData | undefined): string|undefined {
return undefined;
}
function directoryExists(dirname: string, data: MockData | undefined): boolean {
function directoryExists(dirname: string, data: MockFileOrDirectory | undefined): boolean {
let result = find(dirname, data);
return !!result && typeof result !== 'string';
}
export type MockFileArray = {
fileName: string,
content: string
}[];
export type MockData = MockDirectory | Map<string, string>| (MockDirectory | Map<string, string>)[];
export function toMockFileArray(data: MockData, target: MockFileArray = []): MockFileArray {
if (data instanceof Map) {
mapToMockFileArray(data, target);
} else if (Array.isArray(data)) {
data.forEach(entry => toMockFileArray(entry, target));
} else {
mockDirToFileArray(data, '', target);
}
return target;
}
function mockDirToFileArray(dir: MockDirectory, path: string, target: MockFileArray) {
Object.keys(dir).forEach((localFileName) => {
const value = dir[localFileName] !;
const fileName = `${path}/${localFileName}`;
if (typeof value === 'string') {
target.push({fileName, content: value});
} else {
mockDirToFileArray(value, fileName, target);
}
});
}
function mapToMockFileArray(files: Map<string, string>, target: MockFileArray) {
files.forEach((content, fileName) => { target.push({fileName, content}); });
}
export function arrayToMockMap(arr: MockFileArray): Map<string, string> {
const map = new Map<string, string>();
arr.forEach(({fileName, content}) => { map.set(fileName, content); });
return map;
}
export function arrayToMockDir(arr: MockFileArray): MockDirectory {
const rootDir: MockDirectory = {};
arr.forEach(({fileName, content}) => {
let pathParts = fileName.split('/');
// trim trailing slash
let startIndex = pathParts[0] ? 0 : 1;
// get/create the directory
let currentDir = rootDir;
for (let i = startIndex; i < pathParts.length - 1; i++) {
const pathPart = pathParts[i];
let localDir = <MockDirectory>currentDir[pathPart];
if (!localDir) {
currentDir[pathPart] = localDir = {};
}
currentDir = localDir;
}
// write the file
currentDir[pathParts[pathParts.length - 1]] = content;
});
return rootDir;
}
const minCoreIndex = `
export * from './src/application_module';
export * from './src/change_detection';
export * from './src/metadata';
export * from './src/di/metadata';
export * from './src/di/injector';
export * from './src/di/injection_token';
export * from './src/linker';
export * from './src/render';
export * from './src/codegen_private_exports';
`;
export function setup(options: {compileAngular: boolean} = {
compileAngular: true
}) {
let angularFiles = new Map<string, string>();
beforeAll(() => {
if (options.compileAngular) {
const emittingHost = new EmittingCompilerHost([], {emitMetadata: true});
emittingHost.addScript('@angular/core/index.ts', minCoreIndex);
const emittingProgram = ts.createProgram(emittingHost.scripts, settings, emittingHost);
emittingProgram.emit();
emittingHost.writtenAngularFiles(angularFiles);
}
});
// Restore reflector since AoT compiler will update it with a new static reflector
afterEach(() => { reflector.updateCapabilities(new ReflectionCapabilities()); });
return angularFiles;
}
export function expectNoDiagnostics(program: ts.Program) {
function fileInfo(diagnostic: ts.Diagnostic): string {
if (diagnostic.file) {
return `${diagnostic.file.fileName}(${diagnostic.start}): `;
}
return '';
}
function chars(len: number, ch: string): string { return new Array(len).fill(ch).join(''); }
function lineNoOf(offset: number, text: string): number {
let result = 1;
for (let i = 0; i < offset; i++) {
if (text[i] == '\n') result++;
}
return result;
}
function lineInfo(diagnostic: ts.Diagnostic): string {
if (diagnostic.file) {
const start = diagnostic.start;
let end = diagnostic.start + diagnostic.length;
const source = diagnostic.file.text;
let lineStart = start;
let lineEnd = end;
while (lineStart > 0 && source[lineStart] != '\n') lineStart--;
if (lineStart < start) lineStart++;
while (lineEnd < source.length && source[lineEnd] != '\n') lineEnd++;
let line = source.substring(lineStart, lineEnd);
const lineIndex = line.indexOf('/n');
if (lineIndex > 0) {
line = line.substr(0, lineIndex);
end = start + lineIndex;
}
const lineNo = lineNoOf(start, source) + ': ';
return '\n' + lineNo + line + '\n' + chars(start - lineStart + lineNo.length, ' ') +
chars(end - start, '^');
}
return '';
}
function expectNoDiagnostics(diagnostics: ts.Diagnostic[]) {
if (diagnostics && diagnostics.length) {
throw new Error(
'Errors from TypeScript:\n' +
diagnostics.map(d => `${fileInfo(d)}${d.messageText}${lineInfo(d)}`).join(' \n'));
}
}
expectNoDiagnostics(program.getOptionsDiagnostics());
expectNoDiagnostics(program.getSyntacticDiagnostics());
expectNoDiagnostics(program.getSemanticDiagnostics());
}
function isSource(fileName: string): boolean {
return !/\.d\.ts$/.test(fileName) && /\.ts$/.test(fileName);
}
export function compile(rootDirs: MockData, options: {
emit?: boolean,
useSummaries?: boolean,
preCompile?: (program: ts.Program) => void,
postCompile?: (program: ts.Program) => void,
}& AotCompilerOptions = {}): Promise<{genFiles: GeneratedFile[], outDir: MockDirectory}> {
// Make sure we always return errors via the promise...
return Promise.resolve(null).then(() => {
// when using summaries, always emit so the next step can use the results.
const emit = options.emit || options.useSummaries;
const preCompile = options.preCompile || expectNoDiagnostics;
const postCompile = options.postCompile || expectNoDiagnostics;
const rootDirArr = toMockFileArray(rootDirs);
const scriptNames = rootDirArr.map(entry => entry.fileName).filter(isSource);
const host = new MockCompilerHost(scriptNames, arrayToMockDir(rootDirArr));
const aotHost = new MockAotCompilerHost(host);
if (options.useSummaries) {
aotHost.hideMetadata();
aotHost.tsFilesOnly();
}
const scripts = host.scriptNames.slice(0);
const program = ts.createProgram(scripts, settings, host);
if (preCompile) preCompile(program);
const {compiler, reflector} = createAotCompiler(aotHost, options);
return compiler.compileAll(program.getSourceFiles().map(sf => sf.fileName)).then(genFiles => {
genFiles.forEach(
file => isSource(file.genFileUrl) ? host.addScript(file.genFileUrl, file.source) :
host.override(file.genFileUrl, file.source));
const scripts = host.scriptNames.slice(0);
const newProgram = ts.createProgram(scripts, settings, host);
if (postCompile) postCompile(newProgram);
if (emit) {
newProgram.emit();
}
let outDir: MockDirectory = {};
if (emit) {
outDir = arrayToMockDir(toMockFileArray([
host.writtenFiles, host.overrides
]).filter((entry) => !isSource(entry.fileName)));
}
return {genFiles, outDir};
});
});
}

View File

@ -24,23 +24,48 @@ export const APP_INITIALIZER = new InjectionToken<Array<() => void>>('Applicatio
*/
@Injectable()
export class ApplicationInitStatus {
private resolve: Function;
private reject: Function;
private initialized = false;
private _donePromise: Promise<any>;
private _done = false;
constructor(@Inject(APP_INITIALIZER) @Optional() appInits: (() => any)[]) {
constructor(@Inject(APP_INITIALIZER) @Optional() private appInits: (() => any)[]) {
this._donePromise = new Promise((res, rej) => {
this.resolve = res;
this.reject = rej;
});
}
/** @internal */
runInitializers() {
if (this.initialized) {
return;
}
const asyncInitPromises: Promise<any>[] = [];
if (appInits) {
for (let i = 0; i < appInits.length; i++) {
const initResult = appInits[i]();
const complete =
() => {
this._done = true;
this.resolve();
}
if (this.appInits) {
for (let i = 0; i < this.appInits.length; i++) {
const initResult = this.appInits[i]();
if (isPromise(initResult)) {
asyncInitPromises.push(initResult);
}
}
}
this._donePromise = Promise.all(asyncInitPromises).then(() => { this._done = true; });
Promise.all(asyncInitPromises).then(() => { complete(); }).catch(e => { this.reject(e); });
if (asyncInitPromises.length === 0) {
this._done = true;
complete();
}
this.initialized = true;
}
get done(): boolean { return this._done; }

View File

@ -162,8 +162,8 @@ export function getPlatform(): PlatformRef|null {
* has exactly one platform, and services (such as reflection) which are common
* to every Angular application running on the page are bound in its scope.
*
* A page's platform is initialized implicitly when {@link bootstrap}() is called, or
* explicitly by calling {@link createPlatform}().
* A page's platform is initialized implicitly when a platform is created via a platform factory
* (e.g. {@link platformBrowser}), or explicitly by calling the {@link createPlatform} function.
*
* @stable
*/
@ -302,6 +302,7 @@ export class PlatformRef_ extends PlatformRef {
ngZone !.onError.subscribe({next: (error: any) => { exceptionHandler.handleError(error); }});
return _callAndReportToErrorHandler(exceptionHandler, () => {
const initStatus: ApplicationInitStatus = moduleRef.injector.get(ApplicationInitStatus);
initStatus.runInitializers();
return initStatus.donePromise.then(() => {
this._moduleDoBootstrap(moduleRef);
return moduleRef;
@ -344,8 +345,6 @@ export class PlatformRef_ extends PlatformRef {
/**
* A reference to an Angular application running on a page.
*
* For more about Angular applications, see the documentation for {@link bootstrap}.
*
* @stable
*/
export abstract class ApplicationRef {
@ -553,6 +552,9 @@ export class ApplicationRef_ extends ApplicationRef {
if (this._enforceNoNewChanges) {
this._views.forEach((view) => view.checkNoChanges());
}
} catch (e) {
// Attention: Don't rethrow as it could cancel subscriptions to Observables!
this._exceptionHandler.handleError(e);
} finally {
this._runningTick = false;
wtfLeave(scope);

View File

@ -50,7 +50,7 @@ export abstract class Injector {
/**
* Retrieves an instance from the injector based on the provided token.
* If not found:
* - Throws {@link NoProviderError} if no `notFoundValue` that is not equal to
* - Throws an error if no `notFoundValue` that is not equal to
* Injector.THROW_IF_NOT_FOUND is given
* - Returns the `notFoundValue` otherwise
*/

View File

@ -127,7 +127,7 @@ export interface InjectableDecorator {
*
* {@example core/di/ts/metadata_spec.ts region='Injectable'}
*
* {@link Injector} will throw {@link NoAnnotationError} when trying to instantiate a class that
* {@link Injector} will throw an error when trying to instantiate a class that
* does not have `@Injectable` marker, as shown in the example below.
*
* {@example core/di/ts/metadata_spec.ts region='InjectableThrows'}

View File

@ -113,7 +113,7 @@ export abstract class ReflectiveInjector implements Injector {
*
* This function is slower than the corresponding `fromResolvedProviders`
* because it needs to resolve the passed-in providers first.
* See {@link Injector#resolve} and {@link Injector#fromResolvedProviders}.
* See {@link ReflectiveInjector#resolve} and {@link ReflectiveInjector#fromResolvedProviders}.
*/
static resolveAndCreate(providers: Provider[], parent?: Injector): ReflectiveInjector {
const ResolvedReflectiveProviders = ReflectiveInjector.resolve(providers);
@ -190,7 +190,7 @@ export abstract class ReflectiveInjector implements Injector {
*
* This function is slower than the corresponding `createChildFromResolved`
* because it needs to resolve the passed-in providers first.
* See {@link Injector#resolve} and {@link Injector#createChildFromResolved}.
* See {@link ReflectiveInjector#resolve} and {@link ReflectiveInjector#createChildFromResolved}.
*/
abstract resolveAndCreateChild(providers: Provider[]): ReflectiveInjector;

View File

@ -64,8 +64,11 @@ export class EventEmitter<T> extends Subject<T> {
__isAsync: boolean;
/**
* Creates an instance of [EventEmitter], which depending on [isAsync],
* Creates an instance of {@link EventEmitter}, which depending on `isAsync`,
* delivers events synchronously or asynchronously.
*
* @param isAsync By default, events are delivered synchronously (default value: `false`).
* Set to `true` for asynchronous event delivery.
*/
constructor(isAsync: boolean = false) {
super();

View File

@ -54,14 +54,14 @@ export class Compiler {
compileModuleAsync<T>(moduleType: Type<T>): Promise<NgModuleFactory<T>> { throw _throwError(); }
/**
* Same as {@link compileModuleSync} but also creates ComponentFactories for all components.
* Same as {@link #compileModuleSync} but also creates ComponentFactories for all components.
*/
compileModuleAndAllComponentsSync<T>(moduleType: Type<T>): ModuleWithComponentFactories<T> {
throw _throwError();
}
/**
* Same as {@link compileModuleAsync} but also creates ComponentFactories for all components.
* Same as {@link #compileModuleAsync} but also creates ComponentFactories for all components.
*/
compileModuleAndAllComponentsAsync<T>(moduleType: Type<T>):
Promise<ModuleWithComponentFactories<T>> {

View File

@ -16,7 +16,8 @@ import {getSymbolIterator} from '../util';
* An unmodifiable list of items that Angular keeps up to date when the state
* of the application changes.
*
* The type of object that {@link Query} and {@link ViewQueryMetadata} provide.
* The type of object that {@link ViewChildren}, {@link ContentChildren}, and {@link QueryList}
* provide.
*
* Implements an iterable interface, therefore it can be used in both ES6
* javascript `for (var i of items)` loops as well as in Angular templates with

View File

@ -287,7 +287,7 @@ export const ContentChild: ContentChildDecorator = makePropDecorator(
/**
* Type of the ViewChildren decorator / constructor function.
*
* See {@ViewChildren}.
* See {@link ViewChildren}.
*
* @stable
*/

View File

@ -662,8 +662,8 @@ export interface Component extends Directive {
* encapsulation.
*
* When no `encapsulation` is defined for the component, the default value from the
* {@link CompilerConfig} is used. The default is `ViewEncapsulation.Emulated`}. Provide a new
* `CompilerConfig` to override this value.
* {@link CompilerOptions} is used. The default is `ViewEncapsulation.Emulated`}. Provide a new
* `CompilerOptions` to override this value.
*
* If the encapsulation is set to `ViewEncapsulation.Emulated` and the component has no `styles`
* nor `styleUrls` the encapsulation will automatically be switched to `ViewEncapsulation.None`.

View File

@ -9,14 +9,14 @@
/**
* Defines template and style encapsulation options available for Component's {@link Component}.
*
* See {@link ViewMetadata#encapsulation}.
* See {@link Component#encapsulation}.
* @stable
*/
export enum ViewEncapsulation {
/**
* Emulate `Native` scoping of styles by adding an attribute containing surrogate id to the Host
* Element and pre-processing the style rules provided via
* {@link ViewMetadata#styles} or {@link ViewMetadata#stylesUrls}, and adding the new Host Element
* {@link Component#styles} or {@link Component#styleUrls}, and adding the new Host Element
* attribute to all selectors.
*
* This is the default option.
@ -61,19 +61,19 @@ export enum ViewEncapsulation {
* {@link Component}
*/
export class ViewMetadata {
/** {@link Component.templateUrl} */
/** {@link Component#templateUrl} */
templateUrl: string|undefined;
/** {@link Component.template} */
/** {@link Component#template} */
template: string|undefined;
/** {@link Component.stylesUrl} */
/** {@link Component#stylesUrl} */
styleUrls: string[]|undefined;
/** {@link Component.styles} */
/** {@link Component#styles} */
styles: string[]|undefined;
/** {@link Component.encapsulation} */
/** {@link Component#encapsulation} */
encapsulation: ViewEncapsulation|undefined;
/** {@link Component.animation} */
/** {@link Component#animation} */
animations: any[]|undefined;
/** {@link Component.interpolation} */
/** {@link Component#interpolation} */
interpolation: [string, string]|undefined;
constructor(

View File

@ -99,7 +99,8 @@ export const Renderer2Interceptor = new InjectionToken<Renderer2[]>('Renderer2In
*
* Use this service to bypass Angular's templating and make custom UI changes that can't be
* expressed declaratively. For example if you need to set a property or an attribute whose name is
* not statically known, use {@link #setElementProperty} or {@link #setElementAttribute}
* not statically known, use {@link Renderer#setElementProperty} or {@link
* Renderer#setElementAttribute}
* respectively.
*
* If you are implementing a custom renderer, you must implement this interface.

View File

@ -189,7 +189,14 @@ export function listenToElementOutputs(view: ViewData, compView: ViewData, def:
}
function renderEventHandlerClosure(view: ViewData, index: number, eventName: string) {
return (event: any) => dispatchEvent(view, index, eventName, event);
return (event: any) => {
try {
return dispatchEvent(view, index, eventName, event);
} catch (e) {
// Attention: Don't rethrow, to keep in sync with directive events.
view.root.errorHandler.handleError(e);
}
}
}

View File

@ -148,7 +148,14 @@ export function createDirectiveInstance(view: ViewData, def: NodeDef): any {
}
function eventHandlerClosure(view: ViewData, index: number, eventName: string) {
return (event: any) => dispatchEvent(view, index, eventName, event);
return (event: any) => {
try {
return dispatchEvent(view, index, eventName, event);
} catch (e) {
// Attention: Don't rethrow, as it would cancel Observable subscriptions!
view.root.errorHandler.handleError(e);
}
}
}
export function checkAndUpdateDirectiveInline(
@ -355,6 +362,12 @@ export function resolveDep(
}
const tokenKey = depDef.tokenKey;
if (tokenKey === ChangeDetectorRefTokenKey) {
// directives on the same element as a component should be able to control the change detector
// of that component as well.
allowPrivateServices = !!(elDef && elDef.element !.componentView);
}
if (elDef && (depDef.flags & DepFlags.SkipSelf)) {
allowPrivateServices = false;
elDef = elDef.parent !;

View File

@ -90,7 +90,9 @@ class ComponentFactory_ extends ComponentFactory<any> {
const view = Services.createRootView(
injector, projectableNodes || [], rootSelectorOrNode, viewDef, ngModule, EMPTY_CONTEXT);
const component = asProviderData(view, componentNodeIndex).instance;
view.renderer.setAttribute(asElementData(view, 0).renderElement, 'ng-version', VERSION.full);
if (rootSelectorOrNode) {
view.renderer.setAttribute(asElementData(view, 0).renderElement, 'ng-version', VERSION.full);
}
return new ComponentRef_(view, new ViewRef_(view), component);
}
@ -236,11 +238,11 @@ export class ViewRef_ implements EmbeddedViewRef<any>, InternalViewRef {
get destroyed(): boolean { return (this._view.state & ViewState.Destroyed) !== 0; }
markForCheck(): void { markParentViewsForCheck(this._view); }
detach(): void { this._view.state &= ~ViewState.ChecksEnabled; }
detach(): void { this._view.state &= ~ViewState.Attached; }
detectChanges(): void { Services.checkAndUpdateView(this._view); }
checkNoChanges(): void { Services.checkNoChangesView(this._view); }
reattach(): void { this._view.state |= ViewState.ChecksEnabled; }
reattach(): void { this._view.state |= ViewState.Attached; }
onDestroy(callback: Function) {
if (!this._view.disposables) {
this._view.disposables = [];

View File

@ -9,6 +9,7 @@
import {isDevMode} from '../application_ref';
import {DebugElement, DebugNode, EventListener, getDebugNode, indexDebugNode, removeDebugNodeFromIndex} from '../debug/debug_node';
import {Injector} from '../di';
import {ErrorHandler} from '../error_handler';
import {NgModuleRef} from '../linker/ng_module_factory';
import {Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2} from '../render/api';
import {Sanitizer} from '../security';
@ -104,11 +105,12 @@ function createRootData(
elInjector: Injector, ngModule: NgModuleRef<any>, rendererFactory: RendererFactory2,
projectableNodes: any[][], rootSelectorOrNode: any): RootData {
const sanitizer = ngModule.injector.get(Sanitizer);
const errorHandler = ngModule.injector.get(ErrorHandler);
const renderer = rendererFactory.createRenderer(null, null);
return {
ngModule,
injector: elInjector, projectableNodes,
selectorOrNode: rootSelectorOrNode, sanitizer, rendererFactory, renderer
selectorOrNode: rootSelectorOrNode, sanitizer, rendererFactory, renderer, errorHandler
};
}
@ -439,7 +441,6 @@ function callWithDebugContext(action: DebugAction, fn: any, self: any, args: any
if (isViewDebugError(e) || !_currentView) {
throw e;
}
_currentView.state |= ViewState.Errored;
throw viewWrappedDebugError(e, getCurrentDebugContext() !);
}
}

View File

@ -7,6 +7,7 @@
*/
import {Injector} from '../di';
import {ErrorHandler} from '../error_handler';
import {NgModuleRef} from '../linker/ng_module_factory';
import {QueryList} from '../linker/query_list';
import {TemplateRef} from '../linker/template_ref';
@ -140,37 +141,38 @@ export const enum NodeFlags {
None = 0,
TypeElement = 1 << 0,
TypeText = 1 << 1,
ProjectedTemplate = 1 << 2,
CatRenderNode = TypeElement | TypeText,
TypeNgContent = 1 << 2,
TypePipe = 1 << 3,
TypePureArray = 1 << 4,
TypePureObject = 1 << 5,
TypePurePipe = 1 << 6,
TypeNgContent = 1 << 3,
TypePipe = 1 << 4,
TypePureArray = 1 << 5,
TypePureObject = 1 << 6,
TypePurePipe = 1 << 7,
CatPureExpression = TypePureArray | TypePureObject | TypePurePipe,
TypeValueProvider = 1 << 7,
TypeClassProvider = 1 << 8,
TypeFactoryProvider = 1 << 9,
TypeUseExistingProvider = 1 << 10,
LazyProvider = 1 << 11,
PrivateProvider = 1 << 12,
TypeDirective = 1 << 13,
Component = 1 << 14,
TypeValueProvider = 1 << 8,
TypeClassProvider = 1 << 9,
TypeFactoryProvider = 1 << 10,
TypeUseExistingProvider = 1 << 11,
LazyProvider = 1 << 12,
PrivateProvider = 1 << 13,
TypeDirective = 1 << 14,
Component = 1 << 15,
CatProvider = TypeValueProvider | TypeClassProvider | TypeFactoryProvider |
TypeUseExistingProvider | TypeDirective,
OnInit = 1 << 15,
OnDestroy = 1 << 16,
DoCheck = 1 << 17,
OnChanges = 1 << 18,
AfterContentInit = 1 << 19,
AfterContentChecked = 1 << 20,
AfterViewInit = 1 << 21,
AfterViewChecked = 1 << 22,
EmbeddedViews = 1 << 23,
ComponentView = 1 << 24,
TypeContentQuery = 1 << 25,
TypeViewQuery = 1 << 26,
StaticQuery = 1 << 27,
DynamicQuery = 1 << 28,
OnInit = 1 << 16,
OnDestroy = 1 << 17,
DoCheck = 1 << 18,
OnChanges = 1 << 19,
AfterContentInit = 1 << 20,
AfterContentChecked = 1 << 21,
AfterViewInit = 1 << 22,
AfterViewChecked = 1 << 23,
EmbeddedViews = 1 << 24,
ComponentView = 1 << 25,
TypeContentQuery = 1 << 26,
TypeViewQuery = 1 << 27,
StaticQuery = 1 << 28,
DynamicQuery = 1 << 29,
CatQuery = TypeContentQuery | TypeViewQuery,
// mutually exclusive values...
@ -323,10 +325,17 @@ export interface ViewData {
* Bitmask of states
*/
export const enum ViewState {
FirstCheck = 1 << 0,
ChecksEnabled = 1 << 1,
Errored = 1 << 2,
Destroyed = 1 << 3
BeforeFirstCheck = 1 << 0,
FirstCheck = 1 << 1,
Attached = 1 << 2,
ChecksEnabled = 1 << 3,
IsProjectedView = 1 << 4,
CheckProjectedView = 1 << 5,
CheckProjectedViews = 1 << 6,
Destroyed = 1 << 7,
CatDetectChanges = Attached | ChecksEnabled,
CatInit = BeforeFirstCheck | CatDetectChanges
}
export interface DisposableFn { (): void; }
@ -429,6 +438,7 @@ export interface RootData {
selectorOrNode: any;
renderer: Renderer2;
rendererFactory: RendererFactory2;
errorHandler: ErrorHandler;
sanitizer: Sanitizer;
}

View File

@ -100,10 +100,10 @@ export function checkAndUpdateBinding(
export function checkBindingNoChanges(
view: ViewData, def: NodeDef, bindingIdx: number, value: any) {
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
if ((view.state & ViewState.FirstCheck) || !devModeEqual(oldValue, value)) {
if ((view.state & ViewState.BeforeFirstCheck) || !devModeEqual(oldValue, value)) {
throw expressionChangedAfterItHasBeenCheckedError(
Services.createDebugContext(view, def.index), oldValue, value,
(view.state & ViewState.FirstCheck) !== 0);
(view.state & ViewState.BeforeFirstCheck) !== 0);
}
}
@ -117,6 +117,14 @@ export function markParentViewsForCheck(view: ViewData) {
}
}
export function markParentViewsForCheckProjectedViews(view: ViewData, endView: ViewData) {
let currView: ViewData|null = view;
while (currView && currView !== endView) {
currView.state |= ViewState.CheckProjectedViews;
currView = currView.viewContainerParent || currView.parent;
}
}
export function dispatchEvent(
view: ViewData, nodeIndex: number, eventName: string, event: any): boolean {
const nodeDef = view.def.nodes[nodeIndex];

View File

@ -17,7 +17,8 @@ import {checkAndUpdateQuery, createQuery} from './query';
import {createTemplateData, createViewContainerData} from './refs';
import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text';
import {ArgumentType, CheckType, ElementData, NodeData, NodeDef, NodeFlags, ProviderData, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewState, ViewUpdateFn, asElementData, asQueryList, asTextData} from './types';
import {NOOP, checkBindingNoChanges, isComponentView, resolveViewDefinition} from './util';
import {NOOP, checkBindingNoChanges, isComponentView, markParentViewsForCheckProjectedViews, resolveViewDefinition} from './util';
import {detachProjectedView} from './view_attach';
export function viewDef(
flags: ViewFlags, nodes: NodeDef[], updateDirectives?: ViewUpdateFn,
@ -211,7 +212,7 @@ function createView(
viewContainerParent: null, parentNodeDef,
context: null,
component: null, nodes,
state: ViewState.FirstCheck | ViewState.ChecksEnabled, root, renderer,
state: ViewState.CatInit, root, renderer,
oldValues: new Array(def.bindingCount), disposables
};
return view;
@ -314,15 +315,24 @@ function createViewNodes(view: ViewData) {
}
export function checkNoChangesView(view: ViewData) {
markProjectedViewsForCheck(view);
Services.updateDirectives(view, CheckType.CheckNoChanges);
execEmbeddedViewsAction(view, ViewAction.CheckNoChanges);
Services.updateRenderer(view, CheckType.CheckNoChanges);
execComponentViewsAction(view, ViewAction.CheckNoChanges);
// Note: We don't check queries for changes as we didn't do this in v2.x.
// TODO(tbosch): investigate if we can enable the check again in v5.x with a nicer error message.
view.state &= ~(ViewState.CheckProjectedViews | ViewState.CheckProjectedView);
}
export function checkAndUpdateView(view: ViewData) {
if (view.state & ViewState.BeforeFirstCheck) {
view.state &= ~ViewState.BeforeFirstCheck;
view.state |= ViewState.FirstCheck;
} else {
view.state &= ~ViewState.FirstCheck;
}
markProjectedViewsForCheck(view);
Services.updateDirectives(view, CheckType.CheckAndUpdate);
execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
execQueriesAction(
@ -337,7 +347,6 @@ export function checkAndUpdateView(view: ViewData) {
execComponentViewsAction(view, ViewAction.CheckAndUpdate);
execQueriesAction(
view, NodeFlags.TypeViewQuery, NodeFlags.DynamicQuery, CheckType.CheckAndUpdate);
callLifecycleHooksChildrenFirst(
view, NodeFlags.AfterViewChecked |
(view.state & ViewState.FirstCheck ? NodeFlags.AfterViewInit : 0));
@ -345,7 +354,7 @@ export function checkAndUpdateView(view: ViewData) {
if (view.def.flags & ViewFlags.OnPush) {
view.state &= ~ViewState.ChecksEnabled;
}
view.state &= ~ViewState.FirstCheck;
view.state &= ~(ViewState.CheckProjectedViews | ViewState.CheckProjectedView);
}
export function checkAndUpdateNode(
@ -358,6 +367,31 @@ export function checkAndUpdateNode(
}
}
function markProjectedViewsForCheck(view: ViewData) {
const def = view.def;
if (!(def.nodeFlags & NodeFlags.ProjectedTemplate)) {
return;
}
for (let i = 0; i < def.nodes.length; i++) {
const nodeDef = def.nodes[i];
if (nodeDef.flags & NodeFlags.ProjectedTemplate) {
const projectedViews = asElementData(view, i).template._projectedViews;
if (projectedViews) {
for (let i = 0; i < projectedViews.length; i++) {
const projectedView = projectedViews[i];
projectedView.state |= ViewState.CheckProjectedView;
markParentViewsForCheckProjectedViews(projectedView, view);
}
}
} else if ((nodeDef.childFlags & NodeFlags.ProjectedTemplate) === 0) {
// a parent with leafs
// no child is a component,
// then skip the children
i += nodeDef.childCount;
}
}
}
function checkAndUpdateNodeInline(
view: ViewData, nodeDef: NodeDef, v0?: any, v1?: any, v2?: any, v3?: any, v4?: any, v5?: any,
v6?: any, v7?: any, v8?: any, v9?: any): boolean {
@ -453,7 +487,7 @@ function checkNoChangesQuery(view: ViewData, nodeDef: NodeDef) {
if (queryList.dirty) {
throw expressionChangedAfterItHasBeenCheckedError(
Services.createDebugContext(view, nodeDef.index), `Query ${nodeDef.query!.id} not dirty`,
`Query ${nodeDef.query!.id} dirty`, (view.state & ViewState.FirstCheck) !== 0);
`Query ${nodeDef.query!.id} dirty`, (view.state & ViewState.BeforeFirstCheck) !== 0);
}
}
@ -469,6 +503,7 @@ export function destroyView(view: ViewData) {
view.disposables[i]();
}
}
detachProjectedView(view);
if (view.renderer.destroyNode) {
destroyViewNodes(view);
}
@ -493,7 +528,9 @@ function destroyViewNodes(view: ViewData) {
enum ViewAction {
CreateViewNodes,
CheckNoChanges,
CheckNoChangesProjectedViews,
CheckAndUpdate,
CheckAndUpdateProjectedViews,
Destroy
}
@ -542,18 +579,44 @@ function callViewAction(view: ViewData, action: ViewAction) {
const viewState = view.state;
switch (action) {
case ViewAction.CheckNoChanges:
if ((viewState & ViewState.ChecksEnabled) &&
(viewState & (ViewState.Errored | ViewState.Destroyed)) === 0) {
checkNoChangesView(view);
if ((viewState & ViewState.Destroyed) === 0) {
if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges) {
checkNoChangesView(view);
} else if (viewState & ViewState.CheckProjectedViews) {
execProjectedViewsAction(view, ViewAction.CheckNoChangesProjectedViews);
}
}
break;
case ViewAction.CheckNoChangesProjectedViews:
if ((viewState & ViewState.Destroyed) === 0) {
if (viewState & ViewState.CheckProjectedView) {
checkNoChangesView(view);
} else if (viewState & ViewState.CheckProjectedViews) {
execProjectedViewsAction(view, action);
}
}
break;
case ViewAction.CheckAndUpdate:
if ((viewState & ViewState.ChecksEnabled) &&
(viewState & (ViewState.Errored | ViewState.Destroyed)) === 0) {
checkAndUpdateView(view);
if ((viewState & ViewState.Destroyed) === 0) {
if ((viewState & ViewState.CatDetectChanges) === ViewState.CatDetectChanges) {
checkAndUpdateView(view);
} else if (viewState & ViewState.CheckProjectedViews) {
execProjectedViewsAction(view, ViewAction.CheckAndUpdateProjectedViews);
}
}
break;
case ViewAction.CheckAndUpdateProjectedViews:
if ((viewState & ViewState.Destroyed) === 0) {
if (viewState & ViewState.CheckProjectedView) {
checkAndUpdateView(view);
} else if (viewState & ViewState.CheckProjectedViews) {
execProjectedViewsAction(view, action);
}
}
break;
case ViewAction.Destroy:
// Note: destroyView recurses over all views,
// so we don't need to special case projected views here.
destroyView(view);
break;
case ViewAction.CreateViewNodes:
@ -562,6 +625,11 @@ function callViewAction(view: ViewData, action: ViewAction) {
}
}
function execProjectedViewsAction(view: ViewData, action: ViewAction) {
execEmbeddedViewsAction(view, action);
execComponentViewsAction(view, action);
}
function execQueriesAction(
view: ViewData, queryFlags: NodeFlags, staticDynamicQueryFlag: NodeFlags,
checkType: CheckType) {

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ElementData, Services, ViewData} from './types';
import {RenderNodeAction, declaredViewContainer, renderNode, visitRootRenderNodes} from './util';
import {ElementData, NodeDef, NodeFlags, Services, ViewData, ViewDefinition, ViewState} from './types';
import {RenderNodeAction, declaredViewContainer, isComponentView, renderNode, visitRootRenderNodes} from './util';
export function attachEmbeddedView(
parentView: ViewData, elementData: ElementData, viewIndex: number | undefined | null,
@ -18,14 +18,7 @@ export function attachEmbeddedView(
}
view.viewContainerParent = parentView;
addToArray(embeddedViews, viewIndex !, view);
const dvcElementData = declaredViewContainer(view);
if (dvcElementData && dvcElementData !== elementData) {
let projectedViews = dvcElementData.template._projectedViews;
if (!projectedViews) {
projectedViews = dvcElementData.template._projectedViews = [];
}
projectedViews.push(view);
}
attachProjectedView(elementData, view);
Services.dirtyParentQueries(view);
@ -33,6 +26,43 @@ export function attachEmbeddedView(
renderAttachEmbeddedView(elementData, prevView, view);
}
function attachProjectedView(vcElementData: ElementData, view: ViewData) {
const dvcElementData = declaredViewContainer(view);
if (!dvcElementData || dvcElementData === vcElementData ||
view.state & ViewState.IsProjectedView) {
return;
}
// Note: For performance reasons, we
// - add a view to template._projectedViews only 1x throughout its lifetime,
// and remove it not until the view is destroyed.
// (hard, as when a parent view is attached/detached we would need to attach/detach all
// nested projected views as well, even accross component boundaries).
// - don't track the insertion order of views in the projected views array
// (hard, as when the views of the same template are inserted different view containers)
view.state |= ViewState.IsProjectedView;
let projectedViews = dvcElementData.template._projectedViews;
if (!projectedViews) {
projectedViews = dvcElementData.template._projectedViews = [];
}
projectedViews.push(view);
// Note: we are changing the NodeDef here as we cannot calculate
// the fact whether a template is used for projection during compilation.
markNodeAsProjectedTemplate(view.parent !.def, view.parentNodeDef !);
}
function markNodeAsProjectedTemplate(viewDef: ViewDefinition, nodeDef: NodeDef) {
if (nodeDef.flags & NodeFlags.ProjectedTemplate) {
return;
}
viewDef.nodeFlags |= NodeFlags.ProjectedTemplate;
nodeDef.flags |= NodeFlags.ProjectedTemplate;
let parentNodeDef = nodeDef.parent;
while (parentNodeDef) {
parentNodeDef.childFlags |= NodeFlags.ProjectedTemplate;
parentNodeDef = parentNodeDef.parent;
}
}
export function detachEmbeddedView(elementData: ElementData, viewIndex?: number): ViewData|null {
const embeddedViews = elementData.viewContainer !._embeddedViews;
if (viewIndex == null || viewIndex >= embeddedViews.length) {
@ -45,12 +75,7 @@ export function detachEmbeddedView(elementData: ElementData, viewIndex?: number)
view.viewContainerParent = null;
removeFromArray(embeddedViews, viewIndex);
const dvcElementData = declaredViewContainer(view);
if (dvcElementData && dvcElementData !== elementData) {
const projectedViews = dvcElementData.template._projectedViews;
removeFromArray(projectedViews, projectedViews.indexOf(view));
}
// See attachProjectedView for why we don't update projectedViews here.
Services.dirtyParentQueries(view);
renderDetachView(view);
@ -58,6 +83,20 @@ export function detachEmbeddedView(elementData: ElementData, viewIndex?: number)
return view;
}
export function detachProjectedView(view: ViewData) {
if (!(view.state & ViewState.IsProjectedView)) {
return;
}
const dvcElementData = declaredViewContainer(view);
if (dvcElementData) {
const projectedViews = dvcElementData.template._projectedViews;
if (projectedViews) {
removeFromArray(projectedViews, projectedViews.indexOf(view));
Services.dirtyParentQueries(view);
}
}
}
export function moveEmbeddedView(
elementData: ElementData, oldViewIndex: number, newViewIndex: number): ViewData {
const embeddedViews = elementData.viewContainer !._embeddedViews;

View File

@ -13,8 +13,8 @@ import {EventEmitter} from '../event_emitter';
*
* The most common use of this service is to optimize performance when starting a work consisting of
* one or more asynchronous tasks that don't require UI updates or error handling to be handled by
* Angular. Such tasks can be kicked off via {@link runOutsideAngular} and if needed, these tasks
* can reenter the Angular zone via {@link run}.
* Angular. Such tasks can be kicked off via {@link #runOutsideAngular} and if needed, these tasks
* can reenter the Angular zone via {@link #run}.
*
* <!-- TODO: add/fix links to:
* - docs explaining zones and the use of zones in Angular and change-detection
@ -132,7 +132,7 @@ export class NgZone {
* the function.
*
* Running functions via `run` allows you to reenter Angular zone from a task that was executed
* outside of the Angular zone (typically started via {@link runOutsideAngular}).
* outside of the Angular zone (typically started via {@link #runOutsideAngular}).
*
* Any future tasks or microtasks scheduled from within this function will continue executing from
* within the Angular zone.
@ -151,13 +151,14 @@ export class NgZone {
* Executes the `fn` function synchronously in Angular's parent zone and returns value returned by
* the function.
*
* Running functions via `runOutsideAngular` allows you to escape Angular's zone and do work that
* Running functions via {@link #runOutsideAngular} allows you to escape Angular's zone and do
* work that
* doesn't trigger Angular change-detection or is subject to Angular's error handling.
*
* Any future tasks or microtasks scheduled from within this function will continue executing from
* outside of the Angular zone.
*
* Use {@link run} to reenter the Angular zone and do work that updates the application model.
* Use {@link #run} to reenter the Angular zone and do work that updates the application model.
*/
runOutsideAngular(fn: () => any): any { return this.outer.run(fn); }

View File

@ -5,6 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Injector} from '@angular/core';
import {APP_INITIALIZER, ApplicationInitStatus} from '../src/application_init';
import {TestBed, async, inject} from '../testing';
@ -14,11 +15,13 @@ export function main() {
it('should return true for `done`',
async(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
status.runInitializers();
expect(status.done).toBe(true);
})));
it('should return a promise that resolves immediately for `donePromise`',
async(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
status.runInitializers();
status.donePromise.then(() => { expect(status.done).toBe(true); });
})));
});
@ -26,15 +29,32 @@ export function main() {
describe('with async initializers', () => {
let resolve: (result: any) => void;
let promise: Promise<any>;
let completerResolver = false;
beforeEach(() => {
let initializerFactory = (injector: Injector) => {
return () => {
const initStatus = injector.get(ApplicationInitStatus);
initStatus.donePromise.then(() => { expect(completerResolver).toBe(true); });
}
};
promise = new Promise((res) => { resolve = res; });
TestBed.configureTestingModule(
{providers: [{provide: APP_INITIALIZER, multi: true, useValue: () => promise}]});
TestBed.configureTestingModule({
providers: [
{provide: APP_INITIALIZER, multi: true, useValue: () => promise},
{
provide: APP_INITIALIZER,
multi: true,
useFactory: initializerFactory,
deps: [Injector]
},
]
});
});
it('should update the status once all async initializers are done',
async(inject([ApplicationInitStatus], (status: ApplicationInitStatus) => {
let completerResolver = false;
status.runInitializers();
setTimeout(() => {
completerResolver = true;
resolve(null);

View File

@ -103,20 +103,33 @@ export function main() {
describe('ApplicationRef', () => {
beforeEach(() => { TestBed.configureTestingModule({imports: [createModule()]}); });
it('should throw when reentering tick', inject([ApplicationRef], (ref: ApplicationRef_) => {
const view = jasmine.createSpyObj('view', ['detach', 'attachToAppRef']);
const viewRef = jasmine.createSpyObj(
'viewRef', ['detectChanges', 'detachFromAppRef', 'attachToAppRef']);
viewRef.internalView = view;
view.ref = viewRef;
try {
ref.attachView(viewRef);
viewRef.detectChanges.and.callFake(() => ref.tick());
expect(() => ref.tick()).toThrowError('ApplicationRef.tick is called recursively');
} finally {
ref.detachView(viewRef);
}
}));
it('should throw when reentering tick', () => {
@Component({template: '{{reenter()}}'})
class ReenteringComponent {
reenterCount = 1;
reenterErr: any;
constructor(private appRef: ApplicationRef) {}
reenter() {
if (this.reenterCount--) {
try {
this.appRef.tick();
} catch (e) {
this.reenterErr = e;
}
}
}
}
const fixture = TestBed.configureTestingModule({declarations: [ReenteringComponent]})
.createComponent(ReenteringComponent);
const appRef = TestBed.get(ApplicationRef) as ApplicationRef;
appRef.attachView(fixture.componentRef.hostView);
appRef.tick();
expect(fixture.componentInstance.reenterErr.message)
.toBe('ApplicationRef.tick is called recursively');
});
describe('APP_BOOTSTRAP_LISTENER', () => {
let capturedCompRefs: ComponentRef<any>[];
@ -184,11 +197,9 @@ export function main() {
[{provide: APP_INITIALIZER, useValue: () => { throw 'Test'; }, multi: true}]))
.then(() => expect(false).toBe(true), (e) => {
expect(e).toBe('Test');
// Note: if the modules throws an error during construction,
// we don't have an injector and therefore no way of
// getting the exception handler. So
// the error is only rethrown but not logged via the exception handler.
expect(mockConsole.res).toEqual([]);
// Error rethrown will be seen by the exception handler since it's after
// construction.
expect(mockConsole.res[0].join('#')).toEqual('ERROR#Test');
});
}));
@ -281,11 +292,9 @@ export function main() {
const moduleFactory = compilerFactory.createCompiler().compileModuleSync(createModule(
[{provide: APP_INITIALIZER, useValue: () => { throw 'Test'; }, multi: true}]));
expect(() => defaultPlatform.bootstrapModuleFactory(moduleFactory)).toThrow('Test');
// Note: if the modules throws an error during construction,
// we don't have an injector and therefore no way of
// getting the exception handler. So
// the error is only rethrown but not logged via the exception handler.
expect(mockConsole.res).toEqual([]);
// Error rethrown will be seen by the exception handler since it's after
// construction.
expect(mockConsole.res[0].join('#')).toEqual('ERROR#Test');
}));
it('should rethrow promise errors even if the exceptionHandler is not rethrowing',

View File

@ -8,11 +8,12 @@
import {ElementSchemaRegistry} from '@angular/compiler/src/schema/element_schema_registry';
import {TEST_COMPILER_PROVIDERS} from '@angular/compiler/testing/src/test_bindings';
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DebugElement, Directive, DoCheck, EventEmitter, HostBinding, Inject, Injectable, Input, OnChanges, OnDestroy, OnInit, Output, Pipe, PipeTransform, RenderComponentType, Renderer, RendererFactory2, RootRenderer, SimpleChange, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef, WrappedValue} from '@angular/core';
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, DebugElement, Directive, DoCheck, EventEmitter, HostBinding, Inject, Injectable, Input, OnChanges, OnDestroy, OnInit, Output, Pipe, PipeTransform, RenderComponentType, Renderer, RendererFactory2, RootRenderer, SimpleChange, SimpleChanges, TemplateRef, Type, ViewChild, ViewContainerRef, WrappedValue} from '@angular/core';
import {ComponentFixture, TestBed, fakeAsync} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {DomElementSchemaRegistry} from '../../../compiler/index';
import {MockSchemaRegistry} from '../../../compiler/testing/index';
@ -765,6 +766,7 @@ export function main() {
try {
ctx.detectChanges(false);
} catch (e) {
expect(e.message).toBe('Boom!');
errored = true;
}
expect(errored).toBe(true);
@ -776,7 +778,8 @@ export function main() {
try {
ctx.detectChanges(false);
} catch (e) {
throw new Error('Second detectChanges() should not have run detection.');
expect(e.message).toBe('Boom!');
throw new Error('Second detectChanges() should not have called ngOnInit.');
}
expect(directiveLog.filter(['ngOnInit'])).toEqual([]);
}));
@ -1175,6 +1178,21 @@ export function main() {
expect(renderLog.log).toEqual([]);
}));
it('Detached should disable OnPush', fakeAsync(() => {
const ctx = createCompFixture('<push-cmp [value]="value"></push-cmp>');
ctx.componentInstance.value = 0;
ctx.detectChanges();
renderLog.clear();
const cmp: CompWithRef = queryDirs(ctx.debugElement, PushComp)[0];
cmp.changeDetectorRef.detach();
ctx.componentInstance.value = 1;
ctx.detectChanges();
expect(renderLog.log).toEqual([]);
}));
it('Detached view can be checked locally', fakeAsync(() => {
const ctx = createCompFixture('<wrap-comp-with-ref></wrap-comp-with-ref>');
const cmp: CompWithRef = queryDirs(ctx.debugElement, CompWithRef)[0];
@ -1225,7 +1243,6 @@ export function main() {
ctx.detectChanges();
expect(cmp.renderCount).toBe(count);
}));
});
@ -1277,6 +1294,120 @@ export function main() {
ctx.detectChanges();
expect(renderLog.loggedValues).toEqual(['Tom']);
});
describe('projected views', () => {
let log: string[];
@Directive({selector: '[i]'})
class DummyDirective {
@Input()
i: any;
}
@Component({
selector: 'main-cmp',
template:
`<span [i]="log('start')"></span><outer-cmp><ng-template><span [i]="log('tpl')"></span></ng-template></outer-cmp>`
})
class MainComp {
constructor(public cdRef: ChangeDetectorRef) {}
log(id: string) { log.push(`main-${id}`); }
}
@Component({
selector: 'outer-cmp',
template:
`<span [i]="log('start')"></span><inner-cmp [outerTpl]="tpl"><ng-template><span [i]="log('tpl')"></span></ng-template></inner-cmp>`
})
class OuterComp {
@ContentChild(TemplateRef)
tpl: TemplateRef<any>;
constructor(public cdRef: ChangeDetectorRef) {}
log(id: string) { log.push(`outer-${id}`); }
}
@Component({
selector: 'inner-cmp',
template:
`<span [i]="log('start')"></span>><ng-container [ngTemplateOutlet]="outerTpl"></ng-container><ng-container [ngTemplateOutlet]="tpl"></ng-container>`
})
class InnerComp {
@ContentChild(TemplateRef)
tpl: TemplateRef<any>;
@Input()
outerTpl: TemplateRef<any>
constructor(public cdRef: ChangeDetectorRef) {}
log(id: string) { log.push(`inner-${id}`); }
}
let ctx: ComponentFixture<MainComp>;
let mainComp: MainComp;
let outerComp: OuterComp;
let innerComp: InnerComp;
beforeEach(() => {
log = [];
ctx = TestBed
.configureTestingModule(
{declarations: [MainComp, OuterComp, InnerComp, DummyDirective]})
.createComponent(MainComp);
mainComp = ctx.componentInstance;
outerComp = ctx.debugElement.query(By.directive(OuterComp)).injector.get(OuterComp);
innerComp = ctx.debugElement.query(By.directive(InnerComp)).injector.get(InnerComp);
});
it('should dirty check projected views in regular order', () => {
ctx.detectChanges(false);
expect(log).toEqual(
['main-start', 'outer-start', 'inner-start', 'main-tpl', 'outer-tpl']);
log = [];
ctx.detectChanges(false);
expect(log).toEqual(
['main-start', 'outer-start', 'inner-start', 'main-tpl', 'outer-tpl']);
});
it('should not dirty check projected views if neither the declaration nor the insertion place is dirty checked',
() => {
ctx.detectChanges(false);
log = [];
mainComp.cdRef.detach();
ctx.detectChanges(false);
expect(log).toEqual([]);
});
it('should dirty check projected views if the insertion place is dirty checked', () => {
ctx.detectChanges(false);
log = [];
innerComp.cdRef.detectChanges();
expect(log).toEqual(['inner-start', 'main-tpl', 'outer-tpl']);
});
it('should dirty check projected views if the declaration place is dirty checked', () => {
ctx.detectChanges(false);
log = [];
innerComp.cdRef.detach();
mainComp.cdRef.detectChanges();
expect(log).toEqual(['main-start', 'outer-start', 'main-tpl', 'outer-tpl']);
log = [];
outerComp.cdRef.detectChanges();
expect(log).toEqual(['outer-start', 'outer-tpl']);
log = [];
outerComp.cdRef.detach();
mainComp.cdRef.detectChanges();
expect(log).toEqual(['main-start', 'main-tpl']);
});
});
});
describe('class binding', () => {

View File

@ -7,7 +7,7 @@
*/
import {CommonModule} from '@angular/common';
import {Compiler, ComponentFactory, EventEmitter, Host, Inject, Injectable, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, NgModuleRef, OnDestroy, ReflectiveInjector, SkipSelf} from '@angular/core';
import {Compiler, ComponentFactory, ErrorHandler, EventEmitter, Host, Inject, Injectable, InjectionToken, Injector, NO_ERRORS_SCHEMA, NgModule, NgModuleRef, OnDestroy, ReflectiveInjector, SkipSelf} from '@angular/core';
import {ChangeDetectionStrategy, ChangeDetectorRef, PipeTransform} from '@angular/core/src/change_detection/change_detection';
import {getDebugContext} from '@angular/core/src/errors';
import {ComponentFactoryResolver} from '@angular/core/src/linker/component_factory_resolver';
@ -1480,16 +1480,18 @@ function declareTests({useJit}: {useJit: boolean}) {
const tc = fixture.debugElement.children[0];
try {
tc.injector.get(DirectiveEmittingEvent).fireEvent('boom');
} catch (e) {
const c = getDebugContext(e);
expect(getDOM().nodeName(c.renderNode).toUpperCase()).toEqual('SPAN');
expect(getDOM().nodeName(c.componentRenderElement).toUpperCase()).toEqual('DIV');
expect((<Injector>c.injector).get).toBeTruthy();
expect(c.context).toBe(fixture.componentInstance);
expect(c.references['local']).toBeDefined();
}
const errorHandler = tc.injector.get(ErrorHandler);
let err: any;
spyOn(errorHandler, 'handleError').and.callFake((e: any) => err = e);
tc.injector.get(DirectiveEmittingEvent).fireEvent('boom');
expect(err).toBeTruthy();
const c = getDebugContext(err);
expect(getDOM().nodeName(c.renderNode).toUpperCase()).toEqual('SPAN');
expect(getDOM().nodeName(c.componentRenderElement).toUpperCase()).toEqual('DIV');
expect((<Injector>c.injector).get).toBeTruthy();
expect(c.context).toBe(fixture.componentInstance);
expect(c.references['local']).toBeDefined();
}));
}
});

View File

@ -536,6 +536,52 @@ export function main() {
expect(q.query.length).toBe(0);
});
// Note: This tests is just document our current behavior, which we do
// for performance reasons.
it('should not affected queries for projected templates if views are detached or moved', () => {
const template =
'<manual-projecting #q><ng-template let-x="x"><div [text]="x"></div></ng-template></manual-projecting>';
const view = createTestCmpAndDetectChanges(MyComp0, template);
const q = view.debugElement.children[0].references !['q'] as ManualProjecting;
expect(q.query.length).toBe(0);
const view1 = q.vc.createEmbeddedView(q.template, {'x': '1'});
const view2 = q.vc.createEmbeddedView(q.template, {'x': '2'});
view.detectChanges();
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
q.vc.detach(1);
q.vc.detach(0);
view.detectChanges();
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
q.vc.insert(view2);
q.vc.insert(view1);
view.detectChanges();
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1', '2']);
});
it('should remove manually projected templates if their parent view is destroyed', () => {
const template = `
<manual-projecting #q><ng-template #tpl><div text="1"></div></ng-template></manual-projecting>
<div *ngIf="shouldShow">
<ng-container [ngTemplateOutlet]="tpl"></ng-container>
</div>
`;
const view = createTestCmp(MyComp0, template);
const q = view.debugElement.children[0].references !['q'];
view.componentInstance.shouldShow = true;
view.detectChanges();
expect(q.query.length).toBe(1);
view.componentInstance.shouldShow = false;
view.detectChanges();
expect(q.query.length).toBe(0);
});
it('should not throw if a content template is queried and created in the view during change detection',
() => {
@Component(

View File

@ -6,15 +6,19 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ANALYZE_FOR_ENTRY_COMPONENTS, Component, ContentChild, Directive, InjectionToken, Injector, Input, Pipe, PipeTransform, Provider, QueryList, Renderer2, SimpleChanges, TemplateRef, ViewChildren, ViewContainerRef} from '@angular/core';
import {TestBed, fakeAsync, tick} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {ANALYZE_FOR_ENTRY_COMPONENTS, ApplicationRef, Component, ComponentRef, ContentChild, Directive, ErrorHandler, EventEmitter, HostListener, InjectionToken, Injector, Input, NgModule, NgModuleRef, NgZone, Output, Pipe, PipeTransform, Provider, QueryList, Renderer2, SimpleChanges, TemplateRef, ViewChildren, ViewContainerRef, destroyPlatform} from '@angular/core';
import {TestBed, async, fakeAsync, inject, tick} from '@angular/core/testing';
import {BrowserModule, By, DOCUMENT} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/src/matchers';
export function main() {
describe('jit', () => { declareTests({useJit: true}); });
describe('no jit', () => { declareTests({useJit: false}); });
declareTestsUsingBootstrap();
}
function declareTests({useJit}: {useJit: boolean}) {
@ -365,6 +369,132 @@ function declareTests({useJit}: {useJit: boolean}) {
expect(testDirs[1].tpl).toBeDefined();
expect(testDirs[2].tpl).toBeDefined();
});
it('should not add ng-version for dynamically created components', () => {
@Component({template: ''})
class App {
}
@NgModule({declarations: [App], entryComponents: [App]})
class MyModule {
}
const modRef = TestBed.configureTestingModule({imports: [MyModule]})
.get(NgModuleRef) as NgModuleRef<MyModule>;
const compRef =
modRef.componentFactoryResolver.resolveComponentFactory(App).create(Injector.NULL);
expect(getDOM().hasAttribute(compRef.location.nativeElement, 'ng-version')).toBe(false);
});
});
}
function declareTestsUsingBootstrap() {
// Place to put reproductions for regressions
describe('regressions using bootstrap', () => {
const COMP_SELECTOR = 'root-comp';
class MockConsole {
errors: any[][] = [];
error(...s: any[]): void { this.errors.push(s); }
}
let logger: MockConsole;
let errorHandler: ErrorHandler;
beforeEach(inject([DOCUMENT], (doc: any) => {
destroyPlatform();
const el = getDOM().createElement(COMP_SELECTOR, doc);
getDOM().appendChild(doc.body, el);
logger = new MockConsole();
errorHandler = new ErrorHandler();
errorHandler._console = logger as any;
}));
afterEach(() => { destroyPlatform(); });
if (getDOM().supportsDOMEvents()) {
// This test needs a real DOM....
it('should keep change detecting if there was an error', (done) => {
@Component({
selector: COMP_SELECTOR,
template:
'<button (click)="next()"></button><button (click)="nextAndThrow()"></button><button (dirClick)="nextAndThrow()"></button><span>Value:{{value}}</span><span>{{throwIfNeeded()}}</span>'
})
class ErrorComp {
value = 0;
thrownValue = 0;
next() { this.value++; }
nextAndThrow() {
this.value++;
this.throwIfNeeded();
}
throwIfNeeded() {
NgZone.assertInAngularZone();
if (this.thrownValue !== this.value) {
this.thrownValue = this.value;
throw new Error(`Error: ${this.value}`);
}
}
}
@Directive({selector: '[dirClick]'})
class EventDir {
@Output()
dirClick = new EventEmitter();
@HostListener('click', ['$event'])
onClick(event: any) { this.dirClick.next(event); }
}
@NgModule({
imports: [BrowserModule],
declarations: [ErrorComp, EventDir],
bootstrap: [ErrorComp],
providers: [{provide: ErrorHandler, useValue: errorHandler}],
})
class TestModule {
}
platformBrowserDynamic().bootstrapModule(TestModule).then((ref) => {
NgZone.assertNotInAngularZone();
const appRef = ref.injector.get(ApplicationRef) as ApplicationRef;
const compRef = appRef.components[0] as ComponentRef<ErrorComp>;
const compEl = compRef.location.nativeElement;
const nextBtn = compEl.children[0];
const nextAndThrowBtn = compEl.children[1];
const nextAndThrowDirBtn = compEl.children[2];
nextBtn.click();
assertValueAndErrors(compEl, 1, 0);
nextBtn.click();
assertValueAndErrors(compEl, 2, 2);
nextAndThrowBtn.click();
assertValueAndErrors(compEl, 3, 4);
nextAndThrowBtn.click();
assertValueAndErrors(compEl, 4, 6);
nextAndThrowDirBtn.click();
assertValueAndErrors(compEl, 5, 8);
nextAndThrowDirBtn.click();
assertValueAndErrors(compEl, 6, 10);
// Assert that there were no more errors
expect(logger.errors.length).toBe(12);
done();
});
function assertValueAndErrors(compEl: any, value: number, errorIndex: number) {
expect(compEl).toHaveText(`Value:${value}`);
expect(logger.errors[errorIndex][0]).toBe('ERROR');
expect(logger.errors[errorIndex][1].message).toBe(`Error: ${value}`);
expect(logger.errors[errorIndex + 1][0]).toBe('ERROR CONTEXT');
}
});
}
});
}

View File

@ -10,7 +10,7 @@ import {ResourceLoader} from '@angular/compiler';
import {SourceMap} from '@angular/compiler/src/output/source_map';
import {extractSourceMap, originalPositionFor} from '@angular/compiler/test/output/source_map_util';
import {MockResourceLoader} from '@angular/compiler/testing/src/resource_loader_mock';
import {Attribute, Component, Directive, ɵglobal} from '@angular/core';
import {Attribute, Component, Directive, ErrorHandler, ɵglobal} from '@angular/core';
import {getErrorLogger} from '@angular/core/src/errors';
import {ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing';
@ -231,11 +231,10 @@ export function main() {
const comp = compileAndCreateComponent(MyComp);
let error: any;
try {
comp.debugElement.children[0].children[0].triggerEventHandler('click', 'EVENT');
} catch (e) {
error = e;
}
const errorHandler = TestBed.get(ErrorHandler);
spyOn(errorHandler, 'handleError').and.callFake((e: any) => error = e);
comp.debugElement.children[0].children[0].triggerEventHandler('click', 'EVENT');
expect(error).toBeTruthy();
// the stack should point to the binding
expect(getSourcePositionForStack(error.stack)).toEqual({
line: 2,

View File

@ -652,6 +652,46 @@ export function main() {
expect(compEl.nativeElement).toHaveText('1');
});
it('should inject ChangeDetectorRef of a same element component into a directive', () => {
TestBed.configureTestingModule(
{declarations: [PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef]});
const cf = createComponentFixture(
'<div componentNeedsChangeDetectorRef directiveNeedsChangeDetectorRef></div>');
cf.detectChanges();
const compEl = cf.debugElement.children[0];
const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef);
const dir = compEl.injector.get(DirectiveNeedsChangeDetectorRef);
comp.counter = 1;
cf.detectChanges();
expect(compEl.nativeElement).toHaveText('0');
dir.changeDetectorRef.markForCheck();
cf.detectChanges();
expect(compEl.nativeElement).toHaveText('1');
});
it(`should not inject ChangeDetectorRef of a parent element's component into a directive`, () => {
TestBed
.configureTestingModule({
declarations: [PushComponentNeedsChangeDetectorRef, DirectiveNeedsChangeDetectorRef]
})
.overrideComponent(
PushComponentNeedsChangeDetectorRef,
{set: {template: '<ng-content></ng-content>{{counter}}'}});
const cf = createComponentFixture(
'<div componentNeedsChangeDetectorRef><div directiveNeedsChangeDetectorRef></div></div>');
cf.detectChanges();
const compEl = cf.debugElement.children[0];
const comp = compEl.injector.get(PushComponentNeedsChangeDetectorRef);
const dirEl = compEl.children[0];
const dir = dirEl.injector.get(DirectiveNeedsChangeDetectorRef);
comp.counter = 1;
cf.detectChanges();
expect(compEl.nativeElement).toHaveText('0');
dir.changeDetectorRef.markForCheck();
cf.detectChanges();
expect(compEl.nativeElement).toHaveText('0');
});
it('should inject ViewContainerRef', () => {
TestBed.configureTestingModule({declarations: [NeedsViewContainerRef]});
const el = createComponent('<div needsViewContainerRef></div>');

View File

@ -230,7 +230,7 @@ export function main() {
});
}
it('should stop dirty checking views that threw errors in change detection', () => {
it('should not stop dirty checking views that threw errors in change detection', () => {
class AComp {
a: any;
}
@ -255,8 +255,8 @@ export function main() {
expect(update).toHaveBeenCalled();
update.calls.reset();
Services.checkAndUpdateView(view);
expect(update).not.toHaveBeenCalled();
expect(() => Services.checkAndUpdateView(view)).toThrowError('Test');
expect(update).toHaveBeenCalled();
});
});

View File

@ -6,9 +6,10 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core';
import {ErrorHandler, Injector, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core';
import {getDebugContext} from '@angular/core/src/errors';
import {ArgumentType, BindingFlags, DebugContext, NodeDef, NodeFlags, OutputType, RootData, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, elementDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
import {TestBed} from '@angular/core/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {ARG_TYPE_VALUES, checkNodeInlineOrDynamic, createRootView, isBrowser, removeNodes} from './helper';
@ -282,17 +283,14 @@ export function main() {
});
it('should report debug info on event errors', () => {
const handleErrorSpy = spyOn(TestBed.get(ErrorHandler), 'handleError');
const addListenerSpy = spyOn(HTMLElement.prototype, 'addEventListener').and.callThrough();
const {view, rootNodes} = createAndAttachAndGetRootNodes(compViewDef([elementDef(
NodeFlags.None, null !, null !, 0, 'button', null !, null !, [[null !, 'click']],
() => { throw new Error('Test'); })]));
let err: any;
try {
addListenerSpy.calls.mostRecent().args[1]('SomeEvent');
} catch (e) {
err = e;
}
addListenerSpy.calls.mostRecent().args[1]('SomeEvent');
const err = handleErrorSpy.calls.mostRecent().args[0];
expect(err).toBeTruthy();
expect(err.message).toBe('Test');
const debugCtx = getDebugContext(err);

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectorRef, DoCheck, ElementRef, EventEmitter, Injector, OnChanges, OnDestroy, OnInit, RenderComponentType, Renderer, Renderer2, RootRenderer, Sanitizer, SecurityContext, SimpleChange, TemplateRef, ViewContainerRef, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core';
import {AfterContentChecked, AfterContentInit, AfterViewChecked, AfterViewInit, ChangeDetectorRef, DoCheck, ElementRef, ErrorHandler, EventEmitter, Injector, OnChanges, OnDestroy, OnInit, RenderComponentType, Renderer, Renderer2, RootRenderer, Sanitizer, SecurityContext, SimpleChange, TemplateRef, ViewContainerRef, ViewEncapsulation, WrappedValue, getDebugNode} from '@angular/core';
import {getDebugContext} from '@angular/core/src/errors';
import {ArgumentType, BindingFlags, DebugContext, DepFlags, NodeDef, NodeFlags, RootData, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, asElementData, asProviderData, directiveDef, elementDef, providerDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
import {TestBed, inject, withModule} from '@angular/core/testing';
@ -381,6 +381,7 @@ export function main() {
});
it('should report debug info on event errors', () => {
const handleErrorSpy = spyOn(TestBed.get(ErrorHandler), 'handleError');
let emitter = new EventEmitter<any>();
class SomeService {
@ -395,12 +396,8 @@ export function main() {
NodeFlags.None, null !, 0, SomeService, [], null !, {emitter: 'someEventName'})
]));
let err: any;
try {
emitter.emit('someEventInstance');
} catch (e) {
err = e;
}
emitter.emit('someEventInstance');
const err = handleErrorSpy.calls.mostRecent().args[0];
expect(err).toBeTruthy();
const debugCtx = getDebugContext(err);
expect(debugCtx.view).toBe(view);

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {CompilerOptions, Component, Directive, InjectionToken, Injector, ModuleWithComponentFactories, NgModule, NgModuleRef, NgZone, Pipe, PlatformRef, Provider, ReflectiveInjector, SchemaMetadata, Type, ɵERROR_COMPONENT_TYPE, ɵstringify as stringify} from '@angular/core';
import {ApplicationInitStatus, CompilerOptions, Component, Directive, InjectionToken, Injector, ModuleWithComponentFactories, NgModule, NgModuleRef, NgZone, Pipe, PlatformRef, Provider, ReflectiveInjector, SchemaMetadata, Type, ɵERROR_COMPONENT_TYPE, ɵstringify as stringify} from '@angular/core';
import {AsyncTestCompleter} from './async_test_completer';
import {ComponentFixture} from './component_fixture';
import {MetadataOverride} from './metadata_override';
@ -283,6 +283,9 @@ export class TestBed implements Injector {
const ngZoneInjector = ReflectiveInjector.resolveAndCreate(
[{provide: NgZone, useValue: ngZone}], this.platform.injector);
this._moduleRef = this._moduleWithComponentFactories.ngModuleFactory.create(ngZoneInjector);
// ApplicationInitStatus.runInitializers() is marked @internal to core. So casting to any
// before accessing it.
(this._moduleRef.injector.get(ApplicationInitStatus) as any).runInitializers();
this._instantiated = true;
}

View File

@ -59,7 +59,7 @@ const resolvedPromise = Promise.resolve(null);
* This directive can be used by itself or as part of a larger form. All you need is the
* `ngModel` selector to activate it.
*
* It accepts a domain model as an optional {@link @Input}. If you have a one-way binding
* It accepts a domain model as an optional {@link Input}. If you have a one-way binding
* to `ngModel` with `[]` syntax, changing the value of the domain model in the component
* class will set the value in the view. If you have a two-way binding with `[()]` syntax
* (also known as 'banana-box syntax'), the value in the UI will always be synced back to

View File

@ -45,12 +45,12 @@ export const formControlBinding: any = {
* {@link AbstractControl}.
*
* **Set the value**: You can pass in an initial value when instantiating the {@link FormControl},
* or you can set it programmatically later using {@link AbstractControl.setValue} or
* {@link AbstractControl.patchValue}.
* or you can set it programmatically later using {@link AbstractControl#setValue} or
* {@link AbstractControl#patchValue}.
*
* **Listen to value**: If you want to listen to changes in the value of the control, you can
* subscribe to the {@link AbstractControl.valueChanges} event. You can also listen to
* {@link AbstractControl.statusChanges} to be notified when the validation status is
* subscribe to the {@link AbstractControl#valueChanges} event. You can also listen to
* {@link AbstractControl#statusChanges} to be notified when the validation status is
* re-calculated.
*
* ### Example

View File

@ -45,7 +45,7 @@ export const controlNameBinding: any = {
* closest {@link FormGroup} or {@link FormArray} above it.
*
* **Access the control**: You can access the {@link FormControl} associated with
* this directive by using the {@link AbstractControl.get} method.
* this directive by using the {@link AbstractControl#get} method.
* Ex: `this.form.get('first');`
*
* **Get value**: the `value` property is always synced and available on the {@link FormControl}.
@ -53,11 +53,11 @@ export const controlNameBinding: any = {
*
* **Set value**: You can set an initial value for the control when instantiating the
* {@link FormControl}, or you can set it programmatically later using
* {@link AbstractControl.setValue} or {@link AbstractControl.patchValue}.
* {@link AbstractControl#setValue} or {@link AbstractControl#patchValue}.
*
* **Listen to value**: If you want to listen to changes in the value of the control, you can
* subscribe to the {@link AbstractControl.valueChanges} event. You can also listen to
* {@link AbstractControl.statusChanges} to be notified when the validation status is
* subscribe to the {@link AbstractControl#valueChanges} event. You can also listen to
* {@link AbstractControl#statusChanges} to be notified when the validation status is
* re-calculated.
*
* ### Example

View File

@ -34,11 +34,11 @@ export const formDirectiveProvider: any = {
*
* **Set value**: You can set the form's initial value when instantiating the
* {@link FormGroup}, or you can set it programmatically later using the {@link FormGroup}'s
* {@link AbstractControl.setValue} or {@link AbstractControl.patchValue} methods.
* {@link AbstractControl#setValue} or {@link AbstractControl#patchValue} methods.
*
* **Listen to value**: If you want to listen to changes in the value of the form, you can subscribe
* to the {@link FormGroup}'s {@link AbstractControl.valueChanges} event. You can also listen to
* its {@link AbstractControl.statusChanges} event to be notified when the validation status is
* to the {@link FormGroup}'s {@link AbstractControl#valueChanges} event. You can also listen to
* its {@link AbstractControl#statusChanges} event to be notified when the validation status is
* re-calculated.
*
* Furthermore, you can listen to the directive's `ngSubmit` event to be notified when the user has

View File

@ -40,7 +40,7 @@ export const formGroupNameProvider: any = {
* controls into their own nested object.
*
* **Access the group**: You can access the associated {@link FormGroup} using the
* {@link AbstractControl.get} method. Ex: `this.form.get('name')`.
* {@link AbstractControl#get} method. Ex: `this.form.get('name')`.
*
* You can also access individual controls within the group using dot syntax.
* Ex: `this.form.get('name.first')`
@ -50,11 +50,11 @@ export const formGroupNameProvider: any = {
*
* **Set the value**: You can set an initial value for each child control when instantiating
* the {@link FormGroup}, or you can set it programmatically later using
* {@link AbstractControl.setValue} or {@link AbstractControl.patchValue}.
* {@link AbstractControl#setValue} or {@link AbstractControl#patchValue}.
*
* **Listen to value**: If you want to listen to changes in the value of the group, you can
* subscribe to the {@link AbstractControl.valueChanges} event. You can also listen to
* {@link AbstractControl.statusChanges} to be notified when the validation status is
* subscribe to the {@link AbstractControl#valueChanges} event. You can also listen to
* {@link AbstractControl#statusChanges} to be notified when the validation status is
* re-calculated.
*
* ### Example
@ -111,7 +111,7 @@ export const formArrayNameProvider: any = {
* form controls dynamically.
*
* **Access the array**: You can access the associated {@link FormArray} using the
* {@link AbstractControl.get} method on the parent {@link FormGroup}.
* {@link AbstractControl#get} method on the parent {@link FormGroup}.
* Ex: `this.form.get('cities')`.
*
* **Get the value**: the `value` property is always synced and available on the
@ -119,16 +119,16 @@ export const formArrayNameProvider: any = {
*
* **Set the value**: You can set an initial value for each child control when instantiating
* the {@link FormArray}, or you can set the value programmatically later using the
* {@link FormArray}'s {@link AbstractControl.setValue} or {@link AbstractControl.patchValue}
* {@link FormArray}'s {@link AbstractControl#setValue} or {@link AbstractControl#patchValue}
* methods.
*
* **Listen to value**: If you want to listen to changes in the value of the array, you can
* subscribe to the {@link FormArray}'s {@link AbstractControl.valueChanges} event. You can also
* listen to its {@link AbstractControl.statusChanges} event to be notified when the validation
* subscribe to the {@link FormArray}'s {@link AbstractControl#valueChanges} event. You can also
* listen to its {@link AbstractControl#statusChanges} event to be notified when the validation
* status is re-calculated.
*
* **Add new controls**: You can add new controls to the {@link FormArray} dynamically by
* calling its {@link FormArray.push} method.
* calling its {@link FormArray#push} method.
* Ex: `this.form.get('cities').push(new FormControl());`
*
* ### Example

View File

@ -659,24 +659,25 @@ export class FormControl extends AbstractControl {
* If `emitViewToModelChange` is `true`, an ngModelChange event will be fired to update the
* model. This is the default behavior if `emitViewToModelChange` is not specified.
*/
setValue(value: any, {onlySelf, emitEvent, emitModelToViewChange, emitViewToModelChange}: {
setValue(value: any, options: {
onlySelf?: boolean,
emitEvent?: boolean,
emitModelToViewChange?: boolean,
emitViewToModelChange?: boolean
} = {}): void {
this._value = value;
if (this._onChange.length && emitModelToViewChange !== false) {
this._onChange.forEach((changeFn) => changeFn(this._value, emitViewToModelChange !== false));
if (this._onChange.length && options.emitModelToViewChange !== false) {
this._onChange.forEach(
(changeFn) => changeFn(this._value, options.emitViewToModelChange !== false));
}
this.updateValueAndValidity({onlySelf, emitEvent});
this.updateValueAndValidity(options);
}
/**
* Patches the value of a control.
*
* This function is functionally the same as {@link FormControl.setValue} at this level.
* It exists for symmetry with {@link FormGroup.patchValue} on `FormGroups` and `FormArrays`,
* This function is functionally the same as {@link FormControl#setValue} at this level.
* It exists for symmetry with {@link FormGroup#patchValue} on `FormGroups` and `FormArrays`,
* where it does behave differently.
*/
patchValue(value: any, options: {
@ -716,12 +717,11 @@ export class FormControl extends AbstractControl {
* console.log(this.control.status); // 'DISABLED'
* ```
*/
reset(formState: any = null, {onlySelf, emitEvent}: {onlySelf?: boolean,
emitEvent?: boolean} = {}): void {
reset(formState: any = null, options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
this._applyFormState(formState);
this.markAsPristine({onlySelf});
this.markAsUntouched({onlySelf});
this.setValue(this._value, {onlySelf, emitEvent});
this.markAsPristine(options);
this.markAsUntouched(options);
this.setValue(this._value, options);
}
/**
@ -842,7 +842,7 @@ export class FormGroup extends AbstractControl {
* Registers a control with the group's list of controls.
*
* This method does not update value or validity of the control, so for
* most cases you'll want to use {@link FormGroup.addControl} instead.
* most cases you'll want to use {@link FormGroup#addControl} instead.
*/
registerControl(name: string, control: AbstractControl): AbstractControl {
if (this.controls[name]) return this.controls[name];
@ -886,7 +886,7 @@ export class FormGroup extends AbstractControl {
* Check whether there is an enabled control with the given name in the group.
*
* It will return false for disabled controls. If you'd like to check for
* existence in the group only, use {@link AbstractControl.get} instead.
* existence in the group only, use {@link AbstractControl#get} instead.
*/
contains(controlName: string): boolean {
return this.controls.hasOwnProperty(controlName) && this.controls[controlName].enabled;
@ -914,15 +914,14 @@ export class FormGroup extends AbstractControl {
*
* ```
*/
setValue(
value: {[key: string]: any},
{onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
setValue(value: {[key: string]: any}, options: {onlySelf?: boolean, emitEvent?: boolean} = {}):
void {
this._checkAllValuesPresent(value);
Object.keys(value).forEach(name => {
this._throwIfControlMissing(name);
this.controls[name].setValue(value[name], {onlySelf: true, emitEvent});
this.controls[name].setValue(value[name], {onlySelf: true, emitEvent: options.emitEvent});
});
this.updateValueAndValidity({onlySelf, emitEvent});
this.updateValueAndValidity(options);
}
/**
@ -946,15 +945,14 @@ export class FormGroup extends AbstractControl {
*
* ```
*/
patchValue(
value: {[key: string]: any},
{onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
patchValue(value: {[key: string]: any}, options: {onlySelf?: boolean, emitEvent?: boolean} = {}):
void {
Object.keys(value).forEach(name => {
if (this.controls[name]) {
this.controls[name].patchValue(value[name], {onlySelf: true, emitEvent});
this.controls[name].patchValue(value[name], {onlySelf: true, emitEvent: options.emitEvent});
}
});
this.updateValueAndValidity({onlySelf, emitEvent});
this.updateValueAndValidity(options);
}
/**
@ -989,14 +987,13 @@ export class FormGroup extends AbstractControl {
* console.log(this.form.get('first').status); // 'DISABLED'
* ```
*/
reset(value: any = {}, {onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}):
void {
reset(value: any = {}, options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
this._forEachChild((control: AbstractControl, name: string) => {
control.reset(value[name], {onlySelf: true, emitEvent});
control.reset(value[name], {onlySelf: true, emitEvent: options.emitEvent});
});
this.updateValueAndValidity({onlySelf, emitEvent});
this._updatePristine({onlySelf});
this._updateTouched({onlySelf});
this.updateValueAndValidity(options);
this._updatePristine(options);
this._updateTouched(options);
}
/**
@ -1222,14 +1219,13 @@ export class FormArray extends AbstractControl {
* console.log(arr.value); // ['Nancy', 'Drew']
* ```
*/
setValue(value: any[], {onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}):
void {
setValue(value: any[], options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
this._checkAllValuesPresent(value);
value.forEach((newValue: any, index: number) => {
this._throwIfControlMissing(index);
this.at(index).setValue(newValue, {onlySelf: true, emitEvent});
this.at(index).setValue(newValue, {onlySelf: true, emitEvent: options.emitEvent});
});
this.updateValueAndValidity({onlySelf, emitEvent});
this.updateValueAndValidity(options);
}
/**
@ -1252,14 +1248,13 @@ export class FormArray extends AbstractControl {
* console.log(arr.value); // ['Nancy', null]
* ```
*/
patchValue(value: any[], {onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}):
void {
patchValue(value: any[], options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
value.forEach((newValue: any, index: number) => {
if (this.at(index)) {
this.at(index).patchValue(newValue, {onlySelf: true, emitEvent});
this.at(index).patchValue(newValue, {onlySelf: true, emitEvent: options.emitEvent});
}
});
this.updateValueAndValidity({onlySelf, emitEvent});
this.updateValueAndValidity(options);
}
/**
@ -1293,14 +1288,13 @@ export class FormArray extends AbstractControl {
* console.log(this.arr.get(0).status); // 'DISABLED'
* ```
*/
reset(value: any = [], {onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}):
void {
reset(value: any = [], options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
this._forEachChild((control: AbstractControl, index: number) => {
control.reset(value[index], {onlySelf: true, emitEvent});
control.reset(value[index], {onlySelf: true, emitEvent: options.emitEvent});
});
this.updateValueAndValidity({onlySelf, emitEvent});
this._updatePristine({onlySelf});
this._updateTouched({onlySelf});
this.updateValueAndValidity(options);
this._updatePristine(options);
this._updateTouched(options);
}
/**

View File

@ -24,9 +24,6 @@ function isEmptyInputValue(value: any): boolean {
*
* Provide this using `multi: true` to add validators.
*
* ### Example
*
* {@example core/forms/ts/ng_validators/ng_validators.ts region='ng_validators'}
* @stable
*/
export const NG_VALIDATORS = new InjectionToken<Array<Validator|Function>>('NgValidators');

View File

@ -37,14 +37,32 @@ export abstract class Body {
/**
* Returns the body as a string, presuming `toString()` can be called on the response body.
*
* When decoding an `ArrayBuffer`, the optional `encodingHint` parameter determines how the
* bytes in the buffer will be interpreted. Valid values are:
*
* - `legacy` - incorrectly interpret the bytes as UTF-16 (technically, UCS-2). Only characters
* in the Basic Multilingual Plane are supported, surrogate pairs are not handled correctly.
* In addition, the endianness of the 16-bit octet pairs in the `ArrayBuffer` is not taken
* into consideration. This is the default behavior to avoid breaking apps, but should be
* considered deprecated.
*
* - `iso-8859` - interpret the bytes as ISO-8859 (which can be used for ASCII encoded text).
*/
text(): string {
text(encodingHint: 'legacy'|'iso-8859' = 'legacy'): string {
if (this._body instanceof URLSearchParams) {
return this._body.toString();
}
if (this._body instanceof ArrayBuffer) {
return String.fromCharCode.apply(null, new Uint16Array(<ArrayBuffer>this._body));
switch (encodingHint) {
case 'legacy':
return String.fromCharCode.apply(null, new Uint16Array(this._body as ArrayBuffer));
case 'iso-8859':
return String.fromCharCode.apply(null, new Uint8Array(this._body as ArrayBuffer));
default:
throw new Error(`Invalid value for encodingHint: ${encodingHint}`);
}
}
if (this._body == null) {

View File

@ -42,6 +42,15 @@ export function getResponseURL(xhr: any): string|null {
return null;
}
export function stringToArrayBuffer8(input: String): ArrayBuffer {
const view = new Uint8Array(input.length);
for (let i = 0, strLen = input.length; i < strLen; i++) {
view[i] = input.charCodeAt(i);
}
return view.buffer;
}
export function stringToArrayBuffer(input: String): ArrayBuffer {
const view = new Uint16Array(input.length);
for (let i = 0, strLen = input.length; i < strLen; i++) {

View File

@ -76,8 +76,14 @@ export class Request extends Body {
// TODO: assert that url is present
const url = requestOptions.url;
this.url = requestOptions.url !;
if (requestOptions.params) {
const params = requestOptions.params.toString();
const paramsArg = requestOptions.params || requestOptions.search;
if (paramsArg) {
let params: string;
if (typeof paramsArg === 'object' && !(paramsArg instanceof URLSearchParams)) {
params = urlEncodeParams(paramsArg).toString();
} else {
params = paramsArg.toString();
}
if (params.length > 0) {
let prefix = '?';
if (this.url.indexOf('?') != -1) {
@ -163,8 +169,22 @@ export class Request extends Body {
}
}
function urlEncodeParams(params: {[key: string]: any}): URLSearchParams {
const searchParams = new URLSearchParams();
Object.keys(params).forEach(key => {
const value = params[key];
if (value && Array.isArray(value)) {
value.forEach(element => searchParams.append(key, element.toString()));
} else {
searchParams.append(key, value.toString());
}
});
return searchParams;
}
const noop = function() {};
const w = typeof window == 'object' ? window : noop;
const FormData = (w as any /** TODO #9100 */)['FormData'] || noop;
const Blob = (w as any /** TODO #9100 */)['Blob'] || noop;
export const ArrayBuffer = (w as any /** TODO #9100 */)['ArrayBuffer'] || noop;
export const ArrayBuffer: ArrayBufferConstructor =
(w as any /** TODO #9100 */)['ArrayBuffer'] || noop;

View File

@ -7,10 +7,12 @@
*/
import {describe, expect, it} from '@angular/core/testing/src/testing_internal';
import {ɵgetDOM as getDOM} from '@angular/platform-browser';
import {RequestOptions} from '../src/base_request_options';
import {ContentType} from '../src/enums';
import {Headers} from '../src/headers';
import {stringToArrayBuffer, stringToArrayBuffer8} from '../src/http_utils';
import {ArrayBuffer, Request} from '../src/static_request';
export function main() {
@ -109,5 +111,28 @@ export function main() {
expect(req.text()).toEqual('');
});
it('should use object params', () => {
const req = new Request({url: 'http://test.com', params: {'a': 3, 'b': ['x', 'y']}});
expect(req.url).toBe('http://test.com?a=3&b=x&b=y');
});
it('should use search if present', () => {
const req = new Request({url: 'http://test.com', search: 'a=1&b=2'});
expect(req.url).toBe('http://test.com?a=1&b=2');
});
if (getDOM().supportsWebAnimation()) {
it('should serialize an ArrayBuffer to string via legacy encoding', () => {
const str = '\u89d2\u5ea6';
expect(new Request({body: stringToArrayBuffer(str), url: '/'}).text()).toEqual(str);
});
it('should serialize an ArrayBuffer to string via iso-8859 encoding', () => {
const str = 'abcd';
expect(new Request({body: stringToArrayBuffer8(str), url: '/'}).text('iso-8859'))
.toEqual(str);
});
}
});
}

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
*/
/**
* @module
* @description
* Entry point for all public APIs of the http testing package.
*/
export * from './src/testing';

View File

@ -1,6 +1,7 @@
{
"extends": "../tsconfig-build",
"compilerOptions": {
"strictNullChecks": true,
"paths": {
"@angular/core": ["../../dist/packages/core"],
"@angular/http": ["../../dist/packages/http"],
@ -8,9 +9,12 @@
}
},
"files": [
"index.ts"
"public_api.ts"
],
"angularCompilerOptions": {
"strictMetadataEmit": true
"annotateForClosureCompiler": true,
"strictMetadataEmit": true,
"flatModuleOutFile": "index.js",
"flatModuleId": "@angular/http/testing"
}
}

View File

@ -3,6 +3,7 @@
"baseUrl": ".",
"declaration": true,
"stripInternal": true,
"strictNullChecks": true,
"experimentalDecorators": true,
"module": "es2015",
"moduleResolution": "node",

View File

@ -13,7 +13,9 @@ export class AstPath<T> {
get head(): T|undefined { return this.path[0]; }
get tail(): T|undefined { return this.path[this.path.length - 1]; }
parentOf(node: T): T|undefined { return this.path[this.path.indexOf(node) - 1]; }
parentOf(node: T|undefined): T|undefined {
return node && this.path[this.path.indexOf(node) - 1];
}
childOf(node: T): T|undefined { return this.path[this.path.indexOf(node) + 1]; }
first<N extends T>(ctor: {new (...args: any[]): N}): N|undefined {

View File

@ -33,71 +33,73 @@ export function getTemplateCompletions(templateInfo: TemplateInfo): Completions|
let result: Completions|undefined = undefined;
let {htmlAst, templateAst, template} = templateInfo;
// The templateNode starts at the delimiter character so we add 1 to skip it.
let templatePosition = templateInfo.position ! - template.span.start;
let path = new HtmlAstPath(htmlAst, templatePosition);
let mostSpecific = path.tail;
if (path.empty) {
result = elementCompletions(templateInfo, path);
} else {
let astPosition = templatePosition - mostSpecific !.sourceSpan !.start.offset;
mostSpecific !.visit(
{
visitElement(ast) {
let startTagSpan = spanOf(ast.sourceSpan);
let tagLen = ast.name.length;
if (templatePosition <=
startTagSpan !.start + tagLen + 1 /* 1 for the opening angle bracked */) {
// If we are in the tag then return the element completions.
result = elementCompletions(templateInfo, path);
} else if (templatePosition < startTagSpan !.end) {
// We are in the attribute section of the element (but not in an attribute).
// Return the attribute completions.
result = attributeCompletions(templateInfo, path);
}
},
visitAttribute(ast) {
if (!ast.valueSpan || !inSpan(templatePosition, spanOf(ast.valueSpan))) {
// We are in the name of an attribute. Show attribute completions.
result = attributeCompletions(templateInfo, path);
} else if (ast.valueSpan && inSpan(templatePosition, spanOf(ast.valueSpan))) {
result = attributeValueCompletions(templateInfo, templatePosition, ast);
}
},
visitText(ast) {
// Check if we are in a entity.
result = entityCompletions(getSourceText(template, spanOf(ast) !), astPosition);
if (result) return result;
result = interpolationCompletions(templateInfo, templatePosition);
if (result) return result;
let element = path.first(Element);
if (element) {
let definition = getHtmlTagDefinition(element.name);
if (definition.contentType === TagContentType.PARSABLE_DATA) {
if (templateInfo.position != null) {
let templatePosition = templateInfo.position - template.span.start;
let path = new HtmlAstPath(htmlAst, templatePosition);
let mostSpecific = path.tail;
if (path.empty || !mostSpecific) {
result = elementCompletions(templateInfo, path);
} else {
let astPosition = templatePosition - mostSpecific.sourceSpan.start.offset;
mostSpecific.visit(
{
visitElement(ast) {
let startTagSpan = spanOf(ast.sourceSpan);
let tagLen = ast.name.length;
if (templatePosition <=
startTagSpan.start + tagLen + 1 /* 1 for the opening angle bracked */) {
// If we are in the tag then return the element completions.
result = elementCompletions(templateInfo, path);
} else if (templatePosition < startTagSpan.end) {
// We are in the attribute section of the element (but not in an attribute).
// Return the attribute completions.
result = attributeCompletions(templateInfo, path);
}
},
visitAttribute(ast) {
if (!ast.valueSpan || !inSpan(templatePosition, spanOf(ast.valueSpan))) {
// We are in the name of an attribute. Show attribute completions.
result = attributeCompletions(templateInfo, path);
} else if (ast.valueSpan && inSpan(templatePosition, spanOf(ast.valueSpan))) {
result = attributeValueCompletions(templateInfo, templatePosition, ast);
}
},
visitText(ast) {
// Check if we are in a entity.
result = entityCompletions(getSourceText(template, spanOf(ast)), astPosition);
if (result) return result;
result = interpolationCompletions(templateInfo, templatePosition);
if (result) return result;
let element = path.first(Element);
if (element) {
let definition = getHtmlTagDefinition(element.name);
if (definition.contentType === TagContentType.PARSABLE_DATA) {
result = voidElementAttributeCompletions(templateInfo, path);
if (!result) {
// If the element can hold content Show element completions.
result = elementCompletions(templateInfo, path);
}
}
} else {
// If no element container, implies parsable data so show elements.
result = voidElementAttributeCompletions(templateInfo, path);
if (!result) {
// If the element can hold content Show element completions.
result = elementCompletions(templateInfo, path);
}
}
} else {
// If no element container, implies parsable data so show elements.
result = voidElementAttributeCompletions(templateInfo, path);
if (!result) {
result = elementCompletions(templateInfo, path);
}
}
},
visitComment(ast) {},
visitExpansion(ast) {},
visitExpansionCase(ast) {}
},
visitComment(ast) {},
visitExpansion(ast) {},
visitExpansionCase(ast) {}
},
null);
null);
}
}
return result;
}
function attributeCompletions(info: TemplateInfo, path: HtmlAstPath): Completions|undefined {
let item = path.tail instanceof Element ? path.tail : path.parentOf(path.tail !);
let item = path.tail instanceof Element ? path.tail : path.parentOf(path.tail);
if (item instanceof Element) {
return attributeCompletionsForElement(info, item.name, item);
}
@ -213,11 +215,12 @@ function elementCompletions(info: TemplateInfo, path: HtmlAstPath): Completions|
let htmlNames = elementNames().filter(name => !(name in hiddenHtmlElements));
// Collect the elements referenced by the selectors
let directiveElements =
getSelectors(info).selectors.map(selector => selector.element).filter(name => !!name);
let directiveElements = getSelectors(info)
.selectors.map(selector => selector.element)
.filter(name => !!name) as string[];
let components =
directiveElements.map<Completion>(name => ({kind: 'component', name: name !, sort: name !}));
directiveElements.map<Completion>(name => ({kind: 'component', name, sort: name}));
let htmlElements = htmlNames.map<Completion>(name => ({kind: 'element', name: name, sort: name}));
// Return components and html elements
@ -262,25 +265,25 @@ function voidElementAttributeCompletions(info: TemplateInfo, path: HtmlAstPath):
undefined {
let tail = path.tail;
if (tail instanceof Text) {
let match = tail.value.match(/<(\w(\w|\d|-)*:)?(\w(\w|\d|-)*)\s/) !;
let match = tail.value.match(/<(\w(\w|\d|-)*:)?(\w(\w|\d|-)*)\s/);
// The position must be after the match, otherwise we are still in a place where elements
// are expected (such as `<|a` or `<a|`; we only want attributes for `<a |` or after).
if (match && path.position >= match.index ! + match[0].length + tail.sourceSpan.start.offset) {
if (match &&
path.position >= (match.index || 0) + match[0].length + tail.sourceSpan.start.offset) {
return attributeCompletionsForElement(info, match[3]);
}
}
}
class ExpressionVisitor extends NullTemplateVisitor {
private getExpressionScope: () => SymbolTable;
result: Completions;
constructor(
private info: TemplateInfo, private position: number, private attr?: Attribute,
private getExpressionScope?: () => SymbolTable) {
getExpressionScope?: () => SymbolTable) {
super();
if (!getExpressionScope) {
this.getExpressionScope = () => info.template.members;
}
this.getExpressionScope = getExpressionScope || (() => info.template.members);
}
visitDirectiveProperty(ast: BoundDirectivePropertyAst): void {
@ -311,7 +314,8 @@ class ExpressionVisitor extends NullTemplateVisitor {
this.info.expressionParser.parseTemplateBindings(key, this.attr.value, null);
// find the template binding that contains the position
const valueRelativePosition = this.position - this.attr.valueSpan !.start.offset - 1;
if (!this.attr.valueSpan) return;
const valueRelativePosition = this.position - this.attr.valueSpan.start.offset - 1;
const bindings = templateBindingResult.templateBindings;
const binding =
bindings.find(
@ -340,10 +344,12 @@ class ExpressionVisitor extends NullTemplateVisitor {
// We are after the '=' in a let clause. The valid values here are the members of the
// template reference's type parameter.
const directiveMetadata = selectorInfo.map.get(selector);
const contextTable =
this.info.template.query.getTemplateContext(directiveMetadata !.type.reference);
if (contextTable) {
this.result = this.symbolsToCompletions(contextTable.values());
if (directiveMetadata) {
const contextTable =
this.info.template.query.getTemplateContext(directiveMetadata.type.reference);
if (contextTable) {
this.result = this.symbolsToCompletions(contextTable.values());
}
}
} else if (binding.key && valueRelativePosition <= (binding.key.length - key.length)) {
keyCompletions();
@ -371,7 +377,7 @@ class ExpressionVisitor extends NullTemplateVisitor {
const expressionPosition = this.position - ast.sourceSpan.start.offset;
if (inSpan(expressionPosition, ast.value.span)) {
const completions = getExpressionCompletions(
this.getExpressionScope !(), ast.value, expressionPosition, this.info.template.query);
this.getExpressionScope(), ast.value, expressionPosition, this.info.template.query);
if (completions) {
this.result = this.symbolsToCompletions(completions);
}
@ -380,8 +386,8 @@ class ExpressionVisitor extends NullTemplateVisitor {
private attributeValueCompletions(value: AST, position?: number) {
const symbols = getExpressionCompletions(
this.getExpressionScope !(), value,
position == null ? this.attributeValuePosition : position, this.info.template.query);
this.getExpressionScope(), value, position == null ? this.attributeValuePosition : position,
this.info.template.query);
if (symbols) {
this.result = this.symbolsToCompletions(symbols);
}
@ -393,12 +399,13 @@ class ExpressionVisitor extends NullTemplateVisitor {
}
private get attributeValuePosition() {
return this.position - this.attr !.valueSpan !.start.offset - 1;
if (this.attr && this.attr.valueSpan) {
return this.position - this.attr.valueSpan.start.offset - 1;
}
return 0;
}
}
function getSourceText(template: TemplateSource, span: Span): string {
return template.source.substring(span.start, span.end);
}

View File

@ -29,7 +29,7 @@ export function getTemplateDiagnostics(
results.push(...ast.parseErrors.map<Diagnostic>(
e => ({
kind: DiagnosticKind.Error,
span: offsetSpan(spanOf(e.span) !, template.span.start),
span: offsetSpan(spanOf(e.span), template.span.start),
message: e.msg
})));
} else if (ast.templateAst) {
@ -91,20 +91,24 @@ export function getDeclarationDiagnostics(
function getTemplateExpressionDiagnostics(
template: TemplateSource, astResult: AstResult): Diagnostics {
const info: TemplateInfo = {
template,
htmlAst: astResult.htmlAst !,
directive: astResult.directive !,
directives: astResult.directives !,
pipes: astResult.pipes !,
templateAst: astResult.templateAst !,
expressionParser: astResult.expressionParser !
};
const visitor = new ExpressionDiagnosticsVisitor(
info, (path: TemplateAstPath, includeEvent: boolean) =>
getExpressionScope(info, path, includeEvent));
templateVisitAll(visitor, astResult.templateAst !);
return visitor.diagnostics;
if (astResult.htmlAst && astResult.directive && astResult.directives && astResult.pipes &&
astResult.templateAst && astResult.expressionParser) {
const info: TemplateInfo = {
template,
htmlAst: astResult.htmlAst,
directive: astResult.directive,
directives: astResult.directives,
pipes: astResult.pipes,
templateAst: astResult.templateAst,
expressionParser: astResult.expressionParser
};
const visitor = new ExpressionDiagnosticsVisitor(
info, (path: TemplateAstPath, includeEvent: boolean) =>
getExpressionScope(info, path, includeEvent));
templateVisitAll(visitor, astResult.templateAst !);
return visitor.diagnostics;
}
return [];
}
class ExpressionDiagnosticsVisitor extends TemplateAstChildVisitor {
@ -158,11 +162,11 @@ class ExpressionDiagnosticsVisitor extends TemplateAstChildVisitor {
if (context && !context.has(ast.value)) {
if (ast.value === '$implicit') {
this.reportError(
'The template context does not have an implicit value', spanOf(ast.sourceSpan) !);
'The template context does not have an implicit value', spanOf(ast.sourceSpan));
} else {
this.reportError(
`The template context does not defined a member called '${ast.value}'`,
spanOf(ast.sourceSpan) !);
spanOf(ast.sourceSpan));
}
}
}
@ -233,11 +237,13 @@ class ExpressionDiagnosticsVisitor extends TemplateAstChildVisitor {
}
}
private reportError(message: string, span: Span) {
this.diagnostics.push({
span: offsetSpan(span, this.info.template.span.start),
kind: DiagnosticKind.Error, message
});
private reportError(message: string, span: Span|undefined) {
if (span) {
this.diagnostics.push({
span: offsetSpan(span, this.info.template.span.start),
kind: DiagnosticKind.Error, message
});
}
}
private reportWarning(message: string, span: Span) {

View File

@ -495,8 +495,9 @@ class AstType implements ExpressionVisitor {
// The type of a method is the selected methods result type.
const method = receiverType.members().get(ast.name);
if (!method) return this.reportError(`Unknown method ${ast.name}`, ast);
if (!method.type !.callable) return this.reportError(`Member ${ast.name} is not callable`, ast);
const signature = method.type !.selectSignature(ast.args.map(arg => this.getType(arg)));
if (!method.type) return this.reportError(`Could not find a type for ${ast.name}`, ast);
if (!method.type.callable) return this.reportError(`Member ${ast.name} is not callable`, ast);
const signature = method.type.selectSignature(ast.args.map(arg => this.getType(arg)));
if (!signature)
return this.reportError(`Unable to resolve signature for call of method ${ast.name}`, ast);
return signature.result;
@ -601,7 +602,9 @@ function visitChildren(ast: AST, visitor: ExpressionVisitor) {
visit(ast.falseExp);
},
visitFunctionCall(ast) {
visit(ast.target !);
if (ast.target) {
visit(ast.target);
}
visitAll(ast.args);
},
visitImplicitReceiver(ast) {},
@ -676,7 +679,7 @@ function getReferences(info: TemplateInfo): SymbolDeclaration[] {
function processReferences(references: ReferenceAst[]) {
for (const reference of references) {
let type: Symbol = undefined !;
let type: Symbol|undefined = undefined;
if (reference.value) {
type = info.template.query.getTypeSymbol(tokenReference(reference.value));
}
@ -721,7 +724,7 @@ function getVarDeclarations(info: TemplateInfo, path: TemplateAstPath): SymbolDe
.find(c => !!c);
// Determine the type of the context field referenced by variable.value.
let type: Symbol = undefined !;
let type: Symbol|undefined = undefined;
if (context) {
const value = context.get(variable.value);
if (value) {
@ -762,7 +765,10 @@ function refinedVariableType(
const bindingType =
new AstType(info.template.members, info.template.query, {}).getType(ngForOfBinding.value);
if (bindingType) {
return info.template.query.getElementType(bindingType) !;
const result = info.template.query.getElementType(bindingType);
if (result) {
return result;
}
}
}
}

View File

@ -81,24 +81,25 @@ class LanguageServiceImpl implements LanguageService {
let template = this.host.getTemplateAt(fileName, position);
if (template) {
let astResult = this.getTemplateAst(template, fileName);
if (astResult && astResult.htmlAst && astResult.templateAst)
if (astResult && astResult.htmlAst && astResult.templateAst && astResult.directive &&
astResult.directives && astResult.pipes && astResult.expressionParser)
return {
position,
fileName,
template,
htmlAst: astResult.htmlAst,
directive: astResult.directive !,
directives: astResult.directives !,
pipes: astResult.pipes !,
directive: astResult.directive,
directives: astResult.directives,
pipes: astResult.pipes,
templateAst: astResult.templateAst,
expressionParser: astResult.expressionParser !
expressionParser: astResult.expressionParser
};
}
return undefined;
}
getTemplateAst(template: TemplateSource, contextFile: string): AstResult {
let result: AstResult = undefined !;
let result: AstResult|undefined = undefined;
try {
const resolvedMetadata =
this.metadataResolver.getNonNormalizedDirectiveMetadata(template.type as any);
@ -112,7 +113,7 @@ class LanguageServiceImpl implements LanguageService {
config, expressionParser, new DomElementSchemaRegistry(), htmlParser, null !, []);
const htmlResult = htmlParser.parse(template.source, '', true);
const analyzedModules = this.host.getAnalyzedModules();
let errors: Diagnostic[] = undefined !;
let errors: Diagnostic[]|undefined = undefined;
let ngModule = analyzedModules.ngModuleByPipeOrDirective.get(template.type);
if (!ngModule) {
// Reported by the the declaration diagnostics.
@ -121,8 +122,7 @@ class LanguageServiceImpl implements LanguageService {
if (ngModule) {
const resolvedDirectives = ngModule.transitiveModule.directives.map(
d => this.host.resolver.getNonNormalizedDirectiveMetadata(d.reference));
const directives =
resolvedDirectives.filter(d => d !== null).map(d => d !.metadata.toSummary());
const directives = removeMissing(resolvedDirectives).map(d => d.metadata.toSummary());
const pipes = ngModule.transitiveModule.pipes.map(
p => this.host.resolver.getOrLoadPipeMetadata(p.reference).toSummary());
const schemas = ngModule.schemas;
@ -142,10 +142,14 @@ class LanguageServiceImpl implements LanguageService {
}
result = {errors: [{kind: DiagnosticKind.Error, message: e.message, span}]};
}
return result;
return result || {};
}
}
function removeMissing<T>(values: (T | null | undefined)[]): T[] {
return values.filter(e => !!e) as T[];
}
function uniqueBySpan < T extends {
span: Span;
}
@ -169,8 +173,8 @@ function uniqueBySpan < T extends {
}
}
function findSuitableDefaultModule(modules: NgAnalyzedModules): CompileNgModuleMetadata {
let result: CompileNgModuleMetadata = undefined !;
function findSuitableDefaultModule(modules: NgAnalyzedModules): CompileNgModuleMetadata|undefined {
let result: CompileNgModuleMetadata|undefined = undefined;
let resultSize = 0;
for (const module of modules.ngModules) {
const moduleSize = module.transitiveModule.directives.length;

View File

@ -21,22 +21,25 @@ export interface SymbolInfo {
}
export function locateSymbol(info: TemplateInfo): SymbolInfo|undefined {
const templatePosition = info.position ! - info.template.span.start;
if (!info.position) return undefined;
const templatePosition = info.position - info.template.span.start;
const path = new TemplateAstPath(info.templateAst, templatePosition);
if (path.tail) {
let symbol: Symbol = undefined !;
let span: Span = undefined !;
let symbol: Symbol|undefined = undefined;
let span: Span|undefined = undefined;
const attributeValueSymbol = (ast: AST, inEvent: boolean = false): boolean => {
const attribute = findAttribute(info);
if (attribute) {
if (inSpan(templatePosition, spanOf(attribute.valueSpan))) {
const scope = getExpressionScope(info, path, inEvent);
const expressionOffset = attribute.valueSpan !.start.offset + 1;
const result = getExpressionSymbol(
scope, ast, templatePosition - expressionOffset, info.template.query);
if (result) {
symbol = result.symbol;
span = offsetSpan(result.span, expressionOffset);
if (attribute.valueSpan) {
const expressionOffset = attribute.valueSpan.start.offset + 1;
const result = getExpressionSymbol(
scope, ast, templatePosition - expressionOffset, info.template.query);
if (result) {
symbol = result.symbol;
span = offsetSpan(result.span, expressionOffset);
}
}
return true;
}
@ -52,28 +55,28 @@ export function locateSymbol(info: TemplateInfo): SymbolInfo|undefined {
if (component) {
symbol = info.template.query.getTypeSymbol(component.directive.type.reference);
symbol = symbol && new OverrideKindSymbol(symbol, 'component');
span = spanOf(ast) !;
span = spanOf(ast);
} else {
// Find a directive that matches the element name
const directive =
ast.directives.find(d => d.directive.selector !.indexOf(ast.name) >= 0);
const directive = ast.directives.find(
d => d.directive.selector != null && d.directive.selector.indexOf(ast.name) >= 0);
if (directive) {
symbol = info.template.query.getTypeSymbol(directive.directive.type.reference);
symbol = symbol && new OverrideKindSymbol(symbol, 'directive');
span = spanOf(ast) !;
span = spanOf(ast);
}
}
},
visitReference(ast) {
symbol = info.template.query.getTypeSymbol(tokenReference(ast.value));
span = spanOf(ast) !;
span = spanOf(ast);
},
visitVariable(ast) {},
visitEvent(ast) {
if (!attributeValueSymbol(ast.handler, /* inEvent */ true)) {
symbol = findOutputBinding(info, path, ast) !;
symbol = findOutputBinding(info, path, ast);
symbol = symbol && new OverrideKindSymbol(symbol, 'event');
span = spanOf(ast) !;
span = spanOf(ast);
}
},
visitElementProperty(ast) { attributeValueSymbol(ast.value); },
@ -93,12 +96,12 @@ export function locateSymbol(info: TemplateInfo): SymbolInfo|undefined {
visitText(ast) {},
visitDirective(ast) {
symbol = info.template.query.getTypeSymbol(ast.directive.type.reference);
span = spanOf(ast) !;
span = spanOf(ast);
},
visitDirectiveProperty(ast) {
if (!attributeValueSymbol(ast.value)) {
symbol = findInputBinding(info, path, ast) !;
span = spanOf(ast) !;
symbol = findInputBinding(info, path, ast);
span = spanOf(ast);
}
}
},
@ -110,9 +113,11 @@ export function locateSymbol(info: TemplateInfo): SymbolInfo|undefined {
}
function findAttribute(info: TemplateInfo): Attribute|undefined {
const templatePosition = info.position ! - info.template.span.start;
const path = new HtmlAstPath(info.htmlAst, templatePosition);
return path.first(Attribute);
if (info.position) {
const templatePosition = info.position - info.template.span.start;
const path = new HtmlAstPath(info.htmlAst, templatePosition);
return path.first(Attribute);
}
}
function findInputBinding(

View File

@ -22,18 +22,24 @@ class ReflectorModuleModuleResolutionHost implements ts.ModuleResolutionHost {
if (snapshot) {
return snapshot.getText(0, snapshot.getLength());
}
// Typescript readFile() declaration should be `readFile(fileName: string): string | undefined
return undefined !;
}
directoryExists: (directoryName: string) => boolean;
}
// This reflector host's purpose is to first set verboseInvalidExpressions to true so the
// reflector will collect errors instead of throwing, and second to all deferring the creation
// of the program until it is actually needed.
export class ReflectorHost extends CompilerHost {
constructor(
private getProgram: () => ts.Program, serviceHost: ts.LanguageServiceHost,
options: AngularCompilerOptions) {
super(
null !, options,
// The ancestor value for program is overridden below so passing null here is safe.
/* program */ null !, options,
new ModuleResolutionHostAdapter(new ReflectorModuleModuleResolutionHost(serviceHost)),
{verboseInvalidExpression: true});
}

View File

@ -104,10 +104,10 @@ class TemplateAstPathBuilder extends TemplateAstChildVisitor {
constructor(private position: number, private allowWidening: boolean) { super(); }
visit(ast: TemplateAst, context: any): any {
let span = spanOf(ast) !;
let span = spanOf(ast);
if (inSpan(this.position, span)) {
const len = this.path.length;
if (!len || this.allowWidening || isNarrower(span, spanOf(this.path[len - 1]) !)) {
if (!len || this.allowWidening || isNarrower(span, spanOf(this.path[len - 1]))) {
this.path.push(ast);
}
} else {

View File

@ -25,14 +25,16 @@ export function create(info: any /* ts.server.PluginCreateInfo */): ts.LanguageS
}
function diagnosticToDiagnostic(d: Diagnostic, file: ts.SourceFile): ts.Diagnostic {
return {
const result = {
file,
start: d.span.start,
length: d.span.end - d.span.start,
messageText: d.message,
category: ts.DiagnosticCategory.Error,
code: 0
code: 0,
source: 'ng'
};
return result;
}
function tryOperation(attempting: string, callback: () => void) {
@ -78,7 +80,7 @@ export function create(info: any /* ts.server.PluginCreateInfo */): ts.LanguageS
if (ours) {
const displayParts: typeof base.displayParts = [];
for (const part of ours.text) {
displayParts.push({kind: part.language !, text: part.text});
displayParts.push({kind: part.language || 'angular', text: part.text});
}
base = <any>{
displayParts,

View File

@ -8,8 +8,6 @@
import {CompileDirectiveMetadata, CompileMetadataResolver, NgAnalyzedModules, StaticSymbol} from '@angular/compiler';
/**
* The range of a span of text in a source file.
*
@ -455,7 +453,7 @@ export interface LanguageServiceHost {
* refers to a template file then the `position` should be ignored. If the `position` is not in a
* template literal string then this method should return `undefined`.
*/
getTemplateAt(fileName: string, position: number): TemplateSource /* |undefined */;
getTemplateAt(fileName: string, position: number): TemplateSource|undefined;
/**
* Return the template source information for all templates in `fileName` or for `fileName` if it

View File

@ -74,22 +74,22 @@ export class DummyResourceLoader extends ResourceLoader {
* @experimental
*/
export class TypeScriptServiceHost implements LanguageServiceHost {
private _resolver: CompileMetadataResolver;
private _resolver: CompileMetadataResolver|null;
private _staticSymbolCache = new StaticSymbolCache();
private _summaryResolver: AotSummaryResolver;
private _staticSymbolResolver: StaticSymbolResolver;
private _reflector: StaticReflector;
private _reflector: StaticReflector|null;
private _reflectorHost: ReflectorHost;
private _checker: ts.TypeChecker;
private _checker: ts.TypeChecker|null;
private _typeCache: Symbol[] = [];
private context: string|undefined;
private lastProgram: ts.Program|undefined;
private modulesOutOfDate: boolean = true;
private analyzedModules: NgAnalyzedModules;
private analyzedModules: NgAnalyzedModules|null;
private service: LanguageService;
private fileToComponent: Map<string, StaticSymbol>;
private templateReferences: string[];
private collectedErrors: Map<string, any[]>;
private fileToComponent: Map<string, StaticSymbol>|null;
private templateReferences: string[]|null;
private collectedErrors: Map<string, any[]>|null;
private fileVersions = new Map<string, string>();
constructor(private host: ts.LanguageServiceHost, private tsService: ts.LanguageService) {}
@ -127,28 +127,28 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
getTemplateReferences(): string[] {
this.ensureTemplateMap();
return this.templateReferences;
return this.templateReferences || [];
}
getTemplateAt(fileName: string, position: number): TemplateSource {
getTemplateAt(fileName: string, position: number): TemplateSource|undefined {
let sourceFile = this.getSourceFile(fileName);
if (sourceFile) {
this.context = sourceFile.fileName;
let node = this.findNode(sourceFile, position);
if (node) {
return this.getSourceFromNode(
fileName, this.host.getScriptVersion(sourceFile.fileName), node) !;
fileName, this.host.getScriptVersion(sourceFile.fileName), node);
}
} else {
this.ensureTemplateMap();
// TODO: Cannocalize the file?
const componentType = this.fileToComponent.get(fileName);
const componentType = this.fileToComponent !.get(fileName);
if (componentType) {
return this.getSourceFromType(
fileName, this.host.getScriptVersion(fileName), componentType) !;
fileName, this.host.getScriptVersion(fileName), componentType);
}
}
return null !;
return undefined;
}
getAnalyzedModules(): NgAnalyzedModules {
@ -172,7 +172,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
getTemplates(fileName: string): TemplateSources {
this.ensureTemplateMap();
const componentType = this.fileToComponent.get(fileName);
const componentType = this.fileToComponent !.get(fileName);
if (componentType) {
const templateSource = this.getTemplateAt(fileName, 0);
if (templateSource) {
@ -225,10 +225,10 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
updateAnalyzedModules() {
this.validate();
if (this.modulesOutOfDate) {
this.analyzedModules = null !;
this._reflector = null !;
this.templateReferences = null !;
this.fileToComponent = null !;
this.analyzedModules = null;
this._reflector = null;
this.templateReferences = null;
this.fileToComponent = null;
this.ensureAnalyzedModules();
this.modulesOutOfDate = false;
}
@ -273,10 +273,10 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
}
private clearCaches() {
this._checker = null !;
this._checker = null;
this._typeCache = [];
this._resolver = null !;
this.collectedErrors = null !;
this._resolver = null;
this.collectedErrors = null;
this.modulesOutOfDate = true;
}
@ -345,7 +345,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
if (declaration && declaration.name) {
const sourceFile = this.getSourceFile(fileName);
return this.getSourceFromDeclaration(
fileName, version, this.stringOf(node) !, shrink(spanOf(node)),
fileName, version, this.stringOf(node) || '', shrink(spanOf(node)),
this.reflector.getStaticSymbol(sourceFile.fileName, declaration.name.text),
declaration, node, sourceFile);
}
@ -359,11 +359,13 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
let result: TemplateSource|undefined = undefined;
const declaration = this.getTemplateClassFromStaticSymbol(type);
if (declaration) {
const snapshot = this.host.getScriptSnapshot(fileName) !;
const source = snapshot.getText(0, snapshot.getLength());
result = this.getSourceFromDeclaration(
fileName, version, source, {start: 0, end: source.length}, type, declaration, declaration,
declaration.getSourceFile());
const snapshot = this.host.getScriptSnapshot(fileName);
if (snapshot) {
const source = snapshot.getText(0, snapshot.getLength());
result = this.getSourceFromDeclaration(
fileName, version, source, {start: 0, end: source.length}, type, declaration,
declaration, declaration.getSourceFile());
}
}
return result;
}
@ -398,17 +400,19 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
return result;
}
private collectError(error: any, filePath: string) {
let errorMap = this.collectedErrors;
if (!errorMap) {
errorMap = this.collectedErrors = new Map();
private collectError(error: any, filePath: string|null) {
if (filePath) {
let errorMap = this.collectedErrors;
if (!errorMap || !this.collectedErrors) {
errorMap = this.collectedErrors = new Map();
}
let errors = errorMap.get(filePath);
if (!errors) {
errors = [];
this.collectedErrors.set(filePath, errors);
}
errors.push(error);
}
let errors = errorMap.get(filePath);
if (!errors) {
errors = [];
this.collectedErrors.set(filePath, errors);
}
errors.push(error);
}
private get staticSymbolResolver(): StaticSymbolResolver {
@ -416,9 +420,9 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
if (!result) {
this._summaryResolver = new AotSummaryResolver(
{
loadSummary(filePath: string) { return null !; },
isSourceFile(sourceFilePath: string) { return true !; },
getOutputFileName(sourceFilePath: string) { return null !; }
loadSummary(filePath: string) { return null; },
isSourceFile(sourceFilePath: string) { return true; },
getOutputFileName(sourceFilePath: string) { return sourceFilePath; }
},
this._staticSymbolCache);
result = this._staticSymbolResolver = new StaticSymbolResolver(
@ -445,7 +449,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
const declarationNode = ts.forEachChild(source, child => {
if (child.kind === ts.SyntaxKind.ClassDeclaration) {
const classDeclaration = child as ts.ClassDeclaration;
if (classDeclaration.name !.text === type.name) {
if (classDeclaration.name != null && classDeclaration.name.text === type.name) {
return classDeclaration;
}
}
@ -614,7 +618,7 @@ class TypeScriptSymbolQuery implements SymbolQuery {
private program: ts.Program, private checker: ts.TypeChecker, private source: ts.SourceFile,
private fetchPipes: () => SymbolTable) {}
getTypeKind(symbol: Symbol): BuiltinType { return typeKindOf(this.getTsTypeOf(symbol) !); }
getTypeKind(symbol: Symbol): BuiltinType { return typeKindOf(this.getTsTypeOf(symbol)); }
getBuiltinType(kind: BuiltinType): Symbol {
// TODO: Replace with typeChecker API when available.
@ -1303,7 +1307,7 @@ function getTypeParameterOf(type: ts.Type, name: string): ts.Type|undefined {
}
}
function typeKindOf(type: ts.Type): BuiltinType {
function typeKindOf(type: ts.Type | undefined): BuiltinType {
if (type) {
if (type.flags & ts.TypeFlags.Any) {
return BuiltinType.Any;
@ -1318,17 +1322,19 @@ function typeKindOf(type: ts.Type): BuiltinType {
return BuiltinType.Null;
} else if (type.flags & ts.TypeFlags.Union) {
// If all the constituent types of a union are the same kind, it is also that kind.
let candidate: BuiltinType = undefined !;
let candidate: BuiltinType|null = null;
const unionType = type as ts.UnionType;
if (unionType.types.length > 0) {
candidate = typeKindOf(unionType.types[0]) !;
candidate = typeKindOf(unionType.types[0]);
for (const subType of unionType.types) {
if (candidate != typeKindOf(subType)) {
return BuiltinType.Other;
}
}
}
return candidate;
if (candidate != null) {
return candidate;
}
} else if (type.flags & ts.TypeFlags.TypeParameter) {
return BuiltinType.Unbound;
}

View File

@ -21,6 +21,9 @@ export function isParseSourceSpan(value: any): value is ParseSourceSpan {
return value && !!value.start;
}
export function spanOf(span: SpanHolder): Span;
export function spanOf(span: ParseSourceSpan): Span;
export function spanOf(span: SpanHolder | ParseSourceSpan | undefined): Span|undefined;
export function spanOf(span?: SpanHolder | ParseSourceSpan): Span|undefined {
if (!span) return undefined;
if (isParseSourceSpan(span)) {
@ -39,8 +42,8 @@ export function spanOf(span?: SpanHolder | ParseSourceSpan): Span|undefined {
}
export function inSpan(position: number, span?: Span, exclusive?: boolean): boolean {
return span && exclusive ? position >= span.start && position < span.end :
position >= span !.start && position <= span !.end;
return span != null && (exclusive ? position >= span.start && position < span.end :
position >= span.start && position <= span.end);
}
export function offsetSpan(span: Span, amount: number): Span {
@ -54,7 +57,8 @@ export function isNarrower(spanA: Span, spanB: Span): boolean {
export function hasTemplateReference(type: CompileTypeMetadata): boolean {
if (type.diDeps) {
for (let diDep of type.diDeps) {
if (diDep.token !.identifier && identifierName(diDep.token !.identifier !) == 'TemplateRef')
if (diDep.token && diDep.token.identifier &&
identifierName(diDep.token !.identifier !) == 'TemplateRef')
return true;
}
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {APP_INITIALIZER, Inject, InjectionToken, Provider} from '@angular/core';
import {APP_INITIALIZER, ApplicationInitStatus, Inject, InjectionToken, Injector, Provider} from '@angular/core';
import {getDOM} from '../dom/dom_adapter';
import {DOCUMENT} from '../dom/dom_tokens';
@ -17,22 +17,25 @@ import {DOCUMENT} from '../dom/dom_tokens';
*/
export const TRANSITION_ID = new InjectionToken('TRANSITION_ID');
export function bootstrapListenerFactory(transitionId: string, document: any) {
const factory = () => {
const dom = getDOM();
const styles: any[] =
Array.prototype.slice.apply(dom.querySelectorAll(document, `style[ng-transition]`));
styles.filter(el => dom.getAttribute(el, 'ng-transition') === transitionId)
.forEach(el => dom.remove(el));
export function appInitializerFactory(transitionId: string, document: any, injector: Injector) {
return () => {
// Wait for all application initializers to be completed before removing the styles set by
// the server.
injector.get(ApplicationInitStatus).donePromise.then(() => {
const dom = getDOM();
const styles: any[] =
Array.prototype.slice.apply(dom.querySelectorAll(document, `style[ng-transition]`));
styles.filter(el => dom.getAttribute(el, 'ng-transition') === transitionId)
.forEach(el => dom.remove(el));
});
};
return factory;
}
export const SERVER_TRANSITION_PROVIDERS: Provider[] = [
{
provide: APP_INITIALIZER,
useFactory: bootstrapListenerFactory,
deps: [TRANSITION_ID, DOCUMENT],
useFactory: appInitializerFactory,
deps: [TRANSITION_ID, DOCUMENT, Injector],
multi: true
},
];

View File

@ -13,8 +13,9 @@ import {WebWorkerPlatformLocation} from './platform_location';
/**
* Those providers should be added when the router is used in a worker context in addition to the
* {@link ROUTER_PROVIDERS} and after them.
* The {@link PlatformLocation} providers that should be added when the {@link Location} is used in
* a worker context.
*
* @experimental
*/
export const WORKER_APP_LOCATION_PROVIDERS = [

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