Compare commits

...

63 Commits

Author SHA1 Message Date
6353b77f89 docs: add changelog for 5.1.2 2017-12-20 12:50:50 -08:00
d9c40b7dd5 release: cut the 5.1.2 release 2017-12-20 12:49:12 -08:00
a36dfd453b ci(router): update the public API guard for the router
This fixes a badly applied revert earlier.
2017-12-20 12:03:51 -08:00
ced575fd10 fix(compiler): report an error for recursive module references
Modules that directly or indirectly export or imported themselves
reports an error instead of generating a stack fault.

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

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

Fixes: #20479
2017-12-20 09:59:06 -08:00
a33182c4ee fix(service-worker): check for updates on navigation
Currently the Service Worker checks for updates only on SW startup,
an event which happens frequently but also nondeterministically. This
makes it hard for developers to observe the update process or reason
about how updates will be delivered to users. This problem is
exacerbated by the DevTools behavior of keeping the SW alive
indefinitely while opened, effectively preventing the page from
updating at all.

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

Fixes #20877
2017-12-20 08:37:20 -08:00
66cc2fabf1 fix(upgrade): replaces get/setAngularLib with get/setAngularJSGlobal
The current names are confusing because Angular should refer to the latest version of the framework.
2017-12-20 08:37:02 -08:00
cf8269ee94 docs(aio): Rename service worker files, update examples, move service worker under Techniques 2017-12-19 11:08:25 -08:00
c7241ca0e6 docs(aio): fix inconsistency in lifecycle hooks table 2017-12-19 10:58:43 -08:00
4a49e19bd7 ci: add router/testing to public API guard 2017-12-19 10:58:43 -08:00
4a3a74b7b0 fix(aio): improve transitions between pages
- Avoid unnecessary animations, style transitions, repositioning on
  initial rendering.
- Better handle transitioning from/to Home page (which is the only page
  with transparent top-menu).
- Better coordinate sidenav and hamburger animations with page
  transitions.
- Improve fade-in/out animations.

Fixes #20996
2017-12-19 10:58:43 -08:00
6aa013cb63 refactor(aio): clean up top-menu CSS
- Clean-up and re-organize top-menu styles.
- Clean-up and merge hamburger styles into top-menu styles.
2017-12-19 10:58:43 -08:00
b9531f90b8 feat(aio): support disabling DocViewer animations via class 2017-12-19 10:58:43 -08:00
f239e8496e build(aio): make zipper work correctly with CLI projects 2017-12-19 10:58:43 -08:00
57bed3fe73 docs(core): move core examples into examples/core/ directory
This allows examples to be found during aio's `yarn serve-and-sync`, which only
looks for examples in `packages/examples/<packageName>/**/*`, where
`packageName` is the name of the package that the modified file belonged to;
e.g. `core`, `common`, etc.).
2017-12-18 12:11:07 -08:00
c011ffae30 test(aio): correct usage of fakeAsync and inject together in test
Corrects a test which wrapped the fakeAsync call in an inject call.  This caused
the test to not actually run.
2017-12-15 07:55:23 -08:00
756dd34519 fix(compiler): make tsx file aot compatible
fixes #20555
2017-12-15 07:54:02 -08:00
267ebf323e fix(common): fix a Closure compilation issue.
Closure Compiler cannot infer that the swtich statement is exhaustive,
which causes it to complain that the method does not always return a
value.

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

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

Fixes #20159
2017-12-14 08:56:22 -08:00
390837f76e test(aio): disable DocViewer animations during e2e tests 2017-12-14 08:56:22 -08:00
fbcf7dc889 test(aio): fix and clean up e2e tests 2017-12-14 08:56:22 -08:00
5c8c7d8515 build(common): don't generate .d.ts & .metadata.json files for i18n locales
Fixes #20880
2017-12-14 08:55:50 -08:00
99cc591f63 ci: parallelize bazel build and test
The current setup can cause a de-optimization where unit tests can't start running until the slowest build target completes.
2017-12-14 08:55:42 -08:00
e3140ae888 docs: add changelog for 5.1.1 2017-12-13 11:26:01 -08:00
ca815106c9 release: cut the 5.1.1. release 2017-12-13 11:24:54 -08:00
d6da7988c0 fix(compiler): support referencing enums in namespaces (#20947)
Due to an overly agressive assert the compiler would generate
an internal error when referencing an enum declared in
namspace.

Fixes #18170

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

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

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

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

PR Close #20940
2017-12-11 15:53:22 -08:00
8bcb093bfa build: update node version number in .nvmrc (#20832)
PR Close #20832
2017-12-11 12:59:37 -08:00
abc3a1e844 docs: update DEVELOPER.md with the node and yarn info (#20832)
I intentionally removed version numbers so that we don't need to update them in this file -
because we usually forget.

PR Close #20832
2017-12-11 12:59:37 -08:00
9fbf850897 build: remove bazel option --noshow_results (#20943)
I originally added this when I was trying to build `//packages/core`, which is not what users will do often.
This makes it harder for team members to understand what Bazel is doing. I find myself suggesting to turn it off, so it's better to just remove it.
PR Close #20943
2017-12-11 11:18:18 -08:00
0c9f7b032a fix(bazel): don't equate moduleName with fileName (#20895)
Fixes broken material build.
/cc @jelbourn
PR Close #20895
2017-12-11 11:18:13 -08:00
8b21be5da3 build: require bazel 0.8 (#20897)
Users should get an error if they are running an older version of Bazel than we have on CI.
PR Close #20897
2017-12-11 11:18:07 -08:00
0a9a16183f build: new docker image, faster to boot on circleci 2017-12-11 11:18:00 -08:00
28555bc1e7 ci: print the buildifier command when BUILD lint fails (#20882)
PR Close #20882
2017-12-11 11:17:16 -08:00
d09d4971b3 fix(animations): ensure multi-level route leave animations are queryable (#20787)
Closes #19807

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

PR Close #18515
2017-12-08 13:44:17 -08:00
d713225128 refactor(common): update i18n locale data to CLDR v32 (#20830)
List of changes between v31.0.1 and v32: http://cldr.unicode.org/index/downloads/cldr-32
PR Close #20830
2017-12-08 10:25:01 -08:00
5d3af4cad4 docs(common): fix mistakes in number pipe example (#20788)
PR Close #20788
2017-12-08 10:24:46 -08:00
a7fe063aa5 ci: add IgorMinar to bazel pullapprove group (#20843)
PR Close #20843
2017-12-08 10:24:41 -08:00
affa54ddf9 build(aio): upgrade to latest @angular/cli (#18428)
PR Close #18428
2017-12-08 10:11:22 -08:00
02782c10ea build(aio): upgrade to latest @angular/material and @angular/cdk (#18428)
PR Close #18428
2017-12-08 10:11:22 -08:00
8e5a3a42d6 build(aio): upgrade to latest @angular/* (#18428)
PR Close #18428
2017-12-08 10:11:22 -08:00
dbef8ff2b0 build(aio): upgrade to latest rxjs (#18428)
PR Close #18428
2017-12-08 10:11:22 -08:00
c0f08be3fb fix(aio): fix embedded ToC and improve ToC, destroying components and scroll timing (#18428)
- Fix embedded ToC:
  Previously, the element was added too late and was never instantiated.

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

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

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

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

Fixes #15629

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

PR Close #18428
2017-12-08 10:11:22 -08:00
923227dec2 feat(aio): lazy-load embedded components (#18428)
Fixes #16127

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

PR Close #20779
2017-12-08 10:05:51 -08:00
e6a28054d8 fix(animations): properly recover and cleanup DOM when CD failures occur (#20719)
Closes #19093

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

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

Closes #20374

PR Close #20725
2017-12-07 17:17:43 -08:00
c3e8731145 fix(animations): ensure the web-animations driver properly handles empty keyframes (#20648)
Closes #15858

PR Close #20648
2017-12-07 17:17:32 -08:00
501f01e9ac fix(animations): support webkit-based vendor prefixes for prop validations (#19055)
Closes #18921

PR Close #19055
2017-12-07 17:17:27 -08:00
baeec4dbe2 fix(router): NavigatonError and NavigationCancel should be emitted after resetting the URL (#20803)
PR Close #20803
2017-12-07 13:34:40 -08:00
160a154553 fix(compiler-cli): disable checkTypes in emit. (#20828)
Closure Compiler by default will report diagnostics from type checks in
any JavaScript code, including code emitted by the Angular compiler.
Disabling `checkTypes` substantially reduces warning spam for users, and
allows them to run with stricter compiler flags (e.g. treating actual
diagnostics from user code as errors).

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

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

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

PR Close #20768
2017-12-07 13:25:47 -08:00
672733608b fix(compiler-cli): Fix swallowed Error messages (#20846)
This commit fixes a bug in which non-formatted errors are silently
dropped.

Internal issue: b/67739418

PR Close #20846
2017-12-06 17:42:19 -08:00
489 changed files with 5824 additions and 2567 deletions

View File

@ -7,17 +7,25 @@
# To validate changes, use an online parser, eg.
# http://yaml-online-parser.appspot.com/
# Variables
## IMPORTANT
# If you change the `docker_image` version, also change the `cache_key` suffix and the version of
# `com_github_bazelbuild_buildtools` in the `/WORKSPACE` file.
var_1: &docker_image angular/ngcontainer:0.0.8
var_2: &cache_key angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.0.8
# Settings common to each job
anchor_1: &job_defaults
working_directory: ~/ng
docker:
- image: angular/ngcontainer:0.0.6
- image: *docker_image
# After checkout, rebase on top of master.
# Similar to travis behavior, but not quite the same.
# See https://discuss.circleci.com/t/1662
anchor_2: &post_checkout
post: git pull --ff-only origin "refs/pull/${CI_PULL_REQUEST//*pull\//}/merge"
post: git pull --ff-only origin "refs/pull/${CIRCLE_PULL_REQUEST//*pull\//}/merge"
version: 2
jobs:
@ -26,8 +34,13 @@ jobs:
steps:
- checkout:
<<: *post_checkout
# Check BUILD.bazel formatting before we have a node_modules directory
# Then we don't need any exclude pattern to avoid checking those files
- run: 'buildifier -mode=check $(find . -type f \( -name BUILD.bazel -or -name BUILD \)) ||
(echo "BUILD files not formatted. Please run ''yarn buildifier''" ; exit 1)'
- restore_cache:
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
key: *cache_key
- run: yarn install --frozen-lockfile --non-interactive
- run: ./node_modules/.bin/gulp lint
@ -38,13 +51,17 @@ jobs:
- checkout:
<<: *post_checkout
- restore_cache:
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
key: *cache_key
- run: bazel info release
- run: bazel run @yarn//:yarn
- run: bazel build packages/...
- run: bazel test @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: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
key: *cache_key
paths:
- "node_modules"

2
.nvmrc
View File

@ -1 +1 @@
6.9.5
8.9

View File

@ -44,6 +44,7 @@ groups:
include:
- "*"
exclude:
- ".circleci/*"
- "aio/*"
- "integration/*"
- "modules/*"
@ -73,6 +74,7 @@ groups:
users:
- alexeagle #primary
- chuckjaz
- IgorMinar
- vikerman #fallback
build-and-ci:

View File

@ -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

View File

@ -1,4 +1,5 @@
package(default_visibility = ["//visibility:public"])
exports_files(["tsconfig.json"])
# This rule belongs in node_modules/BUILD
@ -11,17 +12,25 @@ filegroup(
# bazel query "deps(:node_modules)" | wc -l
# This won't scale in the general case.
# TODO(alexeagle): figure out what to do
srcs = glob(["/".join(["node_modules", pkg, "**", ext]) for pkg in [
srcs = glob(["/".join([
"node_modules",
pkg,
"**",
ext,
]) for pkg in [
"jasmine",
"typescript",
"zone.js",
"rxjs",
"@types",
"tsutils",
"@types/jasmine",
"@types/node",
"@types/source-map",
"tsickle",
"hammerjs",
"protobufjs",
"bytebuffer",
"reflect-metadata",
"source-map-support",
"minimist",
] for ext in [
"*.js",

View File

@ -1,3 +1,38 @@
<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.1.1"></a>
## [5.1.1](https://github.com/angular/angular/compare/5.1.0...5.1.1) (2017-12-13)
### Bug Fixes
* **animations:** ensure multi-level route leave animations are queryable ([#20787](https://github.com/angular/angular/issues/20787)) ([d09d497](https://github.com/angular/angular/commit/d09d497)), closes [#19807](https://github.com/angular/angular/issues/19807)
* **animations:** ensure the web-animations driver properly handles empty keyframes ([#20648](https://github.com/angular/angular/issues/20648)) ([c3e8731](https://github.com/angular/angular/commit/c3e8731)), closes [#15858](https://github.com/angular/angular/issues/15858)
* **animations:** properly recover and cleanup DOM when CD failures occur ([#20719](https://github.com/angular/angular/issues/20719)) ([e6a2805](https://github.com/angular/angular/commit/e6a2805)), closes [#19093](https://github.com/angular/angular/issues/19093)
* **animations:** support webkit-based vendor prefixes for prop validations ([#19055](https://github.com/angular/angular/issues/19055)) ([501f01e](https://github.com/angular/angular/commit/501f01e)), closes [#18921](https://github.com/angular/angular/issues/18921)
* **bazel:** don't equate moduleName with fileName ([#20895](https://github.com/angular/angular/issues/20895)) ([0c9f7b0](https://github.com/angular/angular/commit/0c9f7b0))
* **compiler:** support referencing enums in namespaces ([#20947](https://github.com/angular/angular/issues/20947)) ([d6da798](https://github.com/angular/angular/commit/d6da798)), closes [#18170](https://github.com/angular/angular/issues/18170)
* **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))
<a name="5.1.0"></a>
# [5.1.0](https://github.com/angular/angular/compare/5.1.0-rc.1...5.1.0) (2017-12-06)

View File

@ -5,19 +5,51 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
git_repository(
name = "build_bazel_rules_nodejs",
remote = "https://github.com/bazelbuild/rules_nodejs.git",
commit = "0.2.1",
tag = "0.3.1",
)
load("@build_bazel_rules_nodejs//:defs.bzl", "node_repositories")
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories")
check_bazel_version("0.8.1")
node_repositories(package_json = ["//:package.json"])
local_repository(
git_repository(
name = "build_bazel_rules_typescript",
path = "node_modules/@bazel/typescript",
remote = "https://github.com/bazelbuild/rules_typescript.git",
tag = "0.6.0",
)
load("@build_bazel_rules_typescript//:defs.bzl", "ts_repositories")
ts_repositories()
local_repository(
name = "angular",
path = "packages/bazel",
)
local_repository(
name = "rxjs",
path = "node_modules/rxjs/src",
)
git_repository(
name = "com_github_bazelbuild_buildtools",
remote = "https://github.com/bazelbuild/buildtools.git",
# Note, this commit matches the version of buildifier in angular/ngcontainer
# If you change this, also check if it matches the version in the angular/ngcontainer
# version in /.circleci/config.yml
commit = "b3b620e8bcff18ed3378cd3f35ebeb7016d71f71",
)
http_archive(
name = "io_bazel_rules_go",
url = "https://github.com/bazelbuild/rules_go/releases/download/0.7.1/rules_go-0.7.1.tar.gz",
sha256 = "341d5eacef704415386974bc82a1783a8b7ffbff2ab6ba02375e1ca20d9b031c",
)
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
go_rules_dependencies()
go_register_toolchains()

View File

@ -5,7 +5,7 @@
"!**/*.d.ts",
"!**/*.js",
"!**/*.[0,1,2].*",
"**/dummy.module.ts"
"!**/dummy.module.ts"
],
"tags": ["dependency", "di"]
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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"

View File

@ -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>

View File

@ -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';
}

View File

@ -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: ');
});
});

View File

@ -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"
}
}

View File

@ -0,0 +1,5 @@
{
"description": "Service Worker",
"basePath": "src/",
"tags": ["service worker"]
}

View File

@ -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>

View File

@ -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!');
}));
});

