Compare commits

...

86 Commits
4.2.4 ... 2.2.4

Author SHA1 Message Date
9a9a7ac7b5 docs(changelog): add changelog for 2.2.4 2016-11-30 15:18:38 -08:00
423dd2898a chore(release): cut the 2.2.4 release 2016-11-30 15:16:41 -08:00
ee2d6e572a fix(compiler): fix performance regression caused by 5b0f9e2
Fixes #13146
2016-11-30 15:13:39 -08:00
ba8645c529 Fix(http): invalidStateError response body
Check on null value failed with last version of mozilla.
Check on undefined type instead.
2016-11-30 15:13:39 -08:00
eba53fd16c fix(common): update DatePipe to allow closure compilation
Quote the date formats to prevent closure renaming.  These are specified as strings in templates using DatePipes and also need to be quoted here.
2016-11-30 15:13:39 -08:00
c0698de2ea doc(common): fix a typo in async pipe 2016-11-30 15:13:38 -08:00
dc6728e7ad refactor(core): remove unused import
APP_ID  was removed after 2.2.x
2016-11-30 15:13:38 -08:00
eb173bcd30 fix(compiler-cli): fix paths in source maps to be relative
The change looks bigger than it really is because I reordered the properties to match other tsconfigs we have.

The only real change is removal of sourceRoot property.

