Compare commits
142 Commits
5.1.1
...
5.2.0-rc.0
Author | SHA1 | Date | |
---|---|---|---|
cdc66f6164 | |||
5bceb89a7f | |||
2582eca265 | |||
3e47ea27f5 | |||
d365077dfa | |||
bc7a6d7b00 | |||
9f538a6cac | |||
3750ea9dff | |||
afd89ed8d9 | |||
a62371c0eb | |||
c516bc3b35 | |||
8bf1305490 | |||
d8abf70f1f | |||
efd9c09456 | |||
a66cd526c3 | |||
a0ffdf1ef2 | |||
83d207d0a7 | |||
83c1383701 | |||
c66283ad66 | |||
ae97920fe2 | |||
533a010b28 | |||
6b81d1c9b9 | |||
fefc081e1b | |||
bdee1f4a25 | |||
40dfe39e64 | |||
3d50fd7cac | |||
cc1058f6e1 | |||
47e251a80a | |||
47bcb5bc35 | |||
f3fc74ab67 | |||
86a36eaadd | |||
5ba1cf1063 | |||
07b81ae741 | |||
5a7bf36723 | |||
66528a21f6 | |||
a77757277b | |||
4f05d022c1 | |||
5df343169e | |||
764fea1344 | |||
bbdea96a66 | |||
d1de587ce0 | |||
147aec43bd | |||
1f5049f30c | |||
09e3839994 | |||
19eeba2281 | |||
6cc8f2298e | |||
83b27bac17 | |||
b462f49ce7 | |||
1a9064ba2b | |||
5bc869cb24 | |||
8fdb1e09c1 | |||
f3d38ce053 | |||
05953b3b83 | |||
f75296e04e | |||
0867e85163 | |||
93b00cceb6 | |||
0fa818b318 | |||
bc66d27938 | |||
6a5818454f | |||
27fc458ef6 | |||
0487a9f140 | |||
871ece6123 | |||
abca7c0243 | |||
1f5256e745 | |||
6990354047 | |||
135ead6c97 | |||
33c0ee3441 | |||
5efea2f6a0 | |||
5f23a1223f | |||
30208759cd | |||
e4c53f8529 | |||
b61e3e9d20 | |||
f593552cfe | |||
8458647232 | |||
a693c5614c | |||
8ceffd8b48 | |||
2986e25abb | |||
f8fe53aeb0 | |||
74e3115686 | |||
3846f19f22 | |||
057513536b | |||
82bcd83566 | |||
e48f477477 | |||
20e1cc049f | |||
0b2d636b75 | |||
f5bb999319 | |||
a4742763b9 | |||
05ff6c09ca | |||
d91ff17adc | |||
d213a20dfc | |||
2e7e935b02 | |||
b89e7c2cb7 | |||
b4db2e25d6 | |||
a33eaf6e07 | |||
0d47c39609 | |||
cbe7e39bbe | |||
6d57cb04f6 | |||
6e2a8a2ba4 | |||
7874697b6c | |||
767141761a | |||
b3eb1db6dd | |||
ee0dab025b | |||
b7738e1fe5 | |||
634d33f5dd | |||
3401283399 | |||
981947d104 | |||
8c52088346 | |||
add3589451 | |||
81d497ce1f | |||
70cd124ede | |||
7363b3d4b5 | |||
f05937db4d | |||
d684f55423 | |||
db06cb170f | |||
77a1f9f2e8 | |||
13e663c232 | |||
d098cf5a8b | |||
3ce3b4d2af | |||
e7d9cb3e4c | |||
e544742156 | |||
c9ad529afc | |||
75e468494c | |||
ddada6e2be | |||
22ae17bb0b | |||
d546be48e1 | |||
753a130aaa | |||
94e2ea7361 | |||
1539cd8819 | |||
131c8ab6be | |||
7d81309e11 | |||
225baf4686 | |||
70b061be2e | |||
46aa0a1cf6 | |||
661fdcd3e2 | |||
590d93b30d | |||
c26e1bba1d | |||
10771d0bd8 | |||
d8cc09b76c | |||
d41d2c460a | |||
4efc32dabf | |||
ef534c0cc1 | |||
073f485c72 |
@ -47,15 +47,20 @@ jobs:
|
||||
|
||||
build:
|
||||
<<: *job_defaults
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- restore_cache:
|
||||
key: *cache_key
|
||||
|
||||
- run: bazel info release
|
||||
- run: bazel run @yarn//:yarn
|
||||
- run: bazel build --config=ci packages/...
|
||||
- run: bazel test --config=ci packages/... @angular//...
|
||||
# Use bazel query so that we explicitly ask for all buildable targets to be built as well
|
||||
# This avoids waiting for a build command to finish before running the first test
|
||||
# See https://github.com/bazelbuild/bazel/issues/4257
|
||||
- run: bazel query --output=label '//packages/... union @angular//...' | xargs bazel test --config=ci
|
||||
|
||||
- save_cache:
|
||||
key: *cache_key
|
||||
paths:
|
||||
|
@ -15,7 +15,6 @@
|
||||
# hansl - Hans Larsen
|
||||
# IgorMinar - Igor Minar
|
||||
# jasonaden - Jason Aden
|
||||
# juleskremer - Jules Kremer
|
||||
# kara - Kara Erickson
|
||||
# matsko - Matias Niemelä
|
||||
# mhevery - Misko Hevery
|
||||
@ -25,7 +24,6 @@
|
||||
# tinayuangao - Tina Gao
|
||||
# vicb - Victor Berchet
|
||||
# vikerman - Vikram Subramanian
|
||||
# wardbell - Ward Bell
|
||||
|
||||
|
||||
version: 2
|
||||
@ -36,14 +34,32 @@ group_defaults:
|
||||
enabled: true
|
||||
approve_by_comment:
|
||||
enabled: false
|
||||
# see http://docs.pullapprove.com/groups/author_approval/
|
||||
author_approval:
|
||||
# If the author is a reviewer on the PR, they will automatically have an "approved" status.
|
||||
auto: true
|
||||
|
||||
groups:
|
||||
# Require all PRs to have at least one approval from *someone*
|
||||
all:
|
||||
users: all
|
||||
required: 1
|
||||
# In this group, your self-approval does not count
|
||||
author_approval:
|
||||
auto: false
|
||||
ignored: true
|
||||
files:
|
||||
include:
|
||||
- "*"
|
||||
|
||||
root:
|
||||
conditions:
|
||||
files:
|
||||
include:
|
||||
- "*"
|
||||
exclude:
|
||||
- "WORKSPACE"
|
||||
- "BUILD.bazel"
|
||||
- ".circleci/*"
|
||||
- "aio/*"
|
||||
- "integration/*"
|
||||
@ -71,6 +87,7 @@ groups:
|
||||
- "*.bazel"
|
||||
- "*.bzl"
|
||||
- "packages/bazel/*"
|
||||
- "tools/bazel.rc"
|
||||
users:
|
||||
- alexeagle #primary
|
||||
- chuckjaz
|
||||
@ -86,6 +103,7 @@ groups:
|
||||
- "*.lock"
|
||||
- "tools/*"
|
||||
exclude:
|
||||
- "tools/bazel.rc"
|
||||
- "tools/public_api_guard/*"
|
||||
- "aio/*"
|
||||
users:
|
||||
@ -281,7 +299,7 @@ groups:
|
||||
files:
|
||||
- "packages/benchpress/*"
|
||||
users:
|
||||
# needs primary
|
||||
- alxhub # primary
|
||||
# needs secondary
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
@ -309,10 +327,8 @@ groups:
|
||||
- "aio/content/navigation.json"
|
||||
- "aio/content/license.md"
|
||||
users:
|
||||
- juleskremer #primary
|
||||
- Foxandxss
|
||||
- stephenfluin
|
||||
- wardbell
|
||||
- Foxandxss
|
||||
- petebacondarwin
|
||||
- gkalpak
|
||||
- IgorMinar #fallback
|
||||
@ -326,7 +342,6 @@ groups:
|
||||
- "aio/content/navigation.json"
|
||||
- "aio/content/license.md"
|
||||
users:
|
||||
- juleskremer #primary
|
||||
- stephenfluin
|
||||
- petebacondarwin
|
||||
- gkalpak
|
||||
|
@ -54,7 +54,6 @@ env:
|
||||
- CI_MODE=browserstack_optional
|
||||
- CI_MODE=aio_tools_test
|
||||
- CI_MODE=aio
|
||||
- CI_MODE=aio_optional
|
||||
- CI_MODE=aio_e2e AIO_SHARD=0
|
||||
- CI_MODE=aio_e2e AIO_SHARD=1
|
||||
- CI_MODE=bazel
|
||||
@ -64,7 +63,6 @@ matrix:
|
||||
allow_failures:
|
||||
- env: "CI_MODE=saucelabs_optional"
|
||||
- env: "CI_MODE=browserstack_optional"
|
||||
- env: "CI_MODE=aio_optional"
|
||||
|
||||
before_install:
|
||||
# source the env.sh script so that the exported variables are available to other scripts later on
|
||||
|
27
BUILD.bazel
@ -1,6 +1,8 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
exports_files(["tsconfig.json"])
|
||||
exports_files([
|
||||
"tsconfig.json",
|
||||
])
|
||||
|
||||
# This rule belongs in node_modules/BUILD
|
||||
# It's here as a workaround for
|
||||
@ -38,3 +40,26 @@ filegroup(
|
||||
"*.d.ts",
|
||||
]]),
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "web_test_bootstrap_scripts",
|
||||
# do not sort
|
||||
srcs = [
|
||||
"//:node_modules/reflect-metadata/Reflect.js",
|
||||
"//:node_modules/zone.js/dist/zone.js",
|
||||
"//:node_modules/zone.js/dist/async-test.js",
|
||||
"//:node_modules/zone.js/dist/sync-test.js",
|
||||
"//:node_modules/zone.js/dist/fake-async-test.js",
|
||||
"//:node_modules/zone.js/dist/proxy.js",
|
||||
"//:node_modules/zone.js/dist/jasmine-patch.js",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "angularjs",
|
||||
# do not sort
|
||||
srcs = [
|
||||
"//:node_modules/angular/angular.js",
|
||||
"//:node_modules/angular-mocks/angular-mocks.js",
|
||||
],
|
||||
)
|
||||
|
76
CHANGELOG.md
@ -1,3 +1,77 @@
|
||||
<a name="5.2.0-rc.0"></a>
|
||||
# [5.2.0-rc.0](https://github.com/angular/angular/compare/5.2.0-beta.1...5.2.0-rc.0) (2018-01-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** avoid infinite loop with multiple blocked sub triggers ([#21119](https://github.com/angular/angular/issues/21119)) ([86a36ea](https://github.com/angular/angular/commit/86a36ea))
|
||||
* **animations:** renaming issue with DOMAnimation. ([#21125](https://github.com/angular/angular/issues/21125)) ([871ece6](https://github.com/angular/angular/commit/871ece6))
|
||||
* **common:** handle JS floating point errors in percent pipe ([#20329](https://github.com/angular/angular/issues/20329)) ([07b81ae](https://github.com/angular/angular/commit/07b81ae)), closes [#20136](https://github.com/angular/angular/issues/20136)
|
||||
* **language-service:** ignore null metadatas ([#20557](https://github.com/angular/angular/issues/20557)) ([3e47ea2](https://github.com/angular/angular/commit/3e47ea2)), closes [#20260](https://github.com/angular/angular/issues/20260)
|
||||
* **router:** fix wildcard route with lazy loaded module (again) ([#18139](https://github.com/angular/angular/issues/18139)) ([5ba1cf1](https://github.com/angular/angular/commit/5ba1cf1)), closes [#13848](https://github.com/angular/angular/issues/13848)
|
||||
|
||||
|
||||
|
||||
<a name="5.1.3"></a>
|
||||
## [5.1.3](https://github.com/angular/angular/compare/5.1.2...5.1.3) (2018-01-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** avoid infinite loop with multiple blocked sub triggers ([#21119](https://github.com/angular/angular/issues/21119)) ([3e34fa8](https://github.com/angular/angular/commit/3e34fa8))
|
||||
* **animations:** renaming issue with DOMAnimation. ([#21125](https://github.com/angular/angular/issues/21125)) ([d1f4500](https://github.com/angular/angular/commit/d1f4500))
|
||||
* **common:** handle JS floating point errors in percent pipe ([#20329](https://github.com/angular/angular/issues/20329)) ([fa0e8ef](https://github.com/angular/angular/commit/fa0e8ef)), closes [#20136](https://github.com/angular/angular/issues/20136)
|
||||
* **language-service:** ignore null metadatas ([#20557](https://github.com/angular/angular/issues/20557)) ([48a1f32](https://github.com/angular/angular/commit/48a1f32)), closes [#20260](https://github.com/angular/angular/issues/20260)
|
||||
* **router:** fix wildcard route with lazy loaded module (again) ([#18139](https://github.com/angular/angular/issues/18139)) ([8c99175](https://github.com/angular/angular/commit/8c99175)), closes [#13848](https://github.com/angular/angular/issues/13848)
|
||||
|
||||
|
||||
<a name="5.2.0-beta.1"></a>
|
||||
# [5.2.0-beta.1](https://github.com/angular/angular/compare/5.2.0-beta.0...5.2.0-beta.1) (2017-12-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** generate the correct imports for summary type-check ([d91ff17](https://github.com/angular/angular/commit/d91ff17))
|
||||
* **forms:** avoid producing an error with hostBindingTypeCheck ([d213a20](https://github.com/angular/angular/commit/d213a20))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler:** allow ngIf to use the ngIf expression directly as a guard ([82bcd83](https://github.com/angular/angular/commit/82bcd83))
|
||||
* **router:** add "paramsInheritanceStrategy" router configuration option ([5efea2f](https://github.com/angular/angular/commit/5efea2f)), closes [#20572](https://github.com/angular/angular/issues/20572)
|
||||
|
||||
|
||||
|
||||
<a name="5.1.2"></a>
|
||||
## [5.1.2](https://github.com/angular/angular/compare/5.1.1...5.1.2) (2017-12-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **common:** fix a Closure compilation issue. ([267ebf3](https://github.com/angular/angular/commit/267ebf3))
|
||||
* **compiler:** make tsx file aot compatible ([756dd34](https://github.com/angular/angular/commit/756dd34)), closes [#20555](https://github.com/angular/angular/issues/20555)
|
||||
* **compiler:** report an error for recursive module references ([ced575f](https://github.com/angular/angular/commit/ced575f))
|
||||
* **compiler-cli:** do not emit invalid .metadata.json files ([a1d4c2d](https://github.com/angular/angular/commit/a1d4c2d))
|
||||
* **compiler-cli:** do not force type checking on .js files ([3b63e16](https://github.com/angular/angular/commit/3b63e16))
|
||||
* **service-worker:** check for updates on navigation ([a33182c](https://github.com/angular/angular/commit/a33182c)), closes [#20877](https://github.com/angular/angular/issues/20877)
|
||||
* **upgrade:** replaces get/setAngularLib with get/setAngularJSGlobal ([66cc2fa](https://github.com/angular/angular/commit/66cc2fa))
|
||||
|
||||
|
||||
|
||||
<a name="5.2.0-beta.0"></a>
|
||||
# [5.2.0-beta.0](https://github.com/angular/angular/compare/5.1.0...5.2.0-beta.0) (2017-12-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **animations:** re-introduce support for transition matching functions ([#20723](https://github.com/angular/angular/issues/20723)) ([590d93b](https://github.com/angular/angular/commit/590d93b)), closes [#18959](https://github.com/angular/angular/issues/18959)
|
||||
* **compiler:** add a pseudo $any() function to disable type checking ([#20876](https://github.com/angular/angular/issues/20876)) ([70cd124](https://github.com/angular/angular/commit/70cd124))
|
||||
* **compiler:** narrow types of expressions used in *ngIf ([#20702](https://github.com/angular/angular/issues/20702)) ([e7d9cb3](https://github.com/angular/angular/commit/e7d9cb3))
|
||||
* **core:** add source to `StaticInjectorError` message ([#20817](https://github.com/angular/angular/issues/20817)) ([b7738e1](https://github.com/angular/angular/commit/b7738e1)), closes [#19302](https://github.com/angular/angular/issues/19302)
|
||||
* **forms:** allow nulls on setAsyncValidators ([#20327](https://github.com/angular/angular/issues/20327)) ([d41d2c4](https://github.com/angular/angular/commit/d41d2c4)), closes [#20296](https://github.com/angular/angular/issues/20296)
|
||||
|
||||
|
||||
|
||||
<a name="5.1.1"></a>
|
||||
## [5.1.1](https://github.com/angular/angular/compare/5.1.0...5.1.1) (2017-12-13)
|
||||
|
||||
@ -13,7 +87,7 @@
|
||||
* **compiler-cli:** disable checkTypes in emit. ([#20828](https://github.com/angular/angular/issues/20828)) ([160a154](https://github.com/angular/angular/commit/160a154))
|
||||
* **compiler-cli:** fix swallowed Error messages ([#20846](https://github.com/angular/angular/issues/20846)) ([6727336](https://github.com/angular/angular/commit/6727336))
|
||||
* **compiler-cli:** merge [@fileoverview](https://github.com/fileoverview) comments. ([#20870](https://github.com/angular/angular/issues/20870)) ([be9a737](https://github.com/angular/angular/commit/be9a737))
|
||||
* **router:** NavigatonError and NavigationCancel should be emitted after resetting the URL ([#20803](https://github.com/angular/angular/issues/20803)) ([baeec4d](https://github.com/angular/angular/commit/baeec4d))
|
||||
* **router:** NavigationError and NavigationCancel should be emitted after resetting the URL ([#20803](https://github.com/angular/angular/issues/20803)) ([baeec4d](https://github.com/angular/angular/commit/baeec4d))
|
||||
|
||||
|
||||
|
||||
|
@ -16,12 +16,13 @@ node_repositories(package_json = ["//:package.json"])
|
||||
git_repository(
|
||||
name = "build_bazel_rules_typescript",
|
||||
remote = "https://github.com/bazelbuild/rules_typescript.git",
|
||||
tag = "0.6.0",
|
||||
# tag = "0.7.1+",
|
||||
commit = "89d2c75066bea3d9c942f29dd1d2ea543c58d6d5"
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", "ts_repositories")
|
||||
load("@build_bazel_rules_typescript//:setup.bzl", "ts_setup_workspace")
|
||||
|
||||
ts_repositories()
|
||||
ts_setup_workspace()
|
||||
|
||||
local_repository(
|
||||
name = "angular",
|
||||
|
@ -5,7 +5,7 @@
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[0,1,2].*",
|
||||
"**/dummy.module.ts"
|
||||
"!**/dummy.module.ts"
|
||||
],
|
||||
"tags": ["dependency", "di"]
|
||||
}
|
||||
|
@ -5,17 +5,6 @@
|
||||
<title>NgModule Minimal</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Polyfills -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.0.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -5,17 +5,6 @@
|
||||
<title>NgModule Minimal</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Polyfills -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.1.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -5,17 +5,6 @@
|
||||
<title>NgModule Minimal</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Polyfills -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.1b.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -5,17 +5,6 @@
|
||||
<title>NgModule Minimal</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Polyfills -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.2.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -5,17 +5,6 @@
|
||||
<title>NgModule Minimal</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<!-- Polyfills -->
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main.3.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -5,13 +5,17 @@
|
||||
"styles.css",
|
||||
|
||||
"app/app.component.ts",
|
||||
"app/app.component.html",
|
||||
"app/app.component.css",
|
||||
"app/app.module.ts",
|
||||
"app/data-model.ts",
|
||||
"app/hero.service.ts",
|
||||
"app/hero-detail.component.html",
|
||||
"app/hero-detail.component.ts",
|
||||
"app/hero-list.component.html",
|
||||
"app/hero-list.component.ts",
|
||||
"app/hero-detail/hero-detail.component.html",
|
||||
"app/hero-detail/hero-detail.component.ts",
|
||||
"app/hero-detail/hero-detail.component.css",
|
||||
"app/hero-list/hero-list.component.html",
|
||||
"app/hero-list/hero-list.component.ts",
|
||||
"app/hero-list/hero-list.component.css",
|
||||
|
||||
"main-final.ts",
|
||||
"index-final.html"
|
||||
|
@ -6,18 +6,6 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/reflect-metadata/Reflect.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('main-final.js').catch(function(err){ console.error(err); });
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -1,10 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'Service Workers';
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import { AppPage } from './app.po';
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
|
||||
describe('sw-example App', () => {
|
||||
let page: AppPage;
|
||||
let logo = element(by.css('img'));
|
||||
|
||||
beforeEach(() => {
|
||||
page = new AppPage();
|
||||
@ -15,17 +15,18 @@ describe('sw-example App', () => {
|
||||
});
|
||||
|
||||
it('should display the Angular logo', () => {
|
||||
let logo = element(by.css('img'));
|
||||
page.navigateTo();
|
||||
expect(logo.isPresent()).toBe(true);
|
||||
});
|
||||
|
||||
it('should show a header for the list of links', function () {
|
||||
it('should show a header for the list of links', () => {
|
||||
const listHeader = element(by.css('app-root > h2'));
|
||||
expect(listHeader.getText()).toEqual('Here are some links to help you start:');
|
||||
});
|
||||
|
||||
it('should show a list of links', function () {
|
||||
element.all(by.css('ul > li > h2 > a')).then(function(items) {
|
||||
element.all(by.css('ul > li > h2 > a')).then((items) => {
|
||||
expect(items.length).toBe(4);
|
||||
expect(items[0].getText()).toBe('Angular Service Worker Intro');
|
||||
expect(items[1].getText()).toBe('Tour of Heroes');
|
||||
@ -33,5 +34,11 @@ describe('sw-example App', () => {
|
||||
expect(items[3].getText()).toBe('Angular blog');
|
||||
});
|
||||
});
|
||||
|
||||
// Check for a rejected promise as the service worker is not enabled
|
||||
it('SwUpdate.checkForUpdate() should return a rejected promise', () => {
|
||||
const button = element(by.css('button'));
|
||||
const rejectMessage = element(by.css('p'));
|
||||
button.click();
|
||||
expect(rejectMessage.getText()).toContain('rejected: ');
|
||||
});
|
||||
});
|
@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "angular.io-example",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^5.0.0",
|
||||
"@angular/common": "^5.0.0",
|
||||
"@angular/compiler": "^5.0.0",
|
||||
"@angular/core": "^5.0.0",
|
||||
"@angular/forms": "^5.0.0",
|
||||
"@angular/http": "^5.0.0",
|
||||
"@angular/service-worker": "^5.0.0",
|
||||
"@angular/platform-browser": "^5.0.0",
|
||||
"@angular/platform-browser-dynamic": "^5.0.0",
|
||||
"@angular/router": "^5.0.0",
|
||||
"core-js": "^2.4.1",
|
||||
"rxjs": "^5.5.2",
|
||||
"zone.js": "^0.8.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/cli": "1.5.4",
|
||||
"@angular/compiler-cli": "^5.0.0",
|
||||
"@angular/language-service": "^5.0.0",
|
||||
"@types/jasmine": "~2.5.53",
|
||||
"@types/jasminewd2": "~2.0.2",
|
||||
"@types/node": "~6.0.60",
|
||||
"codelyzer": "^4.0.1",
|
||||
"jasmine-core": "~2.6.2",
|
||||
"jasmine-spec-reporter": "~4.1.0",
|
||||
"karma": "~1.7.0",
|
||||
"karma-chrome-launcher": "~2.1.1",
|
||||
"karma-cli": "~1.0.1",
|
||||
"karma-coverage-istanbul-reporter": "^1.2.1",
|
||||
"karma-jasmine": "~1.1.0",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"protractor": "~5.1.2",
|
||||
"ts-node": "~3.2.0",
|
||||
"tslint": "~5.7.0",
|
||||
"typescript": "~2.4.2"
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"description": "Service Worker",
|
||||
"basePath": "src/",
|
||||
"tags": ["service worker"]
|
||||
}
|
@ -5,6 +5,10 @@
|
||||
</h1>
|
||||
<img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
|
||||
</div>
|
||||
|
||||
<button id="check" (click)="updateCheck()">Check for Update</button>
|
||||
<p id="checkResult">{{updateCheckText}}</p>
|
||||
|
||||
<h2>Here are some links to help you start: </h2>
|
||||
<ul>
|
||||
<li>
|
@ -16,12 +16,12 @@ describe('AppComponent', () => {
|
||||
it(`should have as title 'app'`, async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app.title).toEqual('app');
|
||||
expect(app.title).toEqual('Service Workers');
|
||||
}));
|
||||
it('should render title in a h1 tag', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
|
||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to Service Workers!');
|
||||
}));
|
||||
});
|
20
aio/content/examples/service-worker-getting-started/src/app/app.component.ts
Executable file
@ -0,0 +1,20 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { SwUpdate } from '@angular/service-worker';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html'
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'Service Workers';
|
||||
updateCheckText = '';
|
||||
|
||||
constructor(private update: SwUpdate) {}
|
||||
|
||||
updateCheck(): void {
|
||||
this.update
|
||||
.checkForUpdate()
|
||||
.then(() => this.updateCheckText = 'resolved')
|
||||
.catch(err => this.updateCheckText = `rejected: ${err.message}`);
|
||||
}
|
||||
}
|
@ -1,19 +1,15 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { SwUpdate } from '@angular/service-worker';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/observable/interval';
|
||||
|
||||
function promptUser(event): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
// #docregion sw-check-update
|
||||
import { interval } from 'rxjs/observable/interval';
|
||||
|
||||
@Injectable()
|
||||
export class CheckForUpdateService {
|
||||
|
||||
constructor(updates: SwUpdate) {
|
||||
Observable.interval(6 * 60 * 60).subscribe(() => updates.checkForUpdate());
|
||||
interval(6 * 60 * 60).subscribe(() => updates.checkForUpdate());
|
||||
}
|
||||
}
|
||||
// #enddocregion sw-check-update
|
@ -5,8 +5,6 @@
|
||||
<title>SwExample</title>
|
||||
<base href="/">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
@ -547,12 +547,12 @@ In order to prevent collisions in environments where multiple Angular apps share
|
||||
|
||||
### Configuring custom cookie/header names
|
||||
|
||||
If your backend service uses different names for the XSRF token cookie or header, use `HttpClientXsrfModule.withConfig()` to override the defaults.
|
||||
If your backend service uses different names for the XSRF token cookie or header, use `HttpClientXsrfModule.withOptions()` to override the defaults.
|
||||
|
||||
```javascript
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
HttpClientXsrfModule.withConfig({
|
||||
HttpClientXsrfModule.withOptions({
|
||||
cookieName: 'My-Xsrf-Cookie',
|
||||
headerName: 'My-Xsrf-Header',
|
||||
}),
|
||||
|
@ -140,7 +140,7 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
</tr>
|
||||
<tr style='vertical-align:top'>
|
||||
<td>
|
||||
<code>ngOnDestroy</code>
|
||||
<code>ngOnDestroy()</code>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
|
@ -1,7 +1,15 @@
|
||||
# Communicating with service workers
|
||||
# Service Worker Communication
|
||||
|
||||
Importing `ServiceWorkerModule` into your `AppModule` doesn't just register the service worker, it also provides a few services you can use to interact with the service worker and control the caching of your app.
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
A basic understanding of the following:
|
||||
* [Getting Started with Service Workers](guide/service-worker-getting-started).
|
||||
|
||||
<hr />
|
||||
|
||||
|
||||
## `SwUpdate` service
|
||||
|
||||
The `SwUpdate` service gives you access to events that indicate when the service worker has discovered an available update for your app or when it has activated such an update—meaning it is now serving content from that update to your app.
|
||||
@ -16,7 +24,7 @@ The `SwUpdate` service supports four separate operations:
|
||||
|
||||
The two update events, `available` and `activated`, are `Observable` properties of `SwUpdate`:
|
||||
|
||||
<code-example path="service-worker-getstart/src/app/log-update.service.ts" linenums="false" title="log-update.service.ts" region="sw-update"> </code-example>
|
||||
<code-example path="service-worker-getting-started/src/app/log-update.service.ts" linenums="false" title="log-update.service.ts" region="sw-update"> </code-example>
|
||||
|
||||
|
||||
You can use these events to notify the user of a pending update or to refresh their pages when the code they are running is out of date.
|
||||
@ -27,7 +35,7 @@ It's possible to ask the service worker to check if any updates have been deploy
|
||||
|
||||
Do this with the `checkForUpdate()` method:
|
||||
|
||||
<code-example path="service-worker-getstart/src/app/check-for-update.service.ts" linenums="false" title="check-for-update.service.ts" region="sw-check-update"> </code-example>
|
||||
<code-example path="service-worker-getting-started/src/app/check-for-update.service.ts" linenums="false" title="check-for-update.service.ts" region="sw-check-update"> </code-example>
|
||||
|
||||
|
||||
This method returns a `Promise` which indicates that the update check has completed successfully, though it does not indicate whether an update was discovered as a result of the check. Even if one is found, the service worker must still successfully download the changed files, which can fail. If successful, the `available` event will indicate availability of a new version of the app.
|
||||
@ -36,6 +44,11 @@ This method returns a `Promise` which indicates that the update check has comple
|
||||
|
||||
If the current tab needs to be updated to the latest app version immediately, it can ask to do so with the `activateUpdate()` method:
|
||||
|
||||
<code-example path="service-worker-getstart/src/app/prompt-update.service.ts" linenums="false" title="prompt-update.service.ts" region="sw-activate"> </code-example>
|
||||
<code-example path="service-worker-getting-started/src/app/prompt-update.service.ts" linenums="false" title="prompt-update.service.ts" region="sw-activate"> </code-example>
|
||||
|
||||
Doing this could break lazy-loading into currently running apps, especially if the lazy-loaded chunks use filenames with hashes, which change every version.
|
||||
|
||||
## More on Angular service workers
|
||||
|
||||
You may also be interested in the following:
|
||||
* [Service Worker in Production](guide/service-worker-devops).
|
@ -1,6 +1,13 @@
|
||||
{@a glob}
|
||||
|
||||
# Reference: Configuration file
|
||||
# Service Worker Configuration
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
A basic understanding of the following:
|
||||
* [Service Worker in Production](guide/service-worker-devops).
|
||||
|
||||
<hr />
|
||||
|
||||
The `src/ngsw-config.json` configuration file specifies which files and data URLs the Angular
|
||||
service worker should cache and how it should update the cached files and data. The
|
||||
@ -8,7 +15,7 @@ CLI processes the configuration file during `ng build --prod`. Manually, you can
|
||||
it with the `ngsw-config` tool:
|
||||
|
||||
```sh
|
||||
ngsw-config dist src/ngswn-config.json /base/href
|
||||
ngsw-config dist src/ngsw-config.json /base/href
|
||||
```
|
||||
|
||||
The configuration file uses the JSON format. All file paths must begin with `/`, which is the deployment directory—usually `dist` in CLI projects.
|
||||
@ -159,3 +166,4 @@ The Angular service worker can use either of two caching strategies for data res
|
||||
* `performance`, the default, optimizes for responses that are as fast as possible. If a resource exists in the cache, the cached version is used. This allows for some staleness, depending on the `maxAge`, in exchange for better performance. This is suitable for resources that don't change often; for example, user avatar images.
|
||||
|
||||
* `freshness` optimizes for currency of data, preferentially fetching requested data from the network. Only if the network times out, according to `timeout`, does the request fall back to the cache. This is useful for resources that change frequently; for example, account balances.
|
||||
|
@ -1,7 +1,14 @@
|
||||
# DevOps: Angular service worker in production
|
||||
# Service Worker in Production
|
||||
|
||||
This page is a reference for deploying and supporting production apps that use the Angular service worker. It explains how the Angular service worker fits into the larger production environment, the service worker's behavior under various conditions, and available recourses and fail-safes.
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
A basic understanding of the following:
|
||||
* [Service Worker Communication](guide/service-worker-communications).
|
||||
|
||||
<hr />
|
||||
|
||||
## Service worker and caching of app resources
|
||||
|
||||
Conceptually, you can imagine the Angular service worker as a forward cache or a CDN edge that is installed in the end user's web browser. The service worker's job is to satisfy requests made by the Angular app for resources or data from a local cache, without needing to wait for the network. Like any cache, it has rules for how content is expired and updated.
|
||||
@ -32,12 +39,10 @@ server can ensure that the Angular app always has a consistent set of files.
|
||||
|
||||
#### Update checks
|
||||
|
||||
Every time the Angular service worker starts, it checks for updates to the
|
||||
app by looking for updates to the `ngsw.json` manifest.
|
||||
|
||||
Note that the service worker starts periodically throughout the usage of
|
||||
the app because the web browser terminates the service worker if the page
|
||||
is idle beyond a given timeout.
|
||||
Every time the user opens or refreshes the application, the Angular service worker
|
||||
checks for updates to the app by looking for updates to the `ngsw.json` manifest. If
|
||||
an update is found, it is downloaded and cached automatically, and will be served
|
||||
the next time the application is loaded.
|
||||
|
||||
### Resource integrity
|
||||
|
||||
@ -195,8 +200,8 @@ versions are safe to use, so existing tabs continue to run from
|
||||
cache, but new loads of the app will be served from the network.
|
||||
|
||||
* `SAFE_MODE`: the service worker cannot guarantee the safety of
|
||||
using cached data. Either an unexpected error occurred or all c
|
||||
ached versions are invalid. All traffic will be served from the
|
||||
using cached data. Either an unexpected error occurred or all
|
||||
cached versions are invalid. All traffic will be served from the
|
||||
network, running as little service worker code as possible.
|
||||
|
||||
In both cases, the parenthetical annotation provides the
|
||||
@ -276,8 +281,8 @@ with service workers. Such tools can be powerful when used properly,
|
||||
but there are a few things to keep in mind.
|
||||
|
||||
* When using developer tools, the service worker is kept running
|
||||
in the background and never restarts. For the Angular service
|
||||
worker, this means that update checks to the app will generally not happen.
|
||||
in the background and never restarts. This can cause behavior with Dev
|
||||
Tools open to differ from behavior a user might experience.
|
||||
|
||||
* If you look in the Cache Storage viewer, the cache is frequently
|
||||
out of date. Right click the Cache Storage title and refresh the caches.
|
||||
@ -299,4 +304,8 @@ for `ngsw.json` returns a `404`, then the service worker
|
||||
removes all of its caches and de-registers itself,
|
||||
essentially self-destructing.
|
||||
|
||||
## More on Angular service workers
|
||||
|
||||
You may also be interested in the following:
|
||||
* [Service Worker Configuration](guide/service-worker-config).
|
||||
|
||||
|
@ -1,10 +1,15 @@
|
||||
# Getting started
|
||||
# Getting Started with Service Workers
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
A basic understanding of the following:
|
||||
* [Introduction to Angular service workers](guide/service-worker-intro).
|
||||
|
||||
<hr />
|
||||
|
||||
|
||||
Beginning in Angular 5.0.0, you can easily enable Angular service worker support in any CLI project. This document explains how to enable Angular service worker support in new and existing projects. It then uses a simple example to show you a service worker in action, demonstrating loading and basic caching.
|
||||
|
||||
See the <live-example></live-example>.
|
||||
|
||||
|
||||
## Adding a service worker to a new application
|
||||
|
||||
If you're generating a new CLI project, you can use the CLI to set up the Angular service worker as part of creating the project. To do so, add the `--service-worker` flag to the `ng new` command:
|
||||
@ -54,12 +59,12 @@ To import and register the Angular service worker:
|
||||
|
||||
At the top of the root module, `src/app/app.module.ts`, import `ServiceWorkerModule` and `environment`.
|
||||
|
||||
<code-example path="service-worker-getstart/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts" region="sw-import"> </code-example>
|
||||
<code-example path="service-worker-getting-started/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts" region="sw-import"> </code-example>
|
||||
|
||||
|
||||
Add `ServiceWorkerModule` to the `@NgModule` `imports` array. Use the `register()` helper to take care of registering the service worker, taking care to disable the service worker when not running in production mode.
|
||||
|
||||
<code-example path="service-worker-getstart/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts" region="sw-module"> </code-example>
|
||||
<code-example path="service-worker-getting-started/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts" region="sw-module"> </code-example>
|
||||
|
||||
The file `ngsw-worker.js` is the name of the prebuilt service worker script, which the CLI copies into `dist/` to deploy along with your server.
|
||||
|
||||
@ -72,7 +77,7 @@ You can begin with the boilerplate version from the CLI, which configures sensib
|
||||
|
||||
Alternately, save the following as `src/ngsw-config.json`:
|
||||
|
||||
<code-example path="service-worker-getstart/src/ngsw-config.json" linenums="false" title="src/ngsw-config.json"> </code-example>
|
||||
<code-example path="service-worker-getting-started/src/ngsw-config.json" linenums="false" title="src/ngsw-config.json"> </code-example>
|
||||
|
||||
### Step 5: Build the project
|
||||
|
||||
@ -92,7 +97,9 @@ using an example application.
|
||||
|
||||
### Serving with `http-server`
|
||||
|
||||
As `ng serve` does not work with service workers, you must use a real HTTP server to test your project locally. It's a good idea to test on a dedicated port.
|
||||
Because `ng serve` does not work with service workers, you must use a seperate HTTP server to test your project locally. You can use any HTTP server. The example below uses the [http-server](https://www.npmjs.com/package/http-server) package from npm. To reduce the possibility of conflicts, test on a dedicated port.
|
||||
|
||||
To serve with `http-server`, change to the directory containing your web files and start the web server:
|
||||
|
||||
```sh
|
||||
cd dist
|
||||
@ -181,7 +188,6 @@ What went wrong? Nothing, actually. The Angular service worker is doing its job
|
||||
If you look at the `http-server` logs, you can see the service worker requesting `/ngsw.json`. This is how the service worker checks for updates.
|
||||
|
||||
2. Refresh the page.
|
||||

|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/service-worker/welcome-msg-fr.png" alt="The text has changed to say Bienvenue à app!">
|
||||
@ -189,3 +195,9 @@ If you look at the `http-server` logs, you can see the service worker requesting
|
||||
|
||||
The service worker installed the updated version of your app *in the background*, and the next time the page is loaded or reloaded, the service worker switches to the latest version.
|
||||
|
||||
<hr />
|
||||
|
||||
## More on Angular service workers
|
||||
|
||||
You may also be interested in the following:
|
||||
* [Communicating with service workers](guide/service-worker-communications).
|
@ -1,4 +1,4 @@
|
||||
# Introduction to Angular service workers
|
||||
# Angular Service Worker Introduction
|
||||
|
||||
Service workers augment the traditional web deployment model and empower applications to deliver a user experience with the reliability and performance on par with natively-installed code.
|
||||
|
||||
@ -46,3 +46,8 @@ For more information about browser support, see the [browser support](https://de
|
||||
[Can I Use](http://caniuse.com/#feat=serviceworkers).
|
||||
|
||||
The remainder of this Angular documentation specifically addresses the Angular implementation of service workers.
|
||||
|
||||
## More on Angular service workers
|
||||
|
||||
You may also be interested in the following:
|
||||
* [Getting Started with service workers](guide/service-worker-getting-started).
|
||||
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 28 KiB |
@ -276,38 +276,6 @@
|
||||
"title": "Routing & Navigation",
|
||||
"tooltip": "Discover the basics of screen navigation with the Angular Router."
|
||||
},
|
||||
{
|
||||
"title": "Service Workers",
|
||||
"tooltip": "Angular service workers: Controlling caching of application resources.",
|
||||
"children": [
|
||||
{
|
||||
"url": "guide/service-worker-intro",
|
||||
"title": "Introduction",
|
||||
"tooltip": "Angular's implementation of service workers improves user experience with slow or unreliable network connectivity."
|
||||
},
|
||||
{
|
||||
"url": "guide/service-worker-getstart",
|
||||
"title": "Getting Started",
|
||||
"tooltip": "Enabling the service worker in a CLI project and observing behavior in the browser."
|
||||
},
|
||||
{
|
||||
"url": "guide/service-worker-comm",
|
||||
"title": "Communication",
|
||||
"tooltip": "Services that enable you to interact with an Angular service worker."
|
||||
},
|
||||
{
|
||||
"url": "guide/service-worker-devops",
|
||||
"title": "Service Workers in Production",
|
||||
"tooltip": "Information about running applications with service workers, including application update management, debugging, and killing applications."
|
||||
},
|
||||
{
|
||||
"url": "guide/service-worker-configref",
|
||||
"title": "Reference: Configuration File",
|
||||
"tooltip": "The ngsw-config.json configuration file controls service worker caching behavior."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"url": "guide/testing",
|
||||
"title": "Testing",
|
||||
@ -382,6 +350,37 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Service Workers",
|
||||
"tooltip": "Angular service workers: Controlling caching of application resources.",
|
||||
"children": [
|
||||
{
|
||||
"url": "guide/service-worker-intro",
|
||||
"title": "Introduction",
|
||||
"tooltip": "Angular's implementation of service workers improves user experience with slow or unreliable network connectivity."
|
||||
},
|
||||
{
|
||||
"url": "guide/service-worker-getting-started",
|
||||
"title": "Getting Started",
|
||||
"tooltip": "Enabling the service worker in a CLI project and observing behavior in the browser."
|
||||
},
|
||||
{
|
||||
"url": "guide/service-worker-communications",
|
||||
"title": "Service Worker Communication",
|
||||
"tooltip": "Services that enable you to interact with an Angular service worker."
|
||||
},
|
||||
{
|
||||
"url": "guide/service-worker-devops",
|
||||
"title": "Service Worker in Production",
|
||||
"tooltip": "Running applications with service workers, managing application update, debugging, and killing applications."
|
||||
},
|
||||
{
|
||||
"url": "guide/service-worker-config",
|
||||
"title": "Service Worker Configuration",
|
||||
"tooltip": "Configuring service worker caching behavior."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"title": "Upgrading",
|
||||
|
@ -7,15 +7,16 @@ describe('site App', function() {
|
||||
beforeEach(() => {
|
||||
SitePage.setWindowWidth(1050); // Make the window wide enough to show the SideNav side-by-side.
|
||||
page = new SitePage();
|
||||
page.navigateTo();
|
||||
});
|
||||
|
||||
it('should show features text after clicking "Features"', () => {
|
||||
page.navigateTo('');
|
||||
page.getTopMenuLink('features').click();
|
||||
expect(page.getDocViewerText()).toMatch(/Progressive web apps/i);
|
||||
});
|
||||
|
||||
it('should set appropriate window titles', () => {
|
||||
page.navigateTo('');
|
||||
expect(browser.getTitle()).toBe('Angular');
|
||||
|
||||
page.getTopMenuLink('features').click();
|
||||
@ -25,9 +26,9 @@ describe('site App', function() {
|
||||
expect(browser.getTitle()).toBe('Angular');
|
||||
});
|
||||
|
||||
it('should show the tutorial index page at `/tutorial/` after jitterbugging through features', () => {
|
||||
it('should show the tutorial index page at `/tutorial` after jitterbugging through features', () => {
|
||||
// check that we can navigate directly to the tutorial page
|
||||
page.navigateTo('tutorial/');
|
||||
page.navigateTo('tutorial');
|
||||
expect(page.getDocViewerText()).toMatch(/Tutorial: Tour of Heroes/i);
|
||||
|
||||
// navigate to a different page
|
||||
@ -52,24 +53,24 @@ describe('site App', function() {
|
||||
describe('scrolling to the top', () => {
|
||||
it('should scroll to the top when navigating to another page', () => {
|
||||
page.navigateTo('guide/security');
|
||||
browser.sleep(1000); // Wait for initial async scroll-to-top after `onDocRendered`.
|
||||
|
||||
page.scrollToBottom();
|
||||
page.getScrollTop().then(scrollTop => expect(scrollTop).toBeGreaterThan(0));
|
||||
expect(page.getScrollTop()).toBeGreaterThan(0);
|
||||
|
||||
page.navigateTo('api');
|
||||
page.getScrollTop().then(scrollTop => expect(scrollTop).toBe(0));
|
||||
page.getNavItem(/api/i).click();
|
||||
expect(page.locationPath()).toBe('/api');
|
||||
expect(page.getScrollTop()).toBe(0);
|
||||
});
|
||||
|
||||
it('should scroll to the top when navigating to the same page', () => {
|
||||
page.navigateTo('guide/security');
|
||||
browser.sleep(1000); // Wait for initial async scroll-to-top after `onDocRendered`.
|
||||
|
||||
page.scrollToBottom();
|
||||
page.getScrollTop().then(scrollTop => expect(scrollTop).toBeGreaterThan(0));
|
||||
expect(page.getScrollTop()).toBeGreaterThan(0);
|
||||
|
||||
page.navigateTo('guide/security');
|
||||
page.getScrollTop().then(scrollTop => expect(scrollTop).toBe(0));
|
||||
page.getNavItem(/security/i).click();
|
||||
expect(page.locationPath()).toBe('/guide/security');
|
||||
expect(page.getScrollTop()).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@ -87,42 +88,50 @@ describe('site App', function() {
|
||||
page.navigateTo('api');
|
||||
page.locationPath()
|
||||
.then(p => path = p)
|
||||
.then(() => page.ga().then(calls => {
|
||||
.then(() => page.ga())
|
||||
.then(calls => {
|
||||
// The last call (length-1) will be the `send` command
|
||||
// The second to last call (length-2) will be the command to `set` the page url
|
||||
expect(calls[calls.length - 2]).toEqual(['set', 'page', path]);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it('should call ga with new URL on navigation', done => {
|
||||
let path: string;
|
||||
page.navigateTo('');
|
||||
page.getTopMenuLink('features').click();
|
||||
page.locationPath()
|
||||
.then(p => path = p)
|
||||
.then(() => page.ga().then(calls => {
|
||||
.then(() => page.ga())
|
||||
.then(calls => {
|
||||
// The last call (length-1) will be the `send` command
|
||||
// The second to last call (length-2) will be the command to `set` the page url
|
||||
expect(calls[calls.length - 2]).toEqual(['set', 'page', path]);
|
||||
done();
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('search', () => {
|
||||
it('should find pages when searching by a partial word in the title', () => {
|
||||
page.navigateTo('');
|
||||
|
||||
page.enterSearch('ngCont');
|
||||
expect(page.getSearchResults().map(link => link.getText())).toContain('NgControl');
|
||||
expect(page.getSearchResults()).toContain('NgControl');
|
||||
|
||||
page.enterSearch('accessor');
|
||||
expect(page.getSearchResults().map(link => link.getText())).toContain('ControlValueAccessor');
|
||||
expect(page.getSearchResults()).toContain('ControlValueAccessor');
|
||||
});
|
||||
});
|
||||
|
||||
describe('404 page', () => {
|
||||
it('should search the index for words found in the url', () => {
|
||||
page.navigateTo('http/router');
|
||||
expect(page.getSearchResults().map(link => link.getText())).toContain('Http');
|
||||
expect(page.getSearchResults().map(link => link.getText())).toContain('Router');
|
||||
const results = page.getSearchResults();
|
||||
|
||||
expect(results).toContain('Http');
|
||||
expect(results).toContain('Router');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -28,8 +28,11 @@ export class SitePage {
|
||||
ga() { return browser.executeScript('return window["ga"].q') as promise.Promise<any[][]>; }
|
||||
locationPath() { return browser.executeScript('return document.location.pathname') as promise.Promise<string>; }
|
||||
|
||||
navigateTo(pageUrl = '') {
|
||||
return browser.get('/' + pageUrl);
|
||||
navigateTo(pageUrl) {
|
||||
// Navigate to the page, disable animations, and wait for Angular.
|
||||
return browser.get('/' + pageUrl)
|
||||
.then(() => browser.executeScript('document.body.classList.add(\'no-animations\')'))
|
||||
.then(() => browser.waitForAngular());
|
||||
}
|
||||
|
||||
getDocViewerText() {
|
||||
@ -59,6 +62,6 @@ export class SitePage {
|
||||
getSearchResults() {
|
||||
const results = element.all(by.css('.search-results li'));
|
||||
browser.wait(ExpectedConditions.presenceOf(results.first()), 8000);
|
||||
return results;
|
||||
return results.map(link => link.getText());
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,12 @@
|
||||
{"type": 301, "source": "/docs/ts/latest/:any*", "destination": "/:any*"},
|
||||
|
||||
// aot-compiler.md and metadata.md combined into aot-compiler.md - issue #19510
|
||||
{"type": 301, "source": "/guide/metadata", "destination": "/guide/aot-compiler"}
|
||||
{"type": 301, "source": "/guide/metadata", "destination": "/guide/aot-compiler"},
|
||||
|
||||
// service-worker-getstart.md, service-worker-comm.md, service-worker-configref.md
|
||||
{"type": 301, "source": "/guide/service-worker-getstart", "destination": "/guide/service-worker-getting-started"},
|
||||
{"type": 301, "source": "/guide/service-worker-comm", "destination": "/guide/service-worker-communications"},
|
||||
{"type": 301, "source": "/guide/service-worker-configref", "destination": "/guide/service-worker-config"}
|
||||
],
|
||||
"rewrites": [
|
||||
{
|
||||
|
@ -4,12 +4,14 @@
|
||||
<mat-progress-bar mode="indeterminate" color="warn"></mat-progress-bar>
|
||||
</div>
|
||||
|
||||
<mat-toolbar color="primary" class="app-toolbar">
|
||||
<button class="hamburger" [class.starting]="isStarting" mat-button
|
||||
(click)="sidenav.toggle()" title="Docs menu">
|
||||
<mat-icon [ngClass]="{'sidenav-open': !isSideBySide }" svgIcon="menu"></mat-icon>
|
||||
<mat-toolbar color="primary" class="app-toolbar" [class.transitioning]="isTransitioning">
|
||||
<button mat-button class="hamburger" (click)="sidenav.toggle()" title="Docs menu">
|
||||
<mat-icon svgIcon="menu"></mat-icon>
|
||||
</button>
|
||||
<a class="nav-link home" href="/"><img src="{{ homeImageUrl }}" title="Home" alt="Home"></a>
|
||||
<a class="nav-link home" href="/" [ngSwitch]="isSideBySide">
|
||||
<img *ngSwitchCase="true" src="assets/images/logos/angular/logo-nav@2x.png" width="150" height="40" title="Home" alt="Home">
|
||||
<img *ngSwitchDefault src="assets/images/logos/angular/shield-large.svg" width="37" height="40" title="Home" alt="Home">
|
||||
</a>
|
||||
<aio-top-menu *ngIf="isSideBySide" [nodes]="topMenuNodes"></aio-top-menu>
|
||||
<aio-search-box class="search-container" #searchBox (onSearch)="doSearch($event)" (onFocus)="doSearch($event)"></aio-search-box>
|
||||
</mat-toolbar>
|
||||
@ -17,7 +19,7 @@
|
||||
|
||||
<mat-sidenav-container class="sidenav-container" [class.starting]="isStarting" [class.has-floating-toc]="hasFloatingToc" role="main">
|
||||
|
||||
<mat-sidenav [ngClass]="{'collapsed': !isSideBySide }" #sidenav class="sidenav" [opened]="isOpened" [mode]="mode" (open)="updateHostClasses()" (close)="updateHostClasses()">
|
||||
<mat-sidenav [ngClass]="{'collapsed': !isSideBySide}" #sidenav class="sidenav" [opened]="isOpened" [mode]="mode" (open)="updateHostClasses()" (close)="updateHostClasses()">
|
||||
<aio-nav-menu *ngIf="!isSideBySide" [nodes]="topMenuNarrowNodes" [currentNode]="currentNodes?.TopBarNarrow" [isWide]="false"></aio-nav-menu>
|
||||
<aio-nav-menu [nodes]="sideNavNodes" [currentNode]="currentNodes?.SideNav" [isWide]="isSideBySide"></aio-nav-menu>
|
||||
|
||||
@ -28,7 +30,13 @@
|
||||
|
||||
<section class="sidenav-content" [id]="pageId" role="content">
|
||||
<aio-mode-banner [mode]="deployment.mode" [version]="versionInfo"></aio-mode-banner>
|
||||
<aio-doc-viewer [doc]="currentDocument" (docReady)="onDocReady()" (docRemoved)="onDocRemoved()" (docInserted)="onDocInserted()"></aio-doc-viewer>
|
||||
<aio-doc-viewer [class.no-animations]="isStarting"
|
||||
[doc]="currentDocument"
|
||||
(docReady)="onDocReady()"
|
||||
(docRemoved)="onDocRemoved()"
|
||||
(docInserted)="onDocInserted()"
|
||||
(docRendered)="onDocRendered()">
|
||||
</aio-doc-viewer>
|
||||
<aio-dt [on]="dtOn" [(doc)]="currentDocument"></aio-dt>
|
||||
</section>
|
||||
|
||||
|
@ -154,34 +154,35 @@ describe('AppComponent', () => {
|
||||
});
|
||||
|
||||
describe('SideNav when side-by-side (wide)', () => {
|
||||
const navigateTo = (path: string) => {
|
||||
locationService.go(path);
|
||||
component.updateSideNav();
|
||||
fixture.detectChanges();
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
component.onResize(sideBySideBreakPoint + 1); // side-by-side
|
||||
});
|
||||
|
||||
it('should open when nav to a guide page (guide/pipes)', () => {
|
||||
locationService.go('guide/pipes');
|
||||
fixture.detectChanges();
|
||||
navigateTo('guide/pipes');
|
||||
expect(sidenav.opened).toBe(true);
|
||||
});
|
||||
|
||||
it('should open when nav to an api page', () => {
|
||||
locationService.go('api/a/b/c/d');
|
||||
fixture.detectChanges();
|
||||
navigateTo('api/a/b/c/d');
|
||||
expect(sidenav.opened).toBe(true);
|
||||
});
|
||||
|
||||
it('should be closed when nav to a marketing page (features)', () => {
|
||||
locationService.go('features');
|
||||
fixture.detectChanges();
|
||||
navigateTo('features');
|
||||
expect(sidenav.opened).toBe(false);
|
||||
});
|
||||
|
||||
describe('when manually closed', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
locationService.go('guide/pipes');
|
||||
fixture.detectChanges();
|
||||
navigateTo('guide/pipes');
|
||||
hamburger.click();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
@ -191,56 +192,53 @@ describe('AppComponent', () => {
|
||||
});
|
||||
|
||||
it('should stay closed when nav from one guide page to another', () => {
|
||||
locationService.go('guide/bags');
|
||||
fixture.detectChanges();
|
||||
navigateTo('guide/bags');
|
||||
expect(sidenav.opened).toBe(false);
|
||||
});
|
||||
|
||||
it('should stay closed when nav from a guide page to api page', () => {
|
||||
locationService.go('api');
|
||||
fixture.detectChanges();
|
||||
navigateTo('api');
|
||||
expect(sidenav.opened).toBe(false);
|
||||
});
|
||||
|
||||
it('should reopen when nav to market page and back to guide page', () => {
|
||||
locationService.go('features');
|
||||
fixture.detectChanges();
|
||||
locationService.go('guide/bags');
|
||||
fixture.detectChanges();
|
||||
navigateTo('features');
|
||||
navigateTo('guide/bags');
|
||||
expect(sidenav.opened).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('SideNav when NOT side-by-side (narrow)', () => {
|
||||
const navigateTo = (path: string) => {
|
||||
locationService.go(path);
|
||||
component.updateSideNav();
|
||||
fixture.detectChanges();
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
component.onResize(sideBySideBreakPoint - 1); // NOT side-by-side
|
||||
});
|
||||
|
||||
it('should be closed when nav to a guide page (guide/pipes)', () => {
|
||||
locationService.go('guide/pipes');
|
||||
fixture.detectChanges();
|
||||
navigateTo('guide/pipes');
|
||||
expect(sidenav.opened).toBe(false);
|
||||
});
|
||||
|
||||
it('should be closed when nav to an api page', () => {
|
||||
locationService.go('api/a/b/c/d');
|
||||
fixture.detectChanges();
|
||||
navigateTo('api/a/b/c/d');
|
||||
expect(sidenav.opened).toBe(false);
|
||||
});
|
||||
|
||||
it('should be closed when nav to a marketing page (features)', () => {
|
||||
locationService.go('features');
|
||||
fixture.detectChanges();
|
||||
navigateTo('features');
|
||||
expect(sidenav.opened).toBe(false);
|
||||
});
|
||||
|
||||
describe('when manually opened', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
locationService.go('guide/pipes');
|
||||
fixture.detectChanges();
|
||||
navigateTo('guide/pipes');
|
||||
hamburger.click();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
@ -257,20 +255,17 @@ describe('AppComponent', () => {
|
||||
});
|
||||
|
||||
it('should close when nav to another guide page', () => {
|
||||
locationService.go('guide/bags');
|
||||
fixture.detectChanges();
|
||||
navigateTo('guide/bags');
|
||||
expect(sidenav.opened).toBe(false);
|
||||
});
|
||||
|
||||
it('should close when nav to api page', () => {
|
||||
locationService.go('api');
|
||||
fixture.detectChanges();
|
||||
navigateTo('api');
|
||||
expect(sidenav.opened).toBe(false);
|
||||
});
|
||||
|
||||
it('should close again when nav to market page', () => {
|
||||
locationService.go('features');
|
||||
fixture.detectChanges();
|
||||
navigateTo('features');
|
||||
expect(sidenav.opened).toBe(false);
|
||||
});
|
||||
|
||||
@ -325,101 +320,6 @@ describe('AppComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('pageId', () => {
|
||||
|
||||
it('should set the id of the doc viewer container based on the current doc', () => {
|
||||
const container = fixture.debugElement.query(By.css('section.sidenav-content'));
|
||||
|
||||
locationService.go('guide/pipes');
|
||||
fixture.detectChanges();
|
||||
expect(component.pageId).toEqual('guide-pipes');
|
||||
expect(container.properties['id']).toEqual('guide-pipes');
|
||||
|
||||
locationService.go('news');
|
||||
fixture.detectChanges();
|
||||
expect(component.pageId).toEqual('news');
|
||||
expect(container.properties['id']).toEqual('news');
|
||||
|
||||
locationService.go('');
|
||||
fixture.detectChanges();
|
||||
expect(component.pageId).toEqual('home');
|
||||
expect(container.properties['id']).toEqual('home');
|
||||
});
|
||||
|
||||
it('should not be affected by changes to the query', () => {
|
||||
const container = fixture.debugElement.query(By.css('section.sidenav-content'));
|
||||
|
||||
locationService.go('guide/pipes');
|
||||
fixture.detectChanges();
|
||||
|
||||
locationService.go('guide/other?search=http');
|
||||
fixture.detectChanges();
|
||||
expect(component.pageId).toEqual('guide-other');
|
||||
expect(container.properties['id']).toEqual('guide-other');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hostClasses', () => {
|
||||
|
||||
it('should set the css classes of the host container based on the current doc and navigation view', () => {
|
||||
locationService.go('guide/pipes');
|
||||
fixture.detectChanges();
|
||||
|
||||
checkHostClass('page', 'guide-pipes');
|
||||
checkHostClass('folder', 'guide');
|
||||
checkHostClass('view', 'SideNav');
|
||||
|
||||
locationService.go('features');
|
||||
fixture.detectChanges();
|
||||
checkHostClass('page', 'features');
|
||||
checkHostClass('folder', 'features');
|
||||
checkHostClass('view', 'TopBar');
|
||||
|
||||
locationService.go('');
|
||||
fixture.detectChanges();
|
||||
checkHostClass('page', 'home');
|
||||
checkHostClass('folder', 'home');
|
||||
checkHostClass('view', '');
|
||||
});
|
||||
|
||||
it('should set the css class of the host container based on the open/closed state of the side nav', async () => {
|
||||
locationService.go('guide/pipes');
|
||||
fixture.detectChanges();
|
||||
checkHostClass('sidenav', 'open');
|
||||
|
||||
sidenav.close();
|
||||
await waitForEmit(sidenav.onClose);
|
||||
fixture.detectChanges();
|
||||
checkHostClass('sidenav', 'closed');
|
||||
|
||||
sidenav.open();
|
||||
await waitForEmit(sidenav.onOpen);
|
||||
fixture.detectChanges();
|
||||
checkHostClass('sidenav', 'open');
|
||||
|
||||
function waitForEmit(emitter: Observable<void>): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
emitter.subscribe(resolve);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should set the css class of the host container based on the initial deployment mode', () => {
|
||||
createTestingModule('a/b', 'archive');
|
||||
initializeTest();
|
||||
checkHostClass('mode', 'archive');
|
||||
});
|
||||
|
||||
function checkHostClass(type, value) {
|
||||
const host = fixture.debugElement;
|
||||
const classes = host.properties['className'];
|
||||
const classArray = classes.split(' ').filter(c => c.indexOf(`${type}-`) === 0);
|
||||
expect(classArray.length).toBeLessThanOrEqual(1, `"${classes}" should have only one class matching ${type}-*`);
|
||||
expect(classArray).toEqual([`${type}-${value}`], `"${classes}" should contain ${type}-${value}`);
|
||||
}
|
||||
});
|
||||
|
||||
describe('currentDocument', () => {
|
||||
it('should display a guide page (guide/pipes)', () => {
|
||||
locationService.go('guide/pipes');
|
||||
@ -895,7 +795,9 @@ describe('AppComponent', () => {
|
||||
|
||||
describe('with mocked DocViewer', () => {
|
||||
const getDocViewer = () => fixture.debugElement.query(By.css('aio-doc-viewer'));
|
||||
const triggerDocReady = () => getDocViewer().triggerEventHandler('docReady', undefined);
|
||||
const triggerDocViewerEvent =
|
||||
(evt: 'docReady' | 'docRemoved' | 'docInserted' | 'docRendered') =>
|
||||
getDocViewer().triggerEventHandler(evt, undefined);
|
||||
|
||||
beforeEach(() => {
|
||||
createTestingModule('a/b');
|
||||
@ -907,7 +809,7 @@ describe('AppComponent', () => {
|
||||
});
|
||||
|
||||
describe('initial rendering', () => {
|
||||
it('should initially add the starting class until the first document is ready', fakeAsync(() => {
|
||||
it('should initially add the starting class until a document is rendered', () => {
|
||||
const getSidenavContainer = () => fixture.debugElement.query(By.css('mat-sidenav-container'));
|
||||
|
||||
initializeTest();
|
||||
@ -915,21 +817,181 @@ describe('AppComponent', () => {
|
||||
expect(component.isStarting).toBe(true);
|
||||
expect(getSidenavContainer().classes['starting']).toBe(true);
|
||||
|
||||
triggerDocReady();
|
||||
fixture.detectChanges();
|
||||
expect(component.isStarting).toBe(true);
|
||||
expect(getSidenavContainer().classes['starting']).toBe(true);
|
||||
|
||||
tick(499);
|
||||
fixture.detectChanges();
|
||||
expect(component.isStarting).toBe(true);
|
||||
expect(getSidenavContainer().classes['starting']).toBe(true);
|
||||
|
||||
tick(2);
|
||||
triggerDocViewerEvent('docRendered');
|
||||
fixture.detectChanges();
|
||||
expect(component.isStarting).toBe(false);
|
||||
expect(getSidenavContainer().classes['starting']).toBe(false);
|
||||
}));
|
||||
});
|
||||
|
||||
it('should initially disable animations on the DocViewer for the first rendering', () => {
|
||||
initializeTest();
|
||||
|
||||
expect(component.isStarting).toBe(true);
|
||||
expect(docViewer.classList.contains('no-animations')).toBe(true);
|
||||
|
||||
triggerDocViewerEvent('docRendered');
|
||||
fixture.detectChanges();
|
||||
expect(component.isStarting).toBe(false);
|
||||
expect(docViewer.classList.contains('no-animations')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('subsequent rendering', () => {
|
||||
beforeEach(jasmine.clock().install);
|
||||
afterEach(jasmine.clock().uninstall);
|
||||
|
||||
it('should set the transitioning class on `.app-toolbar` while a document is being rendered', () => {
|
||||
const getToolbar = () => fixture.debugElement.query(By.css('.app-toolbar'));
|
||||
|
||||
initializeTest();
|
||||
|
||||
// Initially, `isTransitoning` is true.
|
||||
expect(component.isTransitioning).toBe(true);
|
||||
expect(getToolbar().classes['transitioning']).toBe(true);
|
||||
|
||||
triggerDocViewerEvent('docRendered');
|
||||
fixture.detectChanges();
|
||||
expect(component.isTransitioning).toBe(false);
|
||||
expect(getToolbar().classes['transitioning']).toBe(false);
|
||||
|
||||
// While a document is being rendered, `isTransitoning` is set to true.
|
||||
triggerDocViewerEvent('docReady');
|
||||
fixture.detectChanges();
|
||||
expect(component.isTransitioning).toBe(true);
|
||||
expect(getToolbar().classes['transitioning']).toBe(true);
|
||||
|
||||
triggerDocViewerEvent('docRendered');
|
||||
fixture.detectChanges();
|
||||
expect(component.isTransitioning).toBe(false);
|
||||
expect(getToolbar().classes['transitioning']).toBe(false);
|
||||
});
|
||||
|
||||
it('should update the sidenav state as soon as a new document is inserted', () => {
|
||||
initializeTest();
|
||||
const updateSideNavSpy = spyOn(component, 'updateSideNav');
|
||||
|
||||
triggerDocViewerEvent('docInserted');
|
||||
jasmine.clock().tick(0);
|
||||
expect(updateSideNavSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
triggerDocViewerEvent('docInserted');
|
||||
jasmine.clock().tick(0);
|
||||
expect(updateSideNavSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('pageId', () => {
|
||||
const navigateTo = (path: string) => {
|
||||
locationService.go(path);
|
||||
triggerDocViewerEvent('docInserted');
|
||||
jasmine.clock().tick(0);
|
||||
fixture.detectChanges();
|
||||
};
|
||||
|
||||
beforeEach(jasmine.clock().install);
|
||||
afterEach(jasmine.clock().uninstall);
|
||||
|
||||
it('should set the id of the doc viewer container based on the current doc', () => {
|
||||
initializeTest();
|
||||
const container = fixture.debugElement.query(By.css('section.sidenav-content'));
|
||||
|
||||
navigateTo('guide/pipes');
|
||||
expect(component.pageId).toEqual('guide-pipes');
|
||||
expect(container.properties['id']).toEqual('guide-pipes');
|
||||
|
||||
navigateTo('news');
|
||||
expect(component.pageId).toEqual('news');
|
||||
expect(container.properties['id']).toEqual('news');
|
||||
|
||||
navigateTo('');
|
||||
expect(component.pageId).toEqual('home');
|
||||
expect(container.properties['id']).toEqual('home');
|
||||
});
|
||||
|
||||
it('should not be affected by changes to the query', () => {
|
||||
initializeTest();
|
||||
const container = fixture.debugElement.query(By.css('section.sidenav-content'));
|
||||
|
||||
navigateTo('guide/pipes');
|
||||
navigateTo('guide/other?search=http');
|
||||
|
||||
expect(component.pageId).toEqual('guide-other');
|
||||
expect(container.properties['id']).toEqual('guide-other');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hostClasses', () => {
|
||||
const triggerUpdateHostClasses = () => {
|
||||
triggerDocViewerEvent('docInserted');
|
||||
jasmine.clock().tick(0);
|
||||
fixture.detectChanges();
|
||||
};
|
||||
const navigateTo = (path: string) => {
|
||||
locationService.go(path);
|
||||
triggerUpdateHostClasses();
|
||||
};
|
||||
|
||||
beforeEach(jasmine.clock().install);
|
||||
afterEach(jasmine.clock().uninstall);
|
||||
|
||||
it('should set the css classes of the host container based on the current doc and navigation view', () => {
|
||||
initializeTest();
|
||||
|
||||
navigateTo('guide/pipes');
|
||||
checkHostClass('page', 'guide-pipes');
|
||||
checkHostClass('folder', 'guide');
|
||||
checkHostClass('view', 'SideNav');
|
||||
|
||||
navigateTo('features');
|
||||
checkHostClass('page', 'features');
|
||||
checkHostClass('folder', 'features');
|
||||
checkHostClass('view', 'TopBar');
|
||||
|
||||
navigateTo('');
|
||||
checkHostClass('page', 'home');
|
||||
checkHostClass('folder', 'home');
|
||||
checkHostClass('view', '');
|
||||
});
|
||||
|
||||
it('should set the css class of the host container based on the open/closed state of the side nav', async () => {
|
||||
initializeTest();
|
||||
|
||||
navigateTo('guide/pipes');
|
||||
checkHostClass('sidenav', 'open');
|
||||
|
||||
sidenav.close();
|
||||
await waitForEmit(sidenav.onClose);
|
||||
fixture.detectChanges();
|
||||
checkHostClass('sidenav', 'closed');
|
||||
|
||||
sidenav.open();
|
||||
await waitForEmit(sidenav.onOpen);
|
||||
fixture.detectChanges();
|
||||
checkHostClass('sidenav', 'open');
|
||||
|
||||
function waitForEmit(emitter: Observable<void>): Promise<void> {
|
||||
return new Promise(resolve => {
|
||||
emitter.subscribe(resolve);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should set the css class of the host container based on the initial deployment mode', () => {
|
||||
createTestingModule('a/b', 'archive');
|
||||
initializeTest();
|
||||
|
||||
triggerUpdateHostClasses();
|
||||
checkHostClass('mode', 'archive');
|
||||
});
|
||||
|
||||
function checkHostClass(type, value) {
|
||||
const host = fixture.debugElement;
|
||||
const classes = host.properties['className'];
|
||||
const classArray = classes.split(' ').filter(c => c.indexOf(`${type}-`) === 0);
|
||||
expect(classArray.length).toBeLessThanOrEqual(1, `"${classes}" should have only one class matching ${type}-*`);
|
||||
expect(classArray).toEqual([`${type}-${value}`], `"${classes}" should contain ${type}-${value}`);
|
||||
}
|
||||
});
|
||||
|
||||
describe('progress bar', () => {
|
||||
@ -938,7 +1000,7 @@ describe('AppComponent', () => {
|
||||
const getProgressBar = () => fixture.debugElement.query(By.directive(MatProgressBar));
|
||||
const initializeAndCompleteNavigation = () => {
|
||||
initializeTest();
|
||||
triggerDocReady();
|
||||
triggerDocViewerEvent('docReady');
|
||||
tick(HIDE_DELAY);
|
||||
};
|
||||
|
||||
@ -975,7 +1037,7 @@ describe('AppComponent', () => {
|
||||
it('should not be shown when re-navigating to the empty path', fakeAsync(() => {
|
||||
initializeAndCompleteNavigation();
|
||||
locationService.urlSubject.next('');
|
||||
triggerDocReady();
|
||||
triggerDocViewerEvent('docReady');
|
||||
|
||||
locationService.urlSubject.next('');
|
||||
|
||||
@ -991,7 +1053,7 @@ describe('AppComponent', () => {
|
||||
locationService.urlSubject.next('c/d');
|
||||
|
||||
tick(SHOW_DELAY - 1);
|
||||
triggerDocReady();
|
||||
triggerDocViewerEvent('docReady');
|
||||
|
||||
tick(1);
|
||||
fixture.detectChanges();
|
||||
@ -1005,7 +1067,7 @@ describe('AppComponent', () => {
|
||||
locationService.urlSubject.next('c/d');
|
||||
|
||||
tick(SHOW_DELAY);
|
||||
triggerDocReady();
|
||||
triggerDocViewerEvent('docReady');
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(getProgressBar()).toBeTruthy();
|
||||
@ -1018,7 +1080,7 @@ describe('AppComponent', () => {
|
||||
locationService.urlSubject.next('c/d');
|
||||
|
||||
tick(SHOW_DELAY);
|
||||
triggerDocReady();
|
||||
triggerDocViewerEvent('docReady');
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(getProgressBar()).toBeTruthy();
|
||||
@ -1037,8 +1099,8 @@ describe('AppComponent', () => {
|
||||
locationService.urlSubject.next('c/d'); // The URL changes.
|
||||
locationService.urlSubject.next('e/f'); // The URL changes again before `onDocReady()`.
|
||||
|
||||
tick(SHOW_DELAY - 1); // `onDocReady()` is triggered (for the last doc),
|
||||
triggerDocReady(); // before the progress bar is shown.
|
||||
tick(SHOW_DELAY - 1); // `onDocReady()` is triggered (for the last doc),
|
||||
triggerDocViewerEvent('docReady'); // before the progress bar is shown.
|
||||
|
||||
tick(1);
|
||||
fixture.detectChanges();
|
||||
|
@ -4,7 +4,6 @@ import { MatSidenav } from '@angular/material/sidenav';
|
||||
|
||||
import { CurrentNodes, NavigationService, NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
|
||||
import { DocumentService, DocumentContents } from 'app/documents/document.service';
|
||||
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
|
||||
import { Deployment } from 'app/shared/deployment.service';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { ScrollService } from 'app/shared/scroll.service';
|
||||
@ -16,6 +15,7 @@ import { TocService } from 'app/shared/toc.service';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { combineLatest } from 'rxjs/observable/combineLatest';
|
||||
import 'rxjs/add/operator/first';
|
||||
|
||||
const sideNavView = 'SideNav';
|
||||
|
||||
@ -58,6 +58,7 @@ export class AppComponent implements OnInit {
|
||||
|
||||
isFetching = false;
|
||||
isStarting = true;
|
||||
isTransitioning = true;
|
||||
isSideBySide = false;
|
||||
private isFetchingTimeout: any;
|
||||
private isSideNavDoc = false;
|
||||
@ -75,18 +76,9 @@ export class AppComponent implements OnInit {
|
||||
|
||||
versionInfo: VersionInfo;
|
||||
|
||||
get homeImageUrl() {
|
||||
return this.isSideBySide ?
|
||||
'assets/images/logos/angular/logo-nav@2x.png' :
|
||||
'assets/images/logos/angular/shield-large.svg';
|
||||
}
|
||||
get isOpened() { return this.isSideBySide && this.isSideNavDoc; }
|
||||
get mode() { return this.isSideBySide ? 'side' : 'over'; }
|
||||
|
||||
// Need the doc-viewer element for scrolling the contents
|
||||
@ViewChild(DocViewerComponent, { read: ElementRef })
|
||||
docViewer: ElementRef;
|
||||
|
||||
// Search related properties
|
||||
showSearchResults = false;
|
||||
searchResults: Observable<SearchResults>;
|
||||
@ -120,12 +112,13 @@ export class AppComponent implements OnInit {
|
||||
|
||||
/* No need to unsubscribe because this root component never dies */
|
||||
|
||||
this.documentService.currentDocument.subscribe(doc => {
|
||||
this.currentDocument = doc;
|
||||
this.setPageId(doc.id);
|
||||
this.setFolderId(doc.id);
|
||||
this.updateHostClasses();
|
||||
});
|
||||
this.documentService.currentDocument.subscribe(doc => this.currentDocument = doc);
|
||||
// Generally, we want to delay updating the host classes for the new document, until after the
|
||||
// leaving document has been removed (to avoid having the styles for the new document applied
|
||||
// prematurely).
|
||||
// On the first document, though, (when we know there is no previous document), we want to
|
||||
// ensure the styles are applied as soon as possible to avoid flicker.
|
||||
this.documentService.currentDocument.first().subscribe(doc => this.updateHostClassesForDoc(doc));
|
||||
|
||||
this.locationService.currentPath.subscribe(path => {
|
||||
// Redirect to docs if we are in not in stable mode and are not hitting a docs page
|
||||
@ -146,21 +139,7 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
});
|
||||
|
||||
this.navigationService.currentNodes.subscribe(currentNodes => {
|
||||
this.currentNodes = currentNodes;
|
||||
|
||||
// Preserve current sidenav open state by default
|
||||
let openSideNav = this.sidenav.opened;
|
||||
const isSideNavDoc = !!currentNodes[sideNavView];
|
||||
|
||||
if (this.isSideNavDoc !== isSideNavDoc) {
|
||||
// View type changed. Is it now a sidenav view (e.g, guide or tutorial)?
|
||||
// Open if changed to a sidenav doc; close if changed to a marketing doc.
|
||||
openSideNav = this.isSideNavDoc = isSideNavDoc;
|
||||
}
|
||||
// May be open or closed when wide; always closed when narrow
|
||||
this.sideNavToggle(this.isSideBySide ? openSideNav : false);
|
||||
});
|
||||
this.navigationService.currentNodes.subscribe(currentNodes => this.currentNodes = currentNodes);
|
||||
|
||||
// Compute the version picker list from the current version and the versions in the navigation map
|
||||
combineLatest(
|
||||
@ -204,14 +183,14 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
onDocReady() {
|
||||
// About to transition to new view.
|
||||
this.isTransitioning = true;
|
||||
|
||||
// Stop fetching timeout (which, when render is fast, means progress bar never shown)
|
||||
clearTimeout(this.isFetchingTimeout);
|
||||
|
||||
// If progress bar has been shown, keep it for at least 500ms (to avoid flashing).
|
||||
setTimeout(() => {
|
||||
this.isStarting = false;
|
||||
this.isFetching = false;
|
||||
}, 500);
|
||||
setTimeout(() => this.isFetching = false, 500);
|
||||
}
|
||||
|
||||
onDocRemoved() {
|
||||
@ -221,11 +200,25 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
onDocInserted() {
|
||||
// TODO: Find a better way to avoid `ExpressionChangedAfterItHasBeenChecked` error.
|
||||
setTimeout(() => {
|
||||
// Update the SideNav state (if necessary).
|
||||
this.updateSideNav();
|
||||
|
||||
// Update the host classes to match the new document.
|
||||
this.updateHostClassesForDoc(this.currentDocument);
|
||||
});
|
||||
|
||||
// Scroll 500ms after the new document has been inserted into the doc-viewer.
|
||||
// The delay is to allow time for async layout to complete.
|
||||
setTimeout(() => this.autoScroll(), 500);
|
||||
}
|
||||
|
||||
onDocRendered() {
|
||||
this.isStarting = false;
|
||||
this.isTransitioning = false;
|
||||
}
|
||||
|
||||
onDocVersionChange(versionIndex: number) {
|
||||
const version = this.docVersions[versionIndex];
|
||||
if (version.url) {
|
||||
@ -290,6 +283,27 @@ export class AppComponent implements OnInit {
|
||||
this.hostClasses = `${mode} ${sideNavOpen} ${pageClass} ${folderClass} ${viewClasses}`;
|
||||
}
|
||||
|
||||
updateHostClassesForDoc(doc: DocumentContents) {
|
||||
this.setPageId(doc.id);
|
||||
this.setFolderId(doc.id);
|
||||
this.updateHostClasses();
|
||||
}
|
||||
|
||||
updateSideNav() {
|
||||
// Preserve current sidenav open state by default.
|
||||
let openSideNav = this.sidenav.opened;
|
||||
const isSideNavDoc = !!this.currentNodes[sideNavView];
|
||||
|
||||
if (this.isSideNavDoc !== isSideNavDoc) {
|
||||
// View type changed. Is it now a sidenav view (e.g, guide or tutorial)?
|
||||
// Open if changed to a sidenav doc; close if changed to a marketing doc.
|
||||
openSideNav = this.isSideNavDoc = isSideNavDoc;
|
||||
}
|
||||
|
||||
// May be open or closed when wide; always closed when narrow.
|
||||
this.sideNavToggle(this.isSideBySide && openSideNav);
|
||||
}
|
||||
|
||||
// Dynamically change height of table of contents container
|
||||
@HostListener('window:scroll')
|
||||
onScroll() {
|
||||
|
@ -43,15 +43,15 @@ export class ApiListComponent implements OnInit {
|
||||
// API types
|
||||
types: Option[] = [
|
||||
{ value: 'all', title: 'All' },
|
||||
{ value: 'directive', title: 'Directive' },
|
||||
{ value: 'pipe', title: 'Pipe'},
|
||||
{ value: 'decorator', title: 'Decorator' },
|
||||
{ value: 'class', title: 'Class' },
|
||||
{ value: 'interface', title: 'Interface' },
|
||||
{ value: 'function', title: 'Function' },
|
||||
{ value: 'const', title: 'Const'},
|
||||
{ value: 'decorator', title: 'Decorator' },
|
||||
{ value: 'directive', title: 'Directive' },
|
||||
{ value: 'enum', title: 'Enum' },
|
||||
{ value: 'type-alias', title: 'Type Alias' },
|
||||
{ value: 'const', title: 'Const'}
|
||||
{ value: 'function', title: 'Function' },
|
||||
{ value: 'interface', title: 'Interface' },
|
||||
{ value: 'pipe', title: 'Pipe'},
|
||||
{ value: 'type-alias', title: 'Type Alias' }
|
||||
];
|
||||
|
||||
statuses: Option[] = [
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
TestDocViewerComponent, TestModule, TestParentComponent
|
||||
} from 'testing/doc-viewer-utils';
|
||||
import { MockLogger } from 'testing/logger.service';
|
||||
import { DocViewerComponent } from './doc-viewer.component';
|
||||
import { DocViewerComponent, NO_ANIMATIONS } from './doc-viewer.component';
|
||||
|
||||
|
||||
describe('DocViewerComponent', () => {
|
||||
@ -368,7 +368,7 @@ describe('DocViewerComponent', () => {
|
||||
});
|
||||
|
||||
it('should display nothing if the document has no contents', async () => {
|
||||
docViewer.currViewContainer.innerHTML = 'Test';
|
||||
await doRender('Test');
|
||||
expect(docViewerEl.textContent).toBe('Test');
|
||||
|
||||
await doRender('');
|
||||
@ -647,6 +647,8 @@ describe('DocViewerComponent', () => {
|
||||
oldCurrViewContainer.innerHTML = 'Current view';
|
||||
oldNextViewContainer.innerHTML = 'Next view';
|
||||
|
||||
docViewerEl.appendChild(oldCurrViewContainer);
|
||||
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
});
|
||||
@ -656,122 +658,142 @@ describe('DocViewerComponent', () => {
|
||||
beforeEach(() => DocViewerComponent.animationsEnabled = animationsEnabled);
|
||||
afterEach(() => DocViewerComponent.animationsEnabled = true);
|
||||
|
||||
it('should return an observable', done => {
|
||||
docViewer.swapViews().subscribe(done, done.fail);
|
||||
});
|
||||
[true, false].forEach(noAnimations => {
|
||||
describe(`(.${NO_ANIMATIONS}: ${noAnimations})`, () => {
|
||||
beforeEach(() => docViewerEl.classList[noAnimations ? 'add' : 'remove'](NO_ANIMATIONS));
|
||||
|
||||
it('should swap the views', async () => {
|
||||
await doSwapViews();
|
||||
it('should return an observable', done => {
|
||||
docViewer.swapViews().subscribe(done, done.fail);
|
||||
});
|
||||
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
expect(docViewer.currViewContainer).toBe(oldNextViewContainer);
|
||||
expect(docViewer.nextViewContainer).toBe(oldCurrViewContainer);
|
||||
it('should swap the views', async () => {
|
||||
await doSwapViews();
|
||||
|
||||
await doSwapViews();
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
expect(docViewer.currViewContainer).toBe(oldNextViewContainer);
|
||||
expect(docViewer.nextViewContainer).toBe(oldCurrViewContainer);
|
||||
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
expect(docViewer.currViewContainer).toBe(oldCurrViewContainer);
|
||||
expect(docViewer.nextViewContainer).toBe(oldNextViewContainer);
|
||||
});
|
||||
await doSwapViews();
|
||||
|
||||
it('should emit `docRemoved` after removing the leaving view', async () => {
|
||||
const onDocRemovedSpy = jasmine.createSpy('onDocRemoved').and.callFake(() => {
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
expect(docViewer.currViewContainer).toBe(oldCurrViewContainer);
|
||||
expect(docViewer.nextViewContainer).toBe(oldNextViewContainer);
|
||||
});
|
||||
|
||||
it('should emit `docRemoved` after removing the leaving view', async () => {
|
||||
const onDocRemovedSpy = jasmine.createSpy('onDocRemoved').and.callFake(() => {
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
});
|
||||
|
||||
docViewer.docRemoved.subscribe(onDocRemovedSpy);
|
||||
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
|
||||
await doSwapViews();
|
||||
|
||||
expect(onDocRemovedSpy).toHaveBeenCalledTimes(1);
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
});
|
||||
|
||||
it('should not emit `docRemoved` if the leaving view is already removed', async () => {
|
||||
const onDocRemovedSpy = jasmine.createSpy('onDocRemoved');
|
||||
|
||||
docViewer.docRemoved.subscribe(onDocRemovedSpy);
|
||||
docViewerEl.removeChild(oldCurrViewContainer);
|
||||
|
||||
await doSwapViews();
|
||||
|
||||
expect(onDocRemovedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit `docInserted` after inserting the entering view', async () => {
|
||||
const onDocInsertedSpy = jasmine.createSpy('onDocInserted').and.callFake(() => {
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
});
|
||||
|
||||
docViewer.docInserted.subscribe(onDocInsertedSpy);
|
||||
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
|
||||
await doSwapViews();
|
||||
|
||||
expect(onDocInsertedSpy).toHaveBeenCalledTimes(1);
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
});
|
||||
|
||||
it('should call the callback after inserting the entering view', async () => {
|
||||
const onInsertedCb = jasmine.createSpy('onInsertedCb').and.callFake(() => {
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
});
|
||||
const onDocInsertedSpy = jasmine.createSpy('onDocInserted');
|
||||
|
||||
docViewer.docInserted.subscribe(onDocInsertedSpy);
|
||||
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
|
||||
await doSwapViews(onInsertedCb);
|
||||
|
||||
expect(onInsertedCb).toHaveBeenCalledTimes(1);
|
||||
expect(onInsertedCb).toHaveBeenCalledBefore(onDocInsertedSpy);
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
});
|
||||
|
||||
it('should empty the previous view', async () => {
|
||||
await doSwapViews();
|
||||
|
||||
expect(docViewer.currViewContainer.innerHTML).toBe('Next view');
|
||||
expect(docViewer.nextViewContainer.innerHTML).toBe('');
|
||||
|
||||
docViewer.nextViewContainer.innerHTML = 'Next view 2';
|
||||
await doSwapViews();
|
||||
|
||||
expect(docViewer.currViewContainer.innerHTML).toBe('Next view 2');
|
||||
expect(docViewer.nextViewContainer.innerHTML).toBe('');
|
||||
});
|
||||
|
||||
if (animationsEnabled && !noAnimations) {
|
||||
// Only test this when there are animations. Without animations, the views are swapped
|
||||
// synchronously, so there is no need (or way) to abort.
|
||||
it('should abort swapping if the returned observable is unsubscribed from', async () => {
|
||||
docViewer.swapViews().subscribe().unsubscribe();
|
||||
await doSwapViews();
|
||||
|
||||
// Since the first call was cancelled, only one swapping should have taken place.
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
expect(docViewer.currViewContainer).toBe(oldNextViewContainer);
|
||||
expect(docViewer.nextViewContainer).toBe(oldCurrViewContainer);
|
||||
expect(docViewer.currViewContainer.innerHTML).toBe('Next view');
|
||||
expect(docViewer.nextViewContainer.innerHTML).toBe('');
|
||||
});
|
||||
} else {
|
||||
it('should swap views synchronously when animations are disabled', () => {
|
||||
const cbSpy = jasmine.createSpy('cb');
|
||||
|
||||
docViewer.swapViews(cbSpy).subscribe();
|
||||
|
||||
expect(cbSpy).toHaveBeenCalledTimes(1);
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
expect(docViewer.currViewContainer).toBe(oldNextViewContainer);
|
||||
expect(docViewer.nextViewContainer).toBe(oldCurrViewContainer);
|
||||
expect(docViewer.currViewContainer.innerHTML).toBe('Next view');
|
||||
expect(docViewer.nextViewContainer.innerHTML).toBe('');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
docViewer.docRemoved.subscribe(onDocRemovedSpy);
|
||||
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
|
||||
await doSwapViews();
|
||||
|
||||
expect(onDocRemovedSpy).toHaveBeenCalledTimes(1);
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
});
|
||||
|
||||
it('should not emit `docRemoved` if the leaving view is already removed', async () => {
|
||||
const onDocRemovedSpy = jasmine.createSpy('onDocRemoved');
|
||||
|
||||
docViewer.docRemoved.subscribe(onDocRemovedSpy);
|
||||
docViewerEl.removeChild(oldCurrViewContainer);
|
||||
|
||||
await doSwapViews();
|
||||
|
||||
expect(onDocRemovedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit `docInserted` after inserting the entering view', async () => {
|
||||
const onDocInsertedSpy = jasmine.createSpy('onDocInserted').and.callFake(() => {
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
});
|
||||
|
||||
docViewer.docInserted.subscribe(onDocInsertedSpy);
|
||||
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
|
||||
await doSwapViews();
|
||||
|
||||
expect(onDocInsertedSpy).toHaveBeenCalledTimes(1);
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
});
|
||||
|
||||
it('should call the callback after inserting the entering view', async () => {
|
||||
const onInsertedCb = jasmine.createSpy('onInsertedCb').and.callFake(() => {
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
});
|
||||
const onDocInsertedSpy = jasmine.createSpy('onDocInserted');
|
||||
|
||||
docViewer.docInserted.subscribe(onDocInsertedSpy);
|
||||
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(true);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(false);
|
||||
|
||||
await doSwapViews(onInsertedCb);
|
||||
|
||||
expect(onInsertedCb).toHaveBeenCalledTimes(1);
|
||||
expect(onInsertedCb).toHaveBeenCalledBefore(onDocInsertedSpy);
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
});
|
||||
|
||||
it('should empty the previous view', async () => {
|
||||
await doSwapViews();
|
||||
|
||||
expect(docViewer.currViewContainer.innerHTML).toBe('Next view');
|
||||
expect(docViewer.nextViewContainer.innerHTML).toBe('');
|
||||
|
||||
docViewer.nextViewContainer.innerHTML = 'Next view 2';
|
||||
await doSwapViews();
|
||||
|
||||
expect(docViewer.currViewContainer.innerHTML).toBe('Next view 2');
|
||||
expect(docViewer.nextViewContainer.innerHTML).toBe('');
|
||||
});
|
||||
|
||||
if (animationsEnabled) {
|
||||
// Without animations, the views are swapped synchronously,
|
||||
// so there is no need (or way) to abort.
|
||||
it('should abort swapping if the returned observable is unsubscribed from', async () => {
|
||||
docViewer.swapViews().subscribe().unsubscribe();
|
||||
await doSwapViews();
|
||||
|
||||
// Since the first call was cancelled, only one swapping should have taken place.
|
||||
expect(docViewerEl.contains(oldCurrViewContainer)).toBe(false);
|
||||
expect(docViewerEl.contains(oldNextViewContainer)).toBe(true);
|
||||
expect(docViewer.currViewContainer).toBe(oldNextViewContainer);
|
||||
expect(docViewer.nextViewContainer).toBe(oldCurrViewContainer);
|
||||
expect(docViewer.currViewContainer.innerHTML).toBe('Next view');
|
||||
expect(docViewer.nextViewContainer.innerHTML).toBe('');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -15,6 +15,9 @@ import { Logger } from 'app/shared/logger.service';
|
||||
import { TocService } from 'app/shared/toc.service';
|
||||
|
||||
|
||||
// Constants
|
||||
export const NO_ANIMATIONS = 'no-animations';
|
||||
|
||||
// Initialization prevents flicker once pre-rendering is on
|
||||
const initialDocViewerElement = document.querySelector('aio-doc-viewer');
|
||||
const initialDocViewerContent = initialDocViewerElement ? initialDocViewerElement.innerHTML : '';
|
||||
@ -77,8 +80,6 @@ export class DocViewerComponent implements DoCheck, OnDestroy {
|
||||
|
||||
if (this.hostElement.firstElementChild) {
|
||||
this.currViewContainer = this.hostElement.firstElementChild as HTMLElement;
|
||||
} else {
|
||||
this.hostElement.appendChild(this.currViewContainer);
|
||||
}
|
||||
|
||||
this.onDestroy$.subscribe(() => this.destroyEmbeddedComponents());
|
||||
@ -176,10 +177,21 @@ export class DocViewerComponent implements DoCheck, OnDestroy {
|
||||
return () => cancelAnimationFrame(rafId);
|
||||
});
|
||||
|
||||
// Get the actual transition duration (taking global styles into account).
|
||||
// According to the [CSSOM spec](https://drafts.csswg.org/cssom/#serializing-css-values),
|
||||
// `time` values should be returned in seconds.
|
||||
const getActualDuration = (elem: HTMLElement) => {
|
||||
const cssValue = getComputedStyle(elem).transitionDuration;
|
||||
const seconds = Number(cssValue.replace(/s$/, ''));
|
||||
return 1000 * seconds;
|
||||
};
|
||||
const animateProp =
|
||||
(elem: HTMLElement, prop: string, from: string, to: string, duration = 333) => {
|
||||
(elem: HTMLElement, prop: string, from: string, to: string, duration = 200) => {
|
||||
const animationsDisabled = !DocViewerComponent.animationsEnabled
|
||||
|| this.hostElement.classList.contains(NO_ANIMATIONS);
|
||||
|
||||
elem.style.transition = '';
|
||||
return !DocViewerComponent.animationsEnabled
|
||||
return animationsDisabled
|
||||
? this.void$.do(() => elem.style[prop] = to)
|
||||
: this.void$
|
||||
// In order to ensure that the `from` value will be applied immediately (i.e.
|
||||
@ -189,11 +201,11 @@ export class DocViewerComponent implements DoCheck, OnDestroy {
|
||||
.switchMap(() => raf$).do(() => elem.style[prop] = from)
|
||||
.switchMap(() => raf$).do(() => elem.style.transition = `all ${duration}ms ease-in-out`)
|
||||
.switchMap(() => raf$).do(() => elem.style[prop] = to)
|
||||
.switchMap(() => timer(duration)).switchMap(() => this.void$);
|
||||
.switchMap(() => timer(getActualDuration(elem))).switchMap(() => this.void$);
|
||||
};
|
||||
|
||||
const animateLeave = (elem: HTMLElement) => animateProp(elem, 'opacity', '1', '0.25');
|
||||
const animateEnter = (elem: HTMLElement) => animateProp(elem, 'opacity', '0.25', '1');
|
||||
const animateLeave = (elem: HTMLElement) => animateProp(elem, 'opacity', '1', '0.1');
|
||||
const animateEnter = (elem: HTMLElement) => animateProp(elem, 'opacity', '0.1', '1');
|
||||
|
||||
let done$ = this.void$;
|
||||
|
||||
|
@ -36,7 +36,7 @@ describe('SearchBoxComponent', () => {
|
||||
|
||||
describe('initialisation', () => {
|
||||
it('should get the current search query from the location service',
|
||||
inject([LocationService], (location: MockLocationService) => fakeAsync(() => {
|
||||
fakeAsync(inject([LocationService], (location: MockLocationService) => {
|
||||
location.search.and.returnValue({ search: 'initial search' });
|
||||
component.ngOnInit();
|
||||
expect(location.search).toHaveBeenCalled();
|
||||
|
4
aio/src/styles/1-layouts/_doc-viewer.scss
Normal file
@ -0,0 +1,4 @@
|
||||
.no-animations aio-doc-viewer > * {
|
||||
// Disable view transition animations.
|
||||
transition: none !important;
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
|
||||
@import 'api-page';
|
||||
@import 'content-layout';
|
||||
@import 'doc-viewer';
|
||||
@import 'footer';
|
||||
@import 'layout-global';
|
||||
@import 'marketing-layout';
|
||||
|
@ -1,3 +1,92 @@
|
||||
// VARIABLES
|
||||
$hamburgerShownMargin: 0;
|
||||
$hamburgerHiddenMargin: 0 24px 0 -88px;
|
||||
|
||||
|
||||
// DOCS PAGE / STANDARD: TOPNAV TOOLBAR FIXED
|
||||
mat-toolbar.mat-toolbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
padding: 0 16px 0 0;
|
||||
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.30);
|
||||
|
||||
mat-icon {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// HOME PAGE OVERRIDE: TOPNAV TOOLBAR
|
||||
aio-shell.page-home mat-toolbar.mat-toolbar {
|
||||
background-color: $blue;
|
||||
|
||||
@media (min-width: 481px) {
|
||||
&:not(.transitioning) {
|
||||
background-color: transparent;
|
||||
transition: background-color .2s linear;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARKETING PAGES OVERRIDE: TOPNAV TOOLBAR AND HAMBURGER
|
||||
aio-shell.page-home mat-toolbar.mat-toolbar,
|
||||
aio-shell.page-features mat-toolbar.mat-toolbar,
|
||||
aio-shell.page-events mat-toolbar.mat-toolbar,
|
||||
aio-shell.page-resources mat-toolbar.mat-toolbar {
|
||||
box-shadow: none;
|
||||
|
||||
// FIXED TOPNAV TOOLBAR FOR SMALL MOBILE
|
||||
@media (min-width: 481px) {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// DOCS PAGES OVERRIDE: HAMBURGER
|
||||
aio-shell.folder-api mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-docs mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-guide mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
|
||||
@media (min-width: 992px) {
|
||||
.hamburger.mat-button {
|
||||
// Hamburger shown on non-marketing pages on large screens.
|
||||
margin: $hamburgerShownMargin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// HAMBURGER BUTTON
|
||||
.hamburger.mat-button {
|
||||
height: 100%;
|
||||
margin: $hamburgerShownMargin;
|
||||
padding: 0;
|
||||
transition-duration: .4s;
|
||||
transition-property: color, margin;
|
||||
transition-timing-function: cubic-bezier(.25, .8, .25, 1);
|
||||
|
||||
@media (min-width: 992px) {
|
||||
// Hamburger hidden by default on large screens.
|
||||
// (Will be shown per doc.)
|
||||
margin: $hamburgerHiddenMargin;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $offwhite;
|
||||
}
|
||||
|
||||
& .mat-icon {
|
||||
color: white;
|
||||
position: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// HOME NAV-LINK
|
||||
.nav-link.home img {
|
||||
position: relative;
|
||||
margin-top: -21px;
|
||||
@ -12,6 +101,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TOP MENU
|
||||
aio-top-menu {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@ -56,55 +147,6 @@ aio-top-menu {
|
||||
}
|
||||
}
|
||||
|
||||
// HOME PAGE OVERRIDE: TOPNAV TOOLBAR HAMBURGER MENU
|
||||
aio-shell.page-home mat-toolbar.app-toolbar.mat-toolbar {
|
||||
background-color: transparent;
|
||||
transition: background-color .2s linear .3s;
|
||||
|
||||
@media (max-width: 480px) {
|
||||
background-color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
// DOCS PAGE / STANDARD: TOPNAV TOOLBAR FIXED
|
||||
mat-toolbar.mat-toolbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
padding: 0 16px 0 0;
|
||||
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.30);
|
||||
|
||||
mat-icon {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
// MARKETING PAGES OVERRIDE: TOPNAV TOOLBAR AND HAMBURGER
|
||||
aio-shell.page-home mat-toolbar.mat-toolbar,
|
||||
aio-shell.page-features mat-toolbar.mat-toolbar,
|
||||
aio-shell.page-events mat-toolbar.mat-toolbar,
|
||||
aio-shell.page-resources mat-toolbar.mat-toolbar {
|
||||
// FIXED TOPNAV TOOLBAR FOR SMALL MOBILE
|
||||
@media (min-width: 481px) {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
button.hamburger {
|
||||
margin: 0 24px 0 -88px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// REMOVE BOX SHADOW ON CERTAIN MARKETING PAGES
|
||||
aio-shell.page-home mat-toolbar.mat-toolbar,
|
||||
aio-shell.page-events mat-toolbar.mat-toolbar,
|
||||
aio-shell.page-resources mat-toolbar.mat-toolbar {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
// SEARCH BOX
|
||||
aio-search-box.search-container {
|
||||
|
@ -1,29 +0,0 @@
|
||||
.hamburger {
|
||||
transition-duration: 150ms;
|
||||
transition-property: background-color, color;
|
||||
transition-timing-function: ease-in-out;
|
||||
&:hover {
|
||||
color: $lightgray;
|
||||
}
|
||||
}
|
||||
|
||||
.hamburger.mat-button {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
&:not(.starting) {
|
||||
transition-duration: .4s;
|
||||
transition-property: color, margin;
|
||||
transition-timing-function: cubic-bezier(.25, .8, .25, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.hamburger.mat-button:hover {
|
||||
color: $offwhite;
|
||||
}
|
||||
|
||||
.hamburger .mat-icon {
|
||||
position: inherit;
|
||||
color: white;
|
||||
}
|
@ -16,7 +16,6 @@
|
||||
@import 'edit-page-cta';
|
||||
@import 'features';
|
||||
@import 'filetree';
|
||||
@import 'hamburger';
|
||||
@import 'heading-anchors';
|
||||
@import 'hr';
|
||||
@import 'images';
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"scripts": [
|
||||
{ "name": "ng", "command": "ng" },
|
||||
{ "name": "build", "command": "ng build" },
|
||||
{ "name": "start", "command": "ng serve" },
|
||||
{ "name": "test", "command": "ng test" },
|
||||
{ "name": "lint", "command": "ng lint" },
|
||||
|
@ -54,6 +54,19 @@ class ExampleZipper {
|
||||
}
|
||||
}
|
||||
|
||||
// rename a custom main.ts or index.html file
|
||||
_renameFile(file) {
|
||||
if (/src\/main[-.]\w+\.ts$/.test(file)) {
|
||||
return 'src/main.ts';
|
||||
}
|
||||
|
||||
if (/src\/index[-.]\w+\.html$/.test(file)) {
|
||||
return 'src/index.html';
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
_zipExample(configFileName, sourceDirName, outputDirName) {
|
||||
let json = require(configFileName, 'utf-8');
|
||||
const basePath = json.basePath || '';
|
||||
@ -79,12 +92,16 @@ class ExampleZipper {
|
||||
'tslint.json',
|
||||
'karma-test-shim.js',
|
||||
'karma.conf.js',
|
||||
'tsconfig.json',
|
||||
'src/testing/**/*',
|
||||
'src/.babelrc',
|
||||
'src/favicon.ico',
|
||||
'src/typings.d.ts'
|
||||
'src/polyfills.ts',
|
||||
'src/typings.d.ts',
|
||||
'src/environments/**/*',
|
||||
'src/tsconfig.*'
|
||||
];
|
||||
var defaultExcludes = [
|
||||
var alwaysExcludes = [
|
||||
'!**/bs-config.e2e.json',
|
||||
'!**/*plnkr.*',
|
||||
'!**/*zipper.*',
|
||||
@ -132,13 +149,14 @@ class ExampleZipper {
|
||||
}
|
||||
});
|
||||
|
||||
Array.prototype.push.apply(gpaths, defaultExcludes);
|
||||
Array.prototype.push.apply(gpaths, alwaysExcludes);
|
||||
|
||||
let fileNames = globby.sync(gpaths, { ignore: ['**/node_modules/**']});
|
||||
|
||||
let zip = this._createZipArchive(outputFileName);
|
||||
fileNames.forEach((fileName) => {
|
||||
let relativePath = path.relative(exampleDirName, fileName);
|
||||
relativePath = this._renameFile(relativePath);
|
||||
let content = fs.readFileSync(fileName, 'utf8');
|
||||
let extn = path.extname(fileName).substr(1);
|
||||
// if we don't need to clean up the file then we can do the following.
|
||||
|
@ -38,6 +38,8 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage])
|
||||
readTypeScriptModules.basePath = API_SOURCE_PATH;
|
||||
readTypeScriptModules.ignoreExportsMatching = [/^[_ɵ]|^VERSION$/];
|
||||
readTypeScriptModules.hidePrivateMembers = true;
|
||||
|
||||
// NOTE: This list shold be in sync with tools/gulp-tasks/public-api.js
|
||||
readTypeScriptModules.sourceFiles = [
|
||||
'animations/index.ts',
|
||||
'animations/browser/index.ts',
|
||||
|
4
build.sh
@ -206,7 +206,7 @@ minify() {
|
||||
base_file=$( basename "${file}" )
|
||||
if [[ "${base_file}" =~ $regex && "${base_file##*.}" != "map" ]]; then
|
||||
local out_file=$(dirname "${file}")/${BASH_REMATCH[1]}.min.js
|
||||
$UGLIFYJS -c --screw-ie8 --comments -o ${out_file} --source-map ${out_file}.map --source-map-include-sources ${file}
|
||||
$UGLIFYJS -c --screw-ie8 --comments -o ${out_file} --source-map ${out_file}.map --prefix relative --source-map-include-sources ${file}
|
||||
fi
|
||||
done
|
||||
}
|
||||
@ -476,7 +476,7 @@ do
|
||||
|
||||
if [[ ${PACKAGE} == "common" ]]; then
|
||||
echo "====== Copy i18n locale data"
|
||||
rsync -a --exclude=*.d.ts --exclude=*.metadata.json ${OUT_DIR}/locales/ ${NPM_DIR}/locales
|
||||
rsync -a ${OUT_DIR}/locales/ ${NPM_DIR}/locales
|
||||
fi
|
||||
else
|
||||
echo "====== Copy ${PACKAGE} node tool"
|
||||
|
@ -50,3 +50,26 @@ keeps the outputs up-to-date as you save sources. Note this is
|
||||
new as of May 2017 and not very stable yet.
|
||||
|
||||
[ibazel]: https://github.com/bazelbuild/bazel-watcher
|
||||
|
||||
## Testing Angular
|
||||
|
||||
- Test package in node: `bazel test packages/core/test:test`
|
||||
- Test package in karma: `bazel test packages/core/test:test_web`
|
||||
- Test all packages: `bazel test packages/...`
|
||||
|
||||
You can use [ibazel] to get a "watch mode" that continuously
|
||||
keeps the outputs up-to-date as you save sources.
|
||||
|
||||
### Debugging a Node Test
|
||||
|
||||
- Open chrome at: [chrome://inspect](chrome://inspect)
|
||||
- Click on `Open dedicated DevTools for Node` to launch a debugger.
|
||||
- Run test: `bazel test packages/core/test:test --config=debug`
|
||||
|
||||
The process should automatically connect to the debugger.
|
||||
|
||||
### Debugging a Karma Test
|
||||
|
||||
- Run test: `bazel run packages/core/test:test_web`
|
||||
- Open chrome at: [http://localhost:9876/debug.html](http://localhost:9876/debug.html)
|
||||
- Open chrome inspector
|
||||
|
@ -7,30 +7,11 @@ Caretaker is responsible for merging PRs into the individual branches and intern
|
||||
- Draining the queue of PRs ready to be merged. (PRs with [`PR action: merge`](https://github.com/angular/angular/pulls?q=is%3Aopen+is%3Apr+label%3A%22PR+action%3A+merge%22) label)
|
||||
- Assigining [new issues](https://github.com/angular/angular/issues?q=is%3Aopen+is%3Aissue+no%3Alabel) to individual component authors.
|
||||
|
||||
## Setup
|
||||
|
||||
### Set `upstream` to fetch PRs into your local repo
|
||||
|
||||
Use this conmmands to configure your `git` to fetch PRs into your local repo.
|
||||
|
||||
```
|
||||
git remote add upstream git@github.com:angular/angular.git
|
||||
git config --add remote.upstream.fetch +refs/pull/*/head:refs/remotes/upstream/pr/*
|
||||
```
|
||||
|
||||
|
||||
## Merging the PR
|
||||
|
||||
A PR needs to have `PR action: merge` and `PR target: *` labels to be considered
|
||||
ready to merge. Merging is performed by running `merge-pr` with a PR number to merge.
|
||||
|
||||
NOTE: before running `merge-pr` ensure that you have synced all of the PRs
|
||||
locally by running:
|
||||
|
||||
```
|
||||
$ git fetch upstream
|
||||
```
|
||||
|
||||
To merge a PR run:
|
||||
|
||||
```
|
||||
@ -40,6 +21,7 @@ $ ./scripts/github/merge-pr 1234
|
||||
The `merge-pr` script will:
|
||||
- Ensure that all approriate labels are on the PR.
|
||||
- That the current branch (`master` or `?.?.x` patch) mathches the `PR target: *` label.
|
||||
- Fetches the latest PR code from the `angular/angular` repo.
|
||||
- It will `cherry-pick` all of the SHAs from the PR into the current branch.
|
||||
- It will rewrite commit history by automatically adding `Close #1234` and `(#1234)` into the commit message.
|
||||
|
||||
@ -53,8 +35,8 @@ $ ./scripts/github/merge-pr 1234
|
||||
======================
|
||||
GitHub Merge PR Steps
|
||||
======================
|
||||
git cherry-pick upstream/pr/1234~1..upstream/pr/1234
|
||||
git filter-branch -f --msg-filter "/usr/local/google/home/misko/angular-pr/scripts/github/utils/github.closes 1234" HEAD~1..HEAD
|
||||
git cherry-pick angular/pr/1234~1..angular/pr/1234
|
||||
git filter-branch -f --msg-filter "/home/misko/angular/scripts/github/utils/github.closes 1234" HEAD~1..HEAD
|
||||
```
|
||||
|
||||
If the `cherry-pick` command fails than resolve conflicts and use `git cherry-pick --continue` once ready. After the `cherry-pick` is done cut&paste and run the `filter-branch` command to properly rewrite the messages
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"cli-hello-world":{"master":{"gzip7":{"inline":847,"main":42533,"polyfills":20207},"gzip9":{"inline":847,"main":42483,"polyfills":20204},"uncompressed":{"inline":1447,"main":154295,"polyfills":61254}}},
|
||||
"cli-hello-world":{"master":{"gzip7":{"inline":847,"main":42533,"polyfills":20207},"gzip9":{"inline":847,"main":42483,"polyfills":20204},"uncompressed":{"inline":1447,"main":151954,"polyfills":61254}}},
|
||||
"hello_world__closure":{"master":{"gzip7":{"bundle":32793},"gzip9":{"bundle":32758},"uncompressed":{"bundle":100661}}}
|
||||
}
|
||||
|
@ -14,12 +14,13 @@ node_repositories(package_json = ["//:package.json"])
|
||||
git_repository(
|
||||
name = "build_bazel_rules_typescript",
|
||||
remote = "https://github.com/bazelbuild/rules_typescript.git",
|
||||
tag = "0.6.0",
|
||||
# tag = "0.6.0",
|
||||
commit = "89d2c75066bea3d9c942f29dd1d2ea543c58d6d5"
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", "ts_repositories")
|
||||
load("@build_bazel_rules_typescript//:setup.bzl", "ts_setup_workspace")
|
||||
|
||||
ts_repositories()
|
||||
ts_setup_workspace()
|
||||
|
||||
local_repository(
|
||||
name = "angular",
|
||||
|
@ -5,7 +5,7 @@
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"postinstall": "webdriver-manager update --gecko false --standalone false $CHROMEDRIVER_VERSION_ARG",
|
||||
"test": "concurrently \"npm run serve\" \"npm run protractor\" --kill-others --success first",
|
||||
"test": "concurrently \"yarn serve\" \"yarn protractor\" --kill-others --success first",
|
||||
"serve": "lite-server -c bs-config.e2e.json",
|
||||
"preprotractor": "tsc -p e2e",
|
||||
"protractor": "protractor protractor.config.js"
|
||||
@ -17,7 +17,7 @@
|
||||
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
|
||||
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",
|
||||
"core-js": "2.4.1",
|
||||
"plugin-typescript": "6.0.4",
|
||||
"plugin-typescript": "8.0.0",
|
||||
"rxjs": "file:../../node_modules/rxjs",
|
||||
"systemjs": "0.20.2",
|
||||
"typescript": "file:../../node_modules/typescript",
|
||||
|
@ -13,13 +13,16 @@ import * as coreTesting from '@angular/core/testing';
|
||||
import * as forms from '@angular/forms';
|
||||
import * as http from '@angular/http';
|
||||
import * as httpTesting from '@angular/http/testing';
|
||||
import * as platformBrowserDynamic from '@angular/platform-browser-dynamic';
|
||||
import * as platformBrowser from '@angular/platform-browser';
|
||||
import * as platformBrowserTesting from '@angular/platform-browser/testing';
|
||||
import * as platformBrowserDynamic from '@angular/platform-browser-dynamic';
|
||||
import * as platformServer from '@angular/platform-server';
|
||||
import * as platformServerTesting from '@angular/platform-server/testing';
|
||||
import * as platformWebworker from '@angular/platform-webworker';
|
||||
import * as platformWebworkerDynamic from '@angular/platform-webworker-dynamic';
|
||||
import * as router from '@angular/router';
|
||||
import * as routerTesting from '@angular/router/testing';
|
||||
import * as serviceWorker from '@angular/service-worker';
|
||||
import * as upgrade from '@angular/upgrade';
|
||||
|
||||
export default {
|
||||
@ -35,7 +38,10 @@ export default {
|
||||
platformBrowserDynamic,
|
||||
platformServer,
|
||||
platformServerTesting,
|
||||
platformWebworker,
|
||||
platformWebworkerDynamic,
|
||||
router,
|
||||
routerTesting,
|
||||
upgrade
|
||||
serviceWorker,
|
||||
upgrade,
|
||||
};
|
||||
|
@ -14,7 +14,10 @@
|
||||
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
|
||||
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",
|
||||
"@angular/platform-server": "file:../../dist/packages-dist/platform-server",
|
||||
"@angular/platform-webworker": "file:../../dist/packages-dist/platform-webworker",
|
||||
"@angular/platform-webworker-dynamic": "file:../../dist/packages-dist/platform-webworker-dynamic",
|
||||
"@angular/router": "file:../../dist/packages-dist/router",
|
||||
"@angular/service-worker": "file:../../dist/packages-dist/service-worker",
|
||||
"@angular/upgrade": "file:../../dist/packages-dist/upgrade",
|
||||
"@types/jasmine": "2.5.41",
|
||||
"rxjs": "file:../../node_modules/rxjs",
|
||||
|
@ -13,13 +13,16 @@ import * as coreTesting from '@angular/core/testing';
|
||||
import * as forms from '@angular/forms';
|
||||
import * as http from '@angular/http';
|
||||
import * as httpTesting from '@angular/http/testing';
|
||||
import * as platformBrowserDynamic from '@angular/platform-browser-dynamic';
|
||||
import * as platformBrowser from '@angular/platform-browser';
|
||||
import * as platformBrowserTesting from '@angular/platform-browser/testing';
|
||||
import * as platformBrowserDynamic from '@angular/platform-browser-dynamic';
|
||||
import * as platformServer from '@angular/platform-server';
|
||||
import * as platformServerTesting from '@angular/platform-server/testing';
|
||||
import * as platformWebworker from '@angular/platform-webworker';
|
||||
import * as platformWebworkerDynamic from '@angular/platform-webworker-dynamic';
|
||||
import * as router from '@angular/router';
|
||||
import * as routerTesting from '@angular/router/testing';
|
||||
import * as serviceWorker from '@angular/service-worker';
|
||||
import * as upgrade from '@angular/upgrade';
|
||||
|
||||
export default {
|
||||
@ -35,7 +38,10 @@ export default {
|
||||
platformBrowserDynamic,
|
||||
platformServer,
|
||||
platformServerTesting,
|
||||
platformWebworker,
|
||||
platformWebworkerDynamic,
|
||||
router,
|
||||
routerTesting,
|
||||
upgrade
|
||||
serviceWorker,
|
||||
upgrade,
|
||||
};
|
||||
|
@ -14,7 +14,10 @@
|
||||
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
|
||||
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",
|
||||
"@angular/platform-server": "file:../../dist/packages-dist/platform-server",
|
||||
"@angular/platform-webworker": "file:../../dist/packages-dist/platform-webworker",
|
||||
"@angular/platform-webworker-dynamic": "file:../../dist/packages-dist/platform-webworker-dynamic",
|
||||
"@angular/router": "file:../../dist/packages-dist/router",
|
||||
"@angular/service-worker": "file:../../dist/packages-dist/service-worker",
|
||||
"@angular/upgrade": "file:../../dist/packages-dist/upgrade",
|
||||
"@types/jasmine": "2.5.41",
|
||||
"rxjs": "file:../../node_modules/rxjs",
|
||||
|
47
integration/typings_test_ts26/include-all.ts
Normal file
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as compiler from '@angular/compiler';
|
||||
import * as compilerTesting from '@angular/compiler/testing';
|
||||
import * as core from '@angular/core';
|
||||
import * as coreTesting from '@angular/core/testing';
|
||||
import * as forms from '@angular/forms';
|
||||
import * as http from '@angular/http';
|
||||
import * as httpTesting from '@angular/http/testing';
|
||||
import * as platformBrowser from '@angular/platform-browser';
|
||||
import * as platformBrowserTesting from '@angular/platform-browser/testing';
|
||||
import * as platformBrowserDynamic from '@angular/platform-browser-dynamic';
|
||||
import * as platformServer from '@angular/platform-server';
|
||||
import * as platformServerTesting from '@angular/platform-server/testing';
|
||||
import * as platformWebworker from '@angular/platform-webworker';
|
||||
import * as platformWebworkerDynamic from '@angular/platform-webworker-dynamic';
|
||||
import * as router from '@angular/router';
|
||||
import * as routerTesting from '@angular/router/testing';
|
||||
import * as serviceWorker from '@angular/service-worker';
|
||||
import * as upgrade from '@angular/upgrade';
|
||||
|
||||
export default {
|
||||
compiler,
|
||||
compilerTesting,
|
||||
core,
|
||||
coreTesting,
|
||||
forms,
|
||||
http,
|
||||
httpTesting,
|
||||
platformBrowser,
|
||||
platformBrowserTesting,
|
||||
platformBrowserDynamic,
|
||||
platformServer,
|
||||
platformServerTesting,
|
||||
platformWebworker,
|
||||
platformWebworkerDynamic,
|
||||
router,
|
||||
routerTesting,
|
||||
serviceWorker,
|
||||
upgrade,
|
||||
};
|
30
integration/typings_test_ts26/package.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "angular-integration",
|
||||
"description": "Assert that users with TypeScript 2.6 can type-check an Angular application",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular/animations": "file:../../dist/packages-dist/animations",
|
||||
"@angular/common": "file:../../dist/packages-dist/common",
|
||||
"@angular/compiler": "file:../../dist/packages-dist/compiler",
|
||||
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
|
||||
"@angular/core": "file:../../dist/packages-dist/core",
|
||||
"@angular/forms": "file:../../dist/packages-dist/forms",
|
||||
"@angular/http": "file:../../dist/packages-dist/http",
|
||||
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
|
||||
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",
|
||||
"@angular/platform-server": "file:../../dist/packages-dist/platform-server",
|
||||
"@angular/platform-webworker": "file:../../dist/packages-dist/platform-webworker",
|
||||
"@angular/platform-webworker-dynamic": "file:../../dist/packages-dist/platform-webworker-dynamic",
|
||||
"@angular/router": "file:../../dist/packages-dist/router",
|
||||
"@angular/service-worker": "file:../../dist/packages-dist/service-worker",
|
||||
"@angular/upgrade": "file:../../dist/packages-dist/upgrade",
|
||||
"@types/jasmine": "2.5.41",
|
||||
"rxjs": "file:../../node_modules/rxjs",
|
||||
"typescript": "2.6.x",
|
||||
"zone.js": "file:../../node_modules/zone.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "tsc"
|
||||
}
|
||||
}
|
24
integration/typings_test_ts26/tsconfig.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "../../dist/typings_test_ts26/",
|
||||
"rootDir": ".",
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection",
|
||||
"es2015.iterable",
|
||||
"es2015.promise"
|
||||
],
|
||||
"types": [],
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"files": [
|
||||
"include-all.ts",
|
||||
"node_modules/@types/jasmine/index.d.ts"
|
||||
]
|
||||
}
|
@ -53,10 +53,12 @@ module.exports = function(config) {
|
||||
],
|
||||
|
||||
exclude: [
|
||||
'dist/all/@angular/_testing_init/**',
|
||||
'dist/all/@angular/**/e2e_test/**',
|
||||
'dist/all/@angular/**/*node_only_spec.js',
|
||||
'dist/all/@angular/benchpress/**',
|
||||
'dist/all/@angular/compiler-cli/**',
|
||||
'dist/all/@angular/core/test/render3/**',
|
||||
'dist/all/@angular/compiler/test/aot/**',
|
||||
'dist/all/@angular/examples/**/e2e_test/*',
|
||||
'dist/all/@angular/language-service/**',
|
||||
|
@ -56,6 +56,24 @@ describe('largetable benchmark perf', () => {
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should run for render3', (done) => {
|
||||
runTableBenchmark({
|
||||
id: `largeTable.render3.${worker.id}`,
|
||||
url: 'all/benchmarks/src/largetable/render3/index.html',
|
||||
ignoreBrowserSynchronization: true,
|
||||
worker: worker
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should run for iv', (done) => {
|
||||
runTableBenchmark({
|
||||
id: `largeTable.iv.${worker.id}`,
|
||||
url: 'all/benchmarks/src/largetable/iv/index.html',
|
||||
ignoreBrowserSynchronization: true,
|
||||
worker: worker
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should run for the baseline', (done) => {
|
||||
runTableBenchmark({
|
||||
id: `largeTable.baseline.${worker.id}`,
|
||||
|
@ -25,6 +25,20 @@ describe('largetable benchmark spec', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should work for render3', () => {
|
||||
testTableBenchmark({
|
||||
url: 'all/benchmarks/src/largetable/render3/index.html',
|
||||
ignoreBrowserSynchronization: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should work for iv', () => {
|
||||
testTableBenchmark({
|
||||
url: 'all/benchmarks/src/largetable/iv/index.html',
|
||||
ignoreBrowserSynchronization: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should work for the baseline', () => {
|
||||
testTableBenchmark({
|
||||
url: 'all/benchmarks/src/largetable/baseline/index.html',
|
||||
|
@ -49,6 +49,24 @@ export const Benchmarks: Benchmark[] = [
|
||||
url: 'all/benchmarks/src/tree/ng2_switch/index.html',
|
||||
buttons: CreateDestroyButtons,
|
||||
},
|
||||
{
|
||||
id: `deepTree.ng2.render3`,
|
||||
url: 'all/benchmarks/src/tree/render3/index.html',
|
||||
buttons: CreateDestroyDetectChangesButtons,
|
||||
ignoreBrowserSynchronization: true,
|
||||
},
|
||||
{
|
||||
id: `deepTree.ng2.render3_function`,
|
||||
url: 'all/benchmarks/src/tree/render3_function/index.html',
|
||||
buttons: CreateDestroyDetectChangesButtons,
|
||||
ignoreBrowserSynchronization: true,
|
||||
},
|
||||
{
|
||||
id: `deepTree.iv`,
|
||||
url: 'all/benchmarks/src/tree/iv/index.html',
|
||||
buttons: CreateDestroyDetectChangesButtons,
|
||||
ignoreBrowserSynchronization: true,
|
||||
},
|
||||
{
|
||||
id: `deepTree.baseline`,
|
||||
url: 'all/benchmarks/src/tree/baseline/index.html',
|
||||
|
31
modules/benchmarks/src/largetable/iv/index.html
Normal file
@ -0,0 +1,31 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<h2>Params</h2>
|
||||
<form>
|
||||
Cols:
|
||||
<input type="number" name="cols" placeholder="cols" value="40">
|
||||
<br>
|
||||
Rows:
|
||||
<input type="number" name="rows" placeholder="rows" value="200">
|
||||
<br>
|
||||
<button>Apply</button>
|
||||
</form>
|
||||
|
||||
<h2>IV Largetable Benchmark</h2>
|
||||
<p>
|
||||
<button id="destroyDom">destroyDom</button>
|
||||
<button id="createDom">createDom</button>
|
||||
<button id="updateDomProfile">profile updateDom</button>
|
||||
<button id="createDomProfile">profile createDom</button>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<largetable id="root"></largetable>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="largetable.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
9
modules/benchmarks/src/largetable/iv/largetable.js
Normal file
34
modules/benchmarks/src/largetable/render3/index.html
Normal file
@ -0,0 +1,34 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<h2>Params</h2>
|
||||
<form>
|
||||
Cols:
|
||||
<input type="number" name="cols" placeholder="cols" value="40">
|
||||
<br>
|
||||
Rows:
|
||||
<input type="number" name="rows" placeholder="rows" value="200">
|
||||
<br>
|
||||
<button>Apply</button>
|
||||
</form>
|
||||
|
||||
<h2>Render3 Largetable Benchmark</h2>
|
||||
<p>
|
||||
<button id="destroyDom">destroyDom</button>
|
||||
<button id="createDom">createDom</button>
|
||||
<button id="updateDomProfile">profile updateDom</button>
|
||||
<button id="createDomProfile">profile createDom</button>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<largetable id="root"></largetable>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var mainUrl = window.location.search.split(/[?&]main=([^&]+)/)[1]
|
||||
|| '../../bootstrap_ng2.js';
|
||||
document.write('<script src="' + mainUrl + '">\u003c/script>');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
28
modules/benchmarks/src/largetable/render3/index.ts
Normal file
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @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 {ɵrenderComponent as renderComponent} from '@angular/core';
|
||||
|
||||
import {bindAction, profile} from '../../util';
|
||||
|
||||
import {LargeTableComponent, createDom, destroyDom} from './table';
|
||||
|
||||
function noop() {}
|
||||
|
||||
export function main() {
|
||||
let component: LargeTableComponent;
|
||||
if (typeof window !== 'undefined') {
|
||||
component = renderComponent<LargeTableComponent>(LargeTableComponent);
|
||||
bindAction('#createDom', () => createDom(component));
|
||||
bindAction('#destroyDom', () => destroyDom(component));
|
||||
bindAction('#updateDomProfile', profile(() => createDom(component), noop, 'update'));
|
||||
bindAction(
|
||||
'#createDomProfile',
|
||||
profile(() => createDom(component), () => destroyDom(component), 'create'));
|
||||
}
|
||||
}
|
81
modules/benchmarks/src/largetable/render3/table.ts
Normal file
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* @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 {ɵC as C, ɵE as E, ɵT as T, ɵV as V, ɵb as b, ɵc as c, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as detectChanges, ɵe as e, ɵs as s, ɵt as t, ɵv as v} from '@angular/core';
|
||||
import {ComponentDef} from '@angular/core/src/render3/definition_interfaces';
|
||||
|
||||
import {TableCell, buildTable, emptyTable} from '../util';
|
||||
|
||||
export class LargeTableComponent {
|
||||
data: TableCell[][] = emptyTable;
|
||||
|
||||
/** @nocollapse */
|
||||
static ngComponentDef: ComponentDef<LargeTableComponent> = defineComponent({
|
||||
type: LargeTableComponent,
|
||||
tag: 'largetable',
|
||||
template: function(ctx: LargeTableComponent, cm: boolean) {
|
||||
if (cm) {
|
||||
E(0, 'table');
|
||||
{
|
||||
E(1, 'tbody');
|
||||
{
|
||||
C(2);
|
||||
c();
|
||||
}
|
||||
e();
|
||||
}
|
||||
e();
|
||||
}
|
||||
cR(2);
|
||||
{
|
||||
for (let row of ctx.data) {
|
||||
let cm1 = V(1);
|
||||
{
|
||||
if (cm1) {
|
||||
E(0, 'tr');
|
||||
C(1);
|
||||
c();
|
||||
e();
|
||||
}
|
||||
cR(1);
|
||||
{
|
||||
for (let cell of row) {
|
||||
let cm2 = V(2);
|
||||
{
|
||||
if (cm2) {
|
||||
E(0, 'td');
|
||||
{ T(1); }
|
||||
e();
|
||||
}
|
||||
s(0, 'background-color', b(cell.row % 2 ? '' : 'grey'));
|
||||
t(1, b(cell.value));
|
||||
}
|
||||
v();
|
||||
}
|
||||
}
|
||||
cr();
|
||||
}
|
||||
v();
|
||||
}
|
||||
}
|
||||
cr();
|
||||
},
|
||||
factory: () => new LargeTableComponent(),
|
||||
inputs: {data: 'data'}
|
||||
});
|
||||
}
|
||||
|
||||
export function destroyDom(component: LargeTableComponent) {
|
||||
component.data = emptyTable;
|
||||
detectChanges(component);
|
||||
}
|
||||
|
||||
export function createDom(component: LargeTableComponent) {
|
||||
component.data = buildTable();
|
||||
detectChanges(component);
|
||||
}
|
30
modules/benchmarks/src/tree/iv/index.html
Normal file
@ -0,0 +1,30 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<h2>Params</h2>
|
||||
<form>
|
||||
Depth:
|
||||
<input type="number" name="depth" placeholder="depth" value="9">
|
||||
<br>
|
||||
<button>Apply</button>
|
||||
</form>
|
||||
|
||||
<h2>IV Tree Benchmark</h2>
|
||||
<p>
|
||||
<button id="destroyDom">destroyDom</button>
|
||||
<button id="createDom">createDom</button>
|
||||
<button id="detectChanges">detectChanges</button>
|
||||
<button id="updateDomProfile">profile updateDom</button>
|
||||
<button id="createDomProfile">profile createDom</button>
|
||||
<button id="detectChangesProfile">profile detectChanges</button>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
Change detection runs:<span id="numberOfChecks"></span>
|
||||
</div>
|
||||
<div id="root"></div>
|
||||
|
||||
<script type="text/javascript" src="tree.js"></script>
|
||||
</body>
|
||||
</html>
|
9
modules/benchmarks/src/tree/iv/tree.js
Normal file
36
modules/benchmarks/src/tree/render3/index.html
Normal file
@ -0,0 +1,36 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<h2>Params</h2>
|
||||
<form>
|
||||
Depth:
|
||||
<input type="number" name="depth" placeholder="depth" value="9">
|
||||
<br>
|
||||
<button>Apply</button>
|
||||
</form>
|
||||
|
||||
<h2>Render3 Tree Benchmark</h2>
|
||||
<p>
|
||||
<button id="destroyDom">destroyDom</button>
|
||||
<button id="createDom">createDom</button>
|
||||
<button id="detectChanges">detectChanges</button>
|
||||
<button id="updateDomProfile">profile updateDom</button>
|
||||
<button id="createDomProfile">profile createDom</button>
|
||||
<button id="detectChangesProfile">profile detectChanges</button>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
Change detection runs:<span id="numberOfChecks"></span>
|
||||
</div>
|
||||
<div>
|
||||
<tree id="root"></tree>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var mainUrl = window.location.search.split(/[?&]main=([^&]+)/)[1]
|
||||
|| '../../bootstrap_ng2.js';
|
||||
document.write('<script src="' + mainUrl + '">\u003c/script>');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
29
modules/benchmarks/src/tree/render3/index.ts
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @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 {ɵrenderComponent as renderComponent} from '@angular/core';
|
||||
import {bindAction, profile} from '../../util';
|
||||
import {TreeComponent, createDom, destroyDom, detectChanges} from './tree';
|
||||
|
||||
function noop() {}
|
||||
|
||||
export function main() {
|
||||
let component: TreeComponent;
|
||||
if (typeof window !== 'undefined') {
|
||||
component = renderComponent(TreeComponent);
|
||||
bindAction('#createDom', () => createDom(component));
|
||||
bindAction('#destroyDom', () => destroyDom(component));
|
||||
bindAction('#detectChanges', () => detectChanges(component));
|
||||
bindAction(
|
||||
'#detectChangesProfile', profile(() => detectChanges(component), noop, 'detectChanges'));
|
||||
bindAction('#updateDomProfile', profile(() => createDom(component), noop, 'update'));
|
||||
bindAction(
|
||||
'#createDomProfile',
|
||||
profile(() => createDom(component), () => destroyDom(component), 'create'));
|
||||
}
|
||||
}
|
141
modules/benchmarks/src/tree/render3/tree.ts
Normal file
@ -0,0 +1,141 @@
|
||||
/**
|
||||
* @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 {ɵC as C, ɵD as D, ɵE as E, ɵT as T, ɵV as V, ɵb as b, ɵb1 as b1, ɵc as c, ɵcR as cR, ɵcr as cr, ɵdefineComponent as defineComponent, ɵdetectChanges as _detectChanges, ɵe as e, ɵp as p, ɵs as s, ɵt as t, ɵv as v} from '@angular/core';
|
||||
import {ComponentDef} from '@angular/core/src/render3/definition_interfaces';
|
||||
|
||||
import {TreeNode, buildTree, emptyTree} from '../util';
|
||||
|
||||
export function destroyDom(component: TreeComponent) {
|
||||
component.data = emptyTree;
|
||||
_detectChanges(component);
|
||||
}
|
||||
|
||||
export function createDom(component: TreeComponent) {
|
||||
component.data = buildTree();
|
||||
_detectChanges(component);
|
||||
}
|
||||
|
||||
const numberOfChecksEl = document.getElementById('numberOfChecks') !;
|
||||
let detectChangesRuns = 0;
|
||||
export function detectChanges(component: TreeComponent) {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
_detectChanges(component);
|
||||
}
|
||||
detectChangesRuns += 10;
|
||||
numberOfChecksEl.textContent = `${detectChangesRuns}`;
|
||||
}
|
||||
|
||||
export class TreeComponent {
|
||||
data: TreeNode = emptyTree;
|
||||
|
||||
/** @nocollapse */
|
||||
static ngComponentDef: ComponentDef<TreeComponent> = defineComponent({
|
||||
type: TreeComponent,
|
||||
tag: 'tree',
|
||||
template: function(ctx: TreeComponent, cm: boolean) {
|
||||
if (cm) {
|
||||
E(0, 'span');
|
||||
{ T(1); }
|
||||
e();
|
||||
C(2);
|
||||
c();
|
||||
C(3);
|
||||
c();
|
||||
}
|
||||
s(0, 'background-color', b(ctx.data.depth % 2 ? '' : 'grey'));
|
||||
t(1, b1(' ', ctx.data.value, ' '));
|
||||
cR(2);
|
||||
{
|
||||
if (ctx.data.left != null) {
|
||||
let cm0 = V(0);
|
||||
{
|
||||
if (cm0) {
|
||||
E(0, TreeComponent.ngComponentDef);
|
||||
{ D(1, TreeComponent.ngComponentDef.n(), TreeComponent.ngComponentDef); }
|
||||
e();
|
||||
}
|
||||
p(0, 'data', b(ctx.data.left));
|
||||
TreeComponent.ngComponentDef.h(1, 0);
|
||||
TreeComponent.ngComponentDef.r(1, 0);
|
||||
}
|
||||
v();
|
||||
}
|
||||
}
|
||||
cr();
|
||||
cR(3);
|
||||
{
|
||||
if (ctx.data.right != null) {
|
||||
let cm0 = V(0);
|
||||
{
|
||||
if (cm0) {
|
||||
E(0, TreeComponent.ngComponentDef);
|
||||
{ D(1, TreeComponent.ngComponentDef.n(), TreeComponent.ngComponentDef); }
|
||||
e();
|
||||
}
|
||||
p(0, 'data', b(ctx.data.right));
|
||||
TreeComponent.ngComponentDef.h(1, 0);
|
||||
TreeComponent.ngComponentDef.r(1, 0);
|
||||
}
|
||||
v();
|
||||
}
|
||||
}
|
||||
cr();
|
||||
},
|
||||
factory: () => new TreeComponent,
|
||||
inputs: {data: 'data'}
|
||||
});
|
||||
}
|
||||
|
||||
export class TreeFunction extends TreeComponent {
|
||||
data: TreeNode = emptyTree;
|
||||
|
||||
/** @nocollapse */
|
||||
static ngComponentDef: ComponentDef<TreeFunction> = defineComponent({
|
||||
type: TreeFunction,
|
||||
tag: 'tree',
|
||||
template: function(ctx: TreeFunction, cm: boolean) {
|
||||
// bit of a hack
|
||||
TreeTpl(ctx.data, cm);
|
||||
},
|
||||
factory: () => new TreeFunction,
|
||||
inputs: {data: 'data'}
|
||||
});
|
||||
}
|
||||
|
||||
export function TreeTpl(ctx: TreeNode, cm: boolean) {
|
||||
if (cm) {
|
||||
E(0, 'span');
|
||||
{ T(1); }
|
||||
e();
|
||||
C(2);
|
||||
c();
|
||||
C(3);
|
||||
c();
|
||||
}
|
||||
s(0, 'background-color', b(ctx.depth % 2 ? '' : 'grey'));
|
||||
t(1, b1(' ', ctx.value, ' '));
|
||||
cR(2);
|
||||
{
|
||||
if (ctx.left != null) {
|
||||
let cm0 = V(0);
|
||||
{ TreeTpl(ctx.left, cm0); }
|
||||
v();
|
||||
}
|
||||
}
|
||||
cr();
|
||||
cR(3);
|
||||
{
|
||||
if (ctx.right != null) {
|
||||
let cm0 = V(0);
|
||||
{ TreeTpl(ctx.right, cm0); }
|
||||
v();
|
||||
}
|
||||
}
|
||||
cr();
|
||||
}
|
36
modules/benchmarks/src/tree/render3_function/index.html
Normal file
@ -0,0 +1,36 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<h2>Params</h2>
|
||||
<form>
|
||||
Depth:
|
||||
<input type="number" name="depth" placeholder="depth" value="9">
|
||||
<br>
|
||||
<button>Apply</button>
|
||||
</form>
|
||||
|
||||
<h2>Render3 Tree Benchmark with functions</h2>
|
||||
<p>
|
||||
<button id="destroyDom">destroyDom</button>
|
||||
<button id="createDom">createDom</button>
|
||||
<button id="detectChanges">detectChanges</button>
|
||||
<button id="updateDomProfile">profile updateDom</button>
|
||||
<button id="createDomProfile">profile createDom</button>
|
||||
<button id="detectChangesProfile">profile detectChanges</button>
|
||||
</p>
|
||||
|
||||
<div>
|
||||
Change detection runs:<span id="numberOfChecks"></span>
|
||||
</div>
|
||||
<div>
|
||||
<tree id="root"></tree>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var mainUrl = window.location.search.split(/[?&]main=([^&]+)/)[1]
|
||||
|| '../../bootstrap_ng2.js';
|
||||
document.write('<script src="' + mainUrl + '">\u003c/script>');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
29
modules/benchmarks/src/tree/render3_function/index.ts
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @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 {ɵrenderComponent as renderComponent} from '@angular/core';
|
||||
import {bindAction, profile} from '../../util';
|
||||
import {TreeFunction, createDom, destroyDom, detectChanges} from '../render3/tree';
|
||||
|
||||
function noop() {}
|
||||
|
||||
export function main() {
|
||||
let component: TreeFunction;
|
||||
if (typeof window !== 'undefined') {
|
||||
component = renderComponent(TreeFunction);
|
||||
bindAction('#createDom', () => createDom(component));
|
||||
bindAction('#destroyDom', () => destroyDom(component));
|
||||
bindAction('#detectChanges', () => detectChanges(component));
|
||||
bindAction(
|
||||
'#detectChangesProfile', profile(() => detectChanges(component), noop, 'detectChanges'));
|
||||
bindAction('#updateDomProfile', profile(() => createDom(component), noop, 'update'));
|
||||
bindAction(
|
||||
'#createDomProfile',
|
||||
profile(() => createDom(component), () => destroyDom(component), 'create'));
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "5.1.1",
|
||||
"version": "5.2.0-rc.0",
|
||||
"private": true,
|
||||
"branchPattern": "2.0.*",
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
@ -96,11 +96,11 @@
|
||||
"source-map-support": "0.4.18",
|
||||
"systemjs": "0.18.10",
|
||||
"ts-api-guardian": "0.2.2",
|
||||
"tsickle": "0.25.5",
|
||||
"tsickle": "0.26.0",
|
||||
"tslint": "5.7.0",
|
||||
"tslint-eslint-rules": "4.1.1",
|
||||
"tsutils": "2.12.1",
|
||||
"typescript": "2.5.x",
|
||||
"typescript": "2.6.x",
|
||||
"uglify-js": "2.8.29",
|
||||
"universal-analytics": "0.4.15",
|
||||
"vlq": "0.2.2",
|
||||
|
@ -1393,9 +1393,9 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
|
||||
|
||||
public markedForDestroy: boolean = false;
|
||||
|
||||
constructor(public namespaceId: string, public triggerName: string, public element: any) {}
|
||||
readonly queued: boolean = true;
|
||||
|
||||
get queued() { return this._containsRealPlayer == false; }
|
||||
constructor(public namespaceId: string, public triggerName: string, public element: any) {}
|
||||
|
||||
setRealPlayer(player: AnimationPlayer) {
|
||||
if (this._containsRealPlayer) return;
|
||||
@ -1407,6 +1407,7 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
|
||||
});
|
||||
this._queuedCallbacks = {};
|
||||
this._containsRealPlayer = true;
|
||||
(this as{queued: boolean}).queued = false;
|
||||
}
|
||||
|
||||
getRealPlayer() { return this._player; }
|
||||
|
@ -6,7 +6,15 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export interface DOMAnimation {
|
||||
/**
|
||||
* DOMAnimation represents the Animation Web API.
|
||||
*
|
||||
* It is an external API by the browser, and must thus use "declare interface",
|
||||
* to prevent renaming by Closure Compiler.
|
||||
*
|
||||
* @see https://developer.mozilla.org/de/docs/Web/API/Animation
|
||||
*/
|
||||
export declare interface DOMAnimation {
|
||||
cancel(): void;
|
||||
play(): void;
|
||||
pause(): void;
|
||||
|
@ -1,5 +1,3 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
|
@ -18,7 +18,7 @@ function createDiv() {
|
||||
return document.createElement('div');
|
||||
}
|
||||
|
||||
export function main() {
|
||||
{
|
||||
describe('Animation', () => {
|
||||
// these tests are only mean't to be run within the DOM (for now)
|
||||
if (typeof Element == 'undefined') return;
|
||||
|
@ -14,7 +14,7 @@ import {ENTER_CLASSNAME, LEAVE_CLASSNAME} from '../../src/util';
|
||||
import {MockAnimationDriver} from '../../testing';
|
||||
import {makeTrigger} from '../shared';
|
||||
|
||||
export function main() {
|
||||
{
|
||||
describe('AnimationTrigger', () => {
|
||||
// these tests are only mean't to be run within the DOM (for now)
|
||||
if (typeof Element == 'undefined') return;
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
import {WebAnimationsStyleNormalizer} from '../../../src/dsl/style_normalization/web_animations_style_normalizer';
|
||||
|
||||
export function main() {
|
||||
{
|
||||
describe('WebAnimationsStyleNormalizer', () => {
|
||||
const normalizer = new WebAnimationsStyleNormalizer();
|
||||
|
||||
|
@ -12,7 +12,7 @@ import {AnimationDriver} from '../../src/render/animation_driver';
|
||||
import {TimelineAnimationEngine} from '../../src/render/timeline_animation_engine';
|
||||
import {MockAnimationDriver, MockAnimationPlayer} from '../../testing/src/mock_animation_driver';
|
||||
|
||||
export function main() {
|
||||
(function() {
|
||||
const defaultDriver = new MockAnimationDriver();
|
||||
|
||||
function makeEngine(driver?: AnimationDriver, normalizer?: AnimationStyleNormalizer) {
|
||||
@ -93,7 +93,7 @@ export function main() {
|
||||
expect(player.keyframes).toEqual([{width: '*star*', offset: 0}, {width: '999px', offset: 1}]);
|
||||
});
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
function invokeAnimation(
|
||||
engine: TimelineAnimationEngine, element: any, steps: AnimationMetadata | AnimationMetadata[],
|
||||
|
@ -16,7 +16,7 @@ import {MockAnimationDriver, MockAnimationPlayer} from '../../testing/src/mock_a
|
||||
|
||||
const DEFAULT_NAMESPACE_ID = 'id';
|
||||
|
||||
export function main() {
|
||||
(function() {
|
||||
const driver = new MockAnimationDriver();
|
||||
|
||||
// these tests are only mean't to be run within the DOM
|
||||
@ -616,7 +616,7 @@ export function main() {
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
class SuffixNormalizer extends AnimationStyleNormalizer {
|
||||
constructor(private _suffix: string) { super(); }
|
||||
|
@ -8,7 +8,7 @@
|
||||
import {DOMAnimation} from '../../../src/render/web_animations/dom_animation';
|
||||
import {WebAnimationsPlayer} from '../../../src/render/web_animations/web_animations_player';
|
||||
|
||||
export function main() {
|
||||
{
|
||||
let element: any;
|
||||
let innerPlayer: MockDomAnimation|null = null;
|
||||
beforeEach(() => {
|
||||
|
@ -115,7 +115,7 @@ export interface AnimationStateMetadata extends AnimationMetadata {
|
||||
* @experimental Animation support is experimental.
|
||||
*/
|
||||
export interface AnimationTransitionMetadata extends AnimationMetadata {
|
||||
expr: string;
|
||||
expr: string|((fromState: string, toState: string) => boolean);
|
||||
animation: AnimationMetadata|AnimationMetadata[];
|
||||
options: AnimationOptions|null;
|
||||
}
|
||||
@ -836,7 +836,8 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe
|
||||
* @experimental Animation support is experimental.
|
||||
*/
|
||||
export function transition(
|
||||
stateChangeExpr: string, steps: AnimationMetadata | AnimationMetadata[],
|
||||
stateChangeExpr: string | ((fromState: string, toState: string) => boolean),
|
||||
steps: AnimationMetadata | AnimationMetadata[],
|
||||
options: AnimationOptions | null = null): AnimationTransitionMetadata {
|
||||
return {type: AnimationMetadataType.Transition, expr: stateChangeExpr, animation: steps, options};
|
||||
}
|
||||
|