View 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}`);
}
}

View File

@ -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

View File

@ -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>

View File

@ -100,7 +100,7 @@ locale data itself.
If you want to register the imported locale data with another locale id, use the second parameter to
specify a custom locale id. For example, Angular's locale data defines the locale id for French as
"fr". You can use the second parameter to associate the imported French locale data with the custom
locale id "fr-FR instead of "fr".
locale id "fr-FR" instead of "fr".
The files in `@angular/common/locales` contain most of the locale data that you
need, but some advanced formatting options might only be available in the extra dataset that you can

View File

@ -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>

View File

@ -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&mdash;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).

View File

@ -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&mdash;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.

View File

@ -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
@ -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).

View File

@ -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.
![The text has changed to say "Bienvenue à app!"](generated/images/guide/service-worker/welcome-msg-fr.png)
<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).

View File

@ -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).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -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",

View File

@ -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');
});
});
});

View File

@ -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());
}
}

View File

@ -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": [
{

View File

@ -56,7 +56,7 @@
"~~check-env": "node scripts/check-environment",
"~~build": "ng build --target=production --environment=stable -sm",
"post~~build": "yarn sw-manifest && yarn sw-copy",
"~~update-webdriver": "webdriver-manager update --standalone false --gecko false"
"~~update-webdriver": "webdriver-manager update --standalone false --gecko false $CHROMEDRIVER_VERSION_ARG"
},
"engines": {
"node": ">=8.9.1 <9.0.0",
@ -64,31 +64,31 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^5.0.0",
"@angular/cdk": "^2.0.0-beta.12",
"@angular/common": "^5.0.0",
"@angular/compiler": "^5.0.0",
"@angular/core": "^5.0.0-rc.9",
"@angular/forms": "^5.0.0",
"@angular/http": "^5.0.0",
"@angular/material": "^2.0.0-beta.12",
"@angular/platform-browser": "^5.0.0",
"@angular/platform-browser-dynamic": "^5.0.0",
"@angular/platform-server": "^5.0.0",
"@angular/router": "^5.0.0",
"@angular/animations": "^5.1.0-beta.2",
"@angular/cdk": "^5.0.0-rc.1",
"@angular/common": "^5.1.0-beta.2",
"@angular/compiler": "^5.1.0-beta.2",
"@angular/core": "^5.1.0-beta.2",
"@angular/forms": "^5.1.0-beta.2",
"@angular/http": "^5.1.0-beta.2",
"@angular/material": "^5.0.0-rc.1",
"@angular/platform-browser": "^5.1.0-beta.2",
"@angular/platform-browser-dynamic": "^5.1.0-beta.2",
"@angular/platform-server": "^5.1.0-beta.2",
"@angular/router": "^5.1.0-beta.2",
"@angular/service-worker": "^1.0.0-beta.16",
"classlist.js": "^1.1.20150312",
"core-js": "^2.4.1",
"jasmine": "^2.6.0",
"ng-pwa-tools": "^0.0.10",
"rxjs": "^5.5.0",
"rxjs": "^5.5.2",
"tslib": "^1.7.1",
"web-animations-js": "^2.2.5",
"zone.js": "0.8.16"
},
"devDependencies": {
"@angular/cli": "^1.5.0",
"@angular/compiler-cli": "^5.0.0",
"@angular/cli": "^1.6.0-rc.0",
"@angular/compiler-cli": "^5.1.0-beta.2",
"@types/jasmine": "^2.5.52",
"@types/jasminewd2": "^2.0.3",
"@types/node": "~6.0.60",
@ -113,7 +113,7 @@
"http-server": "^0.9.0",
"ignore": "^3.3.3",
"image-size": "^0.5.1",
"jasmine-core": "^2.6.4",
"jasmine-core": "^2.8.0",
"jasmine-spec-reporter": "^4.1.0",
"jsdom": "^9.12.0",
"karma": "^1.7.0",

View File

@ -2,19 +2,19 @@
"aio": {
"master": {
"gzip7": {
"inline": 925,
"main": 119519,
"polyfills": 11863
"inline": 941,
"main": 116124,
"polyfills": 11860
},
"gzip9": {
"inline": 925,
"main": 119301,
"polyfills": 11861
"inline": 941,
"main": 115954,
"polyfills": 11858
},
"uncompressed": {
"inline": 1533,
"main": 486493,
"polyfills": 37068
"inline": 1558,
"main": 456432,
"polyfills": 37070
}
}
}

View File

@ -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" (docRendered)="onDocRendered()"></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>

View File

@ -6,12 +6,14 @@ import { HttpClient } from '@angular/common/http';
import { MatProgressBar, MatSidenav } from '@angular/material';
import { By } from '@angular/platform-browser';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { AppComponent } from './app.component';
import { AppModule } from './app.module';
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
import { Deployment } from 'app/shared/deployment.service';
import { EmbedComponentsService } from 'app/embed-components/embed-components.service';
import { GaService } from 'app/shared/ga.service';
import { LocationService } from 'app/shared/location.service';
import { Logger } from 'app/shared/logger.service';
@ -24,7 +26,7 @@ import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
import { SearchResultsComponent } from 'app/shared/search-results/search-results.component';
import { SearchService } from 'app/search/search.service';
import { SelectComponent } from 'app/shared/select/select.component';
import { TocComponent } from 'app/embedded/toc/toc.component';
import { TocComponent } from 'app/layout/toc/toc.component';
import { TocItem, TocService } from 'app/shared/toc.service';
const sideBySideBreakPoint = 992;
@ -55,13 +57,18 @@ describe('AppComponent', () => {
tocService = de.injector.get(TocService);
};
describe('with proper DocViewer', () => {
beforeEach(() => {
DocViewerComponent.animationsEnabled = false;
createTestingModule('a/b');
initializeTest();
});
afterEach(() => DocViewerComponent.animationsEnabled = true);
it('should create', () => {
expect(component).toBeDefined();
});
@ -147,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();
});
@ -184,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();
});
@ -246,25 +251,22 @@ describe('AppComponent', () => {
const sidenavBackdrop = fixture.debugElement.query(By.css('.mat-drawer-backdrop')).nativeElement;
sidenavBackdrop.click();
fixture.detectChanges();
expect(sidenav.opened).toBe(false);
expect(sidenav.opened).toBe(false);
});
it('should close when nav to another guide page', () => {
locationService.go('guide/bags');
fixture.detectChanges();
expect(sidenav.opened).toBe(false);
navigateTo('guide/bags');
expect(sidenav.opened).toBe(false);
});
it('should close when nav to api page', () => {
locationService.go('api');
fixture.detectChanges();
expect(sidenav.opened).toBe(false);
navigateTo('api');
expect(sidenav.opened).toBe(false);
});
it('should close again when nav to market page', () => {
locationService.go('features');
fixture.detectChanges();
expect(sidenav.opened).toBe(false);
navigateTo('features');
expect(sidenav.opened).toBe(false);
});
});
@ -318,96 +320,7 @@ 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', () => {
locationService.go('guide/pipes');
fixture.detectChanges();
checkHostClass('sidenav', 'open');
sidenav.close();
sidenav.onClose.next();
fixture.detectChanges();
checkHostClass('sidenav', 'closed');
sidenav.open();
sidenav.onOpen.next();
fixture.detectChanges();
checkHostClass('sidenav', 'open');
});
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');
fixture.detectChanges();
@ -447,16 +360,19 @@ describe('AppComponent', () => {
const scrollDelay = 500;
let scrollService: ScrollService;
let scrollSpy: jasmine.Spy;
let scrollToTopSpy: jasmine.Spy;
beforeEach(() => {
scrollService = fixture.debugElement.injector.get(ScrollService);
scrollSpy = spyOn(scrollService, 'scroll');
scrollToTopSpy = spyOn(scrollService, 'scrollToTop');
});
it('should not scroll immediately when the docId (path) changes', () => {
locationService.go('guide/pipes');
// deliberately not calling `fixture.detectChanges` because don't want `onDocRendered`
// deliberately not calling `fixture.detectChanges` because don't want `onDocInserted`
expect(scrollSpy).not.toHaveBeenCalled();
expect(scrollToTopSpy).not.toHaveBeenCalled();
});
it('should scroll when just the hash changes (# alone)', () => {
@ -486,7 +402,7 @@ describe('AppComponent', () => {
expect(scrollSpy).toHaveBeenCalledTimes(1);
});
it('should scroll when e-nav to the empty path', () => {
it('should scroll when re-nav to the empty path', () => {
locationService.go('');
scrollSpy.calls.reset();
@ -494,17 +410,29 @@ describe('AppComponent', () => {
expect(scrollSpy).toHaveBeenCalledTimes(1);
});
it('should scroll after a delay when call onDocRendered directly', fakeAsync(() => {
component.onDocRendered();
it('should scroll to top when call `onDocRemoved` directly', () => {
scrollToTopSpy.calls.reset();
component.onDocRemoved();
expect(scrollToTopSpy).toHaveBeenCalled();
});
it('should scroll after a delay when call `onDocInserted` directly', fakeAsync(() => {
component.onDocInserted();
expect(scrollSpy).not.toHaveBeenCalled();
tick(scrollDelay);
expect(scrollSpy).toHaveBeenCalled();
}));
it('should scroll (via onDocRendered) when finish navigating to a new doc', fakeAsync(() => {
it('should scroll (via `onDocInserted`) when finish navigating to a new doc', fakeAsync(() => {
expect(scrollToTopSpy).not.toHaveBeenCalled();
locationService.go('guide/pipes');
fixture.detectChanges(); // triggers the event that calls onDocRendered
fixture.detectChanges(); // triggers the event that calls `onDocInserted`
expect(scrollToTopSpy).toHaveBeenCalled();
expect(scrollSpy).not.toHaveBeenCalled();
tick(scrollDelay);
expect(scrollSpy).toHaveBeenCalled();
}));
@ -867,7 +795,9 @@ describe('AppComponent', () => {
describe('with mocked DocViewer', () => {
const getDocViewer = () => fixture.debugElement.query(By.css('aio-doc-viewer'));
const triggerDocRendered = () => getDocViewer().triggerEventHandler('docRendered', {});
const triggerDocViewerEvent =
(evt: 'docReady' | 'docRemoved' | 'docInserted' | 'docRendered') =>
getDocViewer().triggerEventHandler(evt, undefined);
beforeEach(() => {
createTestingModule('a/b');
@ -879,7 +809,7 @@ describe('AppComponent', () => {
});
describe('initial rendering', () => {
it('should initially add the starting class until the first document is rendered', fakeAsync(() => {
it('should initially add the starting class until a document is rendered', () => {
const getSidenavContainer = () => fixture.debugElement.query(By.css('mat-sidenav-container'));
initializeTest();
@ -887,21 +817,181 @@ describe('AppComponent', () => {
expect(component.isStarting).toBe(true);
expect(getSidenavContainer().classes['starting']).toBe(true);
triggerDocRendered();
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', () => {
@ -910,7 +1000,7 @@ describe('AppComponent', () => {
const getProgressBar = () => fixture.debugElement.query(By.directive(MatProgressBar));
const initializeAndCompleteNavigation = () => {
initializeTest();
triggerDocRendered();
triggerDocViewerEvent('docReady');
tick(HIDE_DELAY);
};
@ -947,7 +1037,7 @@ describe('AppComponent', () => {
it('should not be shown when re-navigating to the empty path', fakeAsync(() => {
initializeAndCompleteNavigation();
locationService.urlSubject.next('');
triggerDocRendered();
triggerDocViewerEvent('docReady');
locationService.urlSubject.next('');
@ -958,12 +1048,12 @@ describe('AppComponent', () => {
tick(HIDE_DELAY); // Fire the remaining timer or `fakeAsync()` complains.
}));
it('should not be shown if the doc is rendered quickly', fakeAsync(() => {
it('should not be shown if the doc is prepared quickly', fakeAsync(() => {
initializeAndCompleteNavigation();
locationService.urlSubject.next('c/d');
tick(SHOW_DELAY - 1);
triggerDocRendered();
triggerDocViewerEvent('docReady');
tick(1);
fixture.detectChanges();
@ -972,12 +1062,12 @@ describe('AppComponent', () => {
tick(HIDE_DELAY); // Fire the remaining timer or `fakeAsync()` complains.
}));
it('should be shown if rendering the doc takes too long', fakeAsync(() => {
it('should be shown if preparing the doc takes too long', fakeAsync(() => {
initializeAndCompleteNavigation();
locationService.urlSubject.next('c/d');
tick(SHOW_DELAY);
triggerDocRendered();
triggerDocViewerEvent('docReady');
fixture.detectChanges();
expect(getProgressBar()).toBeTruthy();
@ -985,12 +1075,12 @@ describe('AppComponent', () => {
tick(HIDE_DELAY); // Fire the remaining timer or `fakeAsync()` complains.
}));
it('should be hidden (after a delay) once the doc is rendered', fakeAsync(() => {
it('should be hidden (after a delay) once the doc has been prepared', fakeAsync(() => {
initializeAndCompleteNavigation();
locationService.urlSubject.next('c/d');
tick(SHOW_DELAY);
triggerDocRendered();
triggerDocViewerEvent('docReady');
fixture.detectChanges();
expect(getProgressBar()).toBeTruthy();
@ -1007,10 +1097,10 @@ describe('AppComponent', () => {
it('should only take the latest request into account', fakeAsync(() => {
initializeAndCompleteNavigation();
locationService.urlSubject.next('c/d'); // The URL changes.
locationService.urlSubject.next('e/f'); // The URL changes again before `onDocRendered()`.
locationService.urlSubject.next('e/f'); // The URL changes again before `onDocReady()`.
tick(SHOW_DELAY - 1); // `onDocRendered()` is triggered (for the last doc),
triggerDocRendered(); // 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();
@ -1033,6 +1123,7 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') {
imports: [ AppModule ],
providers: [
{ provide: APP_BASE_HREF, useValue: '/' },
{ provide: EmbedComponentsService, useClass: TestEmbedComponentsService },
{ provide: GaService, useClass: TestGaService },
{ provide: HttpClient, useClass: TestHttpClient },
{ provide: LocationService, useFactory: () => mockLocationService },
@ -1047,6 +1138,10 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') {
});
}
class TestEmbedComponentsService {
embedInto = jasmine.createSpy('embedInto').and.returnValue(of([]));
}
class TestGaService {
locationChanged = jasmine.createSpy('locationChanged');
}

View File

@ -1,10 +1,9 @@
import { Component, ElementRef, HostBinding, HostListener, OnInit,
QueryList, ViewChild, ViewChildren } from '@angular/core';
import { MatSidenav } from '@angular/material';
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(
@ -203,20 +182,41 @@ export class AppComponent implements OnInit {
this.scrollService.scroll();
}
onDocRendered() {
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);
// Put page in a clean visual state
this.scrollService.scrollToTop();
// If progress bar has been shown, keep it for at least 500ms (to avoid flashing).
setTimeout(() => this.isFetching = false, 500);
}
// Scroll 500ms after the doc-viewer has finished rendering the new doc
// The delay is to allow time for async layout to complete
onDocRemoved() {
// The previous document has been removed.
// Scroll to top to restore a clean visual state for the new document.
this.scrollService.scrollToTop();
}
onDocInserted() {
// TODO: Find a better way to avoid `ExpressionChangedAfterItHasBeenChecked` error.
setTimeout(() => {
this.autoScroll();
this.isStarting = false;
this.isFetching = false;
}, 500);
// 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) {
@ -283,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() {
@ -291,7 +312,7 @@ export class AppComponent implements OnInit {
const el = this.hostElement.nativeElement as Element;
this.tocMaxHeightOffset =
el.querySelector('footer').clientHeight +
el.querySelector('mat-toolbar.app-toolbar').clientHeight +
el.querySelector('.app-toolbar').clientHeight +
24; // fudge margin
}

View File

@ -0,0 +1,48 @@
import { TestBed } from '@angular/core/testing';
import { AppModule } from 'app/app.module';
import { ComponentsOrModulePath, EMBEDDED_COMPONENTS } from 'app/embed-components/embed-components.service';
import { embeddedComponents } from 'app/embedded/embedded.module';
describe('AppModule', () => {
let componentsMap: {[multiSelectorstring: string]: ComponentsOrModulePath};
beforeEach(() => {
TestBed.configureTestingModule({imports: [AppModule]});
componentsMap = TestBed.get(EMBEDDED_COMPONENTS);
});
it('should provide a map of selectors to embedded components (or module)', () => {
const allSelectors = Object.keys(componentsMap);
expect(allSelectors.length).toBeGreaterThan(1);
allSelectors.forEach(selector => {
const value = componentsMap[selector];
const isArrayOrString = Array.isArray(value) || (typeof value === 'string');
expect(isArrayOrString).toBe(true);
});
});
it('should provide a list of eagerly-loaded embedded components', () => {
const eagerSelector = Object.keys(componentsMap).find(selector => Array.isArray(componentsMap[selector]));
const selectorCount = eagerSelector.split(',').length;
expect(eagerSelector).not.toBeNull();
expect(selectorCount).toBe(componentsMap[eagerSelector].length);
// For example...
expect(eagerSelector).toContain('aio-toc');
});
it('should provide a list of lazy-loaded embedded components', () => {
const lazySelector = Object.keys(componentsMap).find(selector => selector.includes('code-example'));
const selectorCount = lazySelector.split(',').length;
expect(lazySelector).not.toBeNull();
expect(selectorCount).toBe(embeddedComponents.length);
// For example...
expect(lazySelector).toContain('code-example');
expect(lazySelector).toContain('code-tabs');
expect(lazySelector).toContain('live-example');
});
});

View File

@ -5,36 +5,22 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { Location, LocationStrategy, PathLocationStrategy } from '@angular/common';
import {
MatButtonModule,
MatIconModule,
MatIconRegistry,
MatInputModule,
MatProgressBarModule,
MatSidenavModule,
MatTabsModule,
MatToolbarModule
} from '@angular/material';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule, MatIconRegistry } from '@angular/material/icon';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatToolbarModule } from '@angular/material/toolbar';
import {
Platform
} from '@angular/cdk/platform';
import { ROUTES } from '@angular/router';
// Temporary fix for MatSidenavModule issue:
// crashes with "missing first" operator when SideNav.mode is "over"
import 'rxjs/add/operator/first';
import { SwUpdatesModule } from 'app/sw-updates/sw-updates.module';
import { AppComponent } from 'app/app.component';
import { ApiService } from 'app/embedded/api/api.service';
import { EMBEDDED_COMPONENTS, EmbeddedComponentsMap } from 'app/embed-components/embed-components.service';
import { CustomIconRegistry, SVG_ICONS } from 'app/shared/custom-icon-registry';
import { Deployment } from 'app/shared/deployment.service';
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
import { DtComponent } from 'app/layout/doc-viewer/dt.component';
import { ModeBannerComponent } from 'app/layout/mode-banner/mode-banner.component';
import { EmbeddedModule } from 'app/embedded/embedded.module';
import { GaService } from 'app/shared/ga.service';
import { Logger } from 'app/shared/logger.service';
import { LocationService } from 'app/shared/location.service';
@ -47,11 +33,18 @@ import { NavMenuComponent } from 'app/layout/nav-menu/nav-menu.component';
import { NavItemComponent } from 'app/layout/nav-item/nav-item.component';
import { ScrollService } from 'app/shared/scroll.service';
import { ScrollSpyService } from 'app/shared/scroll-spy.service';
import { SearchBoxComponent } from './search/search-box/search-box.component';
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
import { TocComponent } from 'app/layout/toc/toc.component';
import { TocService } from 'app/shared/toc.service';
import { WindowToken, windowProvider } from 'app/shared/window';
import { EmbedComponentsModule } from 'app/embed-components/embed-components.module';
import { SharedModule } from 'app/shared/shared.module';
import { SwUpdatesModule } from 'app/sw-updates/sw-updates.module';
// The path to the `EmbeddedModule`.
const embeddedModulePath = 'app/embedded/embedded.module#EmbeddedModule';
// These are the hardcoded inline svg sources to be used by the `<mat-icon>` component
export const svgIconProviders = [
@ -78,15 +71,13 @@ export const svgIconProviders = [
@NgModule({
imports: [
BrowserModule,
EmbeddedModule,
HttpClientModule,
BrowserAnimationsModule,
EmbedComponentsModule,
HttpClientModule,
MatButtonModule,
MatIconModule,
MatInputModule,
MatProgressBarModule,
MatSidenavModule,
MatTabsModule,
MatToolbarModule,
SwUpdatesModule,
SharedModule
@ -100,10 +91,10 @@ export const svgIconProviders = [
NavMenuComponent,
NavItemComponent,
SearchBoxComponent,
TocComponent,
TopMenuComponent,
],
providers: [
ApiService,
Deployment,
DocumentService,
GaService,
@ -113,15 +104,32 @@ export const svgIconProviders = [
LocationService,
{ provide: MatIconRegistry, useClass: CustomIconRegistry },
NavigationService,
Platform,
ScrollService,
ScrollSpyService,
SearchService,
svgIconProviders,
TocService,
{ provide: WindowToken, useFactory: windowProvider },
{
provide: EMBEDDED_COMPONENTS,
useValue: {
/* tslint:disable: max-line-length */
'aio-toc': [TocComponent],
'aio-api-list, aio-contributor-list, aio-file-not-found-search, aio-resource-list, code-example, code-tabs, current-location, live-example': embeddedModulePath,
/* tslint:enable: max-line-length */
} as EmbeddedComponentsMap,
},
{
// This is currently the only way to get `@angular/cli`
// to split `EmbeddedModule` into a separate chunk :(
provide: ROUTES,
useValue: [{ path: '/embedded', loadChildren: embeddedModulePath }],
multi: true,
},
],
bootstrap: [AppComponent]
entryComponents: [ TocComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule {
}

View File

@ -0,0 +1,13 @@
import { NgModule, NgModuleFactoryLoader, SystemJsNgModuleLoader } from '@angular/core';
import { EmbedComponentsService } from './embed-components.service';
@NgModule({
providers: [
EmbedComponentsService,
{ provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader },
],
})
export class EmbedComponentsModule {
}

View File

@ -0,0 +1,378 @@
import { ComponentFactory, ComponentFactoryResolver, ComponentRef, NgModuleFactoryLoader } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import {
MockNgModuleFactoryLoader, TestEmbedComponentsService, TestModule, mockEmbeddedModulePath,
testEagerEmbeddedComponents, testEagerEmbeddedSelectors, testLazyEmbeddedComponents
} from 'testing/embed-components-utils';
import { EmbedComponentsService, ComponentsOrModulePath } from './embed-components.service';
describe('EmbedComponentsService', () => {
let service: TestEmbedComponentsService;
let host: HTMLElement;
beforeEach(() => {
TestBed.configureTestingModule({imports: [TestModule]});
service = TestBed.get(EmbedComponentsService);
host = document.createElement('div');
});
it('should be instantiated', () => {
expect(service).toEqual(jasmine.any(EmbedComponentsService));
});
describe('#createComponentFactories()', () => {
let factories: typeof service.componentFactories;
let resolver: ComponentFactoryResolver;
const doCreateComponentFactories = () =>
service.createComponentFactories(testEagerEmbeddedComponents, resolver);
beforeEach(() => {
factories = service.componentFactories;
resolver = TestBed.get(ComponentFactoryResolver) as ComponentFactoryResolver;
});
it('should create a factory entry for each component', () => {
expect(factories.size).toBe(0);
doCreateComponentFactories();
expect(factories.size).toBe(testEagerEmbeddedComponents.length);
});
it('should key the factory entries by selector', () => {
doCreateComponentFactories();
const actualSelectors = Array.from(factories.keys());
const expectedSelectors = testEagerEmbeddedSelectors;
expect(actualSelectors).toEqual(expectedSelectors);
});
it('should store the projected content property name', () => {
doCreateComponentFactories();
const actualContentPropNames = Array.from(factories.values()).map(x => x.contentPropertyName);
const expectedContentPropNames = testEagerEmbeddedSelectors.map(x => service.selectorToContentPropertyName(x));
expect(actualContentPropNames).toEqual(expectedContentPropNames);
});
it('should store the factory for each component', () => {
doCreateComponentFactories();
const actualFactories = Array.from(factories.values()).map(x => x.factory);
const expectedComponentTypes = testEagerEmbeddedComponents;
actualFactories.forEach((factory, i) => {
expect(factory).toEqual(jasmine.any(ComponentFactory));
expect(factory.componentType).toBe(expectedComponentTypes[i]);
});
});
});
describe('#createComponents()', () => {
const FooComponent = testEagerEmbeddedComponents[0];
const BarComponent = testEagerEmbeddedComponents[1];
beforeEach(() => service.prepareComponentFactories(testEagerEmbeddedComponents));
it('should apply all embedded components (and return the `ComponentRef`s)', () => {
host.innerHTML = `
<p>Header</p>
<p><aio-eager-foo></aio-eager-foo></p>
<p><aio-eager-bar></aio-eager-bar></p>
<p>Footer</p>
`;
const componentRefs = service.createComponents(host);
expect(host.innerHTML).toContain('Foo Component');
expect(host.innerHTML).toContain('Bar Component');
expect(componentRefs.length).toBe(2);
expect(componentRefs[0].instance).toEqual(jasmine.any(FooComponent));
expect(componentRefs[1].instance).toEqual(jasmine.any(BarComponent));
});
it('should apply embedded components to all matching elements', () => {
host.innerHTML = `
<p>Header</p>
<p><aio-eager-foo></aio-eager-foo></p>
<p><aio-eager-bar></aio-eager-bar></p>
<p><aio-eager-foo></aio-eager-foo></p>
<p><aio-eager-bar></aio-eager-bar></p>
<p>Footer</p>
`;
const componentRefs = service.createComponents(host);
expect(componentRefs.length).toBe(4);
expect(componentRefs[0].instance).toEqual(jasmine.any(FooComponent));
expect(componentRefs[1].instance).toEqual(jasmine.any(FooComponent));
expect(componentRefs[2].instance).toEqual(jasmine.any(BarComponent));
expect(componentRefs[3].instance).toEqual(jasmine.any(BarComponent));
});
it('should allow projecting content by assigning it on the element', () => {
const projectedContent = 'Projected content';
host.innerHTML = `
<p>Header</p>
<p><aio-eager-bar>${projectedContent}</aio-eager-bar></p>
<p>Footer</p>
`;
const componentRefs = service.createComponents(host);
componentRefs[0].changeDetectorRef.detectChanges();
const barEl = host.querySelector('aio-eager-bar');
expect(barEl['aioEagerBarContent']).toBe(projectedContent);
expect(barEl.innerHTML).toContain(projectedContent);
});
// Because `FooComponent` is processed before `BarComponent`...
it('should apply `FooComponent` within `BarComponent`', () => {
host.innerHTML = `
<aio-eager-bar>
<aio-eager-foo></aio-eager-foo>
</aio-eager-bar>
`;
const componentRefs = service.createComponents(host);
componentRefs.forEach(ref => ref.changeDetectorRef.detectChanges());
expect(host.innerHTML).toContain('Foo Component');
expect(host.innerHTML).toContain('Bar Component');
expect(componentRefs.length).toBe(2);
expect(componentRefs[0].instance).toEqual(jasmine.any(FooComponent));
expect(componentRefs[1].instance).toEqual(jasmine.any(BarComponent));
});
// Because `BarComponent` is processed after `FooComponent`...
it('should not apply `BarComponent` within `FooComponent`', () => {
host.innerHTML = `
<aio-eager-foo>
<aio-eager-bar></aio-eager-bar>
</aio-eager-foo>
`;
const componentRefs = service.createComponents(host);
componentRefs.forEach(ref => ref.changeDetectorRef.detectChanges());
expect(host.innerHTML).toContain('Foo Component');
expect(host.innerHTML).not.toContain('Bar Component');
expect(componentRefs.length).toBe(1);
expect(componentRefs[0].instance).toEqual(jasmine.any(FooComponent));
});
});
describe('#embedInto()', () => {
let mockComponentRefs: ComponentRef<any>[];
let createComponentsSpy: jasmine.Spy;
let prepareComponentFactoriesSpy: jasmine.Spy;
const doEmbed = (contents: string) =>
new Promise<ComponentRef<any>[]>((resolve, reject) => {
host.innerHTML = contents;
service.embedInto(host).subscribe(resolve, reject);
});
beforeEach(() => {
mockComponentRefs = [{foo: true}, {bar: true}] as any as ComponentRef<any>[];
createComponentsSpy = spyOn(service, 'createComponents').and.returnValue(mockComponentRefs);
prepareComponentFactoriesSpy = spyOn(service, 'prepareComponentFactories')
.and.returnValue(Promise.resolve());
});
it('should return an observable', done => {
service.embedInto(host).subscribe(done, done.fail);
});
describe('(preparing component factories)', () => {
it('should return an array of `ComponentRef`s', async () => {
// When there are embedded components.
expect(await doEmbed('<aio-eager-foo></aio-eager-foo>')).toEqual(mockComponentRefs);
expect(await doEmbed('<aio-lazy-bar></aio-lazy-bar>')).toEqual(mockComponentRefs);
// When there are no embedded components.
expect(await doEmbed('<div>Test</div>')).toEqual([]);
expect(await doEmbed('')).toEqual([]);
});
it('should prepare all component factories if there are embedded components', async () => {
await doEmbed(`
<div><aio-eager-foo><b>foo</b></aio-eager-foo></div>
<span><aio-lazy-foo><i>bar</i></aio-lazy-foo></span>
`);
expect(prepareComponentFactoriesSpy).toHaveBeenCalledTimes(2);
expect(prepareComponentFactoriesSpy).toHaveBeenCalledWith(testEagerEmbeddedComponents);
expect(prepareComponentFactoriesSpy).toHaveBeenCalledWith(mockEmbeddedModulePath);
});
it('should only prepare the necessary factories', async () => {
await doEmbed('<aio-eager-foo>Eager only</aio-eager-foo>');
expect(prepareComponentFactoriesSpy).toHaveBeenCalledTimes(1);
expect(prepareComponentFactoriesSpy).toHaveBeenCalledWith(testEagerEmbeddedComponents);
await doEmbed('<aio-lazy-foo>Lazy only</aio-lazy-foo>');
expect(prepareComponentFactoriesSpy).toHaveBeenCalledTimes(2);
expect(prepareComponentFactoriesSpy).toHaveBeenCalledWith(mockEmbeddedModulePath);
});
it('should not load embedded components if the document does not contain any', async () => {
await doEmbed('');
await doEmbed('<no-aio-eager-foo></no-aio-eager-foo>');
await doEmbed('<no-aio-lazy-foo></no-aio-lazy-foo>');
expect(prepareComponentFactoriesSpy).not.toHaveBeenCalled();
});
});
describe('(creating embedded components)', () => {
it('should create embedded components if the element contains any', async () => {
await doEmbed('<div><aio-eager-foo><i>blah</i></aio-eager-foo></div>');
expect(createComponentsSpy).toHaveBeenCalledTimes(1);
expect(prepareComponentFactoriesSpy).toHaveBeenCalledBefore(createComponentsSpy);
prepareComponentFactoriesSpy.calls.reset();
createComponentsSpy.calls.reset();
await doEmbed('<aio-lazy-bar><i>blah</i></aio-lazy-bar>');
expect(createComponentsSpy).toHaveBeenCalledTimes(1);
expect(prepareComponentFactoriesSpy).toHaveBeenCalledBefore(createComponentsSpy);
});
it('should emit the created embedded components', async () => {
const componentRefs = await doEmbed('<aio-eager-foo></aio-eager-foo>');
expect(componentRefs).toBe(mockComponentRefs);
});
it('should not create embedded components if the element does not contain any', async () => {
await doEmbed(`
<aio-eager-foo-not></aio-eager-foo-not>
&lt;aio-lazy-bar&gt;&lt;/aio-lazy-bar&gt;
`);
expect(createComponentsSpy).not.toHaveBeenCalled();
});
it('should not create embedded components if the document is empty', async () => {
await doEmbed('');
expect(createComponentsSpy).not.toHaveBeenCalled();
});
it('should not create embedded components if unsubscribed from', async () => {
const preparePromise = Promise.resolve();
prepareComponentFactoriesSpy.and.returnValue(preparePromise);
// When not unsubscribed from...
host.innerHTML = '<aio-eager-foo></aio-eager-foo>';
service.embedInto(host).subscribe();
await new Promise(resolve => setTimeout(resolve));
expect(createComponentsSpy).toHaveBeenCalledTimes(1);
createComponentsSpy.calls.reset();
// When unsubscribed from...
host.innerHTML = '<aio-eager-foo></aio-eager-foo>';
service.embedInto(host).subscribe().unsubscribe();
await new Promise(resolve => setTimeout(resolve));
expect(createComponentsSpy).not.toHaveBeenCalled();
});
});
});
describe('#prepareComponentFactories()', () => {
let loader: MockNgModuleFactoryLoader;
let resolver: ComponentFactoryResolver;
let createComponentFactoriesSpy: jasmine.Spy;
beforeEach(() => {
loader = TestBed.get(NgModuleFactoryLoader);
resolver = TestBed.get(ComponentFactoryResolver);
createComponentFactoriesSpy = spyOn(service, 'createComponentFactories');
});
[testLazyEmbeddedComponents, mockEmbeddedModulePath].forEach((compsOrPath: ComponentsOrModulePath) => {
const useComponents = Array.isArray(compsOrPath);
describe(`(using ${useComponents ? 'component types' : 'module path'})`, () => {
const doPrepareComponentFactories = () =>
service.prepareComponentFactories(compsOrPath);
it('should return a promise', done => {
doPrepareComponentFactories().then(done, done.fail);
});
it('should create the component factories', async () => {
expect(createComponentFactoriesSpy).not.toHaveBeenCalled();
await doPrepareComponentFactories();
expect(createComponentFactoriesSpy).toHaveBeenCalledTimes(1);
const args = createComponentFactoriesSpy.calls.mostRecent().args;
expect(args[0]).toBe(testLazyEmbeddedComponents);
if (useComponents) {
expect(args[1]).toBe(resolver);
} else {
expect(args[1]).not.toBe(resolver);
}
});
it('should not create create the component factories more than once', async () => {
const results = await Promise.all([
doPrepareComponentFactories(),
doPrepareComponentFactories(),
]);
expect(createComponentFactoriesSpy).toHaveBeenCalledTimes(1);
expect(results[1]).toBe(results[0]);
const anotherResult = await doPrepareComponentFactories();
expect(createComponentFactoriesSpy).toHaveBeenCalledTimes(1);
expect(anotherResult).toBe(results[0]);
});
it(`should ${useComponents ? 'not load' : 'load'} the embedded module`, async () => {
expect(loader.loadedPaths).toEqual([]);
await doPrepareComponentFactories();
const expectedLoadedPaths = useComponents ? [] : [mockEmbeddedModulePath];
expect(loader.loadedPaths).toEqual(expectedLoadedPaths);
});
it(`should not load the embedded module more than once`, async () => {
await Promise.all([
doPrepareComponentFactories(),
doPrepareComponentFactories(),
]);
const loadedPathCount = loader.loadedPaths.length;
expect(loadedPathCount).toBeLessThan(2);
await doPrepareComponentFactories();
expect(loader.loadedPaths.length).toBe(loadedPathCount);
});
});
});
});
describe('#selectorToContentPropertyName()', () => {
it('should convert an element selector to a property name', () => {
expect(service.selectorToContentPropertyName('foobar')).toBe('foobarContent');
expect(service.selectorToContentPropertyName('baz-qux')).toBe('bazQuxContent');
});
});
});

View File

@ -0,0 +1,154 @@
import {
ComponentFactory, ComponentFactoryResolver, ComponentRef, Inject, Injectable, InjectionToken,
Injector, NgModuleFactory, NgModuleFactoryLoader, Type
} from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import 'rxjs/add/operator/switchMap';
export interface EmbeddedComponentFactory {
contentPropertyName: string;
factory: ComponentFactory<any>;
}
/**
* A mapping from combined component selectors (keys) to the corresponding components (values). The
* components can be specified either as a list of embedded components or a path to a module that
* provides embedded components (i.e. implements `WithEmbeddedComponents`).
*/
export interface EmbeddedComponentsMap {
[multiSelector: string]: ComponentsOrModulePath;
}
/**
* Interface expected to be implemented by all modules that contribute components to the
* `EmbeddedComponentsMap`.
*/
export interface WithEmbeddedComponents {
embeddedComponents: Type<any>[];
}
/**
* Either an array of components or the path to a module that implements `WithEmbeddedComponents`.
*/
export type ComponentsOrModulePath = Type<any>[] | string;
/**
* The injection token for the `EmbeddedComponentsMap`.
*/
export const EMBEDDED_COMPONENTS = new InjectionToken<EmbeddedComponentsMap>('EMBEDDED_COMPONENTS');
/**
* Embed components into an element. It takes care of indentifying the embedded components, loading
* the necessary modules and instantiating the components.
*
* Embeddable components are identified and loaded based on the info in `EmbeddedComponentsMap`
* (provided through dependency injection).
*
* The caller is responsible for trigering change detection and destroying the components as
* necessary.
*/
@Injectable()
export class EmbedComponentsService {
private componentFactoriesReady = new Map<ComponentsOrModulePath, Promise<void>>();
protected componentFactories = new Map<string, EmbeddedComponentFactory>();
constructor(
private injector: Injector,
private loader: NgModuleFactoryLoader,
private resolver: ComponentFactoryResolver,
@Inject(EMBEDDED_COMPONENTS) private embeddedComponentsMap: EmbeddedComponentsMap) { }
/**
* Embed components into the specified element:
* - Load the necessary modules (if any).
* - Prepare the component factories.
* - Instantiate the components.
*
* Return the list of `ComponentRef`s.
*/
embedInto(elem: HTMLElement): Observable<ComponentRef<any>[]> {
const requiredComponents = Object.keys(this.embeddedComponentsMap)
.filter(selector => elem.querySelector(selector))
.map(selector => this.embeddedComponentsMap[selector]);
const factoriesReady = requiredComponents.map(compsOrPath => this.prepareComponentFactories(compsOrPath));
return !requiredComponents.length
? of([])
: of(undefined)
.switchMap(() => Promise.all(factoriesReady))
.switchMap(() => [this.createComponents(elem)]);
}
/**
* Resolve the embedded component factories (which will later be used to instantiate components).
*/
protected createComponentFactories(components: Type<any>[], resolver: ComponentFactoryResolver): void {
for (const comp of components) {
const factory = resolver.resolveComponentFactory(comp);
const selector = factory.selector;
const contentPropertyName = this.selectorToContentPropertyName(selector);
this.componentFactories.set(selector, {contentPropertyName, factory});
}
}
/**
* Instantiate embedded components for the current contents of `elem`.
* (Store the original HTML contents of each element on the corresponding property for later
* retrieval by the component instance.)
*/
protected createComponents(elem: HTMLElement): ComponentRef<any>[] {
const componentRefs: ComponentRef<any>[] = [];
this.componentFactories.forEach(({contentPropertyName, factory}, selector) => {
const componentHosts = elem.querySelectorAll(selector);
// Cast due to https://github.com/Microsoft/TypeScript/issues/4947.
for (const host of componentHosts as any as HTMLElement[]) {
// Hack: Preserve the current element content, because the factory will empty it out.
// Security: The source of this `innerHTML` should always be authored by the documentation
// team and is considered to be safe.
host[contentPropertyName] = host.innerHTML;
componentRefs.push(factory.create(this.injector, [], host));
}
});
return componentRefs;
}
/**
* Prepare the component factories for the given components.
* If necessary, load and instantiate the module first.
*/
protected prepareComponentFactories(compsOrPath: ComponentsOrModulePath): Promise<void> {
if (!this.componentFactoriesReady.has(compsOrPath)) {
const componentsAndResolverPromise = (typeof compsOrPath !== 'string')
? Promise.resolve({components: compsOrPath, resolver: this.resolver})
: this.loader.load(compsOrPath).then((ngModuleFactory: NgModuleFactory<WithEmbeddedComponents>) => {
const moduleRef = ngModuleFactory.create(this.injector);
return {
components: moduleRef.instance.embeddedComponents,
resolver: moduleRef.componentFactoryResolver,
};
});
const readyPromise = componentsAndResolverPromise
.then(({components, resolver}) => this.createComponentFactories(components, resolver));
this.componentFactoriesReady.set(compsOrPath, readyPromise);
}
return this.componentFactoriesReady.get(compsOrPath);
}
/**
* Compute the component content property name by converting the selector to camelCase and
* appending `Content`, e.g. `live-example` => `liveExampleContent`.
*/
protected selectorToContentPropertyName(selector: string): string {
return selector.replace(/-(.)/g, (match, $1) => $1.toUpperCase()) + 'Content';
}
}

View File

@ -2,7 +2,7 @@ import { Component, ElementRef, ViewChild, OnChanges, Input } from '@angular/cor
import { Logger } from 'app/shared/logger.service';
import { PrettyPrinter } from './pretty-printer.service';
import { CopierService } from 'app/shared/copier.service';
import { MatSnackBar } from '@angular/material';
import { MatSnackBar } from '@angular/material/snack-bar';
const defaultLineNumsCount = 10; // by default, show linenums over this number

View File

@ -1,20 +1,24 @@
import { NgModule } from '@angular/core';
import { NgModule, Type } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ContributorService } from './contributor/contributor.service';
import { CopierService } from 'app/shared/copier.service';
import { PrettyPrinter } from './code/pretty-printer.service';
import { WithEmbeddedComponents } from 'app/embed-components/embed-components.service';
// Any components that we want to use inside embedded components must be declared or imported here
// It is not enough just to import them inside the AppModule
// Reusable components (used inside embedded components)
import { MatIconModule, MatSnackBarModule, MatTabsModule } from '@angular/material';
import { MatIconModule } from '@angular/material/icon';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatTabsModule } from '@angular/material/tabs';
import { CodeComponent } from './code/code.component';
import { SharedModule } from 'app/shared/shared.module';
// Embedded Components
import { ApiListComponent } from './api/api-list.component';
import { ApiService } from './api/api.service';
import { CodeExampleComponent } from './code/code-example.component';
import { CodeTabsComponent } from './code/code-tabs.component';
import { ContributorListComponent } from './contributor/contributor-list.component';
@ -24,22 +28,16 @@ import { FileNotFoundSearchComponent } from './search/file-not-found-search.comp
import { LiveExampleComponent, EmbeddedPlunkerComponent } from './live-example/live-example.component';
import { ResourceListComponent } from './resource/resource-list.component';
import { ResourceService } from './resource/resource.service';
import { TocComponent } from './toc/toc.component';
/** Components that can be embedded in docs
/**
* Components that can be embedded in docs,
* such as CodeExampleComponent, LiveExampleComponent,...
*/
export const embeddedComponents: any[] = [
export const embeddedComponents: Type<any>[] = [
ApiListComponent, CodeExampleComponent, CodeTabsComponent, ContributorListComponent,
CurrentLocationComponent, FileNotFoundSearchComponent, LiveExampleComponent, ResourceListComponent,
TocComponent
CurrentLocationComponent, FileNotFoundSearchComponent, LiveExampleComponent, ResourceListComponent
];
/** Injectable class w/ property returning components that can be embedded in docs */
export class EmbeddedComponents {
components = embeddedComponents;
}
@NgModule({
imports: [
CommonModule,
@ -54,16 +52,15 @@ export class EmbeddedComponents {
ContributorComponent,
EmbeddedPlunkerComponent
],
exports: [
TocComponent
],
providers: [
ApiService,
ContributorService,
CopierService,
EmbeddedComponents,
PrettyPrinter,
ResourceService
],
entryComponents: [ embeddedComponents ]
})
export class EmbeddedModule { }
export class EmbeddedModule implements WithEmbeddedComponents {
embeddedComponents = embeddedComponents;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,22 @@
import {
Component, ComponentFactory, ComponentFactoryResolver, ComponentRef,
DoCheck, ElementRef, EventEmitter, Injector, Input, OnDestroy,
Output
} from '@angular/core';
import { EmbeddedComponents } from 'app/embedded/embedded.module';
import { DocumentContents } from 'app/documents/document.service';
import { Component, ComponentRef, DoCheck, ElementRef, EventEmitter, Input, OnDestroy, Output } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { timer } from 'rxjs/observable/timer';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/takeUntil';
import { DocumentContents } from 'app/documents/document.service';
import { EmbedComponentsService } from 'app/embed-components/embed-components.service';
import { Logger } from 'app/shared/logger.service';
import { TocService } from 'app/shared/toc.service';
interface EmbeddedComponentFactory {
contentPropertyName: string;
factory: ComponentFactory<any>;
}
// Constants
export const NO_ANIMATIONS = 'no-animations';
// Initialization prevents flicker once pre-rendering is on
const initialDocViewerElement = document.querySelector('aio-doc-viewer');
@ -25,19 +29,48 @@ const initialDocViewerContent = initialDocViewerElement ? initialDocViewerElemen
// encapsulation: ViewEncapsulation.Native
})
export class DocViewerComponent implements DoCheck, OnDestroy {
// Enable/Disable view transition animations.
static animationsEnabled = true;
private embeddedComponents: ComponentRef<any>[] = [];
private embeddedComponentFactories: Map<string, EmbeddedComponentFactory> = new Map();
private hostElement: HTMLElement;
@Output()
docRendered = new EventEmitter();
private void$ = of<void>(undefined);
private onDestroy$ = new EventEmitter<void>();
private docContents$ = new EventEmitter<DocumentContents>();
protected embeddedComponentRefs: ComponentRef<any>[] = [];
protected currViewContainer: HTMLElement = document.createElement('div');
protected nextViewContainer: HTMLElement = document.createElement('div');
@Input()
set doc(newDoc: DocumentContents) {
// Ignore `undefined` values that could happen if the host component
// does not initially specify a value for the `doc` input.
if (newDoc) {
this.docContents$.emit(newDoc);
}
}
// The new document is ready to be inserted into the viewer.
// (Embedded components have been loaded and instantiated, if necessary.)
@Output() docReady = new EventEmitter<void>();
// The previous document has been removed from the viewer.
// (The leaving animation (if any) has been completed and the node has been removed from the DOM.)
@Output() docRemoved = new EventEmitter<void>();
// The new document has been inserted into the viewer.
// (The node has been inserted into the DOM, but the entering animation may still be in progress.)
@Output() docInserted = new EventEmitter<void>();
// The new document has been fully rendered into the viewer.
// (The entering animation has been completed.)
@Output() docRendered = new EventEmitter<void>();
constructor(
componentFactoryResolver: ComponentFactoryResolver,
elementRef: ElementRef,
embeddedComponents: EmbeddedComponents,
private injector: Injector,
private embedComponentsService: EmbedComponentsService,
private logger: Logger,
private titleService: Title,
private tocService: TocService
) {
@ -45,84 +78,157 @@ export class DocViewerComponent implements DoCheck, OnDestroy {
// Security: the initialDocViewerContent comes from the prerendered DOM and is considered to be secure
this.hostElement.innerHTML = initialDocViewerContent;
for (const component of embeddedComponents.components) {
const factory = componentFactoryResolver.resolveComponentFactory(component);
const selector = factory.selector;
const contentPropertyName = this.selectorToContentPropertyName(selector);
this.embeddedComponentFactories.set(selector, { contentPropertyName, factory });
}
}
@Input()
set doc(newDoc: DocumentContents) {
this.ngOnDestroy();
if (newDoc) {
this.build(newDoc);
this.docRendered.emit();
}
}
/**
* Add doc content to host element and build it out with embedded components
*/
private build(doc: DocumentContents) {
// security: the doc.content is always authored by the documentation team
// and is considered to be safe
this.hostElement.innerHTML = doc.contents || '';
if (!doc.contents) { return; }
this.addTitleAndToc(doc.id);
// TODO(i): why can't I use for-of? why doesn't typescript like Map#value() iterators?
this.embeddedComponentFactories.forEach(({ contentPropertyName, factory }, selector) => {
const embeddedComponentElements = this.hostElement.querySelectorAll(selector);
// cast due to https://github.com/Microsoft/TypeScript/issues/4947
for (const element of embeddedComponentElements as any as HTMLElement[]){
// hack: preserve the current element content because the factory will empty it out
// security: the source of this innerHTML is always authored by the documentation team
// and is considered to be safe
element[contentPropertyName] = element.innerHTML;
this.embeddedComponents.push(factory.create(this.injector, [], element));
}
});
}
private addTitleAndToc(docId: string) {
this.tocService.reset();
const titleEl = this.hostElement.querySelector('h1');
let title = '';
// Only create TOC for docs with an <h1> title
// If you don't want a TOC, add "no-toc" class to <h1>
if (titleEl) {
title = (typeof titleEl.innerText === 'string') ? titleEl.innerText : titleEl.textContent;
if (!/(no-toc|notoc)/i.test(titleEl.className)) {
this.tocService.genToc(this.hostElement, docId);
titleEl.insertAdjacentHTML('afterend', '<aio-toc class="embedded"></aio-toc>');
}
if (this.hostElement.firstElementChild) {
this.currViewContainer = this.hostElement.firstElementChild as HTMLElement;
}
this.titleService.setTitle(title ? `Angular - ${title}` : 'Angular');
this.onDestroy$.subscribe(() => this.destroyEmbeddedComponents());
this.docContents$
.switchMap(newDoc => this.render(newDoc))
.takeUntil(this.onDestroy$)
.subscribe();
}
ngDoCheck() {
this.embeddedComponents.forEach(comp => comp.changeDetectorRef.detectChanges());
this.embeddedComponentRefs.forEach(comp => comp.changeDetectorRef.detectChanges());
}
ngOnDestroy() {
// destroy these components else there will be memory leaks
this.embeddedComponents.forEach(comp => comp.destroy());
this.embeddedComponents.length = 0;
this.onDestroy$.emit();
}
/**
* Compute the component content property name by converting the selector to camelCase and appending
* 'Content', e.g. live-example => liveExampleContent
* Destroy the embedded components to avoid memory leaks.
*/
private selectorToContentPropertyName(selector: string) {
return selector.replace(/-(.)/g, (match, $1) => $1.toUpperCase()) + 'Content';
protected destroyEmbeddedComponents(): void {
this.embeddedComponentRefs.forEach(comp => comp.destroy());
this.embeddedComponentRefs = [];
}
/**
* Prepare for setting the window title and ToC.
* Return a function to actually set them.
*/
protected prepareTitleAndToc(targetElem: HTMLElement, docId: string): () => void {
const titleEl = targetElem.querySelector('h1');
const hasToc = !!titleEl && !/no-?toc/i.test(titleEl.className);
if (hasToc) {
titleEl.insertAdjacentHTML('afterend', '<aio-toc class="embedded"></aio-toc>');
}
return () => {
this.tocService.reset();
let title = '';
// Only create ToC for docs with an `<h1>` heading.
// If you don't want a ToC, add "no-toc" class to `<h1>`.
if (titleEl) {
title = (typeof titleEl.innerText === 'string') ? titleEl.innerText : titleEl.textContent;
if (hasToc) {
this.tocService.genToc(targetElem, docId);
}
}
this.titleService.setTitle(title ? `Angular - ${title}` : 'Angular');
};
}
/**
* Add doc content to host element and build it out with embedded components.
*/
protected render(doc: DocumentContents): Observable<void> {
let addTitleAndToc: () => void;
return this.void$
// Security: `doc.contents` is always authored by the documentation team
// and is considered to be safe.
.do(() => this.nextViewContainer.innerHTML = doc.contents || '')
.do(() => addTitleAndToc = this.prepareTitleAndToc(this.nextViewContainer, doc.id))
.switchMap(() => this.embedComponentsService.embedInto(this.nextViewContainer))
.do(() => this.docReady.emit())
.do(() => this.destroyEmbeddedComponents())
.do(componentRefs => this.embeddedComponentRefs = componentRefs)
.switchMap(() => this.swapViews(addTitleAndToc))
.do(() => this.docRendered.emit())
.catch(err => {
this.nextViewContainer.innerHTML = '';
this.logger.error(`[DocViewer]: Error preparing document '${doc.id}'.`, err);
return this.void$;
});
}
/**
* Swap the views, removing `currViewContainer` and inserting `nextViewContainer`.
* (At this point all content should be ready, including having loaded and instantiated embedded
* components.)
*
* Optionally, run a callback as soon as `nextViewContainer` has been inserted, but before the
* entering animation has been completed. This is useful for work that needs to be done as soon as
* the element has been attached to the DOM.
*/
protected swapViews(onInsertedCb = () => undefined): Observable<void> {
const raf$ = new Observable<void>(subscriber => {
const rafId = requestAnimationFrame(() => {
subscriber.next();
subscriber.complete();
});
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 = 200) => {
const animationsDisabled = !DocViewerComponent.animationsEnabled
|| this.hostElement.classList.contains(NO_ANIMATIONS);
elem.style.transition = '';
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.
// without transition) and that the `to` value will be affected by the
// `transition` style, we need to ensure an animation frame has passed between
// setting each style.
.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(getActualDuration(elem))).switchMap(() => this.void$);
};
const animateLeave = (elem: HTMLElement) => animateProp(elem, 'opacity', '1', '0.1');
const animateEnter = (elem: HTMLElement) => animateProp(elem, 'opacity', '0.1', '1');
let done$ = this.void$;
if (this.currViewContainer.parentElement) {
done$ = done$
// Remove the current view from the viewer.
.switchMap(() => animateLeave(this.currViewContainer))
.do(() => this.currViewContainer.parentElement.removeChild(this.currViewContainer))
.do(() => this.docRemoved.emit());
}
return done$
// Insert the next view into the viewer.
.do(() => this.hostElement.appendChild(this.nextViewContainer))
.do(() => onInsertedCb())
.do(() => this.docInserted.emit())
.switchMap(() => animateEnter(this.nextViewContainer))
// Update the view references and clean up unused nodes.
.do(() => {
const prevViewContainer = this.currViewContainer;
this.currViewContainer = this.nextViewContainer;
this.nextViewContainer = prevViewContainer;
this.nextViewContainer.innerHTML = ''; // Empty to release memory.
});
}
}

View File

@ -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();

View File

@ -1,6 +1,6 @@
import { InjectionToken, Inject, Injectable } from '@angular/core';
import { of } from 'rxjs/observable/of';
import { MatIconRegistry } from '@angular/material';
import { MatIconRegistry } from '@angular/material/icon';
import { HttpClient } from '@angular/common/http';
import { DomSanitizer } from '@angular/platform-browser';
@ -27,19 +27,6 @@ interface SvgIconMap {
[iconName: string]: SVGElement;
}
// <hack-alert>
// @angular/material's `MdIconRegitry` currently (v2.0.0-beta.8) requires an instance of `Http`
// (from @angular/http). It is only used to [get some text content][1], so we can create a wrapper
// around `HttpClient` and pretend it is `Http`.
// [1]: https://github.com/angular/material2/blob/2.0.0-beta.8/src/lib/icon/icon-registry.ts#L465-L466
// </hack-alert>
function createFakeHttp(http: HttpClient): any {
return {
get: (url: string) => http.get(url, {responseType: 'text'})
.map(data => ({text: () => data}))
};
}
/**
* A custom replacement for Angular Material's `MdIconRegistry`, which allows
* us to provide preloaded icon SVG sources.
@ -49,7 +36,7 @@ export class CustomIconRegistry extends MatIconRegistry {
private preloadedSvgElements: SvgIconMap = {};
constructor(http: HttpClient, sanitizer: DomSanitizer, @Inject(SVG_ICONS) svgIcons: SvgIconInfo[]) {
super(createFakeHttp(http), sanitizer);
super(http, sanitizer);
this.loadSvgElements(svgIcons);
}

View File

@ -17,7 +17,7 @@ export class ScrollService {
// at the top (e.g. toolbar) + some margin
get topOffset() {
if (!this._topOffset) {
const toolbar = this.document.querySelector('mat-toolbar.app-toolbar');
const toolbar = this.document.querySelector('.app-toolbar');
this._topOffset = (toolbar && toolbar.clientHeight || 0) + topMargin;
}
return this._topOffset;

View File

@ -100,7 +100,7 @@ a {
text-decoration: none;
}
.mat-toolbar-row a {
.app-toolbar a {
font-size: 16px;
font-weight: 400;
color: white;

View File

@ -0,0 +1,4 @@
.no-animations aio-doc-viewer > * {
// Disable view transition animations.
transition: none !important;
}

View File

@ -4,6 +4,7 @@
@import 'api-page';
@import 'content-layout';
@import 'doc-viewer';
@import 'footer';
@import 'layout-global';
@import 'marketing-layout';

View File

@ -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 {

View File

@ -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;
}

View File

@ -16,7 +16,6 @@
@import 'edit-page-cta';
@import 'features';
@import 'filetree';
@import 'hamburger';
@import 'heading-anchors';
@import 'hr';
@import 'images';

View File

@ -0,0 +1,88 @@
import { Component, ComponentRef, NgModule, ViewChild } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { Observable } from 'rxjs/Observable';
import { DocumentContents } from 'app/documents/document.service';
import { EmbedComponentsService } from 'app/embed-components/embed-components.service';
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
import { Logger } from 'app/shared/logger.service';
import { TocService } from 'app/shared/toc.service';
import { MockLogger } from 'testing/logger.service';
////////////////////////////////////////////////////////////////////////////////////////////////////
/// `TestDocViewerComponent` (for exposing internal `DocViewerComponent` methods as public). ///
/// Only used for type-casting; the actual implementation is irrelevant. ///
////////////////////////////////////////////////////////////////////////////////////////////////////
export class TestDocViewerComponent extends DocViewerComponent {
embeddedComponentRefs: ComponentRef<any>[];
currViewContainer: HTMLElement;
nextViewContainer: HTMLElement;
destroyEmbeddedComponents(): void { return null as any; }
prepareTitleAndToc(targetElem: HTMLElement, docId: string): () => void { return null as any; }
render(doc: DocumentContents): Observable<void> { return null as any; }
swapViews(onInsertedCb?: () => void): Observable<void> { return null as any; }
}
////////////////////////////////////////////////////////////////////////////////////////////////////
/// `TestModule` and `TestParentComponent`. ///
////////////////////////////////////////////////////////////////////////////////////////////////////
// Test parent component.
@Component({
selector: 'aio-test',
template: '<aio-doc-viewer [doc]="currentDoc">Test Component</aio-doc-viewer>',
})
export class TestParentComponent {
currentDoc: DocumentContents;
@ViewChild(DocViewerComponent) docViewer: DocViewerComponent;
}
// Mock services.
export class MockEmbedComponentsService {
embedInto = jasmine.createSpy('EmbedComponentsService#embedInto');
}
export class MockTitle {
setTitle = jasmine.createSpy('Title#reset');
}
export class MockTocService {
genToc = jasmine.createSpy('TocService#genToc');
reset = jasmine.createSpy('TocService#reset');
}
@NgModule({
declarations: [
DocViewerComponent,
TestParentComponent,
],
providers: [
{ provide: Logger, useClass: MockLogger },
{ provide: EmbedComponentsService, useClass: MockEmbedComponentsService },
{ provide: Title, useClass: MockTitle },
{ provide: TocService, useClass: MockTocService },
],
})
export class TestModule { }
////////////////////////////////////////////////////////////////////////////////////////////////////
/// An observable with spies to test subscribing/unsubscribing. ///
////////////////////////////////////////////////////////////////////////////////////////////////////
export class ObservableWithSubscriptionSpies<T = void> extends Observable<T> {
unsubscribeSpies: jasmine.Spy[] = [];
subscribeSpy = spyOn(this, 'subscribe').and.callFake((...args) => {
const subscription = super.subscribe(...args);
const unsubscribeSpy = spyOn(subscription, 'unsubscribe').and.callThrough();
this.unsubscribeSpies.push(unsubscribeSpy);
return subscription;
});
constructor(subscriber = () => undefined) { super(subscriber); }
}

View File

@ -0,0 +1,138 @@
import {
Component, ComponentFactoryResolver, ComponentRef, CompilerFactory, ElementRef, NgModule,
NgModuleFactoryLoader, OnInit, Type, ViewChild, getPlatform
} from '@angular/core';
import {
ComponentsOrModulePath, EMBEDDED_COMPONENTS, EmbedComponentsService, EmbeddedComponentFactory,
WithEmbeddedComponents
} from 'app/embed-components/embed-components.service';
////////////////////////////////////////////////////////////////////////////////////////////////////
/// `TestEmbedComponentsService` (for exposing internal methods as public). ///
/// Only used for type-casting; the actual implementation is irrelevant. ///
////////////////////////////////////////////////////////////////////////////////////////////////////
export class TestEmbedComponentsService extends EmbedComponentsService {
componentFactories: Map<string, EmbeddedComponentFactory>;
createComponentFactories(components: Type<any>[], resolver: ComponentFactoryResolver): void { return null as any; }
createComponents(elem: HTMLElement): ComponentRef<any>[] { return null as any; }
prepareComponentFactories(compsOrPath: ComponentsOrModulePath): Promise<void> { return null as any; }
selectorToContentPropertyName(selector: string): string { return null as any; }
}
////////////////////////////////////////////////////////////////////////////////////////////////////
/// Mock `EmbeddedModule` and test components. ///
////////////////////////////////////////////////////////////////////////////////////////////////////
// Test embedded components.
@Component({
selector: 'aio-eager-foo',
template: `Eager Foo Component`,
})
class EagerFooComponent { }
@Component({
selector: 'aio-eager-bar',
template: `
<hr>
<h2>Eager Bar Component</h2>
<p #content></p>
<hr>
`,
})
class EagerBarComponent implements OnInit {
@ViewChild('content') contentRef: ElementRef;
constructor(public elementRef: ElementRef) { }
// Project content in `ngOnInit()` just like in `CodeExampleComponent`.
ngOnInit() {
// Security: This is a test component; never deployed.
this.contentRef.nativeElement.innerHTML = this.elementRef.nativeElement.aioEagerBarContent;
}
}
@Component({
selector: 'aio-lazy-foo',
template: `Lazy Foo Component`,
})
class LazyFooComponent { }
@Component({
selector: 'aio-lazy-bar',
template: `
<hr>
<h2>Lazy Bar Component</h2>
<p #content></p>
<hr>
`,
})
class LazyBarComponent implements OnInit {
@ViewChild('content') contentRef: ElementRef;
constructor(public elementRef: ElementRef) { }
// Project content in `ngOnInit()` just like in `CodeExampleComponent`.
ngOnInit() {
// Security: This is a test component; never deployed.
this.contentRef.nativeElement.innerHTML = this.elementRef.nativeElement.aioLazyBarContent;
}
}
// Export test embedded selectors and components.
export const testEagerEmbeddedSelectors = ['aio-eager-foo', 'aio-eager-bar'];
export const testEagerEmbeddedComponents = [EagerFooComponent, EagerBarComponent];
export const testLazyEmbeddedSelectors = ['aio-lazy-foo', 'aio-lazy-bar'];
export const testLazyEmbeddedComponents = [LazyFooComponent, LazyBarComponent];
// Export mock `EmbeddedModule` and path.
export const mockEmbeddedModulePath = 'mock/mock-embedded#MockEmbeddedModule';
@NgModule({
declarations: [testLazyEmbeddedComponents],
entryComponents: [testLazyEmbeddedComponents],
})
class MockEmbeddedModule implements WithEmbeddedComponents {
embeddedComponents = testLazyEmbeddedComponents;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
/// `TestModule`. ///
////////////////////////////////////////////////////////////////////////////////////////////////////
// Mock services.
export class MockNgModuleFactoryLoader implements NgModuleFactoryLoader {
loadedPaths: string[] = [];
load(path: string) {
this.loadedPaths.push(path);
const platformRef = getPlatform();
const compilerFactory = platformRef.injector.get(CompilerFactory) as CompilerFactory;
const compiler = compilerFactory.createCompiler([]);
return compiler.compileModuleAsync(MockEmbeddedModule);
}
}
@NgModule({
providers: [
EmbedComponentsService,
{ provide: NgModuleFactoryLoader, useClass: MockNgModuleFactoryLoader },
{
provide: EMBEDDED_COMPONENTS,
useValue: {
[testEagerEmbeddedSelectors.join(',')]: testEagerEmbeddedComponents,
[testLazyEmbeddedSelectors.join(',')]: mockEmbeddedModulePath,
},
},
],
declarations: [testEagerEmbeddedComponents],
entryComponents: [testEagerEmbeddedComponents],
})
export class TestModule { }

View File

@ -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" },

View File

@ -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.

View File

@ -200,8 +200,10 @@ function runProtractorAoT(appDir, outputFile) {
// All protractor output is appended to the outputFile.
// CLI version
function runE2eTestsCLI(appDir, outputFile) {
// --preserve-symlinks is needed due the symlinked node_modules in each example
const e2eSpawn = spawnExt('yarn', ['e2e', '--preserve-symlinks'], { cwd: appDir });
// `--preserve-symlinks` is needed due the symlinked `node_modules/` in each example.
// `--no-webdriver-update` is needed to preserve the ChromeDriver version already installed.
const args = ['e2e', '--preserve-symlinks', '--no-webdriver-update'];
const e2eSpawn = spawnExt('yarn', args, { cwd: appDir });
return e2eSpawn.promise.then(
function () {
fs.appendFileSync(outputFile, `Passed: ${appDir}\n\n`);

View File

@ -9,7 +9,6 @@
"exclude": [
"test.ts",
"**/*.spec.ts",
"testing/**",
"**/*.1.*"
"**/testing/*"
]
}

View File

@ -6,7 +6,7 @@
"scripts": {
"http-server": "http-server",
"protractor": "protractor",
"webdriver:update": "webdriver-manager update --standalone false --gecko false",
"webdriver:update": "webdriver-manager update --standalone false --gecko false $CHROMEDRIVER_VERSION_ARG",
"postinstall": "yarn webdriver:update"
},
"keywords": [],

View File

@ -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',

View File

@ -17,6 +17,12 @@
dependencies:
source-map "^0.5.6"
"@angular-devkit/core@0.0.21":
version "0.0.21"
resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-0.0.21.tgz#e2ba4ac0a4e1156f884c083b15eb1c26ddfb2ba8"
dependencies:
source-map "^0.5.6"
"@angular-devkit/schematics@~0.0.34":
version "0.0.34"
resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-0.0.34.tgz#c3ef61b0e49e585d9982f2828e9a67b3879a6b1b"
@ -26,27 +32,27 @@
minimist "^1.2.0"
rxjs "^5.4.2"
"@angular/animations@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-5.0.0.tgz#b5ad199c67f93f759544477effe6679e154991fb"
"@angular/animations@^5.1.0-beta.2":
version "5.1.0-beta.2"
resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-5.1.0-beta.2.tgz#2ed05bfa0ed22d6d1550658785e965c1fbfec5a7"
dependencies:
tslib "^1.7.1"
"@angular/cdk@^2.0.0-beta.12":
version "2.0.0-beta.12"
resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-2.0.0-beta.12.tgz#3a243cb62b93f4e039120ba70f900dc9e235622e"
"@angular/cdk@^5.0.0-rc.1":
version "5.0.0-rc.1"
resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-5.0.0-rc.1.tgz#7d6d937fea50074962db8ce02664b164401d197d"
dependencies:
tslib "^1.7.1"
"@angular/cli@^1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-1.5.0.tgz#2abc3ff1648d54ad4a14f82f9d517f371370406c"
"@angular/cli@^1.6.0-rc.0":
version "1.6.0-rc.0"
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-1.6.0-rc.0.tgz#e6bf974dcb2263e026d3d4ffa4480e4e78f9c7cf"
dependencies:
"@angular-devkit/build-optimizer" "~0.0.31"
"@angular-devkit/schematics" "~0.0.34"
"@ngtools/json-schema" "1.1.0"
"@ngtools/webpack" "1.8.0"
"@schematics/angular" "~0.1.0"
"@ngtools/webpack" "1.9.0-rc.0"
"@schematics/angular" "~0.1.5"
autoprefixer "^6.5.3"
chalk "~2.2.0"
circular-dependency-plugin "^3.0.0"
@ -70,13 +76,14 @@
license-webpack-plugin "^1.0.0"
lodash "^4.11.1"
memory-fs "^0.4.1"
minimatch "^3.0.4"
node-modules-path "^1.0.0"
nopt "^4.0.1"
opn "~5.1.0"
portfinder "~1.0.12"
postcss-custom-properties "^6.1.0"
postcss-loader "^1.3.3"
postcss-url "^5.1.2"
postcss-loader "^2.0.8"
postcss-url "^7.1.2"
raw-loader "^0.5.1"
resolve "^1.1.7"
rxjs "^5.5.2"
@ -101,74 +108,74 @@
optionalDependencies:
node-sass "^4.3.0"
"@angular/common@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@angular/common/-/common-5.0.0.tgz#f96d66a517b995d1ba9b28309f15c2e359675825"
"@angular/common@^5.1.0-beta.2":
version "5.1.0-beta.2"
resolved "https://registry.yarnpkg.com/@angular/common/-/common-5.1.0-beta.2.tgz#027b16b8d2f63eb14d9fbc84a0a5dd91945885d6"
dependencies:
tslib "^1.7.1"
"@angular/compiler-cli@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-5.0.0.tgz#0ecbb937d84a4f8dd94f0c2a47b07d2e4694c853"
"@angular/compiler-cli@^5.1.0-beta.2":
version "5.1.0-beta.2"
resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-5.1.0-beta.2.tgz#62b63e30bdae910c322a62b76fa790dd4a6cdf53"
dependencies:
chokidar "^1.4.2"
minimist "^1.2.0"
reflect-metadata "^0.1.2"
tsickle "^0.24.0"
tsickle "^0.25.5"
"@angular/compiler@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-5.0.0.tgz#b9ffbf18c8a39d8b7dacec473193a90e24cc2bc9"
"@angular/compiler@^5.1.0-beta.2":
version "5.1.0-beta.2"
resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-5.1.0-beta.2.tgz#6c88c3485558dcf85144f55edb1299d99cdb3fec"
dependencies:
tslib "^1.7.1"
"@angular/core@^5.0.0-rc.9":
version "5.0.0-rc.9"
resolved "https://registry.yarnpkg.com/@angular/core/-/core-5.0.0-rc.9.tgz#0aab5d2d9b31ea530a41bb1e3009504d1bd64673"
"@angular/core@^5.1.0-beta.2":
version "5.1.0-beta.2"
resolved "https://registry.yarnpkg.com/@angular/core/-/core-5.1.0-beta.2.tgz#e94b21cae4479caed9486239b065302a97c5fd4f"
dependencies:
tslib "^1.7.1"
"@angular/forms@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-5.0.0.tgz#c7fddfa35396759ae9852920a30cdda8c41ed1de"
"@angular/forms@^5.1.0-beta.2":
version "5.1.0-beta.2"
resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-5.1.0-beta.2.tgz#feeee29041cce227cc50e254ebecc2f19dc529ee"
dependencies:
tslib "^1.7.1"
"@angular/http@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@angular/http/-/http-5.0.0.tgz#0728a2be0cfbb078727c5eb87d4c85d53fec9a51"
"@angular/http@^5.1.0-beta.2":
version "5.1.0-beta.2"
resolved "https://registry.yarnpkg.com/@angular/http/-/http-5.1.0-beta.2.tgz#935beaba2afea192d6a75a1c3b8d7dfd787641b0"
dependencies:
tslib "^1.7.1"
"@angular/material@^2.0.0-beta.12":
version "2.0.0-beta.12"
resolved "https://registry.yarnpkg.com/@angular/material/-/material-2.0.0-beta.12.tgz#71b6d0b7b021891e5d0e3688c1d4bd78c7457f58"
"@angular/material@^5.0.0-rc.1":
version "5.0.0-rc.1"
resolved "https://registry.yarnpkg.com/@angular/material/-/material-5.0.0-rc.1.tgz#63456b7568c102b6bb7983a2837cc317350fe270"
dependencies:
tslib "^1.7.1"
"@angular/platform-browser-dynamic@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-5.0.0.tgz#887e106c8b103b0415cf6156a425da6d83f4c89d"
"@angular/platform-browser-dynamic@^5.1.0-beta.2":
version "5.1.0-beta.2"
resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-5.1.0-beta.2.tgz#bee1d37f7300030edd855edf66311b0d493252d5"
dependencies:
tslib "^1.7.1"
"@angular/platform-browser@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-5.0.0.tgz#c7038f7cde80705b62014897231e182eec976fed"
"@angular/platform-browser@^5.1.0-beta.2":
version "5.1.0-beta.2"
resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-5.1.0-beta.2.tgz#ccfca9d172f033137c83f2fbe8b702fe7204c5d5"
dependencies:
tslib "^1.7.1"
"@angular/platform-server@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-5.0.0.tgz#877d25ef814af92fffc7b0b523b971a6ff222018"
"@angular/platform-server@^5.1.0-beta.2":
version "5.1.0-beta.2"
resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-5.1.0-beta.2.tgz#a36583b274f263eeb9e1c23f6f0b83c36cc4a72a"
dependencies:
domino "^1.0.29"
tslib "^1.7.1"
xhr2 "^0.1.4"
"@angular/router@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@angular/router/-/router-5.0.0.tgz#fe4b521a6738408bce30f93a53499140c93a4f76"
"@angular/router@^5.1.0-beta.2":
version "5.1.0-beta.2"
resolved "https://registry.yarnpkg.com/@angular/router/-/router-5.1.0-beta.2.tgz#a22b8c0e08b81c8b0950b9a668aa893e9f1c0e4c"
dependencies:
tslib "^1.7.1"
@ -254,9 +261,9 @@
version "1.1.0"
resolved "https://registry.yarnpkg.com/@ngtools/json-schema/-/json-schema-1.1.0.tgz#c3a0c544d62392acc2813a42c8a0dc6f58f86922"
"@ngtools/webpack@1.8.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-1.8.0.tgz#443204e016aa3a287544eacadb4c95964ae6e2c4"
"@ngtools/webpack@1.9.0-rc.0":
version "1.9.0-rc.0"
resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-1.9.0-rc.0.tgz#632e91c0e888e20c717eabeef58107e24d4ed663"
dependencies:
chalk "~2.2.0"
enhanced-resolve "^3.1.0"
@ -266,11 +273,11 @@
source-map "^0.5.6"
tree-kill "^1.0.0"
"@schematics/angular@~0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-0.1.0.tgz#cd6c445ead33144a532523d1ccac68c38a2b0050"
"@schematics/angular@~0.1.5":
version "0.1.7"
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-0.1.7.tgz#2306aeec118ca185e180882eff54f5116de4ef05"
dependencies:
"@angular-devkit/core" "0.0.20"
"@angular-devkit/core" "0.0.21"
"@types/core-js@^0.9.41":
version "0.9.43"
@ -704,7 +711,7 @@ async-foreach@^0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542"
async@0.2.x, async@~0.2.6:
async@0.2.x:
version "0.2.10"
resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
@ -1291,7 +1298,7 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0:
chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba"
dependencies:
@ -2093,6 +2100,10 @@ csv-streamify@^3.0.4:
dependencies:
through2 "2.0.1"
cuint@latest:
version "0.2.2"
resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b"
currently-unhandled@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
@ -2347,14 +2358,6 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
directory-encoder@^0.7.2:
version "0.7.2"
resolved "https://registry.yarnpkg.com/directory-encoder/-/directory-encoder-0.7.2.tgz#59b4e2aa4f25422f6c63b527b462f5e2d0dd2c58"
dependencies:
fs-extra "^0.23.1"
handlebars "^1.3.0"
img-stats "^0.5.2"
dns-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
@ -3674,14 +3677,6 @@ handle-thing@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4"
handlebars@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-1.3.0.tgz#9e9b130a93e389491322d975cf3ec1818c37ce34"
dependencies:
optimist "~0.3"
optionalDependencies:
uglify-js "~2.3"
handlebars@^4.0.3:
version "4.0.11"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc"
@ -4123,12 +4118,6 @@ image-ssim@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/image-ssim/-/image-ssim-0.2.0.tgz#83b42c7a2e6e4b85505477fe6917f5dbc56420e5"
img-stats@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/img-stats/-/img-stats-0.5.2.tgz#c203496c42f2d9eb2e5ab8232fa756bab32c9e2b"
dependencies:
xmldom "^0.1.19"
immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
@ -4627,7 +4616,7 @@ isurl@^1.0.0-alpha5:
has-to-string-tag-x "^1.2.0"
is-object "^1.0.1"
jasmine-core@^2.6.4, jasmine-core@~2.8.0:
jasmine-core@^2.8.0, jasmine-core@~2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.8.0.tgz#bcc979ae1f9fd05701e45e52e65d3a5d63f1a24e"
@ -4674,7 +4663,7 @@ jpeg-js@0.1.2, jpeg-js@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.1.2.tgz#135b992c0575c985cfa0f494a3227ed238583ece"
js-base64@^2.1.5, js-base64@^2.1.8, js-base64@^2.1.9:
js-base64@^2.1.8, js-base64@^2.1.9:
version "2.3.2"
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.3.2.tgz#a79a923666372b580f8e27f51845c6f7e8fbfbaf"
@ -5863,7 +5852,7 @@ object-assign@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2"
object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
object-assign@^4.0.1, object-assign@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@ -5938,12 +5927,6 @@ optimist@0.6.x, optimist@^0.6.1, optimist@~0.6.0, optimist@~0.6.1:
minimist "~0.0.1"
wordwrap "~0.0.2"
optimist@~0.3, optimist@~0.3.5:
version "0.3.7"
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.3.7.tgz#c90941ad59e4273328923074d2cf2e7cbc6ec0d9"
dependencies:
wordwrap "~0.0.2"
optionator@^0.8.1, optionator@^0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
@ -6383,14 +6366,14 @@ postcss-load-plugins@^2.3.0:
cosmiconfig "^2.1.1"
object-assign "^4.1.0"
postcss-loader@^1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-1.3.3.tgz#a621ea1fa29062a83972a46f54486771301916eb"
postcss-loader@^2.0.8:
version "2.0.8"
resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.0.8.tgz#8c67ddb029407dfafe684a406cfc16bad2ce0814"
dependencies:
loader-utils "^1.0.2"
object-assign "^4.1.1"
postcss "^5.2.15"
loader-utils "^1.1.0"
postcss "^6.0.0"
postcss-load-config "^1.2.0"
schema-utils "^0.3.0"
postcss-merge-idents@^2.1.5:
version "2.1.7"
@ -6548,17 +6531,15 @@ postcss-unique-selectors@^2.0.2:
postcss "^5.0.4"
uniqs "^2.0.0"
postcss-url@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/postcss-url/-/postcss-url-5.1.2.tgz#98b3165be8d592471cb0caadde2c0d1f832f133e"
postcss-url@^7.1.2:
version "7.2.1"
resolved "https://registry.yarnpkg.com/postcss-url/-/postcss-url-7.2.1.tgz#bf47ff1b5395538567cea19ef3241a70fb925f5c"
dependencies:
directory-encoder "^0.7.2"
js-base64 "^2.1.5"
mime "^1.2.11"
minimatch "^3.0.0"
mime "^1.4.1"
minimatch "^3.0.4"
mkdirp "^0.5.0"
path-is-absolute "^1.0.0"
postcss "^5.0.0"
postcss "^6.0.1"
xxhashjs "^0.2.1"
postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0:
version "3.3.0"
@ -6572,7 +6553,7 @@ postcss-zindex@^2.0.1:
postcss "^5.0.4"
uniqs "^2.0.0"
postcss@^5.0.0, postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.15, postcss@^5.2.16:
postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16:
version "5.2.18"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5"
dependencies:
@ -6581,6 +6562,14 @@ postcss@^5.0.0, postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.
source-map "^0.5.6"
supports-color "^3.2.3"
postcss@^6.0.0:
version "6.0.14"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.14.tgz#5534c72114739e75d0afcf017db853099f562885"
dependencies:
chalk "^2.3.0"
source-map "^0.6.1"
supports-color "^4.4.0"
postcss@^6.0.1, postcss@^6.0.13:
version "6.0.13"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.13.tgz#b9ecab4ee00c89db3ec931145bd9590bbf3f125f"
@ -7319,7 +7308,7 @@ rx@2.3.24:
version "2.3.24"
resolved "https://registry.yarnpkg.com/rx/-/rx-2.3.24.tgz#14f950a4217d7e35daa71bbcbe58eff68ea4b2b7"
rxjs@^5.4.2, rxjs@^5.5.0:
rxjs@^5.4.2:
version "5.5.0"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.0.tgz#26d8f3866eb700e247e0728a147c3d628993d812"
dependencies:
@ -7729,7 +7718,7 @@ source-map-support@^0.4.0, source-map-support@^0.4.1, source-map-support@^0.4.15
dependencies:
source-map "^0.5.6"
source-map@0.1.x, source-map@~0.1.7:
source-map@0.1.x:
version "0.1.43"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346"
dependencies:
@ -8379,9 +8368,9 @@ tsconfig@^6.0.0:
strip-bom "^3.0.0"
strip-json-comments "^2.0.0"
tsickle@^0.24.0:
version "0.24.1"
resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.24.1.tgz#039343b205bf517a333b0703978892f80a7d848e"
tsickle@^0.25.5:
version "0.25.5"
resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.25.5.tgz#2891d29f97c4aab1306e06378d8496d1765a4bfe"
dependencies:
minimist "^1.2.0"
mkdirp "^0.5.1"
@ -8478,14 +8467,6 @@ uglify-js@^2.6, uglify-js@^2.8.29:
optionalDependencies:
uglify-to-browserify "~1.0.0"
uglify-js@~2.3:
version "2.3.6"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.3.6.tgz#fa0984770b428b7a9b2a8058f46355d14fef211a"
dependencies:
async "~0.2.6"
optimist "~0.3.5"
source-map "~0.1.7"
uglify-to-browserify@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
@ -9299,10 +9280,6 @@ xmlbuilder@>=1.0.0, xmlbuilder@~9.0.1:
version "9.0.4"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.4.tgz#519cb4ca686d005a8420d3496f3f0caeecca580f"
xmldom@^0.1.19:
version "0.1.27"
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
xmlhttprequest-ssl@1.5.3:
version "1.5.3"
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d"
@ -9311,6 +9288,12 @@ xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
xxhashjs@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/xxhashjs/-/xxhashjs-0.2.1.tgz#9bbe9be896142976dfa34c061b2d068c43d30de0"
dependencies:
cuint latest
y18n@^3.2.0, y18n@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"

View File

@ -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"

View File

@ -21,16 +21,16 @@ following products on your development machine:
[Windows](http://windows.github.com)); [GitHub's Guide to Installing
Git](https://help.github.com/articles/set-up-git) is a good source of information.
* [Node.js](http://nodejs.org), (version `>=6.9.5 <7.0.0`) which is used to run a development web server,
run tests, and generate distributable files. We also use Node's Package Manager, `npm`
(version `>=3.10.7 <4.0.0`), which comes with Node. Depending on your system, you can install Node either from
source or as a pre-packaged bundle.
* [Node.js](http://nodejs.org), (version specified in the engines field of [`package.json`](../package.json)) which is used to run a development web server,
run tests, and generate distributable files.
* [Yarn](https://yarnpkg.com) (version `>=1.0.2 <2.0.0`) which is used to install dependencies.
* [Yarn](https://yarnpkg.com) (version specified in the engines field of [`package.json`](../package.json)) which is used to install dependencies.
* [Java Development Kit](http://www.oracle.com/technetwork/es/java/javase/downloads/index.html) which is used
to execute the selenium standalone server for e2e testing.
* (Optional for now) [Bazel](https://bazel.build/), please follow instructions in [Bazel.md]
## Getting the Sources
Fork and clone the Angular repository:
@ -61,19 +61,11 @@ Next, install the JavaScript modules needed to build and test Angular:
yarn install
```
**Optional**: In this document, we make use of project local `npm` package scripts and binaries
(stored under `./node_modules/.bin`) by prefixing these command invocations with `$(npm bin)`; in
particular `gulp` and `protractor` commands. If you prefer, you can drop this path prefix by either:
**Optional**: In this document, we make use of installed npm package scripts and binaries
(stored under `./node_modules/.bin`) by prefixing these command invocations with `$(yarn bin)`; in
particular `gulp` and `protractor` commands.
## Installing Bower Modules
Now run `bower` to install additional dependencies:
```shell
# Install other Angular project dependencies (bower.json)
bower install
```
## Windows only