Fixes #13040
2016-11-30 15:13:38 -08:00
e39e16ae6a Keep console.log that are not called during compilation. 2016-11-30 15:13:38 -08:00
ff56da554f Check if console.error is defined 2016-11-30 15:13:38 -08:00
d160591453 refactor(lint): Don't allow console.log
Enable tslint check for `console.log` as a follow-up to
https://github.com/angular/angular/issues/13018
2016-11-30 15:13:38 -08:00
380377139b docs(changelog): add changelog for 2.2.3 2016-11-23 13:00:37 -08:00
9c7680ef69 chore(release): cut the 2.2.3 release 2016-11-23 12:53:13 -08:00
69572ac2f1 Revert "refactor(compiler): move static_reflector into @angular/compiler and rename files"
This reverts commit 8f295287a2.
2016-11-23 12:09:10 -08:00
76f53f929c Revert "refactor(compiler): move findDeclaration into the StaticReflector"
This reverts commit e7025c9423.
2016-11-23 12:09:09 -08:00
ba52f2f252 Revert "refactor(compiler): remove asset: urls"
This reverts commit 0c98f45105.
2016-11-23 12:09:08 -08:00
e122f6bf0f Revert "refactor(compiler): add createAotCompiler factory"
This reverts commit 170525a225.
2016-11-23 12:09:07 -08:00
453c758d1a Revert "refactor(compiler): move symbol extraction to AotCompiler"
This reverts commit b5afe51b26.
2016-11-23 12:09:06 -08:00
015ca47336 Revert "fix(compiler): fix versions of @angular/tsc-wrapped"
This reverts commit 2fe6fb1163.
2016-11-23 12:09:04 -08:00
f32e287812 Revert "refactor(tsc-wrapped): collect all exported functions and classes and bump metadata version from 1 to 2"
This reverts commit 39a71eb0ec.
2016-11-23 12:08:46 -08:00
9946ac5cc7 Revert "refactor(compiler): renames"
This reverts commit 38be2b81c6.
2016-11-23 12:08:45 -08:00
593e05dc97 Revert "refactor(comiler): various cleanups"
This reverts commit ef38676091.
2016-11-23 12:08:44 -08:00
da77b580c9 Revert "refactor(compiler): Reintroduce ReflectorHost and move Extractor into @angular/compiler"
This reverts commit 64bd672e3a.
2016-11-23 12:08:42 -08:00
1733ea09bd Revert "refactor(compiler): further minor fixes"
This reverts commit 3d407fc010.
2016-11-23 12:08:41 -08:00
1f4fa28fac Revert "refactor(compiler): allow control of StaticSymbol lifetime (#12986)"
This reverts commit 2ca67e1674.
2016-11-23 12:08:41 -08:00
c12e56ec0c Revert "fix(animations): blend in all previously transitioned styles into next animation if interrupted (#13014)"
This reverts commit ea4fc9b421.
2016-11-23 12:08:40 -08:00
4a5c8bd25f Revert "test(upgrade): remove setTimeout from lifecycle hook tests (#13027)"
This reverts commit a4ab14bf74.
2016-11-23 12:08:38 -08:00
9c954740d1 chore(release): cut tsc-wrapped 0.4.1 release 2016-11-22 14:51:47 -08:00
11ed8f56ab docs(changelog): add changelog for 2.2.2 2016-11-22 14:36:49 -08:00
a49acbf027 chore(release): cut the 2.2.2 release 2016-11-22 14:33:08 -08:00
8e41910429 fix(build): update versions of umd bundles (#13038)
Fixes #13037
2016-11-22 14:26:20 -08:00
a4ab14bf74 test(upgrade): remove setTimeout from lifecycle hook tests (#13027)
* test(upgrade): remove unnecessary NO_ERRORS_SCHEMA

* test(upgrade): remove `setTimeout` from lifecycle hook tests

Closes #13019
2016-11-22 14:26:20 -08:00
ea4fc9b421 fix(animations): blend in all previously transitioned styles into next animation if interrupted (#13014)
Closes #13013
Closes #13014
2016-11-22 14:26:20 -08:00
0956acee58 fix(closure): quote date pattern aliases (#13012)
Quota the pattern aliases to prevent closure renaming. These are quoted in DatePipe and also need to be quoted here.
2016-11-22 14:26:20 -08:00
2ca67e1674 refactor(compiler): allow control of StaticSymbol lifetime (#12986) 2016-11-22 14:26:20 -08:00
472666fc2b refactor(ngUpgrade): Small cleanup with Testability API and resumeBootstrap (#12926)
* With non-static ngUpgrade apps, callbacks to `whenStable` were being invoked with the wrong
  context
* With non-static ngUpgrade apps, `resumeBootstrap` was being run outside the NgZone
* Remove redundent `whenStableContext` variable

Neither of the first two problems were actually causing bugs (as far as I know), but they *might*
have caused problems in the future.

Inspired by https://github.com/angular/angular/pull/12910, but for non-static apps.
2016-11-22 14:26:20 -08:00
462316b0f1 fix(upgrade): call ng1 lifecycle hooks (#12875) 2016-11-22 14:26:20 -08:00
96c2b2cc25 fix(changelog): replace beta.1 with beta.0 (#12961) 2016-11-22 14:26:19 -08:00
3d407fc010 refactor(compiler): further minor fixes 2016-11-22 14:26:19 -08:00
64bd672e3a refactor(compiler): Reintroduce ReflectorHost and move Extractor into @angular/compiler 2016-11-22 14:26:19 -08:00
ef38676091 refactor(comiler): various cleanups 2016-11-22 14:26:19 -08:00
38be2b81c6 refactor(compiler): renames
- `NgHost` to `CompilerHost`
- `AotCompilerHost.resolveFileToImport` to `AotCompilerHost.fileNameToModuleName`
- `AotCompilerHoset.resolveImportToFile` to `AotCompilerHost.moduleNameToFileName`
2016-11-22 14:26:19 -08:00
39a71eb0ec refactor(tsc-wrapped): collect all exported functions and classes and bump metadata version from 1 to 2
This is needed to resolve symbols without `.d.ts` files.
This bumps the version of the metadata from 1 to 2.
This adds logic into `ng_host.ts` to automatically upgrade
version 1 to version 2 metadata by adding the exported symbols
from the `.d.ts` file.
2016-11-22 14:26:19 -08:00
2fe6fb1163 fix(compiler): fix versions of @angular/tsc-wrapped 2016-11-22 14:26:19 -08:00
b5afe51b26 refactor(compiler): move symbol extraction to AotCompiler 2016-11-22 14:26:19 -08:00
170525a225 refactor(compiler): add createAotCompiler factory
Also adds 2 more methods to the `AotCompilerHost`:
- `loadResource`
- `resolveFileToImport`
2016-11-22 14:26:19 -08:00
0c98f45105 refactor(compiler): remove asset: urls
These urls were just relicts from Dart.
2016-11-22 14:26:19 -08:00
e7025c9423 refactor(compiler): move findDeclaration into the StaticReflector
Previously, this was part of the `AotCompilerHost`.
The `AotCompilerHost` is now also greatly simplified.
2016-11-22 14:26:18 -08:00
8f295287a2 refactor(compiler): move static_reflector into @angular/compiler and rename files
- `src/runtime_compiler.ts` -> `src/jit/compiler.ts`
- `src/compiler.ts` -> `src/jit/compiler_factory.ts`
- `src/offline_compiler` -> `src/aot/compiler.ts`

Part of #12867
2016-11-22 14:26:18 -08:00
030facc66a chore(build): update package.json versions during build (#12957) 2016-11-22 14:26:18 -08:00
45af8f6752 fix(ci): pin version of npm on CircleCI (#12954) 2016-11-22 14:26:18 -08:00
33a79028be fix(benchmarks): use sanitized style values (#12943) 2016-11-22 14:26:18 -08:00
09226d96f8 fix(router): support redirects to named outlets
Closes #12740, #9921
2016-11-22 14:26:18 -08:00
6c3166e6e4 chore(release): cut the 2.3.0-beta.0 realse and add change log 2016-11-22 14:26:18 -08:00
8df328b15a fix(router): add a banner file for the router (#12919) 2016-11-22 14:26:17 -08:00
115f18fa06 fix(router): removes a peer dependency from router to upgrade 2016-11-22 14:26:16 -08:00
511cd4d182 fix(router): add a banner file for the router (#12919) 2016-11-22 14:26:14 -08:00
87d5d49530 fix(router): removes a peer dependency from router to upgrade 2016-11-22 14:26:14 -08:00
933caacad3 chore(release): cut angular 2.2.1 2016-11-16 16:38:28 -08:00
efe9c4f35c fix(tools): fix error when running test.sh (#12927) 2016-11-16 16:26:44 -08:00
5b0f9e2f51 refactor(compiler): allows synchronous retrieving of metadata (#12908)
Allows non-normalized metadata to be retrieved synchronously.

Related to #7482
2016-11-16 16:26:44 -08:00
462879887a fix(core): support ngTemplateOutlet in production mode (#12921)
Fixes #12911
2016-11-16 16:26:44 -08:00
dae0d0fd66 docs(upgrade/static): improve API docs with examples
Closes #12717
2016-11-16 16:26:44 -08:00
c7f750dd5a chore(public_api): remove Angular 1 types from upgrade/static API 2016-11-16 16:26:44 -08:00
73de925551 chore(examples): add upgrade/static example 2016-11-16 16:26:44 -08:00
547c22029a chore(examples): support upgrade/static examples 2016-11-16 16:26:44 -08:00
364642d58c fix(router): add a banner file for the router (#12919) 2016-11-16 16:26:44 -08:00
7b67badc43 fix(platform_browser): fix disableDebugTools() (#12918) 2016-11-16 16:26:44 -08:00
dc1662a447 fix(ngUpgrade): make AoT ngUpgrade work with the testability API and resumeBootstrap() (#12910) 2016-11-16 16:26:43 -08:00
b5f433626b build(build.sh): echo before building examples 2016-11-16 16:26:43 -08:00
dabaf858d9 fix(router): should not create a route state if navigation is canceled (#12868)
Closes #12776
2016-11-16 16:26:43 -08:00
bbc3c9ce0e refactor(forms): remove facade (#12558) 2016-11-16 16:26:43 -08:00
1dcf1f484e fix(router): removes a peer dependency from router to upgrade 2016-11-16 16:26:43 -08:00
583d2833db fix(animations): only pass in same typed players as previous players into web-animations (#12907)
Closes #12907
2016-11-16 16:26:43 -08:00
f502a768d3 chore(router): remove @angular/upgrade peer dep (#12896) 2016-11-16 16:26:43 -08:00
16303ac487 refactor(http): remove all facade methods from http module (#12870) 2016-11-16 16:26:43 -08:00
6cdc3b5c12 fix(tsickle): support ctorParams in function closure (#12876)
See https://github.com/angular/tsickle/issues/261 for context.
2016-11-16 16:26:43 -08:00
5c46c493f2 fix(animations): retain styling when transition destinations are changed (#12208)
Closes #9661
Closes #12208
2016-11-16 16:26:43 -08:00
e02c18049d fix(select): allow for null values in HTML select options bound with ngValue
closes #12829
2016-11-16 16:26:43 -08:00
e0ce5458a2 fix: allow for null values in HTML select options bound with ngValue
This corrects the case of <option [ngValue]="null"> binding a string like "{0: null}" to the model instead of an actual null object.

Closes #10349
2016-11-16 16:26:43 -08:00
6a5ba0ec81 fix: allow for null values in HTML select options bound with ngValue
This corrects the case of <option [ngValue]="null"> binding a string like "{0: null}" to the model instead of an actual null object.

Closes #10349
2016-11-16 16:26:42 -08:00
828c0d24eb refactor(core): remove dead code (#12871) 2016-11-16 16:26:42 -08:00
22536442d6 refactor(core): remove ListWrapper from i18n 2016-11-16 16:26:42 -08:00
845ea235ee fix(http): return request url if it cannot be retrieved from response
closes #12837
2016-11-16 16:26:42 -08:00
21a4de999b fix(http): correctly handle response body for 204 status code
closes #12830
fixes #12393
2016-11-16 16:26:42 -08:00
82b34838bf refactor(xhr_backend): remove facade 2016-11-16 16:26:42 -08:00
120 changed files with 2261 additions and 677 deletions

View File

@ -1,3 +1,86 @@
<a name="2.2.4"></a>
## [2.2.4](https://github.com/angular/angular/compare/2.2.3...2.2.4) (2016-11-30)
### Bug Fixes
* **common:** update DatePipe to allow closure compilation ([eba53fd](https://github.com/angular/angular/commit/eba53fd))
* **compiler:** fix performance regression caused by 5b0f9e2 ([ee2d6e5](https://github.com/angular/angular/commit/ee2d6e5)), closes [#13146](https://github.com/angular/angular/issues/13146)
* **compiler-cli:** fix paths in source maps to be relative ([eb173bc](https://github.com/angular/angular/commit/eb173bc)), closes [#13040](https://github.com/angular/angular/issues/13040)
<a name="2.2.3"></a>
## [2.2.3](https://github.com/angular/angular/compare/2.2.2...2.2.3) (2016-11-23)
### Bug Fixes
* **compiler:** Revert: fix versions of `@angular/tsc-wrapped` ([015ca47](https://github.com/angular/angular/commit/015ca47))
* **animations:** Revert: blend in all previously transitioned styles into next animation if interrupted ([c12e56e](https://github.com/angular/angular/commit/c12e56e))
<a name="2.2.2"></a>
## [2.2.2](https://github.com/angular/angular/compare/2.2.1...2.2.2) (2016-11-22)
### Bug Fixes
* **animations:** blend in all previously transitioned styles into next animation if interrupted ([#13014](https://github.com/angular/angular/issues/13014)) ([ea4fc9b](https://github.com/angular/angular/commit/ea4fc9b)), closes [#13013](https://github.com/angular/angular/issues/13013)
* **benchmarks:** use sanitized style values ([#12943](https://github.com/angular/angular/issues/12943)) ([33a7902](https://github.com/angular/angular/commit/33a7902))
* **closure:** quote date pattern aliases ([#13012](https://github.com/angular/angular/issues/13012)) ([0956ace](https://github.com/angular/angular/commit/0956ace))
* **compiler:** fix versions of `@angular/tsc-wrapped` ([2fe6fb1](https://github.com/angular/angular/commit/2fe6fb1))
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([8df328b](https://github.com/angular/angular/commit/8df328b))
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([511cd4d](https://github.com/angular/angular/commit/511cd4d))
* **router:** removes a peer dependency from router to upgrade ([115f18f](https://github.com/angular/angular/commit/115f18f))
* **router:** removes a peer dependency from router to upgrade ([87d5d49](https://github.com/angular/angular/commit/87d5d49))
* **router:** support redirects to named outlets ([09226d9](https://github.com/angular/angular/commit/09226d9)), closes [#12740](https://github.com/angular/angular/issues/12740) [#9921](https://github.com/angular/angular/issues/9921)
* **upgrade:** call ng1 lifecycle hooks ([#12875](https://github.com/angular/angular/issues/12875)) ([462316b](https://github.com/angular/angular/commit/462316b))
<a name="2.3.0-beta.0"></a>
# [2.3.0-beta.0](https://github.com/angular/angular/compare/2.2.0...2.3.0-beta.0) (2016-11-17)
### Bug Fixes
* **compiler:** assert xliff messages have translations ([7908679](https://github.com/angular/angular/commit/7908679)), closes [#12815](https://github.com/angular/angular/issues/12815) [#12604](https://github.com/angular/angular/issues/12604)
* **compiler:** updates hash algo for xmb/xtb files ([2f14415](https://github.com/angular/angular/commit/2f14415))
* **core:** fix placeholders handling in i18n. ([76e4911](https://github.com/angular/angular/commit/76e4911)), closes [#12512](https://github.com/angular/angular/issues/12512)
* **core:** misc i18n fixes ([ed5e98d](https://github.com/angular/angular/commit/ed5e98d))
* **core:** xmb serializer uses decimal messaged IDs ([08c038e](https://github.com/angular/angular/commit/08c038e)), closes [#12511](https://github.com/angular/angular/issues/12511)
* **platform-browser:** enable AOT ([efbbefd](https://github.com/angular/angular/commit/efbbefd)), closes [#12783](https://github.com/angular/angular/issues/12783)
### Features
* **core:** add `attachView` / `detachView` to ApplicationRef ([9f7d32a](https://github.com/angular/angular/commit/9f7d32a)), closes [#9293](https://github.com/angular/angular/issues/9293)
* **core:** expose `ViewRef` as `ChangeDetectorRef` ([1b5384e](https://github.com/angular/angular/commit/1b5384e)), closes [#12722](https://github.com/angular/angular/issues/12722)
* **core:** implements a decimal fingerprint for i18n ([582550a](https://github.com/angular/angular/commit/582550a))
* **router:** register router with ngprobe ([c2fae72](https://github.com/angular/angular/commit/c2fae72))
* **router_link:** add skipLocationChange and replaceUrl inputs ([#12850](https://github.com/angular/angular/issues/12850)) ([46d1502](https://github.com/angular/angular/commit/46d1502))
Note: The 2.3.0-beta.0 release also contains all the changes present in the 2.2.1 release.
<a name="2.2.1"></a>
## [2.2.1](https://github.com/angular/angular/compare/2.2.0...2.2.1) (2016-11-17)
### Bug Fixes
* **animations:** only pass in same typed players as previous players into web-animations ([#12907](https://github.com/angular/angular/issues/12907)) ([583d283](https://github.com/angular/angular/commit/583d283))
* **animations:** retain styling when transition destinations are changed ([#12208](https://github.com/angular/angular/issues/12208)) ([5c46c49](https://github.com/angular/angular/commit/5c46c49)), closes [#9661](https://github.com/angular/angular/issues/9661)
* **core:** support `ngTemplateOutlet` in production mode ([#12921](https://github.com/angular/angular/issues/12921)) ([4628798](https://github.com/angular/angular/commit/4628798)), closes [#12911](https://github.com/angular/angular/issues/12911)
* **http:** correctly handle response body for 204 status code ([21a4de9](https://github.com/angular/angular/commit/21a4de9)), closes [#12830](https://github.com/angular/angular/issues/12830) [#12393](https://github.com/angular/angular/issues/12393)
* **http:** return request url if it cannot be retrieved from response ([845ea23](https://github.com/angular/angular/commit/845ea23)), closes [#12837](https://github.com/angular/angular/issues/12837)
* **upgrade:** make AoT ngUpgrade work with the testability API and resumeBootstrap() ([#12910](https://github.com/angular/angular/issues/12910)) ([dc1662a](https://github.com/angular/angular/commit/dc1662a))
* **platform-browser:** fix disableDebugTools() ([#12918](https://github.com/angular/angular/issues/12918)) ([7b67bad](https://github.com/angular/angular/commit/7b67bad))
* **router:** add a banner file for the router ([#12919](https://github.com/angular/angular/issues/12919)) ([364642d](https://github.com/angular/angular/commit/364642d))
* **router:** removes a peer dependency from router to upgrade ([1dcf1f4](https://github.com/angular/angular/commit/1dcf1f4))
* **forms** allow for null values in HTML select options bound with ngValue ([e0ce545](https://github.com/angular/angular/commit/e0ce545)), closes [#10349](https://github.com/angular/angular/issues/10349)
* **router:** should not create a route state if navigation is canceled ([#12868](https://github.com/angular/angular/issues/12868)) ([dabaf85](https://github.com/angular/angular/commit/dabaf85)), closes [#12776](https://github.com/angular/angular/issues/12776)
* **common:** select should allow for null values in HTML select options bound with ngValue ([e02c180](https://github.com/angular/angular/commit/e02c180)), closes [#12829](https://github.com/angular/angular/issues/12829)
* **compiler-cli:** support ctorParams in function closure ([#12876](https://github.com/angular/angular/issues/12876)) ([6cdc3b5](https://github.com/angular/angular/commit/6cdc3b5))
<a name="2.2.0"></a>
# [2.2.0 upgrade-firebooster](https://github.com/angular/angular/compare/2.2.0-rc.0...2.2.0) (2016-11-14)

View File

@ -20,6 +20,10 @@ PACKAGES=(core
benchpress)
BUILD_ALL=true
BUNDLE=true
VERSION_PREFIX=$(node -p "require('./package.json').version")
VERSION_SUFFIX="-$(git log --oneline -1 | awk '{print $1}')"
ROUTER_VERSION_PREFIX=$(node -p "require('./package.json').version.replace(/^2/, '3')")
REMOVE_BENCHPRESS=false
for ARG in "$@"; do
case "$ARG" in
@ -31,6 +35,10 @@ for ARG in "$@"; do
--bundle=*)
BUNDLE=( "${ARG#--bundle=}" )
;;
--publish)
VERSION_SUFFIX=""
REMOVE_BENCHPRESS=true
;;
*)
echo "Unknown option $ARG."
exit 1
@ -38,6 +46,10 @@ for ARG in "$@"; do
esac
done
VERSION="${VERSION_PREFIX}${VERSION_SUFFIX}"
ROUTER_VERSION="${ROUTER_VERSION_PREFIX}${VERSION_SUFFIX}"
echo "====== BUILDING: Version ${VERSION} (Router ${ROUTER_VERSION})"
export NODE_PATH=${NODE_PATH}:$(pwd)/dist/all:$(pwd)/dist/tools
TSC="node --max-old-space-size=3000 dist/tools/@angular/tsc-wrapped/src/main"
UGLIFYJS=`pwd`/node_modules/.bin/uglifyjs
@ -106,7 +118,13 @@ do
UMD_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.min.js
UMD_STATIC_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}-static.umd.min.js
UMD_UPGRADE_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}-upgrade.umd.min.js
LICENSE_BANNER=${PWD}/modules/@angular/license-banner.txt
if [[ ${PACKAGE} != router ]]; then
LICENSE_BANNER=${PWD}/modules/@angular/license-banner.txt
fi
if [[ ${PACKAGE} == router ]]; then
LICENSE_BANNER=${PWD}/modules/@angular/router-license-banner.txt
fi
rm -rf ${DESTDIR}
@ -191,8 +209,24 @@ do
$UGLIFYJS -c --screw-ie8 --comments -o ${UMD_UPGRADE_ES5_MIN_PATH} ${UMD_UPGRADE_ES5_PATH}
fi
) 2>&1 | grep -v "as external dependency"
fi
(
echo "====== VERSION: Updating version references"
cd ${DESTDIR}
echo "====== EXECUTE: perl -p -i -e \"s/0\.0\.0\-PLACEHOLDER/${VERSION}/g\" $""(grep -ril 0\.0\.0\-PLACEHOLDER .)"
perl -p -i -e "s/0\.0\.0\-PLACEHOLDER/${VERSION}/g" $(grep -ril 0\.0\.0\-PLACEHOLDER .) < /dev/null 2> /dev/null
echo "====== EXECUTE: perl -p -i -e \"s/0\.0\.0\-ROUTERPLACEHOLDER/${ROUTER_VERSION}/g\" $""(grep -ril 0\.0\.0\-ROUTERPLACEHOLDER .)"
perl -p -i -e "s/0\.0\.0\-ROUTERPLACEHOLDER/${ROUTER_VERSION}/g" $(grep -ril 0\.0\.0\-ROUTERPLACEHOLDER .) < /dev/null 2> /dev/null
)
done
echo ""
echo "====== Building examples: ./modules/@angular/examples/build.sh ====="
./modules/@angular/examples/build.sh
if [[ ${REMOVE_BENCHPRESS} == true ]]; then
echo ""
echo "==== Removing benchpress from publication"
rm -r dist/packages-dist/benchpress
fi

View File

@ -4,7 +4,7 @@ machine:
dependencies:
pre:
- npm install -g npm
- npm install -g npm@3.6.0
test:
override:

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
/* tslint:disable:no-console */
import {browser} from 'protractor';
const assertEventsContainsName = function(events: any[], eventName: string) {

View File

@ -59,7 +59,7 @@ const _observableStrategy = new ObservableStrategy();
* {@example common/pipes/ts/async_pipe.ts region='AsyncPipePromise'}
*
* It's also possible to use `async` with Observables. The example below binds the `time` Observable
* to the view. The Observable continuesly updates the view with the current time.
* to the view. The Observable continuously updates the view with the current time.
*
* {@example common/pipes/ts/async_pipe.ts region='AsyncPipeObservable'}
*

View File

@ -46,7 +46,7 @@ export class CodeGenerator {
let root = this.options.basePath;
for (const eachRootDir of this.options.rootDirs || []) {
if (this.options.trace) {
console.log(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
}
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
root = eachRootDir;
@ -161,7 +161,7 @@ export function extractProgramSymbols(
const moduleMetadata = staticReflector.getModuleMetadata(absSrcPath);
if (!moduleMetadata) {
console.log(`WARNING: no metadata found for ${absSrcPath}`);
console.warn(`WARNING: no metadata found for ${absSrcPath}`);
return;
}

View File

@ -34,34 +34,34 @@ export class Extractor {
const programSymbols: StaticSymbol[] =
extractProgramSymbols(this.program, this.staticReflector, this.reflectorHost, this.options);
return compiler
.analyzeNgModules(programSymbols, {transitiveModules: true}, this.metadataResolver)
.then(({files}) => {
const errors: compiler.ParseError[] = [];
const {ngModules, files} = compiler.analyzeAndValidateNgModules(
programSymbols, {transitiveModules: true}, this.metadataResolver);
return compiler.loadNgModuleDirectives(ngModules).then(() => {
const errors: compiler.ParseError[] = [];
files.forEach(file => {
const compMetas: compiler.CompileDirectiveMetadata[] = [];
file.directives.forEach(directiveType => {
const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType);
if (dirMeta && dirMeta.isComponent) {
compMetas.push(dirMeta);
}
});
compMetas.forEach(compMeta => {
const html = compMeta.template.template;
const interpolationConfig =
compiler.InterpolationConfig.fromArray(compMeta.template.interpolation);
errors.push(
...this.messageBundle.updateFromTemplate(html, file.srcUrl, interpolationConfig));
});
});
if (errors.length) {
throw new Error(errors.map(e => e.toString()).join('\n'));
files.forEach(file => {
const compMetas: compiler.CompileDirectiveMetadata[] = [];
file.directives.forEach(directiveType => {
const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType);
if (dirMeta && dirMeta.isComponent) {
compMetas.push(dirMeta);
}
return this.messageBundle;
});
compMetas.forEach(compMeta => {
const html = compMeta.template.template;
const interpolationConfig =
compiler.InterpolationConfig.fromArray(compMeta.template.interpolation);
errors.push(
...this.messageBundle.updateFromTemplate(html, file.srcUrl, interpolationConfig));
});
});
if (errors.length) {
throw new Error(errors.map(e => e.toString()).join('\n'));
}
return this.messageBundle;
});
}
static create(

View File

@ -49,7 +49,7 @@ export class PathMappedReflectorHost extends ReflectorHost {
ts.resolveModuleName(m, rootedContainingFile, this.options, this.context).resolvedModule;
if (resolved) {
if (this.options.traceResolution) {
console.log('resolve', m, containingFile, '=>', resolved.resolvedFileName);
console.error('resolve', m, containingFile, '=>', resolved.resolvedFileName);
}
return resolved.resolvedFileName;
}
@ -67,7 +67,7 @@ export class PathMappedReflectorHost extends ReflectorHost {
containingFile = this.resolveAssetUrl(containingFile, '');
if (this.options.traceResolution) {
console.log(
console.error(
'getImportPath from containingFile', containingFile, 'to importedFile', importedFile);
}

View File

@ -132,7 +132,6 @@ export class StaticReflector implements ReflectorReader {
const ctor = (<any[]>ctorData).find(a => a['__symbolic'] == 'constructor');
const parameterTypes = <any[]>this.simplify(type, ctor['parameters'] || []);
const parameterDecorators = <any[]>this.simplify(type, ctor['parameterDecorators'] || []);
parameters = [];
parameterTypes.forEach((paramType, index) => {
const nestedResult: any[] = [];
@ -153,7 +152,7 @@ export class StaticReflector implements ReflectorReader {
}
return parameters;
} catch (e) {
console.log(`Failed on type ${JSON.stringify(type)} with error ${e}`);
console.error(`Failed on type ${JSON.stringify(type)} with error ${e}`);
throw e;
}
}

View File

@ -7,7 +7,7 @@
*/
import {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler-cli/src/static_reflector';
import {HostListener, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
import {HostListener, Inject, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core';
import {MetadataCollector} from '@angular/tsc-wrapped';
import * as ts from 'typescript';
@ -410,6 +410,13 @@ describe('StaticReflector', () => {
expect(props).toEqual({foo: []});
});
it('should read ctor parameters with forwardRef', () => {
const src = '/tmp/src/forward-ref.ts';
const dep = host.getStaticSymbol(src, 'Dep');
const props = reflector.parameters(host.getStaticSymbol(src, 'Forward'));
expect(props).toEqual([[dep, new Inject(dep)]]);
});
it('should report an error for invalid function calls', () => {
expect(
() =>
@ -1068,6 +1075,18 @@ class MockReflectorHost implements StaticReflectorHost {
providers: [ { provider: 'a', useValue: (() => 1)() }]
})
export class InvalidMetadata {}
`,
'/tmp/src/forward-ref.ts': `
import {forwardRef} from 'angular2/core';
import {Component} from 'angular2/src/core/metadata';
import {Inject} from 'angular2/src/core/di/metadata';
@Component({})
export class Forward {
constructor(@Inject(forwardRef(() => Dep)) d: Dep) {}
}
export class Dep {
@Input f: Forward;
}
`
};

View File

@ -1,11 +1,11 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"lib": ["es6", "dom"],
"noImplicitAny": true,
"sourceMap": true,
"baseUrl": ".",
"declaration": true,
"experimentalDecorators": true,
"noImplicitAny": true,
"module": "commonjs",
"outDir": "../../../dist/packages-dist/compiler-cli",
"paths": {
"@angular/core": ["../../../dist/packages-dist/core"],
"@angular/common": ["../../../dist/packages-dist/common"],
@ -14,11 +14,11 @@
"@angular/platform-browser": ["../../../dist/packages-dist/platform-browser"],
"@angular/tsc-wrapped": ["../../../dist/tools/@angular/tsc-wrapped"]
},
"experimentalDecorators": true,
"rootDir": ".",
"sourceRoot": ".",
"outDir": "../../../dist/packages-dist/compiler-cli",
"declaration": true,
"sourceMap": true,
"inlineSources": true,
"target": "es5",
"lib": ["es6", "dom"],
"skipLibCheck": true
},
"exclude": ["integrationtest"],

View File

@ -41,7 +41,9 @@ const _ANIMATION_TIME_VAR = o.variable('totalTime');
const _ANIMATION_START_STATE_STYLES_VAR = o.variable('startStateStyles');
const _ANIMATION_END_STATE_STYLES_VAR = o.variable('endStateStyles');
const _ANIMATION_COLLECTED_STYLES = o.variable('collectedStyles');
const EMPTY_MAP = o.literalMap([]);
const _PREVIOUS_ANIMATION_PLAYERS = o.variable('previousPlayers');
const _EMPTY_MAP = o.literalMap([]);
const _EMPTY_ARRAY = o.literalArr([]);
class _AnimationBuilder implements AnimationAstVisitor {
private _fnVarName: string;
@ -110,10 +112,15 @@ class _AnimationBuilder implements AnimationAstVisitor {
_callAnimateMethod(
ast: AnimationStepAst, startingStylesExpr: any, keyframesExpr: any,
context: _AnimationBuilderContext) {
let previousStylesValue: o.Expression = _EMPTY_ARRAY;
if (context.isExpectingFirstAnimateStep) {
previousStylesValue = _PREVIOUS_ANIMATION_PLAYERS;
context.isExpectingFirstAnimateStep = false;
}
context.totalTransitionTime += ast.duration + ast.delay;
return _ANIMATION_FACTORY_RENDERER_VAR.callMethod('animate', [
_ANIMATION_FACTORY_ELEMENT_VAR, startingStylesExpr, keyframesExpr, o.literal(ast.duration),
o.literal(ast.delay), o.literal(ast.easing)
o.literal(ast.delay), o.literal(ast.easing), previousStylesValue
]);
}
@ -150,6 +157,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
context.totalTransitionTime = 0;
context.isExpectingFirstStyleStep = true;
context.isExpectingFirstAnimateStep = true;
const stateChangePreconditions: o.Expression[] = [];
@ -187,17 +195,16 @@ class _AnimationBuilder implements AnimationAstVisitor {
context.stateMap.registerState(DEFAULT_STATE, {});
const statements: o.Statement[] = [];
statements.push(_ANIMATION_FACTORY_VIEW_CONTEXT
.callMethod(
'cancelActiveAnimation',
statements.push(_PREVIOUS_ANIMATION_PLAYERS
.set(_ANIMATION_FACTORY_VIEW_CONTEXT.callMethod(
'getAnimationPlayers',
[
_ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName),
_ANIMATION_NEXT_STATE_VAR.equals(o.literal(EMPTY_STATE))
])
.toStmt());
]))
.toDeclStmt());
statements.push(_ANIMATION_COLLECTED_STYLES.set(EMPTY_MAP).toDeclStmt());
statements.push(_ANIMATION_COLLECTED_STYLES.set(_EMPTY_MAP).toDeclStmt());
statements.push(_ANIMATION_PLAYER_VAR.set(o.NULL_EXPR).toDeclStmt());
statements.push(_ANIMATION_TIME_VAR.set(o.literal(0)).toDeclStmt());
@ -223,17 +230,6 @@ class _AnimationBuilder implements AnimationAstVisitor {
const RENDER_STYLES_FN = o.importExpr(resolveIdentifier(Identifiers.renderStyles));
// before we start any animation we want to clear out the starting
// styles from the element's style property (since they were placed
// there at the end of the last animation
statements.push(RENDER_STYLES_FN
.callFn([
_ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
o.importExpr(resolveIdentifier(Identifiers.clearStyles))
.callFn([_ANIMATION_START_STATE_STYLES_VAR])
])
.toStmt());
ast.stateTransitions.forEach(transAst => statements.push(transAst.visit(this, context)));
// this check ensures that the animation factory always returns a player
@ -269,6 +265,22 @@ class _AnimationBuilder implements AnimationAstVisitor {
])])
.toStmt());
statements.push(o.importExpr(resolveIdentifier(Identifiers.AnimationSequencePlayer))
.instantiate([_PREVIOUS_ANIMATION_PLAYERS])
.callMethod('destroy', [])
.toStmt());
// before we start any animation we want to clear out the starting
// styles from the element's style property (since they were placed
// there at the end of the last animation
statements.push(RENDER_STYLES_FN
.callFn([
_ANIMATION_FACTORY_ELEMENT_VAR, _ANIMATION_FACTORY_RENDERER_VAR,
o.importExpr(resolveIdentifier(Identifiers.clearStyles))
.callFn([_ANIMATION_START_STATE_STYLES_VAR])
])
.toStmt());
statements.push(_ANIMATION_FACTORY_VIEW_CONTEXT
.callMethod(
'queueAnimation',
@ -304,7 +316,7 @@ class _AnimationBuilder implements AnimationAstVisitor {
const lookupMap: any[] = [];
Object.keys(context.stateMap.states).forEach(stateName => {
const value = context.stateMap.states[stateName];
let variableValue = EMPTY_MAP;
let variableValue = _EMPTY_MAP;
if (isPresent(value)) {
const styleMap: any[] = [];
Object.keys(value).forEach(key => { styleMap.push([key, o.literal(value[key])]); });
@ -324,6 +336,7 @@ class _AnimationBuilderContext {
stateMap = new _AnimationBuilderStateMap();
endStateAnimateStep: AnimationStepAst = null;
isExpectingFirstStyleStep = false;
isExpectingFirstAnimateStep = false;
totalTransitionTime = 0;
}

View File

@ -589,7 +589,7 @@ export interface CompileNgModuleDirectiveSummary extends CompileSummary {
exportedDirectives: CompileIdentifierMetadata[];
exportedPipes: CompileIdentifierMetadata[];
exportedModules: CompileNgModuleDirectiveSummary[];
loadingPromises: Promise<any>[];
directiveLoaders: (() => Promise<void>)[];
}
export type CompileNgModuleSummary =
@ -661,7 +661,7 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
exportedModules: this.exportedModules,
exportedDirectives: this.exportedDirectives,
exportedPipes: this.exportedPipes,
loadingPromises: this.transitiveModule.loadingPromises
directiveLoaders: this.transitiveModule.directiveLoaders
};
}
@ -682,7 +682,7 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier {
exportedDirectives: this.exportedDirectives,
exportedPipes: this.exportedPipes,
exportedModules: this.exportedModules,
loadingPromises: this.transitiveModule.loadingPromises
directiveLoaders: this.transitiveModule.directiveLoaders
};
}
}
@ -695,7 +695,7 @@ export class TransitiveCompileNgModuleMetadata {
public modules: CompileNgModuleInjectorSummary[], public providers: CompileProviderMetadata[],
public entryComponents: CompileIdentifierMetadata[],
public directives: CompileIdentifierMetadata[], public pipes: CompileIdentifierMetadata[],
public loadingPromises: Promise<any>[]) {
public directiveLoaders: (() => Promise<void>)[]) {
directives.forEach(dir => this.directivesSet.add(dir.reference));
pipes.forEach(pipe => this.pipesSet.add(pipe.reference));
}

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ListWrapper} from '../../facade/collection';
import * as ml from '../../ml_parser/ast';
import {HtmlParser} from '../../ml_parser/html_parser';
import {InterpolationConfig} from '../../ml_parser/interpolation_config';
@ -162,7 +161,7 @@ class _WriteVisitor implements i18n.Visitor {
serialize(nodes: i18n.Node[]): xml.Node[] {
this._isInIcu = false;
return ListWrapper.flatten(nodes.map(node => node.visit(this)));
return [].concat(...nodes.map(node => node.visit(this)));
}
}

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ListWrapper} from '../../facade/collection';
import * as html from '../../ml_parser/ast';
import * as i18n from '../i18n_ast';
import {MessageBundle} from '../message_bundle';
@ -121,6 +120,6 @@ class _Visitor implements i18n.Visitor {
}
serialize(nodes: i18n.Node[]): xml.Node[] {
return ListWrapper.flatten(nodes.map(node => node.visit(this)));
return [].concat(...nodes.map(node => node.visit(this)));
}
}

View File

@ -141,80 +141,73 @@ export class CompileMetadataResolver {
return null;
}
private _loadDirectiveMetadata(directiveType: any, isSync: boolean): Promise<any> {
private _loadDirectiveMetadata(directiveType: any, isSync: boolean): () => Promise<any> {
if (this._directiveCache.has(directiveType)) {
return;
}
directiveType = resolveForwardRef(directiveType);
const nonNormalizedMetadata = this.getNonNormalizedDirectiveMetadata(directiveType);
const createDirectiveMetadata = (templateMetadata: cpl.CompileTemplateMetadata) => {
const normalizedDirMeta = new cpl.CompileDirectiveMetadata({
type: nonNormalizedMetadata.type,
isComponent: nonNormalizedMetadata.isComponent,
selector: nonNormalizedMetadata.selector,
exportAs: nonNormalizedMetadata.exportAs,
changeDetection: nonNormalizedMetadata.changeDetection,
inputs: nonNormalizedMetadata.inputs,
outputs: nonNormalizedMetadata.outputs,
hostListeners: nonNormalizedMetadata.hostListeners,
hostProperties: nonNormalizedMetadata.hostProperties,
hostAttributes: nonNormalizedMetadata.hostAttributes,
providers: nonNormalizedMetadata.providers,
viewProviders: nonNormalizedMetadata.viewProviders,
queries: nonNormalizedMetadata.queries,
viewQueries: nonNormalizedMetadata.viewQueries,
entryComponents: nonNormalizedMetadata.entryComponents,
template: templateMetadata
});
this._directiveCache.set(directiveType, normalizedDirMeta);
this._directiveSummaryCache.set(directiveType, normalizedDirMeta.toSummary());
return normalizedDirMeta;
};
if (nonNormalizedMetadata.isComponent) {
const templateMeta = this._directiveNormalizer.normalizeTemplate({
componentType: directiveType,
moduleUrl: nonNormalizedMetadata.type.moduleUrl,
encapsulation: nonNormalizedMetadata.template.encapsulation,
template: nonNormalizedMetadata.template.template,
templateUrl: nonNormalizedMetadata.template.templateUrl,
styles: nonNormalizedMetadata.template.styles,
styleUrls: nonNormalizedMetadata.template.styleUrls,
animations: nonNormalizedMetadata.template.animations,
interpolation: nonNormalizedMetadata.template.interpolation
});
if (templateMeta.syncResult) {
createDirectiveMetadata(templateMeta.syncResult);
return null;
} else {
if (isSync) {
throw new ComponentStillLoadingError(directiveType);
}
return () => templateMeta.asyncResult.then(createDirectiveMetadata);
}
} else {
// directive
createDirectiveMetadata(null);
return null;
}
}
getNonNormalizedDirectiveMetadata(directiveType: any): cpl.CompileDirectiveMetadata {
directiveType = resolveForwardRef(directiveType);
const dirMeta = this._directiveResolver.resolve(directiveType);
if (!dirMeta) {
return null;
}
let moduleUrl = staticTypeModuleUrl(directiveType);
const createDirectiveMetadata = (templateMeta: cpl.CompileTemplateMetadata) => {
let changeDetectionStrategy: ChangeDetectionStrategy = null;
let viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
let entryComponentMetadata: cpl.CompileIdentifierMetadata[] = [];
let selector = dirMeta.selector;
if (dirMeta instanceof Component) {
// Component
changeDetectionStrategy = dirMeta.changeDetection;
if (dirMeta.viewProviders) {
viewProviders = this._getProvidersMetadata(
dirMeta.viewProviders, entryComponentMetadata,
`viewProviders for "${stringify(directiveType)}"`);
}
if (dirMeta.entryComponents) {
entryComponentMetadata =
flattenAndDedupeArray(dirMeta.entryComponents)
.map((type) => this._getIdentifierMetadata(type, staticTypeModuleUrl(type)))
.concat(entryComponentMetadata);
}
if (!selector) {
selector = this._schemaRegistry.getDefaultComponentElementName();
}
} else {
// Directive
if (!selector) {
throw new Error(`Directive ${stringify(directiveType)} has no selector, please add it!`);
}
}
let providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
if (isPresent(dirMeta.providers)) {
providers = this._getProvidersMetadata(
dirMeta.providers, entryComponentMetadata,
`providers for "${stringify(directiveType)}"`);
}
let queries: cpl.CompileQueryMetadata[] = [];
let viewQueries: cpl.CompileQueryMetadata[] = [];
if (isPresent(dirMeta.queries)) {
queries = this._getQueriesMetadata(dirMeta.queries, false, directiveType);
viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType);
}
const meta = cpl.CompileDirectiveMetadata.create({
selector: selector,
exportAs: dirMeta.exportAs,
isComponent: !!templateMeta,
type: this._getTypeMetadata(directiveType, moduleUrl),
template: templateMeta,
changeDetection: changeDetectionStrategy,
inputs: dirMeta.inputs,
outputs: dirMeta.outputs,
host: dirMeta.host,
providers: providers,
viewProviders: viewProviders,
queries: queries,
viewQueries: viewQueries,
entryComponents: entryComponentMetadata
});
this._directiveCache.set(directiveType, meta);
this._directiveSummaryCache.set(directiveType, meta.toSummary());
return meta;
};
let nonNormalizedTemplateMetadata: cpl.CompileTemplateMetadata;
if (dirMeta instanceof Component) {
// component
@ -227,9 +220,7 @@ export class CompileMetadataResolver {
dirMeta.animations.map(e => this.getAnimationEntryMetadata(e)) :
null;
const templateMeta = this._directiveNormalizer.normalizeTemplate({
componentType: directiveType,
moduleUrl: moduleUrl,
nonNormalizedTemplateMetadata = new cpl.CompileTemplateMetadata({
encapsulation: dirMeta.encapsulation,
template: dirMeta.template,
templateUrl: dirMeta.templateUrl,
@ -238,20 +229,65 @@ export class CompileMetadataResolver {
animations: animations,
interpolation: dirMeta.interpolation
});
if (templateMeta.syncResult) {
createDirectiveMetadata(templateMeta.syncResult);
return null;
} else {
if (isSync) {
throw new ComponentStillLoadingError(directiveType);
}
return templateMeta.asyncResult.then(createDirectiveMetadata);
}
let changeDetectionStrategy: ChangeDetectionStrategy = null;
let viewProviders: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
let entryComponentMetadata: cpl.CompileIdentifierMetadata[] = [];
let selector = dirMeta.selector;
if (dirMeta instanceof Component) {
// Component
changeDetectionStrategy = dirMeta.changeDetection;
if (dirMeta.viewProviders) {
viewProviders = this._getProvidersMetadata(
dirMeta.viewProviders, entryComponentMetadata,
`viewProviders for "${stringify(directiveType)}"`);
}
if (dirMeta.entryComponents) {
entryComponentMetadata =
flattenAndDedupeArray(dirMeta.entryComponents)
.map((type) => this._getIdentifierMetadata(type, staticTypeModuleUrl(type)))
.concat(entryComponentMetadata);
}
if (!selector) {
selector = this._schemaRegistry.getDefaultComponentElementName();
}
} else {
// directive
createDirectiveMetadata(null);
return null;
// Directive
if (!selector) {
throw new Error(`Directive ${stringify(directiveType)} has no selector, please add it!`);
}
}
let providers: Array<cpl.CompileProviderMetadata|cpl.CompileTypeMetadata|any[]> = [];
if (isPresent(dirMeta.providers)) {
providers = this._getProvidersMetadata(
dirMeta.providers, entryComponentMetadata, `providers for "${stringify(directiveType)}"`);
}
let queries: cpl.CompileQueryMetadata[] = [];
let viewQueries: cpl.CompileQueryMetadata[] = [];
if (isPresent(dirMeta.queries)) {
queries = this._getQueriesMetadata(dirMeta.queries, false, directiveType);
viewQueries = this._getQueriesMetadata(dirMeta.queries, true, directiveType);
}
return cpl.CompileDirectiveMetadata.create({
selector: selector,
exportAs: dirMeta.exportAs,
isComponent: !!nonNormalizedTemplateMetadata,
type: this._getTypeMetadata(directiveType, moduleUrl),
template: nonNormalizedTemplateMetadata,
changeDetection: changeDetectionStrategy,
inputs: dirMeta.inputs,
outputs: dirMeta.outputs,
host: dirMeta.host,
providers: providers,
viewProviders: viewProviders,
queries: queries,
viewQueries: viewQueries,
entryComponents: entryComponentMetadata
});
}
/**
@ -309,11 +345,20 @@ export class CompileMetadataResolver {
loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
{ngModule: cpl.CompileNgModuleMetadata, loading: Promise<any>} {
const ngModule = this._loadNgModuleMetadata(moduleType, isSync, throwIfNotFound);
const loading =
ngModule ? Promise.all(ngModule.transitiveModule.loadingPromises) : Promise.resolve(null);
const loading = ngModule ?
Promise.all(ngModule.transitiveModule.directiveLoaders.map(loader => loader())) :
Promise.resolve(null);
return {ngModule, loading};
}
/**
* Get the NgModule metadata without loading the directives.
*/
getUnloadedNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
cpl.CompileNgModuleMetadata {
return this._loadNgModuleMetadata(moduleType, isSync, throwIfNotFound);
}
private _loadNgModuleMetadata(moduleType: any, isSync: boolean, throwIfNotFound = true):
cpl.CompileNgModuleMetadata {
moduleType = resolveForwardRef(moduleType);
@ -396,9 +441,9 @@ export class CompileMetadataResolver {
transitiveModule.directives.push(declaredIdentifier);
declaredDirectives.push(declaredIdentifier);
this._addTypeToModule(declaredType, moduleType);
const loadingPromise = this._loadDirectiveMetadata(declaredType, isSync);
if (loadingPromise) {
transitiveModule.loadingPromises.push(loadingPromise);
const loader = this._loadDirectiveMetadata(declaredType, isSync);
if (loader) {
transitiveModule.directiveLoaders.push(loader);
}
} else if (this._pipeResolver.isPipe(declaredType)) {
transitiveModule.pipesSet.add(declaredType);
@ -525,10 +570,10 @@ export class CompileMetadataResolver {
const directives =
flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedDirectives));
const pipes = flattenArray(transitiveExportedModules.map((ngModule) => ngModule.exportedPipes));
const loadingPromises =
ListWrapper.flatten(transitiveExportedModules.map(ngModule => ngModule.loadingPromises));
const directiveLoaders =
ListWrapper.flatten(transitiveExportedModules.map(ngModule => ngModule.directiveLoaders));
return new cpl.TransitiveCompileNgModuleMetadata(
transitiveModules, providers, entryComponents, directives, pipes, loadingPromises);
transitiveModules, providers, entryComponents, directives, pipes, directiveLoaders);
}
private _getIdentifierMetadata(type: Type<any>, moduleUrl: string):
@ -584,20 +629,26 @@ export class CompileMetadataResolver {
return pipeSummary;
}
private _loadPipeMetadata(pipeType: Type<any>): void {
pipeType = resolveForwardRef(pipeType);
const pipeMeta = this._pipeResolver.resolve(pipeType);
getOrLoadPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
let pipeMeta = this._pipeCache.get(pipeType);
if (!pipeMeta) {
return null;
pipeMeta = this._loadPipeMetadata(pipeType);
}
return pipeMeta;
}
const meta = new cpl.CompilePipeMetadata({
private _loadPipeMetadata(pipeType: any): cpl.CompilePipeMetadata {
pipeType = resolveForwardRef(pipeType);
const pipeAnnotation = this._pipeResolver.resolve(pipeType);
const pipeMeta = new cpl.CompilePipeMetadata({
type: this._getTypeMetadata(pipeType, staticTypeModuleUrl(pipeType)),
name: pipeMeta.name,
pure: pipeMeta.pure
name: pipeAnnotation.name,
pure: pipeAnnotation.pure
});
this._pipeCache.set(pipeType, meta);
this._pipeSummaryCache.set(pipeType, meta.toSummary());
this._pipeCache.set(pipeType, pipeMeta);
this._pipeSummaryCache.set(pipeType, pipeMeta.toSummary());
return pipeMeta;
}
private _getDependenciesMetadata(typeOrFunc: Type<any>|Function, dependencies: any[]):

View File

@ -27,17 +27,46 @@ export class SourceModule {
constructor(public fileUrl: string, public moduleUrl: string, public source: string) {}
}
export interface NgAnalyzedModules {
ngModules: CompileNgModuleMetadata[];
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>;
files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>;
symbolsMissingModule?: StaticSymbol[];
}
// Returns all the source files and a mapping from modules to directives
export function analyzeNgModules(
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
metadataResolver: CompileMetadataResolver): Promise<{
ngModuleByPipeOrDirective: Map<StaticSymbol, CompileNgModuleMetadata>,
files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}>
}> {
return _loadNgModules(programStaticSymbols, options, metadataResolver).then(_analyzeNgModules);
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const {ngModules, symbolsMissingModule} =
_createNgModules(programStaticSymbols, options, metadataResolver);
return _analyzeNgModules(ngModules, symbolsMissingModule);
}
function _analyzeNgModules(ngModuleMetas: CompileNgModuleMetadata[]) {
export function analyzeAndValidateNgModules(
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
metadataResolver: CompileMetadataResolver): NgAnalyzedModules {
const result = analyzeNgModules(programStaticSymbols, options, metadataResolver);
if (result.symbolsMissingModule && result.symbolsMissingModule.length) {
const messages = result.symbolsMissingModule.map(
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
throw new Error(messages.join('\n'));
}
return result;
}
// Wait for the directives in the given modules have been loaded
export function loadNgModuleDirectives(ngModules: CompileNgModuleMetadata[]) {
return Promise
.all(ListWrapper.flatten(ngModules.map(
(ngModule) => ngModule.transitiveModule.directiveLoaders.map(loader => loader()))))
.then(() => {});
}
function _analyzeNgModules(
ngModuleMetas: CompileNgModuleMetadata[],
symbolsMissingModule: StaticSymbol[]): NgAnalyzedModules {
const moduleMetasByRef = new Map<any, CompileNgModuleMetadata>();
ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule));
const ngModuleByPipeOrDirective = new Map<StaticSymbol, CompileNgModuleMetadata>();
@ -78,10 +107,11 @@ function _analyzeNgModules(ngModuleMetas: CompileNgModuleMetadata[]) {
});
return {
// map directive/pipe to module
ngModuleByPipeOrDirective,
// list modules and directives for every source file
files,
// map directive/pipe to module
ngModuleByPipeOrDirective,
// list modules and directives for every source file
files,
ngModules: ngModuleMetas, symbolsMissingModule
};
}
@ -100,13 +130,14 @@ export class OfflineCompiler {
compileModules(staticSymbols: StaticSymbol[], options: {transitiveModules: boolean}):
Promise<SourceModule[]> {
return analyzeNgModules(staticSymbols, options, this._metadataResolver)
.then(({ngModuleByPipeOrDirective, files}) => {
const sourceModules = files.map(
file => this._compileSrcFile(
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules));
return ListWrapper.flatten(sourceModules);
});
const {ngModuleByPipeOrDirective, files, ngModules} =
analyzeAndValidateNgModules(staticSymbols, options, this._metadataResolver);
return loadNgModuleDirectives(ngModules).then(() => {
const sourceModules = files.map(
file => this._compileSrcFile(
file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.ngModules));
return ListWrapper.flatten(sourceModules);
});
}
private _compileSrcFile(
@ -328,22 +359,21 @@ function _splitTypescriptSuffix(path: string): string[] {
// Load the NgModules and check
// that all directives / pipes that are present in the program
// are also declared by a module.
function _loadNgModules(
function _createNgModules(
programStaticSymbols: StaticSymbol[], options: {transitiveModules: boolean},
metadataResolver: CompileMetadataResolver): Promise<CompileNgModuleMetadata[]> {
metadataResolver: CompileMetadataResolver):
{ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} {
const ngModules = new Map<any, CompileNgModuleMetadata>();
const programPipesAndDirectives: StaticSymbol[] = [];
const ngModulePipesAndDirective = new Set<StaticSymbol>();
const loadingPromises: Promise<any>[] = [];
const addNgModule = (staticSymbol: any) => {
if (ngModules.has(staticSymbol)) {
return false;
}
const {ngModule, loading} = metadataResolver.loadNgModuleMetadata(staticSymbol, false, false);
const ngModule = metadataResolver.getUnloadedNgModuleMetadata(staticSymbol, false, false);
if (ngModule) {
ngModules.set(ngModule.type.reference, ngModule);
loadingPromises.push(loading);
ngModule.declaredDirectives.forEach((dir) => ngModulePipesAndDirective.add(dir.reference));
ngModule.declaredPipes.forEach((pipe) => ngModulePipesAndDirective.add(pipe.reference));
if (options.transitiveModules) {
@ -364,11 +394,5 @@ function _loadNgModules(
const symbolsMissingModule =
programPipesAndDirectives.filter(s => !ngModulePipesAndDirective.has(s));
if (symbolsMissingModule.length) {
const messages = symbolsMissingModule.map(
s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`);
throw new Error(messages.join('\n'));
}
return Promise.all(loadingPromises).then(() => Array.from(ngModules.values()));
return {ngModules: Array.from(ngModules.values()), symbolsMissingModule};
}

View File

@ -88,7 +88,7 @@ export class AnimationGroupPlayer implements AnimationPlayer {
this._started = false;
}
setPosition(p: any /** TODO #9100 */): void {
setPosition(p: number): void {
this._players.forEach(player => { player.setPosition(p); });
}
@ -100,4 +100,6 @@ export class AnimationGroupPlayer implements AnimationPlayer {
});
return min;
}
get players(): AnimationPlayer[] { return this._players; }
}

View File

@ -56,6 +56,6 @@ export class NoOpAnimationPlayer implements AnimationPlayer {
finish(): void { this._onFinish(); }
destroy(): void {}
reset(): void {}
setPosition(p: any /** TODO #9100 */): void {}
setPosition(p: number): void {}
getPosition(): number { return 0; }
}

View File

@ -104,7 +104,9 @@ export class AnimationSequencePlayer implements AnimationPlayer {
}
}
setPosition(p: any /** TODO #9100 */): void { this._players[0].setPosition(p); }
setPosition(p: number): void { this._players[0].setPosition(p); }
getPosition(): number { return this._players[0].getPosition(); }
get players(): AnimationPlayer[] { return this._players; }
}

View File

@ -84,6 +84,8 @@ export function balanceAnimationKeyframes(
firstKeyframe.styles.styles.push(extraFirstKeyframeStyles);
}
collectAndResolveStyles(collectedStyles, [finalStateStyles]);
return keyframes;
}

View File

@ -22,7 +22,7 @@ export class DebugDomRootRenderer implements RootRenderer {
}
}
export class DebugDomRenderer implements Renderer {
export class DebugDomRenderer {
constructor(private _delegate: Renderer) {}
selectRootElement(selectorOrNode: string|any, debugInfo?: RenderDebugInfo): any {
@ -150,7 +150,9 @@ export class DebugDomRenderer implements Renderer {
animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer {
return this._delegate.animate(element, startingStyles, keyframes, duration, delay, easing);
duration: number, delay: number, easing: string,
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
return this._delegate.animate(
element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
}
}

View File

@ -8,8 +8,9 @@
import {AnimationGroupPlayer} from '../animation/animation_group_player';
import {AnimationPlayer} from '../animation/animation_player';
import {queueAnimation as queueAnimationGlobally} from '../animation/animation_queue';
import {AnimationTransitionEvent} from '../animation/animation_transition_event';
import {AnimationSequencePlayer} from '../animation/animation_sequence_player';
import {ViewAnimationMap} from '../animation/view_animation_map';
import {ListWrapper} from '../facade/collection';
export class AnimationViewContext {
private _players = new ViewAnimationMap();
@ -30,15 +31,26 @@ export class AnimationViewContext {
this._players.set(element, animationName, player);
}
cancelActiveAnimation(element: any, animationName: string, removeAllAnimations: boolean = false):
void {
getAnimationPlayers(element: any, animationName: string, removeAllAnimations: boolean = false):
AnimationPlayer[] {
const players: AnimationPlayer[] = [];
if (removeAllAnimations) {
this._players.findAllPlayersByElement(element).forEach(player => player.destroy());
this._players.findAllPlayersByElement(element).forEach(
player => { _recursePlayers(player, players); });
} else {
const player = this._players.find(element, animationName);
if (player) {
player.destroy();
const currentPlayer = this._players.find(element, animationName);
if (currentPlayer) {
_recursePlayers(currentPlayer, players);
}
}
return players;
}
}
function _recursePlayers(player: AnimationPlayer, collectedPlayers: AnimationPlayer[]) {
if ((player instanceof AnimationGroupPlayer) || (player instanceof AnimationSequencePlayer)) {
player.players.forEach(player => _recursePlayers(player, collectedPlayers));
} else {
collectedPlayers.push(player);
}
}

View File

@ -232,8 +232,10 @@ export abstract class AppView<T> {
if (nextSibling) {
this.visitRootNodesInternal(this._directRenderer.insertBefore, nextSibling);
} else {
this.visitRootNodesInternal(
this._directRenderer.appendChild, this._directRenderer.parentElement(prevNode));
const parentElement = this._directRenderer.parentElement(prevNode);
if (parentElement) {
this.visitRootNodesInternal(this._directRenderer.appendChild, parentElement);
}
}
} else {
this.renderer.attachViewAfter(prevNode, this.flatRootNodes);

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
import {APP_ID} from '../application_tokens';
import {SimpleChange, devModeEqual} from '../change_detection/change_detection';
import {UNINITIALIZED} from '../change_detection/change_detection_util';
import {Inject, Injectable} from '../di';

View File

@ -56,8 +56,12 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
}
// API of tsickle for lowering decorators to properties on the class.
if ((<any>type).ctorParameters) {
const ctorParameters = (<any>type).ctorParameters;
const tsickleCtorParams = (<any>type).ctorParameters;
if (tsickleCtorParams) {
// Newer tsickle uses a function closure
// Retain the non-function case for compatibility with older tsickle
const ctorParameters =
typeof tsickleCtorParams === 'function' ? tsickleCtorParams() : tsickleCtorParams;
const paramTypes = ctorParameters.map((ctorParam: any) => ctorParam && ctorParam.type);
const paramAnnotations = ctorParameters.map(
(ctorParam: any) =>

View File

@ -88,7 +88,8 @@ export abstract class Renderer {
abstract animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer;
duration: number, delay: number, easing: string,
previousPlayers?: AnimationPlayer[]): AnimationPlayer;
}
/**

View File

@ -1854,6 +1854,8 @@ function declareTests({useJit}: {useJit: boolean}) {
let animation = driver.log.pop();
let kf = animation['keyframeLookup'];
expect(kf[1]).toEqual([1, {'background': 'green'}]);
let player = animation['player'];
player.finish();
cmp.exp = 'blue';
fixture.detectChanges();
@ -1863,6 +1865,8 @@ function declareTests({useJit}: {useJit: boolean}) {
kf = animation['keyframeLookup'];
expect(kf[0]).toEqual([0, {'background': 'green'}]);
expect(kf[1]).toEqual([1, {'background': 'grey'}]);
player = animation['player'];
player.finish();
cmp.exp = 'red';
fixture.detectChanges();
@ -1872,6 +1876,8 @@ function declareTests({useJit}: {useJit: boolean}) {
kf = animation['keyframeLookup'];
expect(kf[0]).toEqual([0, {'background': 'grey'}]);
expect(kf[1]).toEqual([1, {'background': 'red'}]);
player = animation['player'];
player.finish();
cmp.exp = 'orange';
fixture.detectChanges();
@ -1881,6 +1887,8 @@ function declareTests({useJit}: {useJit: boolean}) {
kf = animation['keyframeLookup'];
expect(kf[0]).toEqual([0, {'background': 'red'}]);
expect(kf[1]).toEqual([1, {'background': 'grey'}]);
player = animation['player'];
player.finish();
}));
it('should seed in the origin animation state styles into the first animation step',
@ -1911,6 +1919,44 @@ function declareTests({useJit}: {useJit: boolean}) {
expect(animation['startingStyles']).toEqual({'height': '100px'});
}));
it('should seed in the previous animation styles into the transition if the previous transition was interupted midway',
fakeAsync(() => {
TestBed.overrideComponent(DummyIfCmp, {
set: {
template: `
<div class="target" [@status]="exp"></div>
`,
animations: [trigger(
'status',
[
state('*', style({ opacity: 0 })),
state('a', style({height: '100px', width: '200px'})),
state('b', style({height: '1000px' })),
transition('* => *', [
animate(1000, style({ fontSize: '20px' })),
animate(1000)
])
])]
}
});
const driver = TestBed.get(AnimationDriver) as MockAnimationDriver;
const fixture = TestBed.createComponent(DummyIfCmp);
const cmp = fixture.componentInstance;
cmp.exp = 'a';
fixture.detectChanges();
flushMicrotasks();
driver.log = [];
cmp.exp = 'b';
fixture.detectChanges();
flushMicrotasks();
const animation = driver.log[0];
expect(animation['previousStyles']).toEqual({opacity: '0', fontSize: '*'});
}));
it('should perform a state change even if there is no transition that is found',
fakeAsync(() => {
TestBed.overrideComponent(DummyIfCmp, {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Component, Injectable, RenderComponentType, Renderer, RootRenderer} from '@angular/core';
import {Component, ContentChild, Injectable, Input, RenderComponentType, Renderer, RootRenderer, TemplateRef} from '@angular/core';
import {DebugDomRenderer} from '@angular/core/src/debug/debug_renderer';
import {DirectRenderer} from '@angular/core/src/render/api';
import {TestBed, inject} from '@angular/core/testing';
@ -125,6 +125,46 @@ export function main() {
const projectedNode = childHostEl.childNodes[1];
expect(directRenderer.appendChild).toHaveBeenCalledWith(projectedNode, childHostEl);
});
it('should support using structural directives with ngTemplateOutlet', () => {
@Component({
template:
'<child [templateCtx]="templateCtx"><template let-shown="shown" #tpl><span *ngIf="shown">hello</span></template></child>'
})
class Parent {
templateCtx = {shown: false};
}
@Component({
selector: 'child',
template:
'(<template [ngTemplateOutlet]="templateRef" [ngOutletContext]="templateCtx"></template>)'
})
class Child {
@Input()
templateCtx: any;
@ContentChild('tpl')
templateRef: TemplateRef<any>;
}
TestBed.configureTestingModule({declarations: [Parent, Child]});
let fixture = TestBed.createComponent(Parent);
fixture.componentInstance.templateCtx.shown = false;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('()');
fixture.destroy();
fixture = TestBed.createComponent(Parent);
fixture.componentInstance.templateCtx.shown = true;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('(hello)');
fixture.componentInstance.templateCtx.shown = false;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('()');
});
});
}

View File

@ -89,6 +89,25 @@ export function main() {
const p = reflector.parameters(ClassWithoutDecorators);
expect(p.length).toEqual(2);
});
// See https://github.com/angular/tsickle/issues/261
it('should read forwardRef down-leveled type', () => {
class Dep {}
class ForwardLegacy {
constructor(d: Dep) {}
// Older tsickle had a bug: wrote a forward reference
static ctorParameters = [{type: Dep}];
}
expect(reflector.parameters(ForwardLegacy)).toEqual([[Dep]]);
class Forward {
constructor(d: Dep) {}
// Newer tsickle generates a functionClosure
static ctorParameters = () => [{type: ForwardDep}];
}
class ForwardDep {}
expect(reflector.parameters(Forward)).toEqual([[ForwardDep]]);
});
});
describe('propMetadata', () => {

View File

@ -5,8 +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 {AnimationPlayer} from '@angular/core';
import {AUTO_STYLE, AnimationPlayer} from '@angular/core';
export class MockAnimationPlayer implements AnimationPlayer {
private _onDoneFns: Function[] = [];
@ -16,8 +15,21 @@ export class MockAnimationPlayer implements AnimationPlayer {
private _started = false;
public parentPlayer: AnimationPlayer = null;
public previousStyles: {[styleName: string]: string | number} = {};
public log: any[] /** TODO #9100 */ = [];
public log: any[] = [];
constructor(
public startingStyles: {[key: string]: string | number} = {},
public keyframes: Array<[number, {[style: string]: string | number}]> = [],
previousPlayers: AnimationPlayer[] = []) {
previousPlayers.forEach(player => {
if (player instanceof MockAnimationPlayer) {
const styles = player._captureStyles();
Object.keys(styles).forEach(prop => this.previousStyles[prop] = styles[prop]);
}
});
}
private _onFinish(): void {
if (!this._finished) {
@ -67,6 +79,32 @@ export class MockAnimationPlayer implements AnimationPlayer {
}
}
setPosition(p: any /** TODO #9100 */): void {}
setPosition(p: number): void {}
getPosition(): number { return 0; }
private _captureStyles(): {[styleName: string]: string | number} {
const captures: {[prop: string]: string | number} = {};
if (this.hasStarted()) {
// when assembling the captured styles, it's important that
// we build the keyframe styles in the following order:
// {startingStyles, ... other styles within keyframes, ... previousStyles }
Object.keys(this.startingStyles).forEach(prop => {
captures[prop] = this.startingStyles[prop];
});
this.keyframes.forEach(kf => {
const [offset, styles] = kf;
const newStyles: {[prop: string]: string | number} = {};
Object.keys(styles).forEach(
prop => { captures[prop] = this._finished ? styles[prop] : AUTO_STYLE; });
});
}
Object.keys(this.previousStyles).forEach(prop => {
captures[prop] = this.previousStyles[prop];
});
return captures;
}
}

View File

@ -10,6 +10,9 @@
writeScriptTag('/vendor/system.js');
writeScriptTag('/vendor/Reflect.js');
writeScriptTag('/_common/system-config.js');
if (location.pathname.indexOf('/upgrade/') != -1) {
writeScriptTag('/vendor/angular.js');
}
function writeScriptTag(scriptUrl: string, onload: string = '') {
document.write('<script src="' + scriptUrl + '" onload="' + onload + '"></script>');

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
*/
/* tslint:disable:no-console */
import * as webdriver from 'selenium-webdriver';
declare var browser: any;
declare var expect: any;

View File

@ -6,6 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './module';
import * as module from './module';
platformBrowserDynamic().bootstrapModule(AppModule);
if (module.AppModule) {
platformBrowserDynamic().bootstrapModule(module.AppModule);
}

View File

@ -19,6 +19,7 @@ System.config({
'/vendor/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/router': '/vendor/@angular/router/bundles/router.umd.js',
'@angular/upgrade': '/vendor/@angular/upgrade/bundles/upgrade.umd.js',
'@angular/upgrade/static': '/vendor/@angular/upgrade/bundles/upgrade-static.umd.js',
'rxjs': '/vendor/rxjs',
},
packages: {

View File

@ -20,6 +20,7 @@ mkdir $DIST/vendor/
ln -s ../../../dist/packages-dist/ $DIST/vendor/@angular
for FILE in \
../../../node_modules/angular/angular.js \
../../../node_modules/zone.js/dist/zone.js \
../../../node_modules/systemjs/dist/system.js \
../../../node_modules/reflect-metadata/Reflect.js \
@ -35,4 +36,6 @@ for MODULE in `find . -name module.ts`; do
cp _common/*.html $FINAL_DIR_PATH
cp $DIST/_common/*.js $FINAL_DIR_PATH
cp $DIST/_common/*.js.map $FINAL_DIR_PATH
find `dirname $MODULE` -name \*.css -exec cp {} $FINAL_DIR_PATH \;
done

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
/* tslint:disable:no-console */
// #docregion Observable
import {Observable} from 'rxjs/Observable';
import {Subscriber} from 'rxjs/Subscriber';

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
/* tslint:disable:no-console */
// #docregion Observable
import 'rxjs/add/operator/map';

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
/* tslint:disable:no-console */
// #docregion Observable
import {Observable} from 'rxjs/Observable';
import {Subscriber} from 'rxjs/Subscriber';

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
/* tslint:disable:no-console */
// #docregion Component
import {Component} from '@angular/core';
import {FormArray, FormControl, FormGroup} from '@angular/forms';

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
/* tslint:disable:no-console */
// #docregion Component
import {Component} from '@angular/core';
import {FormControl, FormGroup, Validators} from '@angular/forms';

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
/* tslint:disable:no-console */
// #docregion Component
import {Component} from '@angular/core';
import {NgForm} from '@angular/forms';

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
/* tslint:disable:no-console */
// #docregion Component
import {Component} from '@angular/core';
import {NgForm} from '@angular/forms';

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
/* tslint:disable:no-console */
// #docregion Component
import {Component} from '@angular/core';
import {FormControl, FormGroup, Validators} from '@angular/forms';

View File

@ -18,7 +18,7 @@
"target": "es5",
"lib": ["es2015", "dom"],
"skipLibCheck": true,
"types": ["jasmine", "node"]
"types": ["jasmine", "node", "angularjs"]
},
"include": [
"./_common/*.ts",

View File

@ -0,0 +1,54 @@
/**
* @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 {browser, by, element} from 'protractor';
import {verifyNoBrowserErrors} from '../../../../_common/e2e_util';
function loadPage(url: string) {
browser.ng12Hybrid = true;
browser.rootEl = 'example-app';
browser.get(url);
}
describe('upgrade(static)', () => {
beforeEach(() => { loadPage('/upgrade/static/ts/'); });
afterEach(verifyNoBrowserErrors);
it('should render the `ng2-heroes` component', () => {
expect(element(by.css('h1')).getText()).toEqual('Heroes');
expect(element.all(by.css('p')).get(0).getText()).toEqual('There are 3 heroes.');
});
it('should render 3 ng1-hero components', () => {
const heroComponents = element.all(by.css('ng1-hero'));
expect(heroComponents.count()).toEqual(3);
});
it('should add a new hero when the "Add Hero" button is pressed', () => {
const addHeroButton = element.all(by.css('button')).last();
expect(addHeroButton.getText()).toEqual('Add Hero');
addHeroButton.click();
const heroComponents = element.all(by.css('ng1-hero'));
expect(heroComponents.last().element(by.css('h2')).getText()).toEqual('Kamala Khan');
});
it('should remove a hero when the "Remove" button is pressed', () => {
let firstHero = element.all(by.css('ng1-hero')).get(0);
expect(firstHero.element(by.css('h2')).getText()).toEqual('Superman');
const removeHeroButton = firstHero.element(by.css('button'));
expect(removeHeroButton.getText()).toEqual('Remove');
removeHeroButton.click();
const heroComponents = element.all(by.css('ng1-hero'));
expect(heroComponents.count()).toEqual(2);
firstHero = element.all(by.css('ng1-hero')).get(0);
expect(firstHero.element(by.css('h2')).getText()).toEqual('Wonder Woman');
});
});

View File

@ -0,0 +1,184 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Component, Directive, DoCheck, ElementRef, EventEmitter, Inject, Injectable, Injector, Input, NgModule, OnChanges, OnDestroy, OnInit, Output, SimpleChanges} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {UpgradeComponent, UpgradeModule, downgradeComponent, downgradeInjectable} from '@angular/upgrade/static';
interface Hero {
name: string;
description: string;
}
// #docregion Angular 2 Stuff
// #docregion ng2-heroes
// This Angular 2 component will be "downgraded" to be used in Angular 1
@Component({
selector: 'ng2-heroes',
// This template uses the upgraded `ng1-hero` component
// Note that because its element is compiled by Angular 2+ we must use camelCased attribute names
template: `<h1>Heroes</h1>
<p><ng-content></ng-content></p>
<div *ngFor="let hero of heroes">
<ng1-hero [hero]="hero" (onRemove)="removeHero.emit(hero)"><strong>Super Hero</strong></ng1-hero>
</div>
<button (click)="addHero.emit()">Add Hero</button>`,
})
class Ng2HeroesComponent {
@Input() heroes: Hero[];
@Output() addHero = new EventEmitter();
@Output() removeHero = new EventEmitter();
}
// #enddocregion
// #docregion ng2-heroes-service
// This Angular 2 service will be "downgraded" to be used in Angular 1
@Injectable()
class HeroesService {
heroes: Hero[] = [
{name: 'superman', description: 'The man of steel'},
{name: 'wonder woman', description: 'Princess of the Amazons'},
{name: 'thor', description: 'The hammer-wielding god'}
];
// #docregion use-ng1-upgraded-service
constructor(@Inject('titleCase') titleCase: (v: string) => string) {
// Change all the hero names to title case, using the "upgraded" Angular 1 service
this.heroes.forEach((hero: Hero) => hero.name = titleCase(hero.name));
}
// #enddocregion
addHero() {
this.heroes =
this.heroes.concat([{name: 'Kamala Khan', description: 'Epic shape-shifting healer'}]);
}
removeHero(hero: Hero) { this.heroes = this.heroes.filter((item: Hero) => item !== hero); }
}
// #enddocregion
// #docregion ng1-hero-wrapper
// This Angular 2 directive will act as an interface to the "upgraded" Angular 1 component
@Directive({selector: 'ng1-hero'})
class Ng1HeroComponentWrapper extends UpgradeComponent implements OnInit, OnChanges, DoCheck,
OnDestroy {
// The names of the input and output properties here must match the names of the
// `<` and `&` bindings in the Angular 1 component that is being wrapped
@Input() hero: Hero;
@Output() onRemove: EventEmitter<void>;
constructor(@Inject(ElementRef) elementRef: ElementRef, @Inject(Injector) injector: Injector) {
// We must pass the name of the directive as used by Angular 1 to the super
super('ng1Hero', elementRef, injector);
}
// For this class to work when compiled with AoT, we must implement these lifecycle hooks
// because the AoT compiler will not realise that the super class implements them
ngOnInit() { super.ngOnInit(); }
ngOnChanges(changes: SimpleChanges) { super.ngOnChanges(changes); }
ngDoCheck() { super.ngDoCheck(); }
ngOnDestroy() { super.ngOnDestroy(); }
}
// #enddocregion
// #docregion ng2-module
// This NgModule represents the Angular 2 pieces of the application
@NgModule({
declarations: [Ng2HeroesComponent, Ng1HeroComponentWrapper],
providers: [
HeroesService,
// #docregion upgrade-ng1-service
// Register an Angular 2+ provider whose value is the "upgraded" Angular 1 service
{provide: 'titleCase', useFactory: (i: any) => i.get('titleCase'), deps: ['$injector']}
// #enddocregion
],
// All components that are to be "downgraded" must be declared as `entryComponents`
entryComponents: [Ng2HeroesComponent],
// We must import `UpgradeModule` to get access to the Angular 1 core services
imports: [BrowserModule, UpgradeModule]
})
class Ng2AppModule {
ngDoBootstrap() { /* this is a placeholder to stop the boostrapper from complaining */
}
}
// #enddocregion
// #enddocregion
// #docregion Angular 1 Stuff
// #docregion ng1-module
// This Angular 1 module represents the Angular 1 pieces of the application
const ng1AppModule = angular.module('ng1AppModule', []);
// #enddocregion
// #docregion ng1-hero
// This Angular 1 component will be "upgraded" to be used in Angular 2+
ng1AppModule.component('ng1Hero', {
bindings: {hero: '<', onRemove: '&'},
transclude: true,
template: `<div class="title" ng-transclude></div>
<h2>{{ $ctrl.hero.name }}</h2>
<p>{{ $ctrl.hero.description }}</p>
<button ng-click="$ctrl.onRemove()">Remove</button>`
});
// #enddocregion
// #docregion ng1-title-case-service
// This Angular 1 service will be "upgraded" to be used in Angular 2+
ng1AppModule.factory(
'titleCase',
() => (value: string) => value.replace(/((^|\s)[a-z])/g, (_, c) => c.toUpperCase()));
// #enddocregion
// #docregion downgrade-ng2-heroes-service
// Register an Angular 1 service, whose value is the "downgraded" Angular 2+ injectable.
ng1AppModule.factory('heroesService', downgradeInjectable(HeroesService));
// #enddocregion
// #docregion ng2-heroes-wrapper
// This is directive will act as the interface to the "downgraded" Angular 2+ component
ng1AppModule.directive(
'ng2Heroes',
downgradeComponent(
// The inputs and outputs here must match the relevant names of the properties on the
// "downgraded" component
{component: Ng2HeroesComponent, inputs: ['heroes'], outputs: ['addHero', 'removeHero']}));
// #enddocregion
// #docregion example-app
// This is our top level application component
ng1AppModule.component('exampleApp', {
// We inject the "downgraded" HeroesService into this Angular 1 component
// (We don't need the `HeroesService` type for Angular 1 DI - it just helps with TypeScript
// compilation)
controller: [
'heroesService', function(heroesService: HeroesService) { this.heroesService = heroesService; }
],
// This template make use of the downgraded `ng2-heroes` component
// Note that because its element is compiled by Angular 1 we must use kebab-case attributes for
// inputs and outputs
template: `<link rel="stylesheet" href="./styles.css">
<ng2-heroes [heroes]="$ctrl.heroesService.heroes" (add-hero)="$ctrl.heroesService.addHero()" (remove-hero)="$ctrl.heroesService.removeHero($event)">
There are {{ $ctrl.heroesService.heroes.length }} heroes.
</ng2-heroes>`
});
// #enddocregion
// #enddocregion
// #docregion bootstrap
// First we bootstrap the Angular 2 HybridModule
// (We are using the dynamic browser platform as this example has not been compiled AoT)
platformBrowserDynamic().bootstrapModule(Ng2AppModule).then(ref => {
// Once Angular 2 bootstrap is complete then we bootstrap the Angular 1 module
const upgrade = ref.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.body, [ng1AppModule.name]);
});
// #enddocregion

View File

@ -0,0 +1,17 @@
ng2-heroes {
border: solid black 2px;
display: block;
padding: 5px;
}
ng1-hero {
border: solid green 2px;
margin-top: 5px;
padding: 5px;
display: block;
}
.title {
background-color: blue;
color: white;
}

View File

@ -44,7 +44,8 @@ const DATE_FORMATS_SPLIT =
/((?:[^yMLdHhmsazZEwGjJ']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|J+|j+|m+|s+|a|z|Z|G+|w+))(.*)/;
const PATTERN_ALIASES: {[format: string]: DateFormatterFn} = {
yMMMdjms: datePartGetterFactory(combine([
// Keys are quoted so they do not get renamed during closure compilation.
'yMMMdjms': datePartGetterFactory(combine([
digitCondition('year', 1),
nameCondition('month', 3),
digitCondition('day', 1),
@ -52,68 +53,70 @@ const PATTERN_ALIASES: {[format: string]: DateFormatterFn} = {
digitCondition('minute', 1),
digitCondition('second', 1),
])),
yMdjm: datePartGetterFactory(combine([
'yMdjm': datePartGetterFactory(combine([
digitCondition('year', 1), digitCondition('month', 1), digitCondition('day', 1),
digitCondition('hour', 1), digitCondition('minute', 1)
])),
yMMMMEEEEd: datePartGetterFactory(combine([
'yMMMMEEEEd': datePartGetterFactory(combine([
digitCondition('year', 1), nameCondition('month', 4), nameCondition('weekday', 4),
digitCondition('day', 1)
])),
yMMMMd: datePartGetterFactory(
'yMMMMd': datePartGetterFactory(
combine([digitCondition('year', 1), nameCondition('month', 4), digitCondition('day', 1)])),
yMMMd: datePartGetterFactory(
'yMMMd': datePartGetterFactory(
combine([digitCondition('year', 1), nameCondition('month', 3), digitCondition('day', 1)])),
yMd: datePartGetterFactory(
'yMd': datePartGetterFactory(
combine([digitCondition('year', 1), digitCondition('month', 1), digitCondition('day', 1)])),
jms: datePartGetterFactory(combine(
'jms': datePartGetterFactory(combine(
[digitCondition('hour', 1), digitCondition('second', 1), digitCondition('minute', 1)])),
jm: datePartGetterFactory(combine([digitCondition('hour', 1), digitCondition('minute', 1)]))
'jm': datePartGetterFactory(combine([digitCondition('hour', 1), digitCondition('minute', 1)]))
};
const DATE_FORMATS: {[format: string]: DateFormatterFn} = {
yyyy: datePartGetterFactory(digitCondition('year', 4)),
yy: datePartGetterFactory(digitCondition('year', 2)),
y: datePartGetterFactory(digitCondition('year', 1)),
MMMM: datePartGetterFactory(nameCondition('month', 4)),
MMM: datePartGetterFactory(nameCondition('month', 3)),
MM: datePartGetterFactory(digitCondition('month', 2)),
M: datePartGetterFactory(digitCondition('month', 1)),
LLLL: datePartGetterFactory(nameCondition('month', 4)),
L: datePartGetterFactory(nameCondition('month', 1)),
dd: datePartGetterFactory(digitCondition('day', 2)),
d: datePartGetterFactory(digitCondition('day', 1)),
HH: digitModifier(
// Keys are quoted so they do not get renamed.
'yyyy': datePartGetterFactory(digitCondition('year', 4)),
'yy': datePartGetterFactory(digitCondition('year', 2)),
'y': datePartGetterFactory(digitCondition('year', 1)),
'MMMM': datePartGetterFactory(nameCondition('month', 4)),
'MMM': datePartGetterFactory(nameCondition('month', 3)),
'MM': datePartGetterFactory(digitCondition('month', 2)),
'M': datePartGetterFactory(digitCondition('month', 1)),
'LLLL': datePartGetterFactory(nameCondition('month', 4)),
'L': datePartGetterFactory(nameCondition('month', 1)),
'dd': datePartGetterFactory(digitCondition('day', 2)),
'd': datePartGetterFactory(digitCondition('day', 1)),
'HH': digitModifier(
hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 2), false)))),
H: hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), false))),
hh: digitModifier(
'H': hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), false))),
'hh': digitModifier(
hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 2), true)))),
h: hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), true))),
jj: datePartGetterFactory(digitCondition('hour', 2)),
j: datePartGetterFactory(digitCondition('hour', 1)),
mm: digitModifier(datePartGetterFactory(digitCondition('minute', 2))),
m: datePartGetterFactory(digitCondition('minute', 1)),
ss: digitModifier(datePartGetterFactory(digitCondition('second', 2))),
s: datePartGetterFactory(digitCondition('second', 1)),
'h': hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), true))),
'jj': datePartGetterFactory(digitCondition('hour', 2)),
'j': datePartGetterFactory(digitCondition('hour', 1)),
'mm': digitModifier(datePartGetterFactory(digitCondition('minute', 2))),
'm': datePartGetterFactory(digitCondition('minute', 1)),
'ss': digitModifier(datePartGetterFactory(digitCondition('second', 2))),
's': datePartGetterFactory(digitCondition('second', 1)),
// while ISO 8601 requires fractions to be prefixed with `.` or `,`
// we can be just safely rely on using `sss` since we currently don't support single or two digit
// fractions
sss: datePartGetterFactory(digitCondition('second', 3)),
EEEE: datePartGetterFactory(nameCondition('weekday', 4)),
EEE: datePartGetterFactory(nameCondition('weekday', 3)),
EE: datePartGetterFactory(nameCondition('weekday', 2)),
E: datePartGetterFactory(nameCondition('weekday', 1)),
a: hourClockExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), true))),
Z: timeZoneGetter('short'),
z: timeZoneGetter('long'),
ww: datePartGetterFactory({}), // Week of year, padded (00-53). Week 01 is the week with the
// first Thursday of the year. not support ?
w: datePartGetterFactory({}), // Week of year (0-53). Week 1 is the week with the first Thursday
'sss': datePartGetterFactory(digitCondition('second', 3)),
'EEEE': datePartGetterFactory(nameCondition('weekday', 4)),
'EEE': datePartGetterFactory(nameCondition('weekday', 3)),
'EE': datePartGetterFactory(nameCondition('weekday', 2)),
'E': datePartGetterFactory(nameCondition('weekday', 1)),
'a': hourClockExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), true))),
'Z': timeZoneGetter('short'),
'z': timeZoneGetter('long'),
'ww': datePartGetterFactory({}), // Week of year, padded (00-53). Week 01 is the week with the
// first Thursday of the year. not support ?
'w':
datePartGetterFactory({}), // Week of year (0-53). Week 1 is the week with the first Thursday
// of the year not support ?
G: datePartGetterFactory(nameCondition('era', 1)),
GG: datePartGetterFactory(nameCondition('era', 2)),
GGG: datePartGetterFactory(nameCondition('era', 3)),
GGGG: datePartGetterFactory(nameCondition('era', 4))
'G': datePartGetterFactory(nameCondition('era', 1)),
'GG': datePartGetterFactory(nameCondition('era', 2)),
'GGG': datePartGetterFactory(nameCondition('era', 3)),
'GGGG': datePartGetterFactory(nameCondition('era', 4))
};

View File

@ -129,6 +129,7 @@ export function isJsObject(o: any): boolean {
}
export function print(obj: Error | Object) {
// tslint:disable-next-line:no-console
console.log(obj);
}

View File

@ -56,22 +56,12 @@ export const TEMPLATE_DRIVEN_DIRECTIVES: Type<any>[] = [NgModel, NgModelGroup, N
export const REACTIVE_DRIVEN_DIRECTIVES: Type<any>[] =
[FormControlDirective, FormGroupDirective, FormControlName, FormGroupName, FormArrayName];
/**
* A list of all the form directives.
*
* @stable
*/
export const FORM_DIRECTIVES: Type<any>[][] = [TEMPLATE_DRIVEN_DIRECTIVES, SHARED_FORM_DIRECTIVES];
/**
* @stable
*/
export const REACTIVE_FORM_DIRECTIVES: Type<any>[][] =
[REACTIVE_DRIVEN_DIRECTIVES, SHARED_FORM_DIRECTIVES];
/**
* Internal module used for sharing directives between FormsModule and ReactiveFormsModule
*/
@NgModule({declarations: SHARED_FORM_DIRECTIVES, exports: SHARED_FORM_DIRECTIVES})
@NgModule({
declarations: SHARED_FORM_DIRECTIVES,
exports: SHARED_FORM_DIRECTIVES,
})
export class InternalFormsSharedModule {
}

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Renderer, forwardRef} from '@angular/core';
import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Provider, Renderer, forwardRef} from '@angular/core';
import {isPrimitive, looseIdentical} from '../facade/lang';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
export const SELECT_VALUE_ACCESSOR: any = {
export const SELECT_VALUE_ACCESSOR: Provider = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SelectControlValueAccessor),
multi: true
@ -115,8 +115,8 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
/** @internal */
_getOptionValue(valueString: string): any {
const value = this._optionMap.get(_extractId(valueString));
return value != null ? value : valueString;
const id: string = _extractId(valueString);
return this._optionMap.has(id) ? this._optionMap.get(id) : valueString;
}
}
@ -158,7 +158,7 @@ export class NgSelectOption implements OnDestroy {
this._renderer.setElementProperty(this._element.nativeElement, 'value', value);
}
ngOnDestroy() {
ngOnDestroy(): void {
if (this._select) {
this._select._optionMap.delete(this.id);
this._select.writeValue(this._select.value);

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Directive, ElementRef, Host, Input, OnDestroy, OpaqueToken, Optional, Renderer, Type, forwardRef} from '@angular/core';
import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Provider, Renderer, forwardRef} from '@angular/core';
import {isPrimitive, looseIdentical} from '../facade/lang';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
export const SELECT_MULTIPLE_VALUE_ACCESSOR = {
export const SELECT_MULTIPLE_VALUE_ACCESSOR: Provider = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SelectMultipleControlValueAccessor),
multi: true
@ -121,8 +121,8 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor
/** @internal */
_getOptionValue(valueString: string): any {
const opt = this._optionMap.get(_extractId(valueString));
return opt ? opt._value : valueString;
const id: string = _extractId(valueString);
return this._optionMap.has(id) ? this._optionMap.get(id)._value : valueString;
}
}
@ -180,12 +180,10 @@ export class NgSelectMultipleOption implements OnDestroy {
this._renderer.setElementProperty(this._element.nativeElement, 'selected', selected);
}
ngOnDestroy() {
ngOnDestroy(): void {
if (this._select) {
this._select._optionMap.delete(this.id);
this._select.writeValue(this._select.value);
}
}
}
export const SELECT_DIRECTIVES = [SelectMultipleControlValueAccessor, NgSelectMultipleOption];

View File

@ -5,10 +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 {Directive, Input, OnChanges, SimpleChanges, forwardRef} from '@angular/core';
import {isPresent} from '../facade/lang';
import {AbstractControl} from '../model';
import {NG_VALIDATORS, Validators} from '../validators';
@ -57,17 +54,17 @@ export const REQUIRED_VALIDATOR: any = {
@Directive({
selector: '[required][formControlName],[required][formControl],[required][ngModel]',
providers: [REQUIRED_VALIDATOR],
host: {'[attr.required]': 'required? "" : null'}
host: {'[attr.required]': 'required ? "" : null'}
})
export class RequiredValidator implements Validator {
private _required: boolean;
private _onChange: () => void;
@Input()
get required(): boolean { return this._required; }
get required(): boolean /*| string*/ { return this._required; }
set required(value: boolean) {
this._required = isPresent(value) && `${value}` !== 'false';
this._required = value != null && value !== false && `${value}` !== 'false';
if (this._onChange) this._onChange();
}
@ -75,7 +72,7 @@ export class RequiredValidator implements Validator {
return this.required ? Validators.required(c) : null;
}
registerOnValidatorChange(fn: () => void) { this._onChange = fn; }
registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }
}
/**
@ -112,7 +109,7 @@ export const MIN_LENGTH_VALIDATOR: any = {
@Directive({
selector: '[minlength][formControlName],[minlength][formControl],[minlength][ngModel]',
providers: [MIN_LENGTH_VALIDATOR],
host: {'[attr.minlength]': 'minlength? minlength : null'}
host: {'[attr.minlength]': 'minlength ? minlength : null'}
})
export class MinLengthValidator implements Validator,
OnChanges {
@ -121,12 +118,8 @@ export class MinLengthValidator implements Validator,
@Input() minlength: string;
private _createValidator() {
this._validator = Validators.minLength(parseInt(this.minlength, 10));
}
ngOnChanges(changes: SimpleChanges) {
if (changes['minlength']) {
ngOnChanges(changes: SimpleChanges): void {
if ('minlength' in changes) {
this._createValidator();
if (this._onChange) this._onChange();
}
@ -136,7 +129,11 @@ export class MinLengthValidator implements Validator,
return this.minlength == null ? null : this._validator(c);
}
registerOnValidatorChange(fn: () => void) { this._onChange = fn; }
registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }
private _createValidator(): void {
this._validator = Validators.minLength(parseInt(this.minlength, 10));
}
}
/**
@ -162,7 +159,7 @@ export const MAX_LENGTH_VALIDATOR: any = {
@Directive({
selector: '[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]',
providers: [MAX_LENGTH_VALIDATOR],
host: {'[attr.maxlength]': 'maxlength? maxlength : null'}
host: {'[attr.maxlength]': 'maxlength ? maxlength : null'}
})
export class MaxLengthValidator implements Validator,
OnChanges {
@ -171,22 +168,22 @@ export class MaxLengthValidator implements Validator,
@Input() maxlength: string;
private _createValidator() {
this._validator = Validators.maxLength(parseInt(this.maxlength, 10));
}
ngOnChanges(changes: SimpleChanges) {
if (changes['maxlength']) {
ngOnChanges(changes: SimpleChanges): void {
if ('maxlength' in changes) {
this._createValidator();
if (this._onChange) this._onChange();
}
}
validate(c: AbstractControl): {[key: string]: any} {
return isPresent(this.maxlength) ? this._validator(c) : null;
return this.maxlength != null ? this._validator(c) : null;
}
registerOnValidatorChange(fn: () => void) { this._onChange = fn; }
registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }
private _createValidator(): void {
this._validator = Validators.maxLength(parseInt(this.maxlength, 10));
}
}
@ -220,20 +217,18 @@ export class PatternValidator implements Validator,
private _validator: ValidatorFn;
private _onChange: () => void;
@Input() pattern: string;
@Input() pattern: string /*|RegExp*/;
private _createValidator() { this._validator = Validators.pattern(this.pattern); }
ngOnChanges(changes: SimpleChanges) {
if (changes['pattern']) {
ngOnChanges(changes: SimpleChanges): void {
if ('pattern' in changes) {
this._createValidator();
if (this._onChange) this._onChange();
}
}
validate(c: AbstractControl): {[key: string]: any} {
return this.pattern ? this._validator(c) : null;
}
validate(c: AbstractControl): {[key: string]: any} { return this._validator(c); }
registerOnValidatorChange(fn: () => void) { this._onChange = fn; }
registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }
private _createValidator(): void { this._validator = Validators.pattern(this.pattern); }
}

View File

@ -23,7 +23,7 @@ export function main() {
NgModelRadioForm, NgModelRangeForm, NgModelSelectForm, NgNoFormComp, InvalidNgModelNoName,
NgModelOptionsStandalone, NgModelCustomComp, NgModelCustomWrapper,
NgModelValidationBindings, NgModelMultipleValidators, NgAsyncValidator,
NgModelAsyncValidation
NgModelAsyncValidation, NgModelSelectWithNullForm
],
imports: [FormsModule]
});
@ -699,6 +699,28 @@ export function main() {
expect(select.nativeElement.value).toEqual('2: Object');
expect(secondNYC.nativeElement.selected).toBe(true);
}));
it('should work with null option', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelSelectWithNullForm);
const comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}];
comp.selectedCity = null;
fixture.detectChanges();
const select = fixture.debugElement.query(By.css('select'));
select.nativeElement.value = '2: Object';
dispatchEvent(select.nativeElement, 'change');
fixture.detectChanges();
tick();
expect(comp.selectedCity['name']).toEqual('NYC');
select.nativeElement.value = '0: null';
dispatchEvent(select.nativeElement, 'change');
fixture.detectChanges();
tick();
expect(comp.selectedCity).toEqual(null);
}));
});
describe('custom value accessors', () => {
@ -771,7 +793,7 @@ export function main() {
expect(form.valid).toEqual(true);
}));
it('should support optional fields with pattern validator', fakeAsync(() => {
it('should support optional fields with string pattern validator', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelMultipleValidators);
fixture.componentInstance.required = false;
fixture.componentInstance.pattern = '[a-z]+';
@ -793,6 +815,28 @@ export function main() {
expect(form.control.hasError('pattern', ['tovalidate'])).toBeTruthy();
}));
it('should support optional fields with RegExp pattern validator', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelMultipleValidators);
fixture.componentInstance.required = false;
fixture.componentInstance.pattern = /^[a-z]+$/;
fixture.detectChanges();
tick();
const form = fixture.debugElement.children[0].injector.get(NgForm);
const input = fixture.debugElement.query(By.css('input'));
input.nativeElement.value = '';
dispatchEvent(input.nativeElement, 'input');
fixture.detectChanges();
expect(form.valid).toBeTruthy();
input.nativeElement.value = '1';
dispatchEvent(input.nativeElement, 'input');
fixture.detectChanges();
expect(form.valid).toBeFalsy();
expect(form.control.hasError('pattern', ['tovalidate'])).toBeTruthy();
}));
it('should support optional fields with minlength validator', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelMultipleValidators);
fixture.componentInstance.required = false;
@ -1078,6 +1122,20 @@ class NgModelSelectForm {
cities: any[] = [];
}
@Component({
selector: 'ng-model-select-null-form',
template: `
<select [(ngModel)]="selectedCity">
<option *ngFor="let c of cities" [ngValue]="c"> {{c.name}} </option>
<option [ngValue]="null">Unspecified</option>
</select>
`
})
class NgModelSelectWithNullForm {
selectedCity: {[k: string]: string} = {};
cities: any[] = [];
}
@Component({
selector: 'ng-model-custom-comp',
template: `
@ -1141,7 +1199,7 @@ class NgModelValidationBindings {
class NgModelMultipleValidators {
required: boolean;
minLen: number;
pattern: string;
pattern: string|RegExp;
}
@Directive({

View File

@ -7,15 +7,15 @@
*/
import {Injectable} from '@angular/core';
import {global} from '../facade/lang';
let _nextRequestId = 0;
export const JSONP_HOME = '__ng_jsonp__';
let _jsonpConnections: {[key: string]: any} = null;
function _getJsonpConnections(): {[key: string]: any} {
const w: {[key: string]: any} = typeof window == 'object' ? window : {};
if (_jsonpConnections === null) {
_jsonpConnections = (<{[key: string]: any}>global)[JSONP_HOME] = {};
_jsonpConnections = w[JSONP_HOME] = {};
}
return _jsonpConnections;
}

View File

@ -12,7 +12,6 @@ import {Observer} from 'rxjs/Observer';
import {ResponseOptions} from '../base_response_options';
import {ReadyState, RequestMethod, ResponseType} from '../enums';
import {isPresent} from '../facade/lang';
import {Connection, ConnectionBackend} from '../interfaces';
import {Request} from '../static_request';
import {Response} from '../static_response';
@ -89,7 +88,7 @@ export class JSONPConnection_ extends JSONPConnection {
if (!this._finished) {
let responseOptions =
new ResponseOptions({body: JSONP_ERR_NO_CALLBACK, type: ResponseType.Error, url});
if (isPresent(baseResponseOptions)) {
if (baseResponseOptions) {
responseOptions = baseResponseOptions.merge(responseOptions);
}
responseObserver.error(new Response(responseOptions));
@ -97,7 +96,7 @@ export class JSONPConnection_ extends JSONPConnection {
}
let responseOptions = new ResponseOptions({body: this._responseData, url});
if (isPresent(this.baseResponseOptions)) {
if (this.baseResponseOptions) {
responseOptions = this.baseResponseOptions.merge(responseOptions);
}
@ -110,7 +109,7 @@ export class JSONPConnection_ extends JSONPConnection {
this.readyState = ReadyState.Done;
_dom.cleanup(script);
let responseOptions = new ResponseOptions({body: error.message, type: ResponseType.Error});
if (isPresent(baseResponseOptions)) {
if (baseResponseOptions) {
responseOptions = baseResponseOptions.merge(responseOptions);
}
responseObserver.error(new Response(responseOptions));
@ -125,10 +124,7 @@ export class JSONPConnection_ extends JSONPConnection {
this.readyState = ReadyState.Cancelled;
script.removeEventListener('load', onLoad);
script.removeEventListener('error', onError);
if (isPresent(script)) {
this._dom.cleanup(script);
}
this._dom.cleanup(script);
};
});
}

View File

@ -10,16 +10,13 @@ import {Injectable} from '@angular/core';
import {__platform_browser_private__} from '@angular/platform-browser';
import {Observable} from 'rxjs/Observable';
import {Observer} from 'rxjs/Observer';
import {ResponseOptions} from '../base_response_options';
import {ContentType, ReadyState, RequestMethod, ResponseContentType, ResponseType} from '../enums';
import {isPresent} from '../facade/lang';
import {Headers} from '../headers';
import {getResponseURL, isSuccess} from '../http_utils';
import {Connection, ConnectionBackend, XSRFStrategy} from '../interfaces';
import {Request} from '../static_request';
import {Response} from '../static_response';
import {BrowserXhr} from './browser_xhr';
const XSSI_PREFIX = /^\)\]\}',?\n/;
@ -47,24 +44,29 @@ export class XHRConnection implements Connection {
this.response = new Observable<Response>((responseObserver: Observer<Response>) => {
const _xhr: XMLHttpRequest = browserXHR.build();
_xhr.open(RequestMethod[req.method].toUpperCase(), req.url);
if (isPresent(req.withCredentials)) {
if (req.withCredentials != null) {
_xhr.withCredentials = req.withCredentials;
}
// load event handler
const onLoad = () => {
// responseText is the old-school way of retrieving response (supported by IE8 & 9)
// response/responseType properties were introduced in ResourceLoader Level2 spec (supported
// by IE10)
let body = _xhr.response === undefined ? _xhr.responseText : _xhr.response;
// Implicitly strip a potential XSSI prefix.
if (typeof body === 'string') body = body.replace(XSSI_PREFIX, '');
const headers = Headers.fromResponseHeaderString(_xhr.getAllResponseHeaders());
const url = getResponseURL(_xhr);
// normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
let status: number = _xhr.status === 1223 ? 204 : _xhr.status;
let body: any = null;
// HTTP 204 means no content
if (status !== 204) {
// responseText is the old-school way of retrieving response (supported by IE8 & 9)
// response/responseType properties were introduced in ResourceLoader Level2 spec
// (supported by IE10)
body = (typeof _xhr.response === 'undefined') ? _xhr.responseText : _xhr.response;
// Implicitly strip a potential XSSI prefix.
if (typeof body === 'string') {
body = body.replace(XSSI_PREFIX, '');
}
}
// fix status code when it is 0 (0 status is undocumented).
// Occurs when accessing file resources or on Android 4.1 stock browser
// while retrieving files from application cache.
@ -72,10 +74,13 @@ export class XHRConnection implements Connection {
status = body ? 200 : 0;
}
const statusText = _xhr.statusText || 'OK';
const headers: Headers = Headers.fromResponseHeaderString(_xhr.getAllResponseHeaders());
// IE 9 does not provide the way to get URL of response
const url = getResponseURL(_xhr) || req.url;
const statusText: string = _xhr.statusText || 'OK';
let responseOptions = new ResponseOptions({body, status, headers, statusText, url});
if (isPresent(baseResponseOptions)) {
if (baseResponseOptions != null) {
responseOptions = baseResponseOptions.merge(responseOptions);
}
const response = new Response(responseOptions);
@ -89,14 +94,14 @@ export class XHRConnection implements Connection {
responseObserver.error(response);
};
// error event handler
const onError = (err: any) => {
const onError = (err: ErrorEvent) => {
let responseOptions = new ResponseOptions({
body: err,
type: ResponseType.Error,
status: _xhr.status,
statusText: _xhr.statusText,
});
if (isPresent(baseResponseOptions)) {
if (baseResponseOptions != null) {
responseOptions = baseResponseOptions.merge(responseOptions);
}
responseObserver.error(new Response(responseOptions));
@ -104,12 +109,12 @@ export class XHRConnection implements Connection {
this.setDetectedContentType(req, _xhr);
if (isPresent(req.headers)) {
if (req.headers != null) {
req.headers.forEach((values, name) => _xhr.setRequestHeader(name, values.join(',')));
}
// Select the correct buffer type to store the response
if (isPresent(req.responseType) && isPresent(_xhr.responseType)) {
if (req.responseType != null && _xhr.responseType != null) {
switch (req.responseType) {
case ResponseContentType.ArrayBuffer:
_xhr.responseType = 'arraybuffer';
@ -141,9 +146,9 @@ export class XHRConnection implements Connection {
});
}
setDetectedContentType(req: any /** TODO #9100 */, _xhr: any /** TODO #9100 */) {
setDetectedContentType(req: any /** TODO Request */, _xhr: any /** XMLHttpRequest */) {
// Skip if a custom Content-Type header is provided
if (isPresent(req.headers) && isPresent(req.headers.get('Content-Type'))) {
if (req.headers != null && req.headers.get('Content-Type') != null) {
return;
}
@ -161,7 +166,7 @@ export class XHRConnection implements Connection {
_xhr.setRequestHeader('content-type', 'text/plain');
break;
case ContentType.BLOB:
let blob = req.blob();
const blob = req.blob();
if (blob.type) {
_xhr.setRequestHeader('content-type', blob.type);
}
@ -185,7 +190,7 @@ export class CookieXSRFStrategy implements XSRFStrategy {
constructor(
private _cookieName: string = 'XSRF-TOKEN', private _headerName: string = 'X-XSRF-TOKEN') {}
configureRequest(req: Request) {
configureRequest(req: Request): void {
const xsrfToken = __platform_browser_private__.getDOM().getCookie(this._cookieName);
if (xsrfToken) {
req.headers.set(this._headerName, xsrfToken);

View File

@ -8,8 +8,6 @@
import {Injectable} from '@angular/core';
import {isPresent} from '../src/facade/lang';
import {RequestMethod, ResponseContentType} from './enums';
import {Headers} from './headers';
import {normalizeMethodName} from './http_utils';
@ -77,16 +75,14 @@ export class RequestOptions {
constructor(
{method, headers, body, url, search, withCredentials,
responseType}: RequestOptionsArgs = {}) {
this.method = isPresent(method) ? normalizeMethodName(method) : null;
this.headers = isPresent(headers) ? headers : null;
this.body = isPresent(body) ? body : null;
this.url = isPresent(url) ? url : null;
this.search = isPresent(search) ?
(typeof search === 'string' ? new URLSearchParams(<string>(search)) :
<URLSearchParams>(search)) :
null;
this.withCredentials = isPresent(withCredentials) ? withCredentials : null;
this.responseType = isPresent(responseType) ? responseType : null;
this.method = method != null ? normalizeMethodName(method) : null;
this.headers = headers != null ? headers : null;
this.body = body != null ? body : null;
this.url = url != null ? url : null;
this.search =
search != null ? (typeof search === 'string' ? new URLSearchParams(search) : search) : null;
this.withCredentials = withCredentials != null ? withCredentials : null;
this.responseType = responseType != null ? responseType : null;
}
/**
@ -116,18 +112,18 @@ export class RequestOptions {
*/
merge(options?: RequestOptionsArgs): RequestOptions {
return new RequestOptions({
method: options && isPresent(options.method) ? options.method : this.method,
headers: options && isPresent(options.headers) ? options.headers : this.headers,
body: options && isPresent(options.body) ? options.body : this.body,
url: options && isPresent(options.url) ? options.url : this.url,
search: options && isPresent(options.search) ?
method: options && options.method != null ? options.method : this.method,
headers: options && options.headers != null ? options.headers : this.headers,
body: options && options.body != null ? options.body : this.body,
url: options && options.url != null ? options.url : this.url,
search: options && options.search != null ?
(typeof options.search === 'string' ? new URLSearchParams(options.search) :
(<URLSearchParams>(options.search)).clone()) :
options.search.clone()) :
this.search,
withCredentials: options && isPresent(options.withCredentials) ? options.withCredentials :
this.withCredentials,
responseType: options && isPresent(options.responseType) ? options.responseType :
this.responseType
withCredentials: options && options.withCredentials != null ? options.withCredentials :
this.withCredentials,
responseType: options && options.responseType != null ? options.responseType :
this.responseType
});
}
}

View File

@ -8,8 +8,6 @@
import {Injectable} from '@angular/core';
import {isPresent} from '../src/facade/lang';
import {ResponseType} from './enums';
import {Headers} from './headers';
import {ResponseOptionsArgs} from './interfaces';
@ -68,12 +66,12 @@ export class ResponseOptions {
type: ResponseType;
url: string;
constructor({body, status, headers, statusText, type, url}: ResponseOptionsArgs = {}) {
this.body = isPresent(body) ? body : null;
this.status = isPresent(status) ? status : null;
this.headers = isPresent(headers) ? headers : null;
this.statusText = isPresent(statusText) ? statusText : null;
this.type = isPresent(type) ? type : null;
this.url = isPresent(url) ? url : null;
this.body = body != null ? body : null;
this.status = status != null ? status : null;
this.headers = headers != null ? headers : null;
this.statusText = statusText != null ? statusText : null;
this.type = type != null ? type : null;
this.url = url != null ? url : null;
}
/**
@ -103,13 +101,12 @@ export class ResponseOptions {
*/
merge(options?: ResponseOptionsArgs): ResponseOptions {
return new ResponseOptions({
body: isPresent(options) && isPresent(options.body) ? options.body : this.body,
status: isPresent(options) && isPresent(options.status) ? options.status : this.status,
headers: isPresent(options) && isPresent(options.headers) ? options.headers : this.headers,
statusText: isPresent(options) && isPresent(options.statusText) ? options.statusText :
this.statusText,
type: isPresent(options) && isPresent(options.type) ? options.type : this.type,
url: isPresent(options) && isPresent(options.url) ? options.url : this.url,
body: options && options.body != null ? options.body : this.body,
status: options && options.status != null ? options.status : this.status,
headers: options && options.headers != null ? options.headers : this.headers,
statusText: options && options.statusText != null ? options.statusText : this.statusText,
type: options && options.type != null ? options.type : this.type,
url: options && options.url != null ? options.url : this.url,
});
}
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {isJsObject, stringToArrayBuffer} from './http_utils';
import {stringToArrayBuffer} from './http_utils';
import {URLSearchParams} from './url_search_params';
@ -51,7 +51,7 @@ export abstract class Body {
return '';
}
if (isJsObject(this._body)) {
if (typeof this._body === 'object') {
return JSON.stringify(this._body, null, 2);
}

View File

@ -1 +0,0 @@
../../facade/src

View File

@ -8,7 +8,6 @@
import {Injectable} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {isPresent} from '../src/facade/lang';
import {BaseRequestOptions, RequestOptions} from './base_request_options';
import {RequestMethod} from './enums';
import {ConnectionBackend, RequestOptionsArgs} from './interfaces';
@ -23,7 +22,7 @@ function mergeOptions(
defaultOpts: BaseRequestOptions, providedOpts: RequestOptionsArgs, method: RequestMethod,
url: string): RequestOptions {
const newOptions = defaultOpts;
if (isPresent(providedOpts)) {
if (providedOpts) {
// Hack so Dart can used named parameters
return newOptions.merge(new RequestOptions({
method: providedOpts.method || method,
@ -35,11 +34,8 @@ function mergeOptions(
responseType: providedOpts.responseType
}));
}
if (isPresent(method)) {
return newOptions.merge(new RequestOptions({method: method, url: url}));
} else {
return newOptions.merge(new RequestOptions({url: url}));
}
return newOptions.merge(new RequestOptions({method, url}));
}
/**

View File

@ -49,5 +49,3 @@ export function stringToArrayBuffer(input: String): ArrayBuffer {
}
return view.buffer;
}
export {isJsObject} from '../src/facade/lang';

View File

@ -6,8 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/
import {isPresent} from '../src/facade/lang';
import {Body} from './body';
import {ContentType, RequestMethod, ResponseContentType} from './enums';
import {Headers} from './headers';
@ -78,7 +76,7 @@ export class Request extends Body {
// TODO: assert that url is present
const url = requestOptions.url;
this.url = requestOptions.url;
if (isPresent(requestOptions.search)) {
if (requestOptions.search) {
const search = requestOptions.search.toString();
if (search.length > 0) {
let prefix = '?';
@ -93,7 +91,6 @@ export class Request extends Body {
this.method = normalizeMethodName(requestOptions.method);
// TODO(jeffbcross): implement behavior
// Defaults to 'omit', consistent with browser
// TODO(jeffbcross): implement behavior
this.headers = new Headers(requestOptions.headers);
this.contentType = this.detectContentType();
this.withCredentials = requestOptions.withCredentials;

View File

@ -14,7 +14,6 @@ import {JSONPBackend, JSONPBackend_, JSONPConnection, JSONPConnection_} from '..
import {BaseRequestOptions, RequestOptions} from '../../src/base_request_options';
import {BaseResponseOptions, ResponseOptions} from '../../src/base_response_options';
import {ReadyState, RequestMethod, ResponseType} from '../../src/enums';
import {isPresent} from '../../src/facade/lang';
import {Request} from '../../src/static_request';
let existingScripts: MockBrowserJsonp[] = [];
@ -22,18 +21,14 @@ let existingScripts: MockBrowserJsonp[] = [];
class MockBrowserJsonp extends BrowserJsonp {
src: string;
callbacks = new Map<string, (data: any) => any>();
constructor() { super(); }
addEventListener(type: string, cb: (data: any) => any) { this.callbacks.set(type, cb); }
removeEventListener(type: string, cb: Function) { this.callbacks.delete(type); }
dispatchEvent(type: string, argument?: any) {
if (!isPresent(argument)) {
argument = {};
}
dispatchEvent(type: string, argument: any = {}) {
const cb = this.callbacks.get(type);
if (isPresent(cb)) {
if (cb) {
cb(argument);
}
}

View File

@ -9,7 +9,6 @@
import {Injectable} from '@angular/core';
import {AsyncTestCompleter, SpyObject, afterEach, beforeEach, beforeEachProviders, describe, expect, inject, it} from '@angular/core/testing/testing_internal';
import {__platform_browser_private__} from '@angular/platform-browser';
import {BrowserXhr} from '../../src/backends/browser_xhr';
import {CookieXSRFStrategy, XHRBackend, XHRConnection} from '../../src/backends/xhr_backend';
import {BaseRequestOptions, RequestOptions} from '../../src/base_request_options';
@ -486,6 +485,7 @@ export function main() {
existingXHRs[0].setStatusCode(statusCode);
existingXHRs[0].dispatchEvent('load');
}));
it('should normalize IE\'s 1223 status code into 204',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const statusCode = 1223;
@ -502,6 +502,22 @@ export function main() {
existingXHRs[0].dispatchEvent('load');
}));
it('should ignore response body for 204 status code',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const statusCode = 204;
const connection = new XHRConnection(
sampleRequest, new MockBrowserXHR(), new ResponseOptions({status: statusCode}));
connection.response.subscribe((res: Response) => {
expect(res.text()).toBe('');
async.done();
});
existingXHRs[0].setStatusCode(statusCode);
existingXHRs[0].setResponseText('Doge');
existingXHRs[0].dispatchEvent('load');
}));
it('should normalize responseText and response',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const responseBody = 'Doge';
@ -623,6 +639,21 @@ Connection: keep-alive`;
existingXHRs[0].dispatchEvent('load');
}));
it('should return request url if it cannot be retrieved from response',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const statusCode = 200;
const connection = new XHRConnection(
sampleRequest, new MockBrowserXHR(), new ResponseOptions({status: statusCode}));
connection.response.subscribe((res: Response) => {
expect(res.url).toEqual('https://google.com');
async.done();
});
existingXHRs[0].setStatusCode(statusCode);
existingXHRs[0].dispatchEvent('load');
}));
it('should set the status text property from the XMLHttpRequest instance if present',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const statusText = 'test';

View File

@ -85,6 +85,7 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter {
log(error: string): void {
if (window.console) {
// tslint:disable-next-line:no-console
window.console.log && window.console.log(error);
}
}

View File

@ -36,6 +36,7 @@ export class AngularProfiler {
constructor(ref: ComponentRef<any>) { this.appRef = ref.injector.get(ApplicationRef); }
// tslint:disable:no-console
/**
* Exercises change detection in a loop and then prints the average amount of
* time in milliseconds how long a single round of change detection takes for

View File

@ -37,5 +37,7 @@ export function enableDebugTools<T>(ref: ComponentRef<T>): ComponentRef<T> {
* @experimental All debugging apis are currently experimental.
*/
export function disableDebugTools(): void {
delete context.ng.profiler;
if (context.ng) {
delete context.ng.profiler;
}
}

View File

@ -13,7 +13,8 @@ import {AnimationKeyframe, AnimationStyles, NoOpAnimationPlayer} from '../privat
class _NoOpAnimationDriver implements AnimationDriver {
animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer {
duration: number, delay: number, easing: string,
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
return new NoOpAnimationPlayer();
}
}
@ -25,5 +26,6 @@ export abstract class AnimationDriver {
static NOOP: AnimationDriver = new _NoOpAnimationDriver();
abstract animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer;
duration: number, delay: number, easing: string,
previousPlayers?: AnimationPlayer[]): AnimationPlayer;
}

View File

@ -260,9 +260,10 @@ export class DomRenderer implements Renderer {
animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer {
duration: number, delay: number, easing: string,
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
return this._animationDriver.animate(
element, startingStyles, keyframes, duration, delay, easing);
element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
}
}

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {AnimationPlayer} from '@angular/core';
import {isPresent} from '../facade/lang';
import {AnimationKeyframe, AnimationStyles} from '../private_import_core';
@ -15,17 +16,18 @@ import {WebAnimationsPlayer} from './web_animations_player';
export class WebAnimationsDriver implements AnimationDriver {
animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): WebAnimationsPlayer {
duration: number, delay: number, easing: string,
previousPlayers: AnimationPlayer[] = []): WebAnimationsPlayer {
let formattedSteps: {[key: string]: string | number}[] = [];
let startingStyleLookup: {[key: string]: string | number} = {};
if (isPresent(startingStyles) && startingStyles.styles.length > 0) {
startingStyleLookup = _populateStyles(element, startingStyles, {});
startingStyleLookup = _populateStyles(startingStyles, {});
startingStyleLookup['offset'] = 0;
formattedSteps.push(startingStyleLookup);
}
keyframes.forEach((keyframe: AnimationKeyframe) => {
const data = _populateStyles(element, keyframe.styles, startingStyleLookup);
const data = _populateStyles(keyframe.styles, startingStyleLookup);
data['offset'] = keyframe.offset;
formattedSteps.push(data);
});
@ -52,13 +54,16 @@ export class WebAnimationsDriver implements AnimationDriver {
playerOptions['easing'] = easing;
}
return new WebAnimationsPlayer(element, formattedSteps, playerOptions);
// there may be a chance a NoOp player is returned depending
// on when the previous animation was cancelled
previousPlayers = previousPlayers.filter(filterWebAnimationPlayerFn);
return new WebAnimationsPlayer(
element, formattedSteps, playerOptions, <WebAnimationsPlayer[]>previousPlayers);
}
}
function _populateStyles(
element: any, styles: AnimationStyles,
defaultStyles: {[key: string]: string | number}): {[key: string]: string | number} {
function _populateStyles(styles: AnimationStyles, defaultStyles: {[key: string]: string | number}):
{[key: string]: string | number} {
const data: {[key: string]: string | number} = {};
styles.styles.forEach(
(entry) => { Object.keys(entry).forEach(prop => { data[prop] = entry[prop]; }); });
@ -69,3 +74,7 @@ function _populateStyles(
});
return data;
}
function filterWebAnimationPlayerFn(player: AnimationPlayer) {
return player instanceof WebAnimationsPlayer;
}

View File

@ -7,6 +7,8 @@
*/
import {AUTO_STYLE} from '@angular/core';
import {isPresent} from '../facade/lang';
import {AnimationPlayer} from '../private_import_core';
import {getDOM} from './dom_adapter';
@ -21,13 +23,22 @@ export class WebAnimationsPlayer implements AnimationPlayer {
private _finished = false;
private _started = false;
private _destroyed = false;
private _finalKeyframe: {[key: string]: string | number};
public parentPlayer: AnimationPlayer = null;
public previousStyles: {[styleName: string]: string | number};
constructor(
public element: any, public keyframes: {[key: string]: string | number}[],
public options: {[key: string]: string | number}) {
public options: {[key: string]: string | number},
previousPlayers: WebAnimationsPlayer[] = []) {
this._duration = <number>options['duration'];
this.previousStyles = {};
previousPlayers.forEach(player => {
let styles = player._captureStyles();
Object.keys(styles).forEach(prop => this.previousStyles[prop] = styles[prop]);
});
}
private _onFinish() {
@ -44,14 +55,30 @@ export class WebAnimationsPlayer implements AnimationPlayer {
const keyframes = this.keyframes.map(styles => {
const formattedKeyframe: {[key: string]: string | number} = {};
Object.keys(styles).forEach(prop => {
const value = styles[prop];
formattedKeyframe[prop] = value == AUTO_STYLE ? _computeStyle(this.element, prop) : value;
Object.keys(styles).forEach((prop, index) => {
let value = styles[prop];
if (value == AUTO_STYLE) {
value = _computeStyle(this.element, prop);
}
if (value != undefined) {
formattedKeyframe[prop] = value;
}
});
return formattedKeyframe;
});
const previousStyleProps = Object.keys(this.previousStyles);
if (previousStyleProps.length) {
let startingKeyframe = findStartingKeyframe(keyframes);
previousStyleProps.forEach(prop => {
if (isPresent(startingKeyframe[prop])) {
startingKeyframe[prop] = this.previousStyles[prop];
}
});
}
this._player = this._triggerWebAnimation(this.element, keyframes, this.options);
this._finalKeyframe = _copyKeyframeStyles(keyframes[keyframes.length - 1]);
// this is required so that the player doesn't start to animate right away
this._resetDomPlayerState();
@ -119,8 +146,47 @@ export class WebAnimationsPlayer implements AnimationPlayer {
setPosition(p: number): void { this._player.currentTime = p * this.totalTime; }
getPosition(): number { return this._player.currentTime / this.totalTime; }
private _captureStyles(): {[prop: string]: string | number} {
const styles: {[key: string]: string | number} = {};
if (this.hasStarted()) {
Object.keys(this._finalKeyframe).forEach(prop => {
if (prop != 'offset') {
styles[prop] =
this._finished ? this._finalKeyframe[prop] : _computeStyle(this.element, prop);
}
});
}
return styles;
}
}
function _computeStyle(element: any, prop: string): string {
return getDOM().getComputedStyle(element)[prop];
}
function _copyKeyframeStyles(styles: {[style: string]: string | number}):
{[style: string]: string | number} {
const newStyles: {[style: string]: string | number} = {};
Object.keys(styles).forEach(prop => {
if (prop != 'offset') {
newStyles[prop] = styles[prop];
}
});
return newStyles;
}
function findStartingKeyframe(keyframes: {[prop: string]: string | number}[]):
{[prop: string]: string | number} {
let startingKeyframe = keyframes[0];
// it's important that we find the LAST keyframe
// to ensure that style overidding is final.
for (let i = 1; i < keyframes.length; i++) {
const kf = keyframes[i];
const offset = kf['offset'];
if (offset !== 0) break;
startingKeyframe = kf;
}
return startingKeyframe;
}

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
import {AnimationPlayer} from '@angular/core';
import {el} from '@angular/platform-browser/testing/browser_util';
import {DomAnimatePlayer} from '../../src/dom/dom_animate_player';
import {WebAnimationsDriver} from '../../src/dom/web_animations_driver';
import {WebAnimationsPlayer} from '../../src/dom/web_animations_player';
import {AnimationKeyframe, AnimationStyles} from '../../src/private_import_core';
import {AnimationKeyframe, AnimationStyles, NoOpAnimationPlayer} from '../../src/private_import_core';
import {MockDomAnimatePlayer} from '../../testing/mock_dom_animate_player';
class ExtendedWebAnimationsDriver extends WebAnimationsDriver {
@ -48,8 +48,7 @@ export function main() {
it('should use a fill mode of `both`', () => {
const startingStyles = _makeStyles({});
const styles = [_makeKeyframe(0, {'color': 'green'}), _makeKeyframe(1, {'color': 'red'})];
const player = driver.animate(elm, startingStyles, styles, 1000, 1000, 'linear');
const player = driver.animate(elm, startingStyles, styles, 1000, 1000, 'linear', []);
const details = _formatOptions(player);
const options = details['options'];
expect(options['fill']).toEqual('both');
@ -58,8 +57,7 @@ export function main() {
it('should apply the provided easing', () => {
const startingStyles = _makeStyles({});
const styles = [_makeKeyframe(0, {'color': 'green'}), _makeKeyframe(1, {'color': 'red'})];
const player = driver.animate(elm, startingStyles, styles, 1000, 1000, 'ease-out');
const player = driver.animate(elm, startingStyles, styles, 1000, 1000, 'ease-out', []);
const details = _formatOptions(player);
const options = details['options'];
expect(options['easing']).toEqual('ease-out');
@ -68,16 +66,32 @@ export function main() {
it('should only apply the provided easing if present', () => {
const startingStyles = _makeStyles({});
const styles = [_makeKeyframe(0, {'color': 'green'}), _makeKeyframe(1, {'color': 'red'})];
const player = driver.animate(elm, startingStyles, styles, 1000, 1000, null);
const player = driver.animate(elm, startingStyles, styles, 1000, 1000, null, []);
const details = _formatOptions(player);
const options = details['options'];
const keys = Object.keys(options);
expect(keys.indexOf('easing')).toEqual(-1);
});
it('should only apply the provided easing if present', () => {
const previousPlayers = [
new NoOpAnimationPlayerWithStyles(),
new NoOpAnimationPlayerWithStyles(),
new NoOpAnimationPlayerWithStyles(),
];
const startingStyles = _makeStyles({});
const styles = [_makeKeyframe(0, {}), _makeKeyframe(1, {})];
const player = driver.animate(
elm, startingStyles, styles, 1000, 1000, null, <AnimationPlayer[]>previousPlayers);
expect(player.previousStyles).toEqual({});
});
});
}
class NoOpAnimationPlayerWithStyles extends NoOpAnimationPlayer {
private _captureStyles() { return {color: 'red'}; }
}
function _formatOptions(player: WebAnimationsPlayer): {[key: string]: any} {
return {'element': player.element, 'keyframes': player.keyframes, 'options': player.options};
}

View File

@ -6,7 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {MockAnimationPlayer, beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
import {AUTO_STYLE, AnimationPlayer} from '@angular/core';
import {MockAnimationPlayer} from '@angular/core/testing/testing_internal';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {el} from '@angular/platform-browser/testing/browser_util';
import {DomAnimatePlayer} from '../../src/dom/dom_animate_player';
@ -18,14 +20,16 @@ class ExtendedWebAnimationsPlayer extends WebAnimationsPlayer {
constructor(
public element: HTMLElement, public keyframes: {[key: string]: string | number}[],
public options: {[key: string]: string | number}) {
super(element, keyframes, options);
public options: {[key: string]: string | number},
public previousPlayers: WebAnimationsPlayer[] = []) {
super(element, keyframes, options, previousPlayers);
}
get domPlayer() { return this._overriddenDomPlayer; }
/** @internal */
_triggerWebAnimation(elm: any, keyframes: any[], options: any): DomAnimatePlayer {
this._overriddenDomPlayer._capture('trigger', {elm, keyframes, options});
return this._overriddenDomPlayer;
}
}
@ -33,7 +37,7 @@ class ExtendedWebAnimationsPlayer extends WebAnimationsPlayer {
export function main() {
function makePlayer(): {[key: string]: any} {
const someElm = el('<div></div>');
const player = new ExtendedWebAnimationsPlayer(someElm, [], {});
const player = new ExtendedWebAnimationsPlayer(someElm, [{}, {}], {}, []);
player.init();
return {'captures': player.domPlayer.captures, 'player': player};
}
@ -156,5 +160,72 @@ export function main() {
player.destroy();
expect(captures['cancel'].length).toBe(1);
});
it('should resolve auto styles based on what is computed from the provided element', () => {
const elm = el('<div></div>');
document.body.appendChild(elm); // required for getComputedStyle() to work
elm.style.opacity = '0.5';
const player = new ExtendedWebAnimationsPlayer(
elm, [{opacity: AUTO_STYLE}, {opacity: '1'}], {duration: 1000}, []);
player.init();
const data = player.domPlayer.captures['trigger'][0];
expect(data['keyframes']).toEqual([{opacity: '0.5'}, {opacity: '1'}]);
});
describe('previousStyle', () => {
it('should merge keyframe styles based on the previous styles passed in when the player has finished its operation',
() => {
const elm = el('<div></div>');
const previousStyles = {width: '100px', height: '666px'};
const previousPlayer =
new ExtendedWebAnimationsPlayer(elm, [previousStyles, previousStyles], {}, []);
previousPlayer.play();
previousPlayer.finish();
const player = new ExtendedWebAnimationsPlayer(
elm,
[
{width: '0px', height: '0px', opacity: 0, offset: 0},
{width: '0px', height: '0px', opacity: 1, offset: 1}
],
{duration: 1000}, [previousPlayer]);
player.init();
const data = player.domPlayer.captures['trigger'][0];
expect(data['keyframes']).toEqual([
{width: '100px', height: '666px', opacity: 0, offset: 0},
{width: '0px', height: '0px', opacity: 1, offset: 1}
]);
});
it('should properly calculate the previous styles for the player even when its currently playing',
() => {
if (!getDOM().supportsWebAnimation()) return;
const elm = el('<div></div>');
document.body.appendChild(elm);
const fromStyles = {width: '100px', height: '666px'};
const toStyles = {width: '50px', height: '333px'};
const previousPlayer =
new WebAnimationsPlayer(elm, [fromStyles, toStyles], {duration: 1000}, []);
previousPlayer.play();
previousPlayer.setPosition(0.5);
previousPlayer.pause();
const newStyles = {width: '0px', height: '0px'};
const player = new WebAnimationsPlayer(
elm, [newStyles, newStyles], {duration: 1000}, [previousPlayer]);
player.init();
const data = player.previousStyles;
expect(player.previousStyles).toEqual({width: '75px', height: '499.5px'});
});
});
});
}

View File

@ -9,19 +9,29 @@
import {AnimationPlayer} from '@angular/core';
import {MockAnimationPlayer} from '@angular/core/testing/testing_internal';
import {AnimationDriver} from '@angular/platform-browser';
import {ListWrapper} from './facade/collection';
import {AnimationKeyframe, AnimationStyles} from './private_import_core';
export class MockAnimationDriver extends AnimationDriver {
public log: {[key: string]: any}[] = [];
animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer {
const player = new MockAnimationPlayer();
duration: number, delay: number, easing: string,
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
const mockPlayers = <MockAnimationPlayer[]>previousPlayers.filter(
player => player instanceof MockAnimationPlayer);
const normalizedStartingStyles = _serializeStyles(startingStyles);
const normalizedKeyframes = _serializeKeyframes(keyframes);
const player =
new MockAnimationPlayer(normalizedStartingStyles, normalizedKeyframes, previousPlayers);
this.log.push({
'element': element,
'startingStyles': _serializeStyles(startingStyles),
'startingStyles': normalizedStartingStyles,
'previousStyles': player.previousStyles,
'keyframes': keyframes,
'keyframeLookup': _serializeKeyframes(keyframes),
'keyframeLookup': normalizedKeyframes,
'duration': duration,
'delay': delay,
'easing': easing,

View File

@ -63,6 +63,7 @@ export class Parse5DomAdapter extends DomAdapter {
logError(error: string) { console.error(error); }
// tslint:disable-next-line:no-console
log(error: string) { console.log(error); }
logGroup(error: string) { console.error(error); }

View File

@ -206,9 +206,10 @@ export class ServerRenderer implements Renderer {
animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer {
duration: number, delay: number, easing: string,
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
return this._animationDriver.animate(
element, startingStyles, keyframes, duration, delay, easing);
element, startingStyles, keyframes, duration, delay, easing, previousPlayers);
}
}

View File

@ -89,7 +89,7 @@ export class MessageBasedRenderer {
'animate',
[
RenderStoreObject, RenderStoreObject, PRIMITIVE, PRIMITIVE, PRIMITIVE, PRIMITIVE,
PRIMITIVE, PRIMITIVE
PRIMITIVE, PRIMITIVE, PRIMITIVE
],
this._animate.bind(this));
@ -248,8 +248,14 @@ export class MessageBasedRenderer {
private _animate(
renderer: Renderer, element: any, startingStyles: any, keyframes: any[], duration: number,
delay: number, easing: string, playerId: any) {
const player = renderer.animate(element, startingStyles, keyframes, duration, delay, easing);
delay: number, easing: string, previousPlayers: number[], playerId: any) {
let normalizedPreviousPlayers: AnimationPlayer[];
if (previousPlayers && previousPlayers.length) {
normalizedPreviousPlayers =
previousPlayers.map(playerId => this._renderStore.deserialize(playerId));
}
const player = renderer.animate(
element, startingStyles, keyframes, duration, delay, easing, normalizedPreviousPlayers);
this._renderStore.store(player, playerId);
}

View File

@ -16,6 +16,7 @@ import {MessageBus} from '../shared/message_bus';
import {EVENT_CHANNEL, RENDERER_CHANNEL} from '../shared/messaging_api';
import {RenderStore} from '../shared/render_store';
import {ANIMATION_WORKER_PLAYER_PREFIX, RenderStoreObject, Serializer} from '../shared/serializer';
import {deserializeGenericEvent} from './event_deserializer';
@Injectable()
@ -239,13 +240,16 @@ export class WebWorkerRenderer implements Renderer, RenderStoreObject {
animate(
renderElement: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer {
duration: number, delay: number, easing: string,
previousPlayers: AnimationPlayer[] = []): AnimationPlayer {
const playerId = this._rootRenderer.allocateId();
const previousPlayerIds: number[] =
previousPlayers.map(player => this._rootRenderer.renderStore.serialize(player));
this._runOnService('animate', [
new FnArg(renderElement, RenderStoreObject), new FnArg(startingStyles, null),
new FnArg(keyframes, null), new FnArg(duration, null), new FnArg(delay, null),
new FnArg(easing, null), new FnArg(playerId, null)
new FnArg(easing, null), new FnArg(previousPlayerIds, null), new FnArg(playerId, null)
]);
const player = new _AnimationWorkerRendererPlayer(this._rootRenderer, renderElement);
@ -325,7 +329,7 @@ export class WebWorkerRenderNode {
animationPlayerEvents = new AnimationPlayerEmitter();
}
class _AnimationWorkerRendererPlayer implements AnimationPlayer, RenderStoreObject {
class _AnimationWorkerRendererPlayer implements RenderStoreObject {
public parentPlayer: AnimationPlayer = null;
private _destroyed: boolean = false;

View File

@ -22,10 +22,12 @@ export class WorkerDomAdapter extends DomAdapter {
if (console.error) {
console.error(error);
} else {
// tslint:disable-next-line:no-console
console.log(error);
}
}
// tslint:disable-next-line:no-console
log(error: any /** TODO #9100 */) { console.log(error); }
logGroup(error: any /** TODO #9100 */) {
@ -33,6 +35,7 @@ export class WorkerDomAdapter extends DomAdapter {
console.group(error);
this.logError(error);
} else {
// tslint:disable-next-line:no-console
console.log(error);
}
}

View File

@ -289,6 +289,30 @@ export function main() {
expect(player.log.indexOf('destroy') >= 0).toBe(true);
}));
it('should properly transition to the next animation if the current one is cancelled',
fakeAsync(() => {
const fixture = TestBed.createComponent(AnimationCmp);
const cmp = fixture.componentInstance;
cmp.state = 'on';
fixture.detectChanges();
flushMicrotasks();
let player = <MockAnimationPlayer>uiDriver.log.shift()['player'];
player.finish();
player = <MockAnimationPlayer>uiDriver.log.shift()['player'];
player.setPosition(0.5);
uiDriver.log = [];
cmp.state = 'off';
fixture.detectChanges();
flushMicrotasks();
const step = uiDriver.log.shift();
expect(step['previousStyles']).toEqual({opacity: AUTO_STYLE, fontSize: AUTO_STYLE});
}));
});
}

View File

@ -0,0 +1,5 @@
/**
* @license Angular v0.0.0-ROUTERPLACEHOLDER
* (c) 2010-2016 Google, Inc. https://angular.io/
* License: MIT
*/

View File

@ -1,6 +1,6 @@
{
"name": "@angular/router",
"version": "3.0.0-rc.1",
"version": "0.0.0-ROUTERPLACEHOLDER",
"description": "Angular - the routing library",
"main": "bundles/router.umd.js",
"module": "index.js",
@ -24,7 +24,6 @@
"@angular/core": "0.0.0-PLACEHOLDER",
"@angular/common": "0.0.0-PLACEHOLDER",
"@angular/platform-browser": "0.0.0-PLACEHOLDER",
"@angular/upgrade": "0.0.0-PLACEHOLDER",
"rxjs": "5.0.0-beta.12"
}
}

View File

@ -20,8 +20,8 @@ import {EmptyError} from 'rxjs/util/EmptyError';
import {Route, Routes, UrlMatchResult} from './config';
import {LoadedRouterConfig, RouterConfigLoader} from './router_config_loader';
import {NavigationCancelingError, PRIMARY_OUTLET, defaultUrlMatcher} from './shared';
import {UrlSegment, UrlSegmentGroup, UrlTree} from './url_tree';
import {NavigationCancelingError, PRIMARY_OUTLET, Params, defaultUrlMatcher} from './shared';
import {UrlSegment, UrlSegmentGroup, UrlSerializer, UrlTree} from './url_tree';
import {andObservables, forEach, merge, waitForMap, wrapIntoObservable} from './utils/collection';
class NoMatch {
@ -29,7 +29,7 @@ class NoMatch {
}
class AbsoluteRedirect {
constructor(public segments: UrlSegment[]) {}
constructor(public urlTree: UrlTree) {}
}
function noMatch(segmentGroup: UrlSegmentGroup): Observable<UrlSegmentGroup> {
@ -37,9 +37,15 @@ function noMatch(segmentGroup: UrlSegmentGroup): Observable<UrlSegmentGroup> {
(obs: Observer<UrlSegmentGroup>) => obs.error(new NoMatch(segmentGroup)));
}
function absoluteRedirect(segments: UrlSegment[]): Observable<UrlSegmentGroup> {
function absoluteRedirect(newTree: UrlTree): Observable<any> {
return new Observable<UrlSegmentGroup>(
(obs: Observer<UrlSegmentGroup>) => obs.error(new AbsoluteRedirect(segments)));
(obs: Observer<UrlSegmentGroup>) => obs.error(new AbsoluteRedirect(newTree)));
}
function namedOutletsRedirect(redirectTo: string): Observable<any> {
return new Observable<UrlSegmentGroup>(
(obs: Observer<UrlSegmentGroup>) => obs.error(new Error(
`Only absolute redirects can have named outlets. redirectTo: '${redirectTo}'`)));
}
function canLoadFails(route: Route): Observable<LoadedRouterConfig> {
@ -50,9 +56,9 @@ function canLoadFails(route: Route): Observable<LoadedRouterConfig> {
export function applyRedirects(
injector: Injector, configLoader: RouterConfigLoader, urlTree: UrlTree,
config: Routes): Observable<UrlTree> {
return new ApplyRedirects(injector, configLoader, urlTree, config).apply();
injector: Injector, configLoader: RouterConfigLoader, urlSerializer: UrlSerializer,
urlTree: UrlTree, config: Routes): Observable<UrlTree> {
return new ApplyRedirects(injector, configLoader, urlSerializer, urlTree, config).apply();
}
class ApplyRedirects {
@ -60,21 +66,20 @@ class ApplyRedirects {
constructor(
private injector: Injector, private configLoader: RouterConfigLoader,
private urlTree: UrlTree, private config: Routes) {}
private urlSerializer: UrlSerializer, private urlTree: UrlTree, private config: Routes) {}
apply(): Observable<UrlTree> {
const expanded$ =
this.expandSegmentGroup(this.injector, this.config, this.urlTree.root, PRIMARY_OUTLET);
const urlTrees$ = map.call(
expanded$, (rootSegmentGroup: UrlSegmentGroup) => this.createUrlTree(rootSegmentGroup));
expanded$, (rootSegmentGroup: UrlSegmentGroup) => this.createUrlTree(
rootSegmentGroup, this.urlTree.queryParams, this.urlTree.fragment));
return _catch.call(urlTrees$, (e: any) => {
if (e instanceof AbsoluteRedirect) {
// after an absolute redirect we do not apply any more redirects!
this.allowRedirects = false;
const group =
new UrlSegmentGroup([], {[PRIMARY_OUTLET]: new UrlSegmentGroup(e.segments, {})});
// we need to run matching, so we can fetch all lazy-loaded modules
return this.match(group);
return this.match(e.urlTree);
} else if (e instanceof NoMatch) {
throw this.noMatchError(e);
} else {
@ -83,11 +88,12 @@ class ApplyRedirects {
});
}
private match(segmentGroup: UrlSegmentGroup): Observable<UrlTree> {
private match(tree: UrlTree): Observable<UrlTree> {
const expanded$ =
this.expandSegmentGroup(this.injector, this.config, segmentGroup, PRIMARY_OUTLET);
this.expandSegmentGroup(this.injector, this.config, tree.root, PRIMARY_OUTLET);
const mapped$ = map.call(
expanded$, (rootSegmentGroup: UrlSegmentGroup) => this.createUrlTree(rootSegmentGroup));
expanded$, (rootSegmentGroup: UrlSegmentGroup) =>
this.createUrlTree(rootSegmentGroup, tree.queryParams, tree.fragment));
return _catch.call(mapped$, (e: any): Observable<UrlTree> => {
if (e instanceof NoMatch) {
throw this.noMatchError(e);
@ -101,11 +107,12 @@ class ApplyRedirects {
return new Error(`Cannot match any routes. URL Segment: '${e.segmentGroup}'`);
}
private createUrlTree(rootCandidate: UrlSegmentGroup): UrlTree {
private createUrlTree(rootCandidate: UrlSegmentGroup, queryParams: Params, fragment: string):
UrlTree {
const root = rootCandidate.segments.length > 0 ?
new UrlSegmentGroup([], {[PRIMARY_OUTLET]: rootCandidate}) :
rootCandidate;
return new UrlTree(root, this.urlTree.queryParams, this.urlTree.fragment);
return new UrlTree(root, queryParams, fragment);
}
private expandSegmentGroup(
@ -191,12 +198,14 @@ class ApplyRedirects {
private expandWildCardWithParamsAgainstRouteUsingRedirect(
injector: Injector, routes: Route[], route: Route,
outlet: string): Observable<UrlSegmentGroup> {
const newSegments = applyRedirectCommands([], route.redirectTo, {});
const newTree = this.applyRedirectCommands([], route.redirectTo, {});
if (route.redirectTo.startsWith('/')) {
return absoluteRedirect(newSegments);
return absoluteRedirect(newTree);
} else {
const group = new UrlSegmentGroup(newSegments, {});
return this.expandSegment(injector, group, routes, newSegments, outlet, false);
return mergeMap.call(this.lineralizeSegments(route, newTree), (newSegments: UrlSegment[]) => {
const group = new UrlSegmentGroup(newSegments, {});
return this.expandSegment(injector, group, routes, newSegments, outlet, false);
});
}
}
@ -207,14 +216,16 @@ class ApplyRedirects {
match(segmentGroup, route, segments);
if (!matched) return noMatch(segmentGroup);
const newSegments =
applyRedirectCommands(consumedSegments, route.redirectTo, <any>positionalParamSegments);
const newTree = this.applyRedirectCommands(
consumedSegments, route.redirectTo, <any>positionalParamSegments);
if (route.redirectTo.startsWith('/')) {
return absoluteRedirect(newSegments);
return absoluteRedirect(newTree);
} else {
return this.expandSegment(
injector, segmentGroup, routes, newSegments.concat(segments.slice(lastChild)), outlet,
false);
return mergeMap.call(this.lineralizeSegments(route, newTree), (newSegments: UrlSegment[]) => {
return this.expandSegment(
injector, segmentGroup, routes, newSegments.concat(segments.slice(lastChild)), outlet,
false);
});
}
}
@ -284,6 +295,92 @@ class ApplyRedirects {
return of (new LoadedRouterConfig([], injector, null, null));
}
}
private lineralizeSegments(route: Route, urlTree: UrlTree): Observable<UrlSegment[]> {
let res: UrlSegment[] = [];
let c = urlTree.root;
while (true) {
res = res.concat(c.segments);
if (c.numberOfChildren === 0) {
return of (res);
} else if (c.numberOfChildren > 1 || !c.children[PRIMARY_OUTLET]) {
return namedOutletsRedirect(route.redirectTo);
} else {
c = c.children[PRIMARY_OUTLET];
}
}
}
private applyRedirectCommands(
segments: UrlSegment[], redirectTo: string, posParams: {[k: string]: UrlSegment}): UrlTree {
const t = this.urlSerializer.parse(redirectTo);
return this.applyRedirectCreatreUrlTree(
redirectTo, this.urlSerializer.parse(redirectTo), segments, posParams);
}
private applyRedirectCreatreUrlTree(
redirectTo: string, urlTree: UrlTree, segments: UrlSegment[],
posParams: {[k: string]: UrlSegment}): UrlTree {
const newRoot = this.createSegmentGroup(redirectTo, urlTree.root, segments, posParams);
return new UrlTree(
newRoot, this.createQueryParams(urlTree.queryParams, this.urlTree.queryParams),
urlTree.fragment);
}
private createQueryParams(redirectToParams: Params, actualParams: Params): Params {
const res: Params = {};
forEach(redirectToParams, (v: any, k: string) => {
if (v.startsWith(':')) {
res[k] = actualParams[v.substring(1)];
} else {
res[k] = v;
}
});
return res;
}
private createSegmentGroup(
redirectTo: string, group: UrlSegmentGroup, segments: UrlSegment[],
posParams: {[k: string]: UrlSegment}): UrlSegmentGroup {
const updatedSegments = this.createSegments(redirectTo, group.segments, segments, posParams);
let children: {[n: string]: UrlSegmentGroup} = {};
forEach(group.children, (child: UrlSegmentGroup, name: string) => {
children[name] = this.createSegmentGroup(redirectTo, child, segments, posParams);
});
return new UrlSegmentGroup(updatedSegments, children);
}
private createSegments(
redirectTo: string, redirectToSegments: UrlSegment[], actualSegments: UrlSegment[],
posParams: {[k: string]: UrlSegment}): UrlSegment[] {
return redirectToSegments.map(
s => s.path.startsWith(':') ? this.findPosParam(redirectTo, s, posParams) :
this.findOrReturn(s, actualSegments));
}
private findPosParam(
redirectTo: string, redirectToUrlSegment: UrlSegment,
posParams: {[k: string]: UrlSegment}): UrlSegment {
const pos = posParams[redirectToUrlSegment.path.substring(1)];
if (!pos)
throw new Error(
`Cannot redirect to '${redirectTo}'. Cannot find '${redirectToUrlSegment.path}'.`);
return pos;
}
private findOrReturn(redirectToUrlSegment: UrlSegment, actualSegments: UrlSegment[]): UrlSegment {
let idx = 0;
for (const s of actualSegments) {
if (s.path === redirectToUrlSegment.path) {
actualSegments.splice(idx);
return s;
}
idx++;
}
return redirectToUrlSegment;
}
}
function runGuards(injector: Injector, route: Route): Observable<boolean> {
@ -328,46 +425,6 @@ function match(segmentGroup: UrlSegmentGroup, route: Route, segments: UrlSegment
};
}
function applyRedirectCommands(
segments: UrlSegment[], redirectTo: string,
posParams: {[k: string]: UrlSegment}): UrlSegment[] {
const r = redirectTo.startsWith('/') ? redirectTo.substring(1) : redirectTo;
if (r === '') {
return [];
} else {
return createSegments(redirectTo, r.split('/'), segments, posParams);
}
}
function createSegments(
redirectTo: string, parts: string[], segments: UrlSegment[],
posParams: {[k: string]: UrlSegment}): UrlSegment[] {
return parts.map(
p => p.startsWith(':') ? findPosParam(p, posParams, redirectTo) :
findOrCreateSegment(p, segments));
}
function findPosParam(
part: string, posParams: {[k: string]: UrlSegment}, redirectTo: string): UrlSegment {
const paramName = part.substring(1);
const pos = posParams[paramName];
if (!pos) throw new Error(`Cannot redirect to '${redirectTo}'. Cannot find '${part}'.`);
return pos;
}
function findOrCreateSegment(part: string, segments: UrlSegment[]): UrlSegment {
let idx = 0;
for (const s of segments) {
if (s.path === part) {
segments.splice(idx);
return s;
}
idx++;
}
return new UrlSegment(part, {});
}
function split(
segmentGroup: UrlSegmentGroup, consumedSegments: UrlSegment[], slicedSegments: UrlSegment[],
config: Route[]) {

View File

@ -154,6 +154,9 @@ import {UrlSegment, UrlSegmentGroup} from './url_tree';
* When navigating to `/team/11/user/jim`, the router will instantiate the wrapper component with
* the user component in it.
*
* An empty path route inherits its parent's params and data. This is because it cannot have its
* own params, and, as a result, it often uses its parent's params and data as its own.
*
* ### Matching Strategy
*
* By default the router will look at what is left in the url, and check if it starts with
@ -219,7 +222,8 @@ import {UrlSegment, UrlSegmentGroup} from './url_tree';
* has to have the primary and aux outlets defined.
*
* The router will also merge the `params`, `data`, and `resolve` of the componentless parent into
* the `params`, `data`, and `resolve` of the children.
* the `params`, `data`, and `resolve` of the children. This is done because there is no component
* that can inject the activated route of the componentless parent.
*
* This is especially useful when child components are defined as follows:
*

View File

@ -623,7 +623,8 @@ export class Router {
Promise.resolve()
.then(
(_) => this.runNavigate(
url, rawUrl, false, false, id, createEmptyState(url, this.rootComponentType)))
url, rawUrl, false, false, id,
createEmptyState(url, this.rootComponentType).snapshot))
.then(resolve, reject);
} else {
@ -634,7 +635,7 @@ export class Router {
private runNavigate(
url: UrlTree, rawUrl: UrlTree, shouldPreventPushState: boolean, shouldReplaceUrl: boolean,
id: number, precreatedState: RouterState): Promise<boolean> {
id: number, precreatedState: RouterStateSnapshot): Promise<boolean> {
if (id !== this.navigationId) {
this.location.go(this.urlSerializer.serialize(this.currentUrlTree));
this.routerEvents.next(new NavigationCancel(
@ -644,68 +645,80 @@ export class Router {
}
return new Promise((resolvePromise, rejectPromise) => {
let state: RouterState;
let navigationIsSuccessful: boolean;
let preActivation: PreActivation;
let appliedUrl: UrlTree;
const storedState = this.currentRouterState;
const storedUrl = this.currentUrlTree;
let routerState$: any;
// create an observable of the url and route state snapshot
// this operation do not result in any side effects
let urlAndSnapshot$: Observable<{appliedUrl: UrlTree, snapshot: RouterStateSnapshot}>;
if (!precreatedState) {
const redirectsApplied$ =
applyRedirects(this.injector, this.configLoader, url, this.config);
applyRedirects(this.injector, this.configLoader, this.urlSerializer, url, this.config);
const snapshot$ = mergeMap.call(redirectsApplied$, (u: UrlTree) => {
appliedUrl = u;
return recognize(
this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl));
});
urlAndSnapshot$ = mergeMap.call(redirectsApplied$, (appliedUrl: UrlTree) => {
return map.call(
recognize(
this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl)),
(snapshot: any) => {
const emitRecognzied$ =
map.call(snapshot$, (newRouterStateSnapshot: RouterStateSnapshot) => {
this.routerEvents.next(new RoutesRecognized(
id, this.serializeUrl(url), this.serializeUrl(appliedUrl),
newRouterStateSnapshot));
return newRouterStateSnapshot;
});
this.routerEvents.next(new RoutesRecognized(
id, this.serializeUrl(url), this.serializeUrl(appliedUrl), snapshot));
routerState$ = map.call(emitRecognzied$, (routerStateSnapshot: RouterStateSnapshot) => {
return createRouterState(routerStateSnapshot, this.currentRouterState);
return {appliedUrl, snapshot};
});
});
} else {
appliedUrl = url;
routerState$ = of (precreatedState);
urlAndSnapshot$ = of ({appliedUrl: url, snapshot: precreatedState});
}
const preactivation$ = map.call(routerState$, (newState: RouterState) => {
state = newState;
// run preactivation: guards and data resolvers
let preActivation: PreActivation;
const preactivationTraverse$ = map.call(urlAndSnapshot$, ({appliedUrl, snapshot}: any) => {
preActivation =
new PreActivation(state.snapshot, this.currentRouterState.snapshot, this.injector);
new PreActivation(snapshot, this.currentRouterState.snapshot, this.injector);
preActivation.traverse(this.outletMap);
return {appliedUrl, snapshot};
});
const preactivation2$ = mergeMap.call(preactivation$, () => {
const preactivationCheckGuards =
mergeMap.call(preactivationTraverse$, ({appliedUrl, snapshot}: any) => {
if (this.navigationId !== id) return of (false);
return map.call(preActivation.checkGuards(), (shouldActivate: boolean) => {
return {appliedUrl: appliedUrl, snapshot: snapshot, shouldActivate: shouldActivate};
});
});
const preactivationResolveData$ = mergeMap.call(preactivationCheckGuards, (p: any) => {
if (this.navigationId !== id) return of (false);
return preActivation.checkGuards();
});
const resolveData$ = mergeMap.call(preactivation2$, (shouldActivate: boolean) => {
if (this.navigationId !== id) return of (false);
if (shouldActivate) {
return map.call(preActivation.resolveData(), () => shouldActivate);
if (p.shouldActivate) {
return map.call(preActivation.resolveData(), () => p);
} else {
return of (shouldActivate);
return of (p);
}
});
resolveData$
.forEach((shouldActivate: boolean) => {
// create router state
// this operation has side effects => route state is being affected
const routerState$ =
map.call(preactivationResolveData$, ({appliedUrl, snapshot, shouldActivate}: any) => {
if (shouldActivate) {
const state = createRouterState(snapshot, this.currentRouterState);
return {appliedUrl, state, shouldActivate};
} else {
return {appliedUrl, state: null, shouldActivate};
}
});
// applied the new router state
// this operation has side effects
let navigationIsSuccessful: boolean;
const storedState = this.currentRouterState;
const storedUrl = this.currentUrlTree;
routerState$
.forEach(({appliedUrl, state, shouldActivate}: any) => {
if (!shouldActivate || id !== this.navigationId) {
navigationIsSuccessful = false;
return;
@ -733,8 +746,8 @@ export class Router {
() => {
this.navigated = true;
if (navigationIsSuccessful) {
this.routerEvents.next(
new NavigationEnd(id, this.serializeUrl(url), this.serializeUrl(appliedUrl)));
this.routerEvents.next(new NavigationEnd(
id, this.serializeUrl(url), this.serializeUrl(this.currentUrlTree)));
resolvePromise(true);
} else {
this.resetUrlToCurrentUrlTree();

View File

@ -15,6 +15,7 @@ import {LoadedRouterConfig} from '../src/router_config_loader';
import {DefaultUrlSerializer, UrlSegmentGroup, UrlTree, equalSegments} from '../src/url_tree';
describe('applyRedirects', () => {
const serializer = new DefaultUrlSerializer();
it('should return the same url tree when no redirects', () => {
checkRedirect(
@ -38,7 +39,7 @@ describe('applyRedirects', () => {
});
it('should throw when cannot handle a positional parameter', () => {
applyRedirects(null, null, tree('/a/1'), [
applyRedirects(null, null, serializer, tree('/a/1'), [
{path: 'a/:id', redirectTo: 'a/:other'}
]).subscribe(() => {}, (e) => {
expect(e.message).toEqual('Cannot redirect to \'a/:other\'. Cannot find \':other\'.');
@ -133,11 +134,11 @@ describe('applyRedirects', () => {
{
path: 'a',
component: ComponentA,
children: [{path: 'b/:id', redirectTo: '/absolute/:id'}]
children: [{path: 'b/:id', redirectTo: '/absolute/:id?a=1&b=:b#f1'}]
},
{path: '**', component: ComponentC}
],
'/a/b/1', (t: UrlTree) => { compareTrees(t, tree('/absolute/1')); });
'/a/b/1?b=2', (t: UrlTree) => { compareTrees(t, tree('/absolute/1?a=1&b=2#f1')); });
});
describe('lazy loading', () => {
@ -153,10 +154,11 @@ describe('applyRedirects', () => {
};
const config = [{path: 'a', component: ComponentA, loadChildren: 'children'}];
applyRedirects(<any>'providedInjector', <any>loader, tree('a/b'), config).forEach(r => {
compareTrees(r, tree('/a/b'));
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
});
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('a/b'), config)
.forEach(r => {
compareTrees(r, tree('/a/b'));
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
});
});
it('should handle the case when the loader errors', () => {
@ -165,9 +167,8 @@ describe('applyRedirects', () => {
};
const config = [{path: 'a', component: ComponentA, loadChildren: 'children'}];
applyRedirects(null, <any>loader, tree('a/b'), config).subscribe(() => {}, (e) => {
expect(e.message).toEqual('Loading Error');
});
applyRedirects(null, <any>loader, serializer, tree('a/b'), config)
.subscribe(() => {}, (e) => { expect(e.message).toEqual('Loading Error'); });
});
it('should load when all canLoad guards return true', () => {
@ -186,7 +187,7 @@ describe('applyRedirects', () => {
loadChildren: 'children'
}];
applyRedirects(<any>injector, <any>loader, tree('a/b'), config).forEach(r => {
applyRedirects(<any>injector, <any>loader, serializer, tree('a/b'), config).forEach(r => {
compareTrees(r, tree('/a/b'));
});
});
@ -208,7 +209,7 @@ describe('applyRedirects', () => {
loadChildren: 'children'
}];
applyRedirects(<any>injector, <any>loader, tree('a/b'), config)
applyRedirects(<any>injector, <any>loader, serializer, tree('a/b'), config)
.subscribe(
() => { throw 'Should not reach'; },
(e) => {
@ -234,7 +235,7 @@ describe('applyRedirects', () => {
loadChildren: 'children'
}];
applyRedirects(<any>injector, <any>loader, tree('a/b'), config)
applyRedirects(<any>injector, <any>loader, serializer, tree('a/b'), config)
.subscribe(
() => { throw 'Should not reach'; }, (e) => { expect(e).toEqual('someError'); });
});
@ -251,7 +252,7 @@ describe('applyRedirects', () => {
const config =
[{path: 'a', component: ComponentA, canLoad: ['guard'], loadChildren: 'children'}];
applyRedirects(<any>injector, <any>loader, tree('a/b'), config)
applyRedirects(<any>injector, <any>loader, serializer, tree('a/b'), config)
.subscribe(
(r) => { compareTrees(r, tree('/a/b')); }, (e) => { throw 'Should not reach'; });
@ -267,10 +268,11 @@ describe('applyRedirects', () => {
const config =
[{path: '', pathMatch: 'full', redirectTo: '/a'}, {path: 'a', loadChildren: 'children'}];
applyRedirects(<any>'providedInjector', <any>loader, tree(''), config).forEach(r => {
compareTrees(r, tree('a'));
expect((<any>config[1])._loadedConfig).toBe(loadedConfig);
});
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree(''), config)
.forEach(r => {
compareTrees(r, tree('a'));
expect((<any>config[1])._loadedConfig).toBe(loadedConfig);
});
});
it('should load the configuration only once', () => {
@ -289,12 +291,13 @@ describe('applyRedirects', () => {
const config = [{path: 'a', loadChildren: 'children'}];
applyRedirects(<any>'providedInjector', <any>loader, tree('a?k1'), config).subscribe(r => {});
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('a?k1'), config)
.subscribe(r => {});
applyRedirects(<any>'providedInjector', <any>loader, tree('a?k2'), config)
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('a?k2'), config)
.subscribe(
r => {
compareTrees(r, tree('a'));
compareTrees(r, tree('a?k2'));
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
},
(e) => { throw 'Should not reach'; });
@ -309,9 +312,8 @@ describe('applyRedirects', () => {
const config = [{path: '**', loadChildren: 'children'}];
applyRedirects(<any>'providedInjector', <any>loader, tree('xyz'), config).forEach(r => {
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
});
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('xyz'), config)
.forEach(r => { expect((<any>config[0])._loadedConfig).toBe(loadedConfig); });
});
it('should load the configuration after a local redirect from a wildcard route', () => {
@ -324,9 +326,8 @@ describe('applyRedirects', () => {
const config =
[{path: 'not-found', loadChildren: 'children'}, {path: '**', redirectTo: 'not-found'}];
applyRedirects(<any>'providedInjector', <any>loader, tree('xyz'), config).forEach(r => {
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
});
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('xyz'), config)
.forEach(r => { expect((<any>config[0])._loadedConfig).toBe(loadedConfig); });
});
it('should load the configuration after an absolute redirect from a wildcard route', () => {
@ -339,9 +340,8 @@ describe('applyRedirects', () => {
const config =
[{path: 'not-found', loadChildren: 'children'}, {path: '**', redirectTo: '/not-found'}];
applyRedirects(<any>'providedInjector', <any>loader, tree('xyz'), config).forEach(r => {
expect((<any>config[0])._loadedConfig).toBe(loadedConfig);
});
applyRedirects(<any>'providedInjector', <any>loader, serializer, tree('xyz'), config)
.forEach(r => { expect((<any>config[0])._loadedConfig).toBe(loadedConfig); });
});
});
@ -388,7 +388,7 @@ describe('applyRedirects', () => {
{path: '', redirectTo: 'a', pathMatch: 'full'}
];
applyRedirects(null, null, tree('b'), config)
applyRedirects(null, null, serializer, tree('b'), config)
.subscribe(
(_) => { throw 'Should not be reached'; },
e => { expect(e.message).toEqual('Cannot match any routes. URL Segment: \'b\''); });
@ -518,7 +518,7 @@ describe('applyRedirects', () => {
]
}];
applyRedirects(null, null, tree('a/(d//aux:e)'), config)
applyRedirects(null, null, serializer, tree('a/(d//aux:e)'), config)
.subscribe(
(_) => { throw 'Should not be reached'; },
e => { expect(e.message).toEqual('Cannot match any routes. URL Segment: \'a\''); });
@ -549,7 +549,7 @@ describe('applyRedirects', () => {
it('should error when no children matching and some url is left', () => {
applyRedirects(
null, null, tree('/a/c'),
null, null, serializer, tree('/a/c'),
[{path: 'a', component: ComponentA, children: [{path: 'b', component: ComponentB}]}])
.subscribe(
(_) => { throw 'Should not be reached'; },
@ -576,10 +576,46 @@ describe('applyRedirects', () => {
'/a/1/b', (t: UrlTree) => { compareTrees(t, tree('a/1/b')); });
});
});
describe('redirecting to named outlets', () => {
it('should work when using absolute redirects', () => {
checkRedirect(
[
{path: 'a/:id', redirectTo: '/b/:id(aux:c/:id)'},
{path: 'b/:id', component: ComponentB},
{path: 'c/:id', component: ComponentC, outlet: 'aux'}
],
'a/1;p=99', (t: UrlTree) => { compareTrees(t, tree('/b/1;p=99(aux:c/1;p=99)')); });
});
it('should work when using absolute redirects (wildcard)', () => {
checkRedirect(
[
{path: '**', redirectTo: '/b(aux:c)'}, {path: 'b', component: ComponentB},
{path: 'c', component: ComponentC, outlet: 'aux'}
],
'a/1', (t: UrlTree) => { compareTrees(t, tree('/b(aux:c)')); });
});
it('should throw when using non-absolute redirects', () => {
applyRedirects(
null, null, serializer, tree('a'),
[
{path: 'a', redirectTo: 'b(aux:c)'},
])
.subscribe(
() => { throw new Error('should not be reached'); },
(e) => {
expect(e.message).toEqual(
'Only absolute redirects can have named outlets. redirectTo: \'b(aux:c)\'');
});
});
});
});
function checkRedirect(config: Routes, url: string, callback: any): void {
applyRedirects(null, null, tree(url), config).subscribe(callback, e => { throw e; });
applyRedirects(null, null, new DefaultUrlSerializer(), tree(url), config)
.subscribe(callback, e => { throw e; });
}
function tree(url: string): UrlTree {
@ -591,6 +627,8 @@ function compareTrees(actual: UrlTree, expected: UrlTree): void {
const error =
`"${serializer.serialize(actual)}" is not equal to "${serializer.serialize(expected)}"`;
compareSegments(actual.root, expected.root, error);
expect(actual.queryParams).toEqual(expected.queryParams);
expect(actual.fragment).toEqual(expected.fragment);
}
function compareSegments(actual: UrlSegmentGroup, expected: UrlSegmentGroup, error: string): void {

View File

@ -1156,8 +1156,6 @@ describe('Integration', () => {
advance(fixture);
expect(location.path()).toEqual('/initial');
})));
// should not break the back button when trigger by initial navigation
});
describe('guards', () => {
@ -1380,6 +1378,11 @@ describe('Integration', () => {
return true;
}
},
{
provide: 'alwaysFalse',
useValue:
(c: any, a: ActivatedRouteSnapshot, b: RouterStateSnapshot) => { return false; }
},
]
});
});
@ -1504,6 +1507,31 @@ describe('Integration', () => {
advance(fixture);
expect(location.path()).toEqual('/team/33/user/fedor');
})));
it('should not create a route state if navigation is canceled',
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
const fixture = createRoot(router, RootCmp);
router.resetConfig([{
path: 'main',
component: TeamCmp,
children: [
{path: 'component1', component: SimpleCmp, canDeactivate: ['alwaysFalse']},
{path: 'component2', component: SimpleCmp}
]
}]);
router.navigateByUrl('/main/component1');
advance(fixture);
router.navigateByUrl('/main/component2');
advance(fixture);
const teamCmp = fixture.debugElement.children[1].componentInstance;
expect(teamCmp.route.firstChild.url.value[0].path).toEqual('component1');
expect(location.path()).toEqual('/main/component1');
})));
});
describe('should work when given a class', () => {

View File

@ -14,6 +14,12 @@ export interface ComponentInfo {
outputs?: string[];
}
/**
* A `PropertyBinding` represents a mapping between a property name
* and an attribute name. It is parsed from a string of the form
* `"prop: attr"`; or simply `"propAndAttr" where the property
* and attribute have the same identifier.
*/
export class PropertyBinding {
prop: string;
attr: string;

View File

@ -13,8 +13,11 @@ export const $INJECTOR = '$injector';
export const $PARSE = '$parse';
export const $ROOT_SCOPE = '$rootScope';
export const $SCOPE = '$scope';
export const $PROVIDE = '$provide';
export const $DELEGATE = '$delegate';
export const $$TESTABILITY = '$$testability';
export const $COMPILE = '$compile';
export const $TEMPLATE_CACHE = '$templateCache';
export const $HTTP_BACKEND = '$httpBackend';
export const $CONTROLLER = '$controller';
export const $CONTROLLER = '$controller';

View File

@ -6,20 +6,67 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ComponentFactory, ComponentFactoryResolver, Injector} from '@angular/core';
import {ComponentFactory, ComponentFactoryResolver, Injector, Type} from '@angular/core';
import * as angular from '../angular_js';
import {ComponentInfo} from './component_info';
import {$INJECTOR, $PARSE, INJECTOR_KEY} from './constants';
import {DowngradeComponentAdapter} from './downgrade_component_adapter';
let downgradeCount = 0;
/**
* @whatItDoes
*
* *Part of the [upgrade/static](/docs/ts/latest/api/#!?query=upgrade%2Fstatic)
* library for hybrid upgrade apps that support AoT compilation*
*
* Allows an Angular 2+ component to be used from Angular 1.
*
* @howToUse
*
* Let's assume that you have an Angular 2+ component called `ng2Heroes` that needs
* to be made available in Angular 1 templates.
*
* {@example upgrade/static/ts/module.ts region="ng2-heroes"}
*
* We must create an Angular 1 [directive](https://docs.angularjs.org/guide/directive)
* that will make this Angular 2+ component available inside Angular 1 templates.
* The `downgradeComponent()` function returns a factory function that we
* can use to define the Angular 1 directive that wraps the "downgraded" component.
*
* {@example upgrade/static/ts/module.ts region="ng2-heroes-wrapper"}
*
* In this example you can see that we must provide information about the component being
* "downgraded". This is because once the AoT compiler has run, all metadata about the
* component has been removed from the code, and so cannot be inferred.
*
* We must do the following:
* * specify the Angular 2+ component class that is to be downgraded
* * specify all inputs and outputs that the Angular 1 component expects
*
* @description
*
* A helper function that returns a factory function to be used for registering an
* Angular 1 wrapper directive for "downgrading" an Angular 2+ component.
*
* The parameter contains information about the Component that is being downgraded:
*
* * `component: Type<any>`: The type of the Component that will be downgraded
* * `inputs: string[]`: A collection of strings that specify what inputs the component accepts.
* * `outputs: string[]`: A collection of strings that specify what outputs the component emits.
*
* The `inputs` and `outputs` are strings that map the names of properties to camelCased
* attribute names. They are of the form `"prop: attr"`; or simply `"propAndAttr" where the
* property and attribute have the same identifier.
*
* @experimental
*/
export function downgradeComponent(info: ComponentInfo): angular.IInjectable {
export function downgradeComponent(info: /* ComponentInfo */ {
component: Type<any>;
inputs?: string[];
outputs?: string[];
}): any /* angular.IInjectable */ {
const idPrefix = `NG2_UPGRADE_${downgradeCount++}_`;
let idCount = 0;

View File

@ -10,14 +10,44 @@ import {Injector} from '@angular/core';
import {INJECTOR_KEY} from './constants';
/**
* Create an Angular 1 factory that will return an Angular 2 injectable thing
* (e.g. service, pipe, component, etc)
* @whatItDoes
*
* Usage:
* *Part of the [upgrade/static](/docs/ts/latest/api/#!?query=upgrade%2Fstatic)
* library for hybrid upgrade apps that support AoT compilation*
*
* ```
* angular1Module.factory('someService', downgradeInjectable(SomeService))
* ```
* Allow an Angular 2+ service to be accessible from Angular 1.
*
* @howToUse
*
* First ensure that the service to be downgraded is provided in an {@link NgModule}
* that will be part of the upgrade application. For example, let's assume we have
* defined `HeroesService`
*
* {@example upgrade/static/ts/module.ts region="ng2-heroes-service"}
*
* and that we have included this in our upgrade app {@link NgModule}
*
* {@example upgrade/static/ts/module.ts region="ng2-module"}
*
* Now we can register the `downgradeInjectable` factory function for the service
* on an Angular 1 module.
*
* {@example upgrade/static/ts/module.ts region="downgrade-ng2-heroes-service"}
*
* Inside an Angular 1 component's controller we can get hold of the
* downgraded service via the name we gave when downgrading.
*
* {@example upgrade/static/ts/module.ts region="example-app"}
*
* @description
*
* Takes a `token` that identifies a service provided from Angular 2+.
*
* Returns a [factory function](https://docs.angularjs.org/guide/di) that can be
* used to register the service on an Angular 1 module.
*
* The factory function provides access to the Angular 2+ service that
* is identified by the `token` parameter.
*
* @experimental
*/

View File

@ -43,6 +43,43 @@ interface IControllerInstance extends IBindingDestination {
type LifecycleHook = '$onChanges' | '$onDestroy' | '$onInit' | '$postLink';
/**
* @whatItDoes
*
* *Part of the [upgrade/static](/docs/ts/latest/api/#!?query=upgrade%2Fstatic)
* library for hybrid upgrade apps that support AoT compilation*
*
* Allows an Angular 1 component to be used from Angular 2+.
*
* @howToUse
*
* Let's assume that you have an Angular 1 component called `ng1Hero` that needs
* to be made available in Angular 2+ templates.
*
* {@example upgrade/static/ts/module.ts region="ng1-hero"}
*
* We must create a {@link Directive} that will make this Angular 1 component
* available inside Angular 2+ templates.
*
* {@example upgrade/static/ts/module.ts region="ng1-hero-wrapper"}
*
* In this example you can see that we must derive from the {@link UpgradeComponent}
* base class but also provide an {@link Directive `@Directive`} decorator. This is
* because the AoT compiler requires that this information is statically available at
* compile time.
*
* Note that we must do the following:
* * specify the directive's selector (`ng1-hero`)
* * specify all inputs and outputs that the Angular 1 component expects
* * derive from `UpgradeComponent`
* * call the base class from the constructor, passing
* * the Angular 1 name of the component (`ng1Hero`)
* * the {@link ElementRef} and {@link Injector} for the component wrapper
*
* @description
*
* A helper class that should be used as a base class for creating Angular directives
* that wrap Angular 1 components that need to be "upgraded".
*
* @experimental
*/
export class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
@ -63,6 +100,22 @@ export class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
private controllerInstance: IControllerInstance = null;
private bindingDestination: IBindingDestination = null;
/**
* Create a new `UpgradeComponent` instance. You should not normally need to do this.
* Instead you should derive a new class from this one and call the super constructor
* from the base class.
*
* {@example upgrade/static/ts/module.ts region="ng1-hero-wrapper" }
*
* * The `name` parameter should be the name of the Angular 1 directive.
* * The `elementRef` and `injector` parameters should be acquired from Angular by dependency
* injection into the base class constructor.
*
* Note that we must manually implement lifecycle hooks that call through to the super class.
* This is because, at the moment, the AoT compiler is not able to tell that the
* `UpgradeComponent`
* already implements them and so does not wire up calls to them at runtime.
*/
constructor(private name: string, private elementRef: ElementRef, private injector: Injector) {
this.$injector = injector.get($INJECTOR);
this.$compile = this.$injector.get($COMPILE);
@ -77,7 +130,7 @@ export class UpgradeComponent implements OnInit, OnChanges, DoCheck, OnDestroy {
this.bindings = this.initializeBindings(this.directive);
this.linkFn = this.compileTemplate(this.directive);
// We ask for the Angular 1 scope from the Angular 2 injector, since
// We ask for the Angular 1 scope from the Angular 2+ injector, since
// we will put the new component scope onto the new injector for each component
const $parentScope = injector.get($SCOPE);
// QUESTION 1: Should we create an isolated scope if the scope is only true?

View File

@ -6,25 +6,141 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Injector, NgModule, NgZone} from '@angular/core';
import {Injector, NgModule, NgZone, Testability} from '@angular/core';
import * as angular from '../angular_js';
import {controllerKey} from '../util';
import {angular1Providers, setTempInjectorRef} from './angular1_providers';
import {$INJECTOR, INJECTOR_KEY, UPGRADE_MODULE_NAME} from './constants';
import {$$TESTABILITY, $DELEGATE, $INJECTOR, $PROVIDE, $ROOT_SCOPE, INJECTOR_KEY, UPGRADE_MODULE_NAME} from './constants';
/**
* The Ng1Module contains providers for the Ng1Adapter and all the core Angular 1 services;
* and also holds the `bootstrapNg1()` method fo bootstrapping an upgraded Angular 1 app.
* @whatItDoes
*
* *Part of the [upgrade/static](/docs/ts/latest/api/#!?query=upgrade%2Fstatic)
* library for hybrid upgrade apps that support AoT compilation*
*
* Allows Angular 1 and Angular 2+ components to be used together inside a hybrid upgrade
* application, which supports AoT compilation.
*
* Specifically, the classes and functions in the `upgrade/static` module allow the following:
* 1. Creation of an Angular 2+ directive that wraps and exposes an Angular 1 component so
* that it can be used in an Angular 2 template. See {@link UpgradeComponent}.
* 2. Creation of an Angular 1 directive that wraps and exposes an Angular 2+ component so
* that it can be used in an Angular 1 template. See {@link downgradeComponent}.
* 3. Creation of an Angular 2+ root injector provider that wraps and exposes an Angular 1
* service so that it can be injected into an Angular 2+ context. See
* {@link UpgradeModule#upgrading-an-angular-1-service Upgrading an Angular 1 service} below.
* 4. Creation of an Angular 1 service that wraps and exposes an Angular 2+ injectable
* so that it can be injected into an Angular 1 context. See {@link downgradeInjectable}.
* 3. Bootstrapping of a hybrid Angular application which contains both of the frameworks
* coexisting in a single application. See the
* {@link UpgradeModule#example example} below.
*
* ## Mental Model
*
* When reasoning about how a hybrid application works it is useful to have a mental model which
* describes what is happening and explains what is happening at the lowest level.
*
* 1. There are two independent frameworks running in a single application, each framework treats
* the other as a black box.
* 2. Each DOM element on the page is owned exactly by one framework. Whichever framework
* instantiated the element is the owner. Each framework only updates/interacts with its own
* DOM elements and ignores others.
* 3. Angular 1 directives always execute inside the Angular 1 framework codebase regardless of
* where they are instantiated.
* 4. Angular 2+ components always execute inside the Angular 2+ framework codebase regardless of
* where they are instantiated.
* 5. An Angular 1 component can be "upgraded"" to an Angular 2+ component. This is achieved by
* defining an Angular 2+ directive, which bootstraps the Angular 1 component at its location
* in the DOM. See {@link UpgradeComponent}.
* 6. An Angular 2+ component can be "downgraded"" to an Angular 1 component. This is achieved by
* defining an Angular 1 directive, which bootstraps the Angular 2+ component at its location
* in the DOM. See {@link downgradeComponent}.
* 7. Whenever an "upgraded"/"downgraded" component is instantiated the host element is owned by
* the framework doing the instantiation. The other framework then instantiates and owns the
* view for that component.
* a. This implies that the component bindings will always follow the semantics of the
* instantiation framework.
* b. The DOM attributes are parsed by the framework that owns the current template. So
* attributes
* in Angular 1 templates must use kebab-case, while Angular 1 templates must use camelCase.
* c. However the template binding syntax will always use the Angular 2+ style, e.g. square
* brackets (`[...]`) for property binding.
* 8. Angular 1 is always bootstrapped first and owns the root component.
* 9. The new application is running in an Angular 2+ zone, and therefore it no longer needs calls
* to
* `$apply()`.
*
* @howToUse
*
* `import {UpgradeModule} from '@angular/upgrade/static';`
*
* ## Example
* Import the {@link UpgradeModule} into your top level {@link NgModule Angular 2+ `NgModule`}.
*
* {@example upgrade/static/ts/module.ts region='ng2-module'}
*
* Then bootstrap the hybrid upgrade app's module, get hold of the {@link UpgradeModule} instance
* and use it to bootstrap the top level [Angular 1
* module](https://docs.angularjs.org/api/ng/type/angular.Module).
*
* {@example upgrade/static/ts/module.ts region='bootstrap'}
*
*
* ## Upgrading an Angular 1 service
*
* There is no specific API for upgrading an Angular 1 service. Instead you should just follow the
* following recipe:
*
* Let's say you have an Angular 1 service:
*
* {@example upgrade/static/ts/module.ts region="ng1-title-case-service"}
*
* Then you should define an Angular 2+ provider to be included in your {@link NgModule} `providers`
* property.
*
* {@example upgrade/static/ts/module.ts region="upgrade-ng1-service"}
*
* Then you can use the "upgraded" Angular 1 service by injecting it into an Angular 2 component
* or service.
*
* {@example upgrade/static/ts/module.ts region="use-ng1-upgraded-service"}
*
* @description
*
* This class is an `NgModule`, which you import to provide Angular 1 core services,
* and has an instance method used to bootstrap the hybrid upgrade application.
*
* ## Core Angular 1 services
* Importing this {@link NgModule} will add providers for the core
* [Angular 1 services](https://docs.angularjs.org/api/ng/service) to the root injector.
*
* ## Bootstrap
* The runtime instance of this class contains a {@link UpgradeModule#bootstrap `bootstrap()`}
* method, which you use to bootstrap the top level Angular 1 module onto an element in the
* DOM for the hybrid upgrade app.
*
* It also contains properties to access the {@link UpgradeModule#injector root injector}, the
* bootstrap {@link NgZone} and the
* [Angular 1 $injector](https://docs.angularjs.org/api/auto/service/$injector).
*
* @experimental
*/
@NgModule({providers: angular1Providers})
export class UpgradeModule {
public $injector: angular.IInjectorService;
/**
* The Angular 1 `$injector` for the upgrade application.
*/
public $injector: any /*angular.IInjectorService*/;
constructor(public injector: Injector, public ngZone: NgZone) {}
constructor(
/** The root {@link Injector} for the upgrade application. */
public injector: Injector,
/** The bootstrap zone for the upgrade application */
public ngZone: NgZone) {}
/**
* Bootstrap an Angular 1 application from this NgModule
@ -32,7 +148,8 @@ export class UpgradeModule {
* @param [modules] the Angular 1 modules to bootstrap for this application
* @param [config] optional extra Angular 1 bootstrap configuration
*/
bootstrap(element: Element, modules: string[] = [], config?: angular.IAngularBootstrapConfig) {
bootstrap(
element: Element, modules: string[] = [], config?: any /*angular.IAngularBootstrapConfig*/) {
// Create an ng1 module to bootstrap
const upgradeModule =
angular
@ -40,6 +157,35 @@ export class UpgradeModule {
.value(INJECTOR_KEY, this.injector)
.config([
$PROVIDE, $INJECTOR,
($provide: angular.IProvideService, $injector: angular.IInjectorService) => {
if ($injector.has($$TESTABILITY)) {
$provide.decorator($$TESTABILITY, [
$DELEGATE,
(testabilityDelegate: angular.ITestabilityService) => {
const originalWhenStable: Function = testabilityDelegate.whenStable;
const injector = this.injector;
// Cannot use arrow function below because we need the context
const newWhenStable = function(callback: Function) {
originalWhenStable.call(this, function() {
const ng2Testability: Testability = injector.get(Testability);
if (ng2Testability.isStable()) {
callback.apply(this, arguments);
} else {
ng2Testability.whenStable(newWhenStable.bind(this, callback));
}
});
};
testabilityDelegate.whenStable = newWhenStable;
return testabilityDelegate;
}
]);
}
}
])
.run([
$INJECTOR,
($injector: angular.IInjectorService) => {
@ -59,7 +205,22 @@ export class UpgradeModule {
}
]);
// Make sure resumeBootstrap() only exists if the current bootstrap is deferred
const windowAngular = (window as any /** TODO #???? */)['angular'];
windowAngular.resumeBootstrap = undefined;
// Bootstrap the angular 1 application inside our zone
this.ngZone.run(() => { angular.bootstrap(element, [upgradeModule.name], config); });
// Patch resumeBootstrap() to run inside the ngZone
if (windowAngular.resumeBootstrap) {
const originalResumeBootstrap: () => void = windowAngular.resumeBootstrap;
const ngZone = this.ngZone;
windowAngular.resumeBootstrap = function() {
let args = arguments;
windowAngular.resumeBootstrap = originalResumeBootstrap;
ngZone.run(() => { windowAngular.resumeBootstrap.apply(this, args); });
};
}
}
}

View File

@ -355,14 +355,14 @@ export class UpgradeAdapter {
function(testabilityDelegate: angular.ITestabilityService) {
const originalWhenStable: Function = testabilityDelegate.whenStable;
const newWhenStable = (callback: Function): void => {
const whenStableContext: any = this;
// Cannot use arrow function below because we need the context
const newWhenStable = function(callback: Function) {
originalWhenStable.call(this, function() {
const ng2Testability: Testability = moduleRef.injector.get(Testability);
if (ng2Testability.isStable()) {
callback.apply(this, arguments);
} else {
ng2Testability.whenStable(newWhenStable.bind(whenStableContext, callback));
ng2Testability.whenStable(newWhenStable.bind(this, callback));
}
});
};
@ -433,8 +433,9 @@ export class UpgradeAdapter {
if (windowAngular.resumeBootstrap) {
const originalResumeBootstrap: () => void = windowAngular.resumeBootstrap;
windowAngular.resumeBootstrap = function() {
let args = arguments;
windowAngular.resumeBootstrap = originalResumeBootstrap;
windowAngular.resumeBootstrap.apply(this, arguments);
ngZone.run(() => { windowAngular.resumeBootstrap.apply(this, args); });
resolve();
};
} else {

View File

@ -49,7 +49,8 @@ export class UpgradeNg1ComponentAdapterBuilder {
],
ngOnInit: function() { /* needs to be here for ng2 to properly detect it */ },
ngOnChanges: function() { /* needs to be here for ng2 to properly detect it */ },
ngDoCheck: function() { /* needs to be here for ng2 to properly detect it */ }
ngDoCheck: function() { /* needs to be here for ng2 to properly detect it */ },
ngOnDestroy: function() { /* needs to be here for ng2 to properly detect it */ },
});
}
@ -262,16 +263,18 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck {
}
ngOnChanges(changes: SimpleChanges) {
for (const name in changes) {
if ((<Object>changes).hasOwnProperty(name)) {
const change: SimpleChange = changes[name];
this.setComponentProperty(name, change.currentValue);
}
const ng1Changes: any = {};
Object.keys(changes).forEach(name => {
const change: SimpleChange = changes[name];
this.setComponentProperty(name, change.currentValue);
ng1Changes[this.propertyMap[name]] = change;
});
if (this.destinationObj.$onChanges) {
this.destinationObj.$onChanges(ng1Changes);
}
}
ngDoCheck(): number {
const count = 0;
ngDoCheck() {
const destinationObj = this.destinationObj;
const lastValues = this.checkLastValues;
const checkProperties = this.checkProperties;
@ -287,7 +290,15 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck {
}
}
}
return count;
if (this.destinationObj.$doCheck && this.directive.controller) {
this.destinationObj.$doCheck();
}
}
ngOnDestroy() {
if (this.destinationObj.$onDestroy && this.directive.controller) {
this.destinationObj.$onDestroy();
}
}
setComponentProperty(name: string, value: any) {

View File

@ -15,7 +15,12 @@ export function stringify(obj: any): string {
export function onError(e: any) {
// TODO: (misko): We seem to not have a stack trace here!
console.log(e, e.stack);
if (console.error) {
console.error(e, e.stack);
} else {
// tslint:disable-next-line:no-console
console.log(e, e.stack);
}
throw e;
}

View File

@ -7,6 +7,7 @@
*/
import {NgModule, Testability, destroyPlatform} from '@angular/core';
import {NgZone} from '@angular/core/src/zone/ng_zone';
import {fakeAsync, tick} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
@ -28,7 +29,11 @@ export function main() {
it('should handle deferred bootstrap', fakeAsync(() => {
let applicationRunning = false;
const ng1Module = angular.module('ng1', []).run(() => { applicationRunning = true; });
let stayedInTheZone: boolean;
const ng1Module = angular.module('ng1', []).run(() => {
applicationRunning = true;
stayedInTheZone = NgZone.isInAngularZone();
});
const element = html('<div></div>');
window.name = 'NG_DEFER_BOOTSTRAP!' + window.name;
@ -40,6 +45,7 @@ export function main() {
expect(applicationRunning).toEqual(false);
tick(100);
expect(applicationRunning).toEqual(true);
expect(stayedInTheZone).toEqual(true);
}));
it('should wait for ng2 testability', fakeAsync(() => {

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