View File

@ -2,9 +2,25 @@ package(default_visibility = ["//visibility:public"])
filegroup(
name = "node_modules",
srcs = glob([
"node_modules/**/*.js",
"node_modules/**/*.d.ts",
"node_modules/**/*.json",
])
# Workaround https://github.com/bazelbuild/bazel/issues/4242
# Can't just glob(["node_modules/**/*.{js,d.ts,json}"])
srcs = glob(["/".join([
"node_modules",
pkg,
"**",
ext,
]) for pkg in [
"@angular",
"@types",
"bytebuffer",
"protobufjs",
"reflect-metadata",
"tsickle",
"typescript",
"zone.js",
] for ext in [
"*.js",
"*.json",
"*.d.ts",
]]),
)

View File

@ -1,24 +1,36 @@
workspace(name = "bazel_integration_test")
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
git_repository(
name = "build_bazel_rules_nodejs",
remote = "https://github.com/bazelbuild/rules_nodejs.git",
tag = "0.2.1",
tag = "0.3.1",
)
load("@build_bazel_rules_nodejs//:defs.bzl", "node_repositories")
node_repositories(package_json = ["//:package.json"])
local_repository(
git_repository(
name = "build_bazel_rules_typescript",
path = "node_modules/@bazel/typescript",
remote = "https://github.com/bazelbuild/rules_typescript.git",
tag = "0.6.0",
)
load("@build_bazel_rules_typescript//:defs.bzl", "ts_repositories")
ts_repositories()
local_repository(
name = "angular",
path = "node_modules/@angular/bazel",
)
local_repository(
name = "rxjs",
path = "node_modules/rxjs/src",
)
git_repository(
name = "io_bazel_rules_sass",
remote = "https://github.com/bazelbuild/rules_sass.git",

View File

@ -20,6 +20,7 @@
},
"scripts": {
"postinstall": "ngc -p angular.tsconfig.json",
"test": "bazel build ... --noshow_progress"
"test": "WORKAROUND https://github.com/bazelbuild/bazel/issues/4242, can't build ...",
"test": "bazel build //src/... --noshow_progress"
}
}

View File

@ -6,6 +6,6 @@ exports_files(["tsconfig.json"])
ng_module(
name = "src",
srcs = glob(["*.ts"]),
deps = ["//src/hello-world"],
tsconfig = ":tsconfig.json",
deps = ["//src/hello-world"],
)

View File

@ -1,4 +1,5 @@
package(default_visibility = ["//visibility:public"])
load("@angular//:index.bzl", "ng_module")
load("@io_bazel_rules_sass//sass:sass.bzl", "sass_binary")
@ -10,6 +11,9 @@ sass_binary(
ng_module(
name = "hello-world",
srcs = glob(["*.ts"]),
tsconfig = "//src:tsconfig.json",
assets = [":hello-world-styles.css"],
tsconfig = "//src:tsconfig.json",
# FIXME(alexeagle): the rxjs dep should come from Angular, but if we use the
# npm distro of angular there is no ts_library rule to propagate the dep.
deps = ["@rxjs"],
)

View File

@ -10,7 +10,7 @@
"ngc": "ngc -p tsconfig.json",
"rollup": "rollup -f iife -c rollup.config.js -o dist/bundle.es2015.js",
"rollup:lazy": "rollup -f cjs -c rollup.lazy.config.js -o dist/lazy.bundle.es2015.js",
"postinstall": "webdriver-manager update --gecko false",
"postinstall": "webdriver-manager update --gecko false --standalone false $CHROMEDRIVER_VERSION_ARG",
"preprotractor": "tsc -p e2e",
"protractor": "protractor e2e/protractor.config.js",
"serve": "lite-server -c e2e/browser.config.json",

View File

@ -22,7 +22,7 @@
"protractor": "file:../../node_modules/protractor"
},
"scripts": {
"postinstall": "webdriver-manager update --gecko false",
"postinstall": "webdriver-manager update --gecko false --standalone false $CHROMEDRIVER_VERSION_ARG",
"closure": "java -jar node_modules/google-closure-compiler/compiler.jar --flagfile closure.conf",
"test": "ngc && yarn run closure && concurrently \"yarn run serve\" \"yarn run protractor\" --kill-others --success first",
"serve": "lite-server -c e2e/browser.config.json",

View File

@ -4,7 +4,7 @@
"version": "0.0.0",
"license": "MIT",
"scripts": {
"postinstall": "webdriver-manager update --gecko false",
"postinstall": "webdriver-manager update --gecko false --standalone false $CHROMEDRIVER_VERSION_ARG",
"test": "concurrently \"npm run serve\" \"npm run protractor\" --kill-others --success first",
"serve": "lite-server -c bs-config.e2e.json",
"preprotractor": "tsc -p e2e",

View File

@ -22,7 +22,7 @@
"protractor": "file:../../node_modules/protractor"
},
"scripts": {
"postinstall": "webdriver-manager update --gecko false",
"postinstall": "webdriver-manager update --gecko false --standalone false $CHROMEDRIVER_VERSION_ARG",
"closure": "java -jar node_modules/google-closure-compiler/compiler.jar --flagfile closure.conf",
"test": "ngc && yarn run closure && concurrently \"yarn run serve\" \"yarn run protractor\" --kill-others --success first",
"serve": "lite-server -c e2e/browser.config.json",

View File

@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "5.1.0",
"version": "5.1.2",
"private": true,
"branchPattern": "2.0.*",
"description": "Angular - a web framework for modern web apps",
@ -16,14 +16,16 @@
"url": "https://github.com/angular/angular.git"
},
"scripts": {
"buildifier": "bazel build @com_github_bazelbuild_buildtools//buildifier && find . -type f \\( -name BUILD -or -name BUILD.bazel \\) ! -path \"*/node_modules/*\" | xargs $(bazel info bazel-bin)/external/com_github_bazelbuild_buildtools/buildifier/buildifier",
"preinstall": "node -e \"if(process.env.npm_execpath.indexOf('yarn') === -1) throw new Error('Please use Yarn instead of NPM to install dependencies. See: https://yarnpkg.com/lang/en/docs/install/')\"",
"postinstall": "webdriver-manager update --gecko false",
"postinstall": "yarn update-webdriver",
"update-webdriver": "webdriver-manager update --gecko false $CHROMEDRIVER_VERSION_ARG",
"check-env": "gulp check-env"
},
"dependencies": {
"core-js": "^2.4.1",
"reflect-metadata": "^0.1.3",
"rxjs": "^5.5.2",
"rxjs": "5.5.5",
"tslib": "^1.7.1",
"zone.js": "^0.8.12"
},
@ -31,7 +33,7 @@
"fsevents": "1.1.2"
},
"devDependencies": {
"@bazel/typescript": "0.3.2",
"@bazel/ibazel": "^0.1.1",
"@types/angularjs": "1.5.14-alpha",
"@types/base64-js": "1.2.5",
"@types/chokidar": "1.7.3",
@ -51,7 +53,7 @@
"canonical-path": "0.0.2",
"chokidar": "1.7.0",
"clang-format": "1.0.41",
"cldr": "4.5.0",
"cldr": "4.7.0",
"cldr-data-downloader": "0.3.2",
"cldrjs": "0.5.0",
"conventional-changelog": "1.1.0",
@ -97,6 +99,7 @@
"tsickle": "0.25.5",
"tslint": "5.7.0",
"tslint-eslint-rules": "4.1.1",
"tsutils": "2.12.1",
"typescript": "2.5.x",
"uglify-js": "2.8.29",
"universal-analytics": "0.4.15",

View File

@ -1 +1,20 @@
exports_files(["tsconfig-build.json"])
package(default_visibility = ["//visibility:public"])
exports_files([
"tsconfig-build.json",
"tsconfig.json",
])
load("@build_bazel_rules_typescript//:defs.bzl", "ts_config", "ts_library")
ts_config(
name = "tsconfig",
src = ":tsconfig-build.json",
deps = [],
)
ts_library(
name = "types",
srcs = glob(["*.ts"]),
tsconfig = ":tsconfig",
)

View File

@ -0,0 +1,18 @@
package(default_visibility = ["//visibility:public"])
load("@build_bazel_rules_typescript//:defs.bzl", "ts_library")
ts_library(
name = "animations",
srcs = glob(
[
"*.ts",
"src/**/*.ts",
],
),
module_name = "@angular/animations",
tsconfig = "//packages:tsconfig",
deps = [
"//packages/core",
],
)

View File

@ -0,0 +1,18 @@
package(default_visibility = ["//visibility:public"])
load("@build_bazel_rules_typescript//:defs.bzl", "ts_library")
ts_library(
name = "browser",
srcs = glob(
[
"*.ts",
"src/**/*.ts",
],
),
module_name = "@angular/animations/browser",
tsconfig = "//packages:tsconfig",
deps = [
"//packages/animations",
],
)

View File

@ -65,13 +65,12 @@ function parseAnimationAlias(alias: string, errors: string[]): string|Transition
}
}
const TRUE_BOOLEAN_VALUES = new Set<string>();
TRUE_BOOLEAN_VALUES.add('true');
TRUE_BOOLEAN_VALUES.add('1');
const FALSE_BOOLEAN_VALUES = new Set<string>();
FALSE_BOOLEAN_VALUES.add('false');
FALSE_BOOLEAN_VALUES.add('0');
// DO NOT REFACTOR ... keep the follow set instantiations
// with the values intact (closure compiler for some reason
// removes follow-up lines that add the values outside of
// the constructor...
const TRUE_BOOLEAN_VALUES = new Set<string>(['true', '1']);
const FALSE_BOOLEAN_VALUES = new Set<string>(['false', '0']);
function makeLambdaFromStates(lhs: string, rhs: string): TransitionMatcherFn {
const LHS_MATCH_BOOLEAN = TRUE_BOOLEAN_VALUES.has(lhs) || FALSE_BOOLEAN_VALUES.has(lhs);

View File

@ -166,12 +166,30 @@ if (typeof Element != 'undefined') {
};
}
function containsVendorPrefix(prop: string): boolean {
// Webkit is the only real popular vendor prefix nowadays
// cc: http://shouldiprefix.com/
return prop.substring(1, 6) == 'ebkit'; // webkit or Webkit
}
let _CACHED_BODY: {style: any}|null = null;
let _IS_WEBKIT = false;
export function validateStyleProperty(prop: string): boolean {
if (!_CACHED_BODY) {
_CACHED_BODY = getBodyNode() || {};
_IS_WEBKIT = _CACHED_BODY !.style ? ('WebkitAppearance' in _CACHED_BODY !.style) : false;
}
return _CACHED_BODY !.style ? prop in _CACHED_BODY !.style : true;
let result = true;
if (_CACHED_BODY !.style && !containsVendorPrefix(prop)) {
result = prop in _CACHED_BODY !.style;
if (!result && _IS_WEBKIT) {
const camelProp = 'Webkit' + prop.charAt(0).toUpperCase() + prop.substr(1);
result = camelProp in _CACHED_BODY !.style;
}
}
return result;
}
export function getBodyNode(): any|null {

View File

@ -312,9 +312,13 @@ export class AnimationTransitionNamespace {
// If there are no animations found for any of the nodes then clear the cache
// for the element.
this._engine.driver.query(rootElement, NG_TRIGGER_SELECTOR, true).forEach(elm => {
// this means that an inner remove() operation has already kicked off
// the animation on this element...
if (elm[REMOVAL_FLAG]) return;
const namespaces = this._engine.fetchNamespacesByElement(elm);
if (namespaces.size) {
namespaces.forEach(ns => { ns.triggerLeaveAnimation(elm, context, false, true); });
namespaces.forEach(ns => ns.triggerLeaveAnimation(elm, context, false, true));
} else {
this.clearElementCache(elm);
}

View File

@ -65,7 +65,7 @@ export class WebAnimationsPlayer implements AnimationPlayer {
const keyframes = this.keyframes.map(styles => copyStyles(styles, false));
const previousStyleProps = Object.keys(this.previousStyles);
if (previousStyleProps.length) {
if (previousStyleProps.length && keyframes.length) {
let startingKeyframe = keyframes[0];
let missingStyleProps: string[] = [];
previousStyleProps.forEach(prop => {

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