Compare commits
78 Commits
ngcontaine
...
6.0.0
Author | SHA1 | Date | |
---|---|---|---|
d0ccf5f169 | |||
ecde15298a | |||
983e5f2d7e | |||
5fc4299e0a | |||
1823d5dd1c | |||
91d4da0d2f | |||
22eb8e26fc | |||
f6002c1702 | |||
14138f6382 | |||
f11daa2031 | |||
31a435ef5b | |||
3e92b22258 | |||
2e5457c824 | |||
1ab5fba92e | |||
e1e57ddaa7 | |||
ee7cb48877 | |||
a30c57090a | |||
8a49ec4f27 | |||
697b6c040c | |||
4008e36e80 | |||
e47bb52084 | |||
ac2b530f4b | |||
64bf6edf00 | |||
adf6235479 | |||
04c18ac1aa | |||
1b26dd8cdb | |||
f721b06bde | |||
0bc8443e12 | |||
db17231597 | |||
540626a3a6 | |||
391bfcede5 | |||
d8de6488dd | |||
151fb66848 | |||
02424ff0d0 | |||
f0925d9705 | |||
212b806eda | |||
b9431e88fb | |||
7790cfa0d0 | |||
41b5149509 | |||
06f865640d | |||
824f74f27b | |||
5301c43eed | |||
f280d1aef1 | |||
5e741d42a6 | |||
0035d41030 | |||
08f447ceec | |||
8953f123e3 | |||
6274007e3b | |||
ee76be7783 | |||
a8c720bc3a | |||
ac47a3cd93 | |||
041458a3d2 | |||
2cb74b748f | |||
cb0bfe7a43 | |||
8ee11aeaa6 | |||
bf89fcb361 | |||
4f70c5b6f7 | |||
ad3ebec2a5 | |||
3f5d61f2dd | |||
11ada7f78b | |||
0b96bc7456 | |||
fefadadff3 | |||
946057ae29 | |||
d4293aaaaa | |||
e3dcc227f6 | |||
c9230dd90e | |||
234af9ba59 | |||
7204028d3e | |||
f7c55952bf | |||
e38e3bd135 | |||
cd20c01ba1 | |||
12a191ef3f | |||
65e67b3c3a | |||
32e57f6197 | |||
7de69ba29b | |||
44193c0b94 | |||
6db8241ffa | |||
33c594516c |
@ -1,19 +0,0 @@
|
||||
# Encryption
|
||||
|
||||
Based on https://github.com/circleci/encrypted-files
|
||||
|
||||
In the CircleCI web UI, we have a secret variable called `KEY`
|
||||
https://circleci.com/gh/angular/angular/edit#env-vars
|
||||
which is only exposed to non-fork builds
|
||||
(see "Pass secrets to builds from forked pull requests" under
|
||||
https://circleci.com/gh/angular/angular/edit#advanced-settings)
|
||||
|
||||
We use this as a symmetric AES encryption key to encrypt tokens like
|
||||
a GitHub token that enables publishing snapshots.
|
||||
|
||||
To create the github_token file, we take this approach:
|
||||
- Find the angular-builds:token in http://valentine
|
||||
- Go inside the ngcontainer docker image so you use the same version of openssl as we will at runtime: `docker run --rm -it angular/ngcontainer`
|
||||
- echo "https://[token]:@github.com" > credentials
|
||||
- openssl aes-256-cbc -e -in credentials -out .circleci/github_token -k $KEY
|
||||
- If needed, base64-encode the result so you can copy-paste it out of docker: `base64 github_token`
|
@ -12,8 +12,8 @@
|
||||
## 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.3.0
|
||||
var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.3.0
|
||||
var_1: &docker_image angular/ngcontainer:0.2.0
|
||||
var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.2.0
|
||||
|
||||
# Define common ENV vars
|
||||
var_3: &define_env_vars
|
||||
@ -63,7 +63,7 @@ jobs:
|
||||
- run: yarn install --frozen-lockfile --non-interactive
|
||||
- run: ./node_modules/.bin/gulp lint
|
||||
|
||||
test:
|
||||
build:
|
||||
<<: *job_defaults
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
@ -80,111 +80,38 @@ jobs:
|
||||
|
||||
- run: ls /home/circleci/bazel_repository_cache || true
|
||||
- run: bazel info release
|
||||
- run: bazel run @nodejs//:yarn
|
||||
- run: bazel run @yarn//:yarn
|
||||
# Use bazel query so that we explicitly ask for all buildable targets to be built as well
|
||||
# This avoids waiting for the slowest build target to finish before running the first test
|
||||
# See https://github.com/bazelbuild/bazel/issues/4257
|
||||
# NOTE: Angular developers should typically just bazel build //packages/... or bazel test //packages/...
|
||||
- run: bazel query --output=label //... | xargs bazel test --build_tag_filters=-ivy-only --test_tag_filters=-manual,-ivy-only
|
||||
- run: bazel query --output=label //... | xargs bazel test
|
||||
|
||||
# We run the integration tests outside of Bazel for now.
|
||||
# See comments inside this script.
|
||||
- run: xvfb-run --auto-servernum ./integration/run_tests.sh
|
||||
|
||||
# CircleCI will allow us to go back and view/download these artifacts from past builds.
|
||||
# Also we can use a service like https://buildsize.org/ to automatically track binary size of these artifacts.
|
||||
# The destination keys need be format {projectName}/{context}/{fileName} so that the github-robot can process them for size calculations
|
||||
# projectName should remain consistant to group files
|
||||
# context and fileName can be almost anything (within usual URI rules)
|
||||
# There should only be exactly 2 forward slashes in the path
|
||||
# This is so they're backwards compatiable with the existing data we have on bundle sizes
|
||||
- store_artifacts:
|
||||
path: dist/bin/packages/core/test/bundling/hello_world/bundle.min.js
|
||||
destination: core/hello_world/bundle
|
||||
destination: packages/core/test/bundling/hello_world/bundle.min.js
|
||||
- store_artifacts:
|
||||
path: dist/bin/packages/core/test/bundling/todo/bundle.min.js
|
||||
destination: core/todo/bundle
|
||||
destination: packages/core/test/bundling/todo/bundle.min.js
|
||||
- store_artifacts:
|
||||
path: dist/bin/packages/core/test/bundling/hello_world/bundle.min.js.br
|
||||
destination: core/hello_world/bundle.br
|
||||
path: dist/bin/packages/core/test/bundling/hello_world/bundle.min.js.brotli
|
||||
destination: packages/core/test/bundling/hello_world/bundle.min.js.brotli
|
||||
- store_artifacts:
|
||||
path: dist/bin/packages/core/test/bundling/todo/bundle.min.js.br
|
||||
destination: core/todo/bundle.br
|
||||
path: dist/bin/packages/core/test/bundling/todo/bundle.min.js.brotli
|
||||
destination: packages/core/test/bundling/todo/bundle.min.js.brotli
|
||||
|
||||
- save_cache:
|
||||
key: *cache_key
|
||||
paths:
|
||||
- "node_modules"
|
||||
- "~/bazel_repository_cache"
|
||||
|
||||
# This job exists only for backwards-compatibility with old scripts and tests
|
||||
# that rely on the pre-Bazel dist/packages-dist layout.
|
||||
# It duplicates some work with the job above: we build the bazel packages
|
||||
# twice. Even though we have a remote cache, these jobs will typically run in
|
||||
# parallel so up-to-date outputs will not be available at the time the build
|
||||
# starts.
|
||||
# No new jobs should depend on this one.
|
||||
build-packages-dist:
|
||||
<<: *job_defaults
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- *define_env_vars
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
# See remote cache documentation in /docs/BAZEL.md
|
||||
- run: .circleci/setup_cache.sh
|
||||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||
- *setup-bazel-remote-cache
|
||||
|
||||
- run: bazel run @nodejs//:yarn
|
||||
- run: scripts/build-packages-dist.sh
|
||||
|
||||
# Save the npm packages from //packages/... for other workflow jobs to read
|
||||
# https://circleci.com/docs/2.0/workflows/#using-workspaces-to-share-data-among-jobs
|
||||
- persist_to_workspace:
|
||||
root: dist
|
||||
paths:
|
||||
- packages-dist
|
||||
|
||||
# We run the integration tests outside of Bazel for now.
|
||||
# They are a separate workflow job so that they can be easily re-run.
|
||||
# When the tests are ported to bazel test targets, they should move to the "test"
|
||||
# job above, as part of the bazel test command. That has flaky_test_attempts so the
|
||||
# need to re-run manually should be alleviated.
|
||||
# See comments inside the integration/run_tests.sh script.
|
||||
integration_test:
|
||||
<<: *job_defaults
|
||||
steps:
|
||||
- *define_env_vars
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- attach_workspace:
|
||||
at: dist
|
||||
- run: xvfb-run --auto-servernum ./integration/run_tests.sh
|
||||
|
||||
# This job updates the content of repos like github.com/angular/core-builds
|
||||
# for every green build on angular/angular.
|
||||
publish_snapshot:
|
||||
<<: *job_defaults
|
||||
steps:
|
||||
# See below - ideally this job should not trigger for non-upstream builds.
|
||||
# But since it does, we have to check this condition.
|
||||
- run:
|
||||
name: Skip this job for Pull Requests and Fork builds
|
||||
# Note, `|| true` on the end makes this step always exit 0
|
||||
command: '[[
|
||||
-v CIRCLE_PR_NUMBER
|
||||
|| "$CIRCLE_PROJECT_USERNAME" != "angular"
|
||||
|| "$CIRCLE_PROJECT_REPONAME" != "angular"
|
||||
]] && circleci step halt || true'
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- attach_workspace:
|
||||
at: dist
|
||||
# CircleCI has a config setting to force SSH for all github connections
|
||||
# This is not compatible with our mechanism of using a Personal Access Token
|
||||
# Clear the global setting
|
||||
- run: git config --global --unset "url.ssh://git@github.com.insteadof"
|
||||
- run:
|
||||
name: Decrypt github credentials
|
||||
command: 'openssl aes-256-cbc -d -in .circleci/github_token -k "${KEY}" -out ~/.git_credentials'
|
||||
- run: ./scripts/ci/publish-build-artifacts.sh
|
||||
|
||||
aio_monitoring:
|
||||
<<: *job_defaults
|
||||
steps:
|
||||
@ -199,24 +126,7 @@ workflows:
|
||||
default_workflow:
|
||||
jobs:
|
||||
- lint
|
||||
- test
|
||||
- build-packages-dist
|
||||
- integration_test:
|
||||
requires:
|
||||
- build-packages-dist
|
||||
- publish_snapshot:
|
||||
# Note: no filters on this job because we want it to run for all upstream branches
|
||||
# We'd really like to filter out pull requests here, but not yet available:
|
||||
# https://discuss.circleci.com/t/workflows-pull-request-filter/14396/4
|
||||
# Instead, the job just exits immediately at the first step.
|
||||
requires:
|
||||
# Only publish if tests and integration tests pass
|
||||
- test
|
||||
- integration_test
|
||||
# Get the artifacts to publish from the build-packages-dist job
|
||||
# since the publishing script expects the legacy outputs layout.
|
||||
- build-packages-dist
|
||||
|
||||
- build
|
||||
aio_monitoring:
|
||||
jobs:
|
||||
- aio_monitoring
|
||||
|
11
.github/angular-robot.yml
vendored
@ -1,14 +1,5 @@
|
||||
# Configuration for angular-robot
|
||||
|
||||
#options for the size plugin
|
||||
size:
|
||||
disabled: false
|
||||
maxSizeIncrease: 1000
|
||||
circleCiStatusName: "ci/circleci: build-packages-dist"
|
||||
status:
|
||||
disabled: false
|
||||
context: "ci/angular: size"
|
||||
|
||||
# options for the merge plugin
|
||||
merge:
|
||||
# the status will be added to your pull requests
|
||||
@ -45,12 +36,10 @@ merge:
|
||||
- "packages/language-service/**"
|
||||
- "**/.gitignore"
|
||||
- "**/.gitkeep"
|
||||
- "**/package.json"
|
||||
- "**/tsconfig-build.json"
|
||||
- "**/tsconfig.json"
|
||||
- "**/rollup.config.js"
|
||||
- "**/BUILD.bazel"
|
||||
- "packages/**/integrationtest/**"
|
||||
- "packages/**/test/**"
|
||||
|
||||
# comment that will be added to a PR when there is a conflict, leave empty or set to false to disable
|
||||
|
@ -10,6 +10,7 @@
|
||||
# andrewseguin - Andrew Seguin
|
||||
# brandonroberts - Brandon Roberts
|
||||
# brocco - Mike Brocchi
|
||||
# chuckjaz - Chuck Jazdzewski
|
||||
# filipesilva - Filipe Silva
|
||||
# gkalpak - George Kalpakas
|
||||
# hansl - Hans Larsen
|
||||
@ -17,7 +18,6 @@
|
||||
# jasonaden - Jason Aden
|
||||
# kapunahelewong - Kapunahele Wong
|
||||
# kara - Kara Erickson
|
||||
# kyliau - Keen Yee Liau
|
||||
# matsko - Matias Niemelä
|
||||
# mhevery - Misko Hevery
|
||||
# petebacondarwin - Pete Bacon Darwin
|
||||
@ -94,7 +94,7 @@ groups:
|
||||
- "tools/bazel.rc"
|
||||
users:
|
||||
- alexeagle #primary
|
||||
- kyliau
|
||||
- chuckjaz
|
||||
- IgorMinar #fallback
|
||||
- mhevery
|
||||
- vikerman #fallback
|
||||
@ -133,7 +133,7 @@ groups:
|
||||
- "packages/core/*"
|
||||
users:
|
||||
- mhevery #primary
|
||||
- jasonaden
|
||||
- chuckjaz
|
||||
- kara
|
||||
- vicb
|
||||
- IgorMinar #fallback
|
||||
@ -163,7 +163,7 @@ groups:
|
||||
files:
|
||||
- "packages/compiler/*"
|
||||
users:
|
||||
- alxhub #primary
|
||||
- chuckjaz #primary
|
||||
- vicb
|
||||
- mhevery
|
||||
- IgorMinar #fallback
|
||||
@ -237,7 +237,7 @@ groups:
|
||||
files:
|
||||
- "packages/language-service/*"
|
||||
users:
|
||||
- kyliau #primary
|
||||
- chuckjaz #primary
|
||||
# needs secondary
|
||||
- vicb
|
||||
- IgorMinar #fallback
|
||||
|
@ -11,7 +11,7 @@ exports_files([
|
||||
# This ensures that package.json in subdirectories get installed as well.
|
||||
alias(
|
||||
name = "install",
|
||||
actual = "@nodejs//:yarn",
|
||||
actual = "@yarn//:yarn",
|
||||
)
|
||||
|
||||
node_modules_filegroup(
|
||||
@ -48,11 +48,10 @@ filegroup(
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "angularjs_scripts",
|
||||
name = "angularjs",
|
||||
# do not sort
|
||||
srcs = [
|
||||
"//:node_modules/angular-1.5/angular.js",
|
||||
"//:node_modules/angular-mocks-1.5/angular-mocks.js",
|
||||
"//:node_modules/angular-mocks/angular-mocks.js",
|
||||
"//:node_modules/angular/angular.js",
|
||||
"//:node_modules/angular-mocks/angular-mocks.js",
|
||||
],
|
||||
)
|
||||
|
48
CHANGELOG.md
@ -1,49 +1,5 @@
|
||||
<a name="6.0.3"></a>
|
||||
## [6.0.3](https://github.com/angular/angular/compare/6.0.2...6.0.3) (2018-05-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **service-worker:** check platformBrowser before accessing navigator.serviceWorker ([#21231](https://github.com/angular/angular/issues/21231)) ([0ee5b7e](https://github.com/angular/angular/commit/0ee5b7e))
|
||||
|
||||
|
||||
|
||||
<a name="6.0.2"></a>
|
||||
## [6.0.2](https://github.com/angular/angular/compare/6.0.1...6.0.2) (2018-05-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** do not throw errors when a destroyed component is animated ([#23836](https://github.com/angular/angular/issues/23836)) ([752b83a](https://github.com/angular/angular/commit/752b83a))
|
||||
* **service-worker:** deprecate `versionedFiles` in asset-group resources ([#23584](https://github.com/angular/angular/issues/23584)) ([c6b618d](https://github.com/angular/angular/commit/c6b618d))
|
||||
|
||||
|
||||
|
||||
<a name="6.0.1"></a>
|
||||
# [6.0.1](https://github.com/angular/angular/compare/6.0.0...6.0.1) (2018-05-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** properly clean up queried element styles in safari/edge ([#23686](https://github.com/angular/angular/issues/23686)) ([3824e3f](https://github.com/angular/angular/commit/3824e3f))
|
||||
* **animations:** retain state styling for nodes that are moved around ([#23686](https://github.com/angular/angular/issues/23686)) ([05aa5e0](https://github.com/angular/angular/commit/05aa5e0))
|
||||
* **core:** call ngOnDestroy on all services that have it ([#23755](https://github.com/angular/angular/issues/23755)) ([5581e97](https://github.com/angular/angular/commit/5581e97)), closes [#22466](https://github.com/angular/angular/issues/22466) [#22240](https://github.com/angular/angular/issues/22240) [#14818](https://github.com/angular/angular/issues/14818)
|
||||
* **elements:** always check to create strategy ([#23825](https://github.com/angular/angular/issues/23825)) ([d280077](https://github.com/angular/angular/commit/d280077))
|
||||
* **router:** avoid freezing queryParams in-place ([#22663](https://github.com/angular/angular/issues/22663)) ([3d8799b](https://github.com/angular/angular/commit/3d8799b)), closes [#22617](https://github.com/angular/angular/issues/22617)
|
||||
* **router:** correct the segment parsing so it won't break on ampersand ([#23684](https://github.com/angular/angular/issues/23684)) ([8733843](https://github.com/angular/angular/commit/8733843))
|
||||
* **service-worker:** correctly handle requests with empty `clientId` ([#23625](https://github.com/angular/angular/issues/23625)) ([2254ac2](https://github.com/angular/angular/commit/2254ac2)), closes [#23526](https://github.com/angular/angular/issues/23526)
|
||||
|
||||
|
||||
|
||||
<a name="6.0.0"></a>
|
||||
# [6.0.0](https://github.com/angular/angular/compare/6.0.0-beta.0...6.0.0) (2018-05-03)
|
||||
|
||||
### Release Highlights & Update instructions
|
||||
|
||||
Angular v6 is the first release of Angular that unifies the Framework, Material and CLI.
|
||||
|
||||
To learn about the release highlights and our new CLI-powered update workflow for your projects please check out the [v6 release announcement](https://blog.angular.io/version-6-0-0-of-angular-now-available-cc56b0efa7a4).
|
||||
|
||||
# [6.0.0](https://github.com/angular/angular/compare/6.0.0-rc.6...6.0.0) (2018-05-03)
|
||||
|
||||
|
||||
### Dependency updates
|
||||
@ -181,7 +137,7 @@ To learn about the release highlights and our new CLI-powered update workflow fo
|
||||
|
||||
|
||||
|
||||
### Possible Breaking Changes
|
||||
### POSSIBLE BREAKING CHANGES
|
||||
|
||||
* **animations:** When animation is triggered within a disabled zone, the associated event (which an instance of AnimationEvent) will no longer report the totalTime as 0 (it will emit the actual time of the animation).
|
||||
|
||||
|
17
README.md
@ -5,6 +5,10 @@
|
||||
[](https://www.npmjs.com/@angular/core)
|
||||
|
||||
|
||||
[](https://saucelabs.com/u/angular2-ci)
|
||||
|
||||
*Safari (7+), iOS (7+) and IE mobile (11) are tested on [BrowserStack][browserstack].*
|
||||
|
||||
# Angular
|
||||
|
||||
Angular is a development platform for building mobile and desktop web applications using Typescript/JavaScript and other languages.
|
||||
@ -13,19 +17,12 @@ Angular is a development platform for building mobile and desktop web applicatio
|
||||
|
||||
[Get started in 5 minutes][quickstart].
|
||||
|
||||
|
||||
## Changelog
|
||||
|
||||
[Learn about the latest improvements][changelog].
|
||||
|
||||
|
||||
## Want to help?
|
||||
|
||||
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on our
|
||||
guidelines for [contributing][contributing] and then check out one of our issues in the [hotlist: community-help](https://github.com/angular/angular/labels/hotlist%3A%20community-help).
|
||||
|
||||
[browserstack]: https://www.browserstack.com/automate/public-build/LzF3RzBVVGt6VWE2S0hHaC9uYllOZz09LS1BVjNTclBKV0x4eVRlcjA4QVY1M0N3PT0=--eb4ce8c8dc2c1c5b2b5352d473ee12a73ac20e06
|
||||
[contributing]: https://github.com/angular/angular/blob/master/CONTRIBUTING.md
|
||||
[quickstart]: https://angular.io/guide/quickstart
|
||||
[changelog]: https://github.com/angular/angular/blob/master/CHANGELOG.md
|
||||
[ng]: https://angular.io
|
||||
[contributing]: http://github.com/angular/angular/blob/master/CONTRIBUTING.md
|
||||
[quickstart]: https://angular.io/docs/ts/latest/quickstart.html
|
||||
[ng]: http://angular.io
|
||||
|
144
WORKSPACE
@ -1,28 +1,56 @@
|
||||
workspace(name = "angular")
|
||||
|
||||
#
|
||||
# Download Bazel toolchain dependencies as needed by build actions
|
||||
#
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_nodejs",
|
||||
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.9.1.zip",
|
||||
strip_prefix = "rules_nodejs-0.9.1",
|
||||
sha256 = "6139762b62b37c1fd171d7f22aa39566cb7dc2916f0f801d505a9aaf118c117f",
|
||||
url = "https://github.com/bazelbuild/rules_nodejs/archive/1931156c232a08356dfda02e9c8b0275c2e63c00.zip",
|
||||
strip_prefix = "rules_nodejs-1931156c232a08356dfda02e9c8b0275c2e63c00",
|
||||
sha256 = "9cfe33276a6ac0076ee9ee159c4a2576f9851c0f437435b5ac19b2e592493078",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "io_bazel_rules_webtesting",
|
||||
url = "https://github.com/bazelbuild/rules_webtesting/archive/v0.2.0.zip",
|
||||
strip_prefix = "rules_webtesting-0.2.0",
|
||||
sha256 = "cecc12f07e95740750a40d38e8b14b76fefa1551bef9332cb432d564d693723c",
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install")
|
||||
|
||||
check_bazel_version("0.11.1")
|
||||
node_repositories(package_json = ["//:package.json"])
|
||||
|
||||
yarn_install(
|
||||
name = "ts-api-guardian_runtime_deps",
|
||||
package_json = "//tools/ts-api-guardian:package.json",
|
||||
yarn_lock = "//tools/ts-api-guardian:yarn.lock",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_typescript",
|
||||
url = "https://github.com/bazelbuild/rules_typescript/archive/0.15.0.zip",
|
||||
strip_prefix = "rules_typescript-0.15.0",
|
||||
sha256 = "1aa75917330b820cb239b3c10a936a10f0a46fe215063d4492dd76dc6e1616f4",
|
||||
url = "https://github.com/bazelbuild/rules_typescript/archive/0.12.1.zip",
|
||||
strip_prefix = "rules_typescript-0.12.1",
|
||||
sha256 = "24e2c36f60508c6d270ae4265b89b381e3f66d550e70c367ed3755ad8d7ce3b0",
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace")
|
||||
|
||||
ts_setup_workspace()
|
||||
|
||||
local_repository(
|
||||
name = "rxjs",
|
||||
path = "node_modules/rxjs/src",
|
||||
)
|
||||
|
||||
# Point to the integration test workspace just so that Bazel doesn't descend into it
|
||||
# when expanding the //... pattern
|
||||
local_repository(
|
||||
name = "bazel_integration_test",
|
||||
path = "integration/bazel",
|
||||
)
|
||||
|
||||
# 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
|
||||
BAZEL_BUILDTOOLS_VERSION = "70bc7843bb9950fece2bc014ed16de03419e36e2"
|
||||
|
||||
http_archive(
|
||||
name = "com_github_bazelbuild_buildtools",
|
||||
url = "https://github.com/bazelbuild/buildtools/archive/%s.zip" % BAZEL_BUILDTOOLS_VERSION,
|
||||
strip_prefix = "buildtools-%s" % BAZEL_BUILDTOOLS_VERSION,
|
||||
sha256 = "367c23a5fe7fc2a7cb57863d3718b4149f0e57426c48c8ad54c45348a0b53cc1",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
@ -31,24 +59,18 @@ http_archive(
|
||||
sha256 = "feba3278c13cde8d67e341a837f69a029f698d7a27ddbb2a202be7a10b22142a",
|
||||
)
|
||||
|
||||
# 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
|
||||
BAZEL_BUILDTOOLS_VERSION = "fd9878fd5de921e0bbab3dcdcb932c2627812ee1"
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
|
||||
|
||||
http_archive(
|
||||
name = "com_github_bazelbuild_buildtools",
|
||||
url = "https://github.com/bazelbuild/buildtools/archive/%s.zip" % BAZEL_BUILDTOOLS_VERSION,
|
||||
strip_prefix = "buildtools-%s" % BAZEL_BUILDTOOLS_VERSION,
|
||||
sha256 = "27bb461ade23fd44ba98723ad98f84ee9c83cd3540b773b186a1bc5037f3d862",
|
||||
)
|
||||
go_rules_dependencies()
|
||||
|
||||
go_register_toolchains()
|
||||
|
||||
# Fetching the Bazel source code allows us to compile the Skylark linter
|
||||
http_archive(
|
||||
name = "io_bazel",
|
||||
url = "https://github.com/bazelbuild/bazel/archive/968f87900dce45a7af749a965b72dbac51b176b3.zip",
|
||||
strip_prefix = "bazel-968f87900dce45a7af749a965b72dbac51b176b3",
|
||||
sha256 = "e373d2ae24955c1254c495c9c421c009d88966565c35e4e8444c082cb1f0f48f",
|
||||
url = "https://github.com/bazelbuild/bazel/archive/5a35e72f9e97c06540c479f8c31512fb4656202f.zip",
|
||||
strip_prefix = "bazel-5a35e72f9e97c06540c479f8c31512fb4656202f",
|
||||
sha256 = "ed33a52874c14e3b487fb50f390c541fab9c81a33d986d38fb01766a66dbcd21",
|
||||
)
|
||||
|
||||
# We have a source dependency on the Devkit repository, because it's built with
|
||||
@ -66,67 +88,7 @@ http_archive(
|
||||
|
||||
http_archive(
|
||||
name = "org_brotli",
|
||||
url = "https://github.com/google/brotli/archive/f9b8c02673c576a3e807edbf3a9328e9e7af6d7c.zip",
|
||||
strip_prefix = "brotli-f9b8c02673c576a3e807edbf3a9328e9e7af6d7c",
|
||||
sha256 = "8a517806d2b7c8505ba5c53934e7d7c70d341b68ffd268e9044d35b564a48828",
|
||||
)
|
||||
|
||||
#
|
||||
# Load and install our dependencies downloaded above.
|
||||
#
|
||||
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install")
|
||||
|
||||
check_bazel_version("0.13.0")
|
||||
node_repositories(package_json = ["//:package.json"])
|
||||
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
|
||||
|
||||
go_rules_dependencies()
|
||||
go_register_toolchains()
|
||||
|
||||
load("@io_bazel_rules_webtesting//web:repositories.bzl", "browser_repositories", "web_test_repositories")
|
||||
|
||||
web_test_repositories()
|
||||
browser_repositories(
|
||||
chromium = True,
|
||||
firefox = True,
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace")
|
||||
|
||||
ts_setup_workspace()
|
||||
|
||||
#
|
||||
# Point Bazel to WORKSPACEs that live in subdirectories
|
||||
#
|
||||
|
||||
local_repository(
|
||||
name = "rxjs",
|
||||
path = "node_modules/rxjs/src",
|
||||
)
|
||||
|
||||
# Point to the integration test workspace just so that Bazel doesn't descend into it
|
||||
# when expanding the //... pattern
|
||||
local_repository(
|
||||
name = "bazel_integration_test",
|
||||
path = "integration/bazel",
|
||||
)
|
||||
|
||||
#
|
||||
# Ask Bazel to manage these toolchain dependencies for us.
|
||||
# Bazel will run `yarn install` when one of these toolchains is requested during
|
||||
# a build.
|
||||
#
|
||||
|
||||
yarn_install(
|
||||
name = "ts-api-guardian_runtime_deps",
|
||||
package_json = "//tools/ts-api-guardian:package.json",
|
||||
yarn_lock = "//tools/ts-api-guardian:yarn.lock",
|
||||
)
|
||||
|
||||
yarn_install(
|
||||
name = "http-server_runtime_deps",
|
||||
package_json = "//tools/http-server:package.json",
|
||||
yarn_lock = "//tools/http-server:yarn.lock",
|
||||
url = "https://github.com/google/brotli/archive/c6333e1e79fb62ea088443f192293f964409b04e.zip",
|
||||
strip_prefix = "brotli-c6333e1e79fb62ea088443f192293f964409b04e",
|
||||
sha256 = "3f781988dee7dd3bcce2bf238294663cfaaf3b6433505bdb762e24d0a284d1dc",
|
||||
)
|
||||
|
@ -3,7 +3,7 @@
|
||||
set -eux -o pipefail
|
||||
exec 3>&1
|
||||
|
||||
echo -e "\n\n[`date`] - Updating the preview server..."
|
||||
echo "\n\n[`date`] - Updating the preview server..."
|
||||
|
||||
# Input
|
||||
readonly HOST_REPO_DIR=$1
|
||||
|
@ -8,7 +8,6 @@
|
||||
"projects": {
|
||||
"site": {
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"projectType": "application",
|
||||
"architect": {
|
||||
"build": {
|
||||
@ -30,12 +29,36 @@
|
||||
"vendorChunk": false,
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"assets": [
|
||||
"src/assets",
|
||||
"src/generated",
|
||||
"src/app/search/search-worker.js",
|
||||
"src/favicon.ico",
|
||||
"src/pwa-manifest.json",
|
||||
"src/google385281288605d160.html",
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "src/assets",
|
||||
"output": "/assets"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "src/generated",
|
||||
"output": "/generated"
|
||||
},
|
||||
{
|
||||
"glob": "app/search/search-worker.js",
|
||||
"input": "src",
|
||||
"output": "/"
|
||||
},
|
||||
{
|
||||
"glob": "favicon.ico",
|
||||
"input": "src",
|
||||
"output": "/"
|
||||
},
|
||||
{
|
||||
"glob": "pwa-manifest.json",
|
||||
"input": "src",
|
||||
"output": "/"
|
||||
},
|
||||
{
|
||||
"glob": "google385281288605d160.html",
|
||||
"input": "src",
|
||||
"output": "/"
|
||||
},
|
||||
{
|
||||
"glob": "custom-elements.min.js",
|
||||
"input": "node_modules/@webcomponents/custom-elements",
|
||||
@ -48,7 +71,9 @@
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
{
|
||||
"input": "src/styles.scss"
|
||||
}
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
@ -117,15 +142,41 @@
|
||||
"tsConfig": "src/tsconfig.spec.json",
|
||||
"scripts": [],
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
{
|
||||
"input": "src/styles.scss"
|
||||
}
|
||||
],
|
||||
"assets": [
|
||||
"src/assets",
|
||||
"src/generated",
|
||||
"src/app/search/search-worker.js",
|
||||
"src/favicon.ico",
|
||||
"src/pwa-manifest.json",
|
||||
"src/google385281288605d160.html",
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "src/assets",
|
||||
"output": "/assets"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "src/generated",
|
||||
"output": "/generated"
|
||||
},
|
||||
{
|
||||
"glob": "app/search/search-worker.js",
|
||||
"input": "src",
|
||||
"output": "/"
|
||||
},
|
||||
{
|
||||
"glob": "favicon.ico",
|
||||
"input": "src",
|
||||
"output": "/"
|
||||
},
|
||||
{
|
||||
"glob": "pwa-manifest.json",
|
||||
"input": "src",
|
||||
"output": "/"
|
||||
},
|
||||
{
|
||||
"glob": "google385281288605d160.html",
|
||||
"input": "src",
|
||||
"output": "/"
|
||||
},
|
||||
{
|
||||
"glob": "custom-elements.min.js",
|
||||
"input": "node_modules/@webcomponents/custom-elements",
|
||||
|
BIN
aio/content/examples/.DS_Store
vendored
Normal file
27
aio/content/examples/cli-quickstart/README.md
Normal file
@ -0,0 +1,27 @@
|
||||
# MasterProject
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0-rc.0.
|
||||
|
||||
## Development server
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
|
||||
Before running the tests make sure you are serving the app via `ng serve`.
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
@ -5,18 +5,18 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
selector: 'app-voter',
|
||||
template: `
|
||||
<h4>{{name}}</h4>
|
||||
<button (click)="vote(true)" [disabled]="didVote">Agree</button>
|
||||
<button (click)="vote(false)" [disabled]="didVote">Disagree</button>
|
||||
<button (click)="vote(true)" [disabled]="voted">Agree</button>
|
||||
<button (click)="vote(false)" [disabled]="voted">Disagree</button>
|
||||
`
|
||||
})
|
||||
export class VoterComponent {
|
||||
@Input() name: string;
|
||||
@Output() voted = new EventEmitter<boolean>();
|
||||
didVote = false;
|
||||
@Output() onVoted = new EventEmitter<boolean>();
|
||||
voted = false;
|
||||
|
||||
vote(agreed: boolean) {
|
||||
this.voted.emit(agreed);
|
||||
this.didVote = true;
|
||||
this.onVoted.emit(agreed);
|
||||
this.voted = true;
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
||||
|
@ -8,7 +8,7 @@ import { Component } from '@angular/core';
|
||||
<h3>Agree: {{agreed}}, Disagree: {{disagreed}}</h3>
|
||||
<app-voter *ngFor="let voter of voters"
|
||||
[name]="voter"
|
||||
(voted)="onVoted($event)">
|
||||
(onVoted)="onVoted($event)">
|
||||
</app-voter>
|
||||
`
|
||||
})
|
||||
|
@ -38,6 +38,8 @@ export class MyCounterComponent implements OnChanges {
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************/
|
||||
|
||||
@Component({
|
||||
selector: 'counter-parent',
|
||||
template: `
|
||||
|
@ -72,6 +72,8 @@ export class DoCheckComponent implements DoCheck {
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************/
|
||||
|
||||
@Component({
|
||||
selector: 'do-check-parent',
|
||||
templateUrl: './do-check-parent.component.html',
|
||||
|
@ -46,6 +46,8 @@ export class OnChangesComponent implements OnChanges {
|
||||
reset() { this.changeLog = []; }
|
||||
}
|
||||
|
||||
/***************************************/
|
||||
|
||||
@Component({
|
||||
selector: 'on-changes-parent',
|
||||
templateUrl: './on-changes-parent.component.html',
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { HttpClientModule } from '@angular//common/http';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import {
|
||||
|
@ -7,9 +7,12 @@
|
||||
"resources": {
|
||||
"files": [
|
||||
"/favicon.ico",
|
||||
"/index.html",
|
||||
"/*.css",
|
||||
"/*.js"
|
||||
"/index.html"
|
||||
],
|
||||
"versionedFiles": [
|
||||
"/*.bundle.css",
|
||||
"/*.bundle.js",
|
||||
"/*.chunk.js"
|
||||
]
|
||||
}
|
||||
}, {
|
||||
@ -22,4 +25,4 @@
|
||||
]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
@ -1,26 +1,26 @@
|
||||
// Import spec files individually for Stackblitz
|
||||
import './app/about/about.component.spec.ts';
|
||||
import './app/app-initial.component.spec.ts';
|
||||
import './app/app.component.router.spec.ts';
|
||||
import './app/app.component.spec.ts';
|
||||
import './app/banner/banner-initial.component.spec.ts';
|
||||
import './app/banner/banner.component.spec.ts';
|
||||
import './app/banner/banner.component.detect-changes.spec.ts';
|
||||
import './app/banner/banner-external.component.spec.ts';
|
||||
import './app/dashboard/dashboard-hero.component.spec.ts';
|
||||
import './app/dashboard/dashboard.component.no-testbed.spec.ts';
|
||||
import './app/dashboard/dashboard.component.spec.ts';
|
||||
import './app/demo/async-helper.spec.ts';
|
||||
import './app/demo/demo.spec.ts';
|
||||
import './app/demo/demo.testbed.spec.ts';
|
||||
import './app/hero/hero-detail.component.no-testbed.spec.ts';
|
||||
import './app/hero/hero-detail.component.spec.ts';
|
||||
import './app/hero/hero-list.component.spec.ts';
|
||||
import './app/model/hero.service.spec.ts';
|
||||
import './app/model/http-hero.service.spec.ts';
|
||||
import './app/model/testing/http-client.spec.ts';
|
||||
import './app/shared/highlight.directive.spec.ts';
|
||||
import './app/shared/title-case.pipe.spec.ts';
|
||||
import './app/twain/twain.component.spec.ts';
|
||||
import './app/twain/twain.component.marbles.spec.ts';
|
||||
import './app/welcome/welcome.component.spec.ts';
|
||||
import 'app/about/about.component.spec.ts';
|
||||
import 'app/app-initial.component.spec.ts';
|
||||
import 'app/app.component.router.spec.ts';
|
||||
import 'app/app.component.spec.ts';
|
||||
import 'app/banner/banner-initial.component.spec.ts';
|
||||
import 'app/banner/banner.component.spec.ts';
|
||||
import 'app/banner/banner.component.detect-changes.spec.ts';
|
||||
import 'app/banner/banner-external.component.spec.ts';
|
||||
import 'app/dashboard/dashboard-hero.component.spec.ts';
|
||||
import 'app/dashboard/dashboard.component.no-testbed.spec.ts';
|
||||
import 'app/dashboard/dashboard.component.spec.ts';
|
||||
import 'app/demo/async-helper.spec.ts';
|
||||
import 'app/demo/demo.spec.ts';
|
||||
import 'app/demo/demo.testbed.spec.ts';
|
||||
import 'app/hero/hero-detail.component.no-testbed.spec.ts';
|
||||
import 'app/hero/hero-detail.component.spec.ts';
|
||||
import 'app/hero/hero-list.component.spec.ts';
|
||||
import 'app/model/hero.service.spec.ts';
|
||||
import 'app/model/http-hero.service.spec.ts';
|
||||
import 'app/model/testing/http-client.spec.ts';
|
||||
import 'app/shared/highlight.directive.spec.ts';
|
||||
import 'app/shared/title-case.pipe.spec.ts';
|
||||
import 'app/twain/twain.component.spec.ts';
|
||||
import 'app/twain/twain.component.marbles.spec.ts';
|
||||
import 'app/welcome/welcome.component.spec.ts';
|
||||
|
@ -84,7 +84,7 @@ export class HeroService {
|
||||
// if not search term, return empty hero array.
|
||||
return of([]);
|
||||
}
|
||||
return this.http.get<Hero[]>(`${this.heroesUrl}/?name=${term}`).pipe(
|
||||
return this.http.get<Hero[]>(`api/heroes/?name=${term}`).pipe(
|
||||
tap(_ => this.log(`found heroes matching "${term}"`)),
|
||||
catchError(this.handleError<Hero[]>('searchHeroes', []))
|
||||
);
|
||||
|
@ -17,7 +17,7 @@ const PORT = process.env.PORT || 4000;
|
||||
const DIST_FOLDER = join(process.cwd(), 'dist');
|
||||
|
||||
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
|
||||
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main');
|
||||
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main.bundle');
|
||||
|
||||
// Express Engine
|
||||
import { ngExpressEngine } from '@nguniversal/express-engine';
|
||||
@ -51,7 +51,7 @@ app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));
|
||||
// #docregion navigation-request
|
||||
// All regular routes use the Universal engine
|
||||
app.get('*', (req, res) => {
|
||||
res.render('index', { req });
|
||||
res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req });
|
||||
});
|
||||
// #enddocregion navigation-request
|
||||
|
||||
|
@ -64,7 +64,7 @@ export class HeroService {
|
||||
// if not search term, return empty hero array.
|
||||
return of([]);
|
||||
}
|
||||
return this.http.get<Hero[]>(`${this.heroesUrl}/?name=${term}`).pipe(
|
||||
return this.http.get<Hero[]>(`api/heroes/?name=${term}`).pipe(
|
||||
tap(_ => this.log(`found heroes matching "${term}"`)),
|
||||
catchError(this.handleError<Hero[]>('searchHeroes', []))
|
||||
);
|
||||
|
@ -5,9 +5,8 @@ module.exports = {
|
||||
entry: { server: './server.ts' },
|
||||
resolve: { extensions: ['.js', '.ts'] },
|
||||
target: 'node',
|
||||
mode: 'none',
|
||||
// this makes sure we include node_modules and other 3rd party libraries
|
||||
externals: [/node_modules/],
|
||||
externals: [/(node_modules|main\..*\.js)/],
|
||||
output: {
|
||||
path: path.join(__dirname, 'dist'),
|
||||
filename: '[name].js'
|
||||
|
@ -1,4 +1,4 @@
|
||||
# AngularJS to Angular Concepts: Quick Reference
|
||||
# AngularJS to Angular Quick Reference
|
||||
|
||||
|
||||
{@a top}
|
||||
|
@ -440,12 +440,6 @@ The service can be instantiated by configuring a factory function as shown below
|
||||
|
||||
<code-example path="dependency-injection/src/app/tree-shaking/service.0.ts" title="src/app/tree-shaking/service.0.ts" linenums="false"> </code-example>
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
To override tree-shakable providers, register the provider using the `providers: []` array syntax of any Angular decorator that supports it.
|
||||
|
||||
</div>
|
||||
|
||||
{@a injector-config}
|
||||
{@a bootstrap}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Angular Elements Overview
|
||||
|
||||
_Angular elements_ are Angular components packaged as _custom elements_, a web standard for defining new HTML elements in a framework-agnostic way.
|
||||
Angular Elements are Angular components packaged as custom elements, a web standard for defining new html elements in a framework-agnostic way.
|
||||
|
||||
[Custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) are a Web Platform feature currently supported by Chrome, Opera, and Safari, and available in other browsers through polyfills (see [Browser Support](#browser-support)).
|
||||
A custom element extends HTML by allowing you to define a tag whose content is created and controlled by JavaScript code.
|
||||
@ -8,16 +8,7 @@ The browser maintains a `CustomElementRegistry` of defined custom elements (also
|
||||
|
||||
The `@angular/elements` package exports a `createCustomElement()` API that provides a bridge from Angular's component interface and change detection functionality to the built-in DOM API.
|
||||
|
||||
Transforming a component to a custom element makes all of the required Angular infrastructure available to the browser.
|
||||
Creating a custom element is simple and straightforward, and automatically connects your component-defined view with change detection and data binding, mapping Angular functionality to the corresponding native HTML equivalents.
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
We are working on custom elements that can be used by web apps built on other frameworks.
|
||||
A minimal, self-contained version of the Angular framework will be injected as a service to support the component's change-detection and data-binding functionality.
|
||||
For more about the direction of development, check out this [video presentation](https://www.youtube.com/watch?v=Z1gLFPLVJjY&t=4s).
|
||||
|
||||
</div>
|
||||
Transforming a component to a custom element makes all of the required Angular infrastructure available to the browser. Creating a custom element is simple and straightforward, and automatically connects your component-defined view with change detection and data binding, mapping Angular functionality to the corresponding native HTML equivalents.
|
||||
|
||||
## Using custom elements
|
||||
|
||||
@ -50,6 +41,14 @@ When your custom element is placed on a page, the browser creates an instance of
|
||||
|
||||
<hr class="clear">
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
We are working on custom elements that can be used by web apps built on other frameworks.
|
||||
A minimal, self-contained version of the Angular framework will be injected as a service to support the component's change-detection and data-binding functionality.
|
||||
For more about the direction of development, check out this [video presentation](https://www.youtube.com/watch?v=vHI5C-9vH-E).
|
||||
|
||||
</div>
|
||||
|
||||
## Transforming components to custom elements
|
||||
|
||||
Angular provides the `createCustomElement()` function for converting an Angular component,
|
||||
|
@ -28,11 +28,6 @@ By compiling your application using the `ngc` command-line tool, you can bootstr
|
||||
|
||||
Compare [just-in-time (JIT) compilation](guide/glossary#jit).
|
||||
|
||||
## Angular element
|
||||
|
||||
An Angular [component](guide/glossary#component) that has been packaged as a [custom element](guide/glossary#custom-element).
|
||||
|
||||
Learn more in the [_Angular Elements_](guide/elements) guide.
|
||||
|
||||
## Annotation
|
||||
|
||||
@ -439,7 +434,7 @@ Observables can deliver single or multiple values of any type to subscribers, ei
|
||||
|
||||
Angular uses a third-party library called [Reactive Extensions (RxJS)](http://reactivex.io/rxjs/).
|
||||
|
||||
To learn more, see the [Observables](guide/observables) guide.
|
||||
To learn more, see the [Observables](guide/glossary#observable) guide.
|
||||
|
||||
|
||||
{@a observer}
|
||||
|
@ -450,7 +450,7 @@ Here is a `searchHeroes` method that queries for heroes whose names contain the
|
||||
|
||||
If there is a search term, the code constructs an options object with an HTML URL-encoded search parameter. If the term were "foo", the GET request URL would be `api/heroes/?name=foo`.
|
||||
|
||||
The `HttpParams` are immutable so you'll have to use the `set()` method to update the options.
|
||||
The `HttpParms` are immutable so you'll have to use the `set()` method to update the options.
|
||||
|
||||
### Debouncing requests
|
||||
|
||||
@ -1034,7 +1034,7 @@ Call `request.flush()` with an error message, as seen in the following example.
|
||||
|
||||
<code-example
|
||||
path="http/src/testing/http-client.spec.ts"
|
||||
region="network-error"
|
||||
region="404"
|
||||
linenums="false">
|
||||
</code-example>
|
||||
|
||||
@ -1044,4 +1044,4 @@ Alternatively, you can call `request.error()` with an `ErrorEvent`.
|
||||
path="http/src/testing/http-client.spec.ts"
|
||||
region="network-error"
|
||||
linenums="false">
|
||||
</code-example>
|
||||
</code-example>
|
@ -42,10 +42,11 @@ locale id to find the correct corresponding locale data.
|
||||
|
||||
By default, Angular uses the locale `en-US`, which is English as spoken in the United States of America.
|
||||
|
||||
To set your app's locale to another value, use the CLI parameter `--configuration` with the value of the locale id that you want to use:
|
||||
To set your app's locale to another value, use the CLI parameter `--locale` with the value
|
||||
of the locale id that you want to use:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
ng serve --configuration=fr
|
||||
ng serve --aot --locale fr
|
||||
</code-example>
|
||||
|
||||
If you use JIT, you also need to define the `LOCALE_ID` provider in your main module:
|
||||
@ -85,7 +86,7 @@ and `PercentPipe` use locale data to format data based on the `LOCALE_ID`.
|
||||
|
||||
By default, Angular only contains locale data for `en-US`. If you set the value of
|
||||
`LOCALE_ID` to another locale, you must import locale data for that new locale.
|
||||
The CLI imports the locale data for you when you use the parameter `--configuration` with `ng serve` and
|
||||
The CLI imports the locale data for you when you use the parameter `--locale` with `ng serve` and
|
||||
`ng build`.
|
||||
|
||||
If you want to import locale data for other languages, you can do it manually:
|
||||
@ -423,9 +424,9 @@ You can specify the translation format explicitly with the `--i18nFormat` flag a
|
||||
these example commands:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
ng xi18n --i18n-format=xlf
|
||||
ng xi18n --i18n-format=xlf2
|
||||
ng xi18n --i18n-format=xmb
|
||||
ng xi18n --i18nFormat=xlf
|
||||
ng xi18n --i18nFormat=xlf2
|
||||
ng xi18n --i18nFormat=xmb
|
||||
</code-example>
|
||||
|
||||
The sample in this guide uses the default XLIFF 1.2 format.
|
||||
@ -441,11 +442,11 @@ The sample in this guide uses the default XLIFF 1.2 format.
|
||||
### Other options
|
||||
|
||||
You can specify the output path used by the CLI to extract your translation source file with
|
||||
the parameter `--output-path`:
|
||||
the parameter `--outputPath`:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
|
||||
ng xi18n --output-path locale
|
||||
ng xi18n --outputPath src/locale
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -454,15 +455,15 @@ the parameter `--outFile`:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
|
||||
ng xi18n --out-file source.xlf
|
||||
ng xi18n --outFile source.xlf
|
||||
|
||||
</code-example>
|
||||
|
||||
You can specify the base locale of your app with the parameter `--i18n-locale`:
|
||||
You can specify the base locale of your app with the parameter `--locale`:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
|
||||
ng xi18n --i18n-locale fr
|
||||
ng xi18n --locale fr
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -662,7 +663,7 @@ format that Angular understands, such as `.xtb`.
|
||||
How you provide this information depends upon whether you compile with
|
||||
the JIT compiler or the AOT compiler.
|
||||
|
||||
* With [AOT](guide/i18n#merge-aot), you pass the information as a configuration
|
||||
* With [AOT](guide/i18n#merge-aot), you pass the information as a CLI parameter.
|
||||
* With [JIT](guide/i18n#merge-jit), you provide the information at bootstrap time.
|
||||
|
||||
|
||||
@ -676,70 +677,18 @@ When you internationalize with the AOT compiler, you must pre-build a separate a
|
||||
package for each language and serve the appropriate package based on either server-side language
|
||||
detection or url parameters.
|
||||
|
||||
You also need to instruct the AOT compiler to use your translation configuration. To do so, you configure the translation with three options in your `angular.json` file.
|
||||
You also need to instruct the AOT compiler to use your translation file. To do so, you use three
|
||||
options with the `ng serve` or `ng build` commands:
|
||||
|
||||
* `i18nFile`: the path to the translation file.
|
||||
* `i18nFormat`: the format of the translation file.
|
||||
* `i18nLocale`: the locale id.
|
||||
* `--i18nFile`: the path to the translation file.
|
||||
* `--i18nFormat`: the format of the translation file.
|
||||
* `--locale`: the locale id.
|
||||
|
||||
```
|
||||
"configurations": {
|
||||
...
|
||||
"fr": {
|
||||
"aot": true,
|
||||
"outputPath": "dist/my-project-fr/",
|
||||
"i18nFile": "src/locale/messages.fr.xlf",
|
||||
"i18nFormat": "xlf",
|
||||
"i18nLocale": "fr",
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You then pass the configuration with the `ng serve` or `ng build` commands.
|
||||
The example below shows how to serve the French language file created in previous
|
||||
sections of this guide:
|
||||
The example below shows how to serve the French language file created in previous sections of this
|
||||
guide:
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
ng serve --configuration=fr
|
||||
</code-example>
|
||||
|
||||
For production builds, you define a separate `production-fr` build configuration in
|
||||
your `angular.json`.
|
||||
|
||||
```
|
||||
"configurations": {
|
||||
...
|
||||
"production-fr": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"outputPath": "dist/my-project-fr/",
|
||||
"i18nFile": "src/locale/messages.fr.xlf",
|
||||
"i18nFormat": "xlf",
|
||||
"i18nLocale": "fr",
|
||||
"i18nMissingTranslation": "error"
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The same configuration options can also be provided through the CLI with your existing `production` configuration.
|
||||
|
||||
<code-example language="sh" class="code-shell">
|
||||
ng build --prod --i18n-file src/locale/messages.fr.xlf --i18n-format xlf --i18n-locale fr
|
||||
ng serve --aot --i18nFile=src/locale/messages.fr.xlf --i18nFormat=xlf --locale=fr
|
||||
</code-example>
|
||||
|
||||
{@a merge-jit}
|
||||
@ -782,17 +731,12 @@ compilation, the app will fail to load.
|
||||
* Warning (default): show a 'Missing translation' warning in the console or shell.
|
||||
* Ignore: do nothing.
|
||||
|
||||
You specify the warning level in the `configurations` section your Angular CLI build configuration. The example below shows how to set the warning level to error:
|
||||
If you use the AOT compiler, specify the warning level by using the CLI parameter
|
||||
`--missingTranslation`. The example below shows how to set the warning level to error:
|
||||
|
||||
```
|
||||
"configurations": {
|
||||
...
|
||||
"fr": {
|
||||
...
|
||||
"i18nMissingTranslation": "error"
|
||||
}
|
||||
}
|
||||
```
|
||||
<code-example language="sh" class="code-shell">
|
||||
ng serve --aot --missingTranslation=error
|
||||
</code-example>
|
||||
|
||||
If you use the JIT compiler, specify the warning level in the compiler config at bootstrap by adding
|
||||
the 'MissingTranslationStrategy' property. The example below shows how to set the warning level to
|
||||
|
@ -1,33 +1,33 @@
|
||||
# Angular Language Service
|
||||
|
||||
The Angular Language Service is a way to get completions, errors,
|
||||
hints, and navigation inside your Angular templates whether they
|
||||
are external in an HTML file or embedded in annotations/decorators
|
||||
in a string. The Angular Language Service autodetects that you are
|
||||
opening an Angular file, reads your `tsconfig.json` file, finds all the
|
||||
templates you have in your application, and then provides language
|
||||
The Angular Language Service is a way to get completions, errors,
|
||||
hints, and navigation inside your Angular templates whether they
|
||||
are external in an HTML file or embedded in annotations/decorators
|
||||
in a string. The Angular Language Service autodetects that you are
|
||||
opening an Angular file, reads your `tsconfig.json` file, finds all the
|
||||
templates you have in your application, and then provides language
|
||||
services for any templates that you open.
|
||||
|
||||
|
||||
## Autocompletion
|
||||
|
||||
Autocompletion can speed up your development time by providing you with
|
||||
contextual possibilities and hints as you type. This example shows
|
||||
autocomplete in an interpolation. As you type it out,
|
||||
Autocompletion can speed up your development time by providing you with
|
||||
contextual possibilities and hints as you type. This example shows
|
||||
autocomplete in an interpolation. As you type it out,
|
||||
you can hit tab to complete.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/language-service/language-completion.gif" alt="autocompletion">
|
||||
</figure>
|
||||
|
||||
There are also completions within
|
||||
elements. Any elements you have as a component selector will
|
||||
There are also completions within
|
||||
elements. Any elements you have as a component selector will
|
||||
show up in the completion list.
|
||||
|
||||
## Error checking
|
||||
|
||||
The Angular Language Service can also forewarn you of mistakes in your code.
|
||||
In this example, Angular doesn't know what `orders` is or where it comes from.
|
||||
The Angular Language Service can also forewarn you of mistakes in your code.
|
||||
In this example, Angular doesn't know what `orders` is or where it comes from.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/language-service/language-error.gif" alt="error checking">
|
||||
@ -35,8 +35,8 @@ In this example, Angular doesn't know what `orders` is or where it comes from.
|
||||
|
||||
## Navigation
|
||||
|
||||
Navigation allows you to hover to
|
||||
see where a component, directive, module, etc. is from and then
|
||||
Navigation allows you to hover to
|
||||
see where a component, directive, module, etc. is from and then
|
||||
click and press F12 to go directly to its definition.
|
||||
|
||||
<figure>
|
||||
@ -46,52 +46,53 @@ click and press F12 to go directly to its definition.
|
||||
|
||||
## Angular Language Service in your editor
|
||||
|
||||
Angular Language Service is currently available for [Visual Studio Code](https://code.visualstudio.com/) and
|
||||
[WebStorm](https://www.jetbrains.com/webstorm).
|
||||
Angular Language Service is currently available for [Visual Studio Code](https://code.visualstudio.com/) and
|
||||
[WebStorm](https://www.jetbrains.com/webstorm).
|
||||
|
||||
### Visual Studio Code
|
||||
|
||||
In Visual Studio Code, install Angular Language Service from the store,
|
||||
which is accessible from the bottom icon on the left menu pane.
|
||||
You can also use the VS Quick Open (⌘+P) to search for the extension. When you've opened it,
|
||||
enter the following command:
|
||||
In Visual Studio Code, install Angular Language Service from the store,
|
||||
which is accessible from the bottom icon on the left menu pane.
|
||||
You can also use the VS Quick Open (⌘+P) to search for the extension. When you've opened it,
|
||||
enter the following command:
|
||||
|
||||
```sh
|
||||
ext install Angular.ng-template
|
||||
```
|
||||
|
||||
Then click the install button to install the Angular Language Service.
|
||||
Then click the install button to install the Angular Language Service.
|
||||
|
||||
|
||||
### WebStorm
|
||||
|
||||
In webstorm, you have to install the language service as a dev dependency.
|
||||
When Angular sees this dev dependency, it provides the
|
||||
language service inside of WebStorm. Webstorm then gives you
|
||||
In webstorm, you have to install the language service as a dev dependency.
|
||||
When Angular sees this dev dependency, it provides the
|
||||
language service inside of WebStorm. Webstorm then gives you
|
||||
colorization inside the template and autocomplete in addition to the Angular Language Service.
|
||||
|
||||
Here's the dev dependency
|
||||
Here's the dev dependency
|
||||
you need to have in `package.json`:
|
||||
|
||||
```json
|
||||
|
||||
devDependencies {
|
||||
"@angular/language-service": "^6.0.0"
|
||||
"@angular/language-service": "^4.0.0"
|
||||
}
|
||||
```
|
||||
|
||||
Then in the terminal window at the root of your project,
|
||||
install the `devDependencies` with `npm` or `yarn`:
|
||||
Then in the terminal window at the root of your project,
|
||||
install the `devDependencies` with `npm` or `yarn`:
|
||||
|
||||
```sh
|
||||
npm install
|
||||
npm install
|
||||
```
|
||||
*OR*
|
||||
*OR*
|
||||
|
||||
```sh
|
||||
yarn
|
||||
```
|
||||
|
||||
*OR*
|
||||
*OR*
|
||||
|
||||
```sh
|
||||
yarn install
|
||||
@ -100,7 +101,7 @@ yarn install
|
||||
|
||||
### Sublime Text
|
||||
|
||||
In [Sublime Text](https://www.sublimetext.com/), you first need an extension to allow Typescript.
|
||||
In [Sublime Text](https://www.sublimetext.com/), you first need an extension to allow Typescript.
|
||||
Install the latest version of typescript in a local `node_modules` directory:
|
||||
|
||||
```sh
|
||||
@ -112,7 +113,7 @@ Then install the Angular Language Service in the same location:
|
||||
npm install --save-dev @angular/language-service
|
||||
```
|
||||
|
||||
Starting with TypeScript 2.3, TypeScript has a language service plugin model that the language service can use.
|
||||
Starting with TypeScript 2.3, TypeScript has a language service plugin model that the language service can use.
|
||||
|
||||
Next, in your user preferences (`Cmd+,` or `Ctrl+,`), add:
|
||||
|
||||
@ -123,13 +124,13 @@ Next, in your user preferences (`Cmd+,` or `Ctrl+,`), add:
|
||||
|
||||
## Installing in your project
|
||||
|
||||
You can also install Angular Language Service in your project with the
|
||||
You can also install Angular Language Service in your project with the
|
||||
following `npm` command:
|
||||
|
||||
```sh
|
||||
npm install --save-dev @angular/language-service
|
||||
```
|
||||
Additionally, add the following to the `"compilerOptions"` section of
|
||||
Additionally, add the following to the `"compilerOptions"` section of
|
||||
your project's `tsconfig.json`.
|
||||
|
||||
```json
|
||||
@ -137,25 +138,25 @@ your project's `tsconfig.json`.
|
||||
{"name": "@angular/language-service"}
|
||||
]
|
||||
```
|
||||
Note that this only provides diagnostics and completions in `.ts`
|
||||
files. You need a custom sublime plugin (or modifications to the current plugin)
|
||||
Note that this only provides diagnostics and completions in `.ts`
|
||||
files. You need a custom sublime plugin (or modifications to the current plugin)
|
||||
for completions in HTML files.
|
||||
|
||||
|
||||
## How the Language Service works
|
||||
|
||||
When you use an editor with a language service, there's an
|
||||
editor process which starts a separate language process/service
|
||||
to which it speaks through an [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call).
|
||||
Any time you type inside of the editor, it sends information to the other process to
|
||||
track the state of your project. When you trigger a completion list within a template, the editor process first parses the template into an HTML AST, or [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree). Then the Angular compiler interprets
|
||||
what module the template is part of, the scope you're in, and the component selector. Then it figures out where in the template AST your cursor is. When it determines the
|
||||
When you use an editor with a language service, there's an
|
||||
editor process which starts a separate language process/service
|
||||
to which it speaks through an [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call).
|
||||
Any time you type inside of the editor, it sends information to the other process to
|
||||
track the state of your project. When you trigger a completion list within a template, the editor process first parses the template into an HTML AST, or [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree). Then the Angular compiler interprets
|
||||
what module the template is part of, the scope you're in, and the component selector. Then it figures out where in the template AST your cursor is. When it determines the
|
||||
context, it can then determine what the children can be.
|
||||
|
||||
It's a little more involved if you are in an interpolation. If you have an interpolation of `{{data.---}}` inside a `div` and need the completion list after `data.---`, the compiler can't use the HTML AST to find the answer. The HTML AST can only tell the compiler that there is some text with the characters "`{{data.---}}`". That's when the template parser produces an expression AST, which resides within the template AST. The Angular Language Services then looks at `data.---` within its context and asks the TypeScript Language Service what the members of data are. TypeScript then returns the list of possibilities.
|
||||
|
||||
|
||||
For more in-depth information, see the
|
||||
For more in-depth information, see the
|
||||
[Angular Language Service API](https://github.com/angular/angular/blob/master/packages/language-service/src/types.ts)
|
||||
|
||||
|
||||
@ -169,7 +170,7 @@ For more in-depth information, see the
|
||||
|
||||
## More on Information
|
||||
|
||||
For more information, see [Chuck Jazdzewski's presentation](https://www.youtube.com/watch?v=ez3R0Gi4z5A&t=368s) on the Angular Language
|
||||
For more information, see [Chuck Jazdzewski's presentation](https://www.youtube.com/watch?v=ez3R0Gi4z5A&t=368s) on the Angular Language
|
||||
Service from [ng-conf](https://www.ng-conf.org/) 2017.
|
||||
|
||||
|
||||
|
@ -1,168 +0,0 @@
|
||||
# Angular versioning and releases
|
||||
|
||||
We recognize that you need stability from the Angular framework. Stability ensures that reusable components and libraries, tutorials, tools, and learned practices don't become obsolete unexpectedly. Stability is essential for the ecosystem around Angular to thrive.
|
||||
|
||||
We also share with you the desire for Angular to keep evolving. We strive to ensure that the foundation on top of which you are building is continuously improving and enabling you to stay up-to-date with the rest of the web ecosystem and your user needs.
|
||||
|
||||
This document contains the practices that we follow to provide you with a leading-edge app development platform, balanced with stability. We strive to ensure that future changes are always introduced in a predictable way. We want everyone who depends on Angular to know when and how new features are added, and to be well-prepared when obsolete ones are removed.
|
||||
|
||||
See [Updating your projects](guide/updating "Updating your projects") for information about how to update your apps and libraries to the latest version of Angular.
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
The practices described in this document apply to Angular 2.0 and later. If you are currently using AngularJS, see [Upgrading from AngularJS](guide/upgrade "Upgrading from Angular JS"). _AngularJS_ is the name for all v1.x versions of Angular.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{@a angular-versioning}
|
||||
## Angular versioning
|
||||
|
||||
Angular version numbers indicate the level of changes that are introduced by the release. This use of [semantic versioning](https://semver.org/ "Semantic Versioning Specification") helps you understand the potential impact of updating to a new version.
|
||||
|
||||
Angular version numbers have three parts: `major.minor.patch`. For example, version 5.2.9 indicates major version 5, minor version 2, and patch version 9.
|
||||
|
||||
The version number is incremented based on the level of change included in the release.
|
||||
|
||||
* Major releases contain significant new features, some but minimal developer assistance is expected during the update. When updating to a new major release, you may need to run update scripts, refactor code, run additional tests, and learn new APIs.
|
||||
|
||||
* Minor releases contain new smaller features. Minor releases are fully backward-compatible; no developer assistance is expected during update, but you can optionally modify your apps and libraries to begin using new APIs, features, and capabilities that were added in the release. We update peer dependencies in minor versions by expanding the supported versions, but we do not require projects to update these dependencies.
|
||||
|
||||
* Patch releases are low risk, bug fix releases. No developer assistance is expected during update.
|
||||
|
||||
If you are updating within the same major version, then you can skip any intermediate versions and update directly to the targeted version. For example, if you want to update from 5.0.0 to 5.2.9, then you can update directly; you do not need to update from 5.0.0 to 5.1.0 before updating to 5.2.9.
|
||||
|
||||
If you are updating from one major version to another, then we recommend that you don't skip major versions. Follow the instructions to incrementally update to the next major version, testing and validating at each step. For example, if you want to update from version 4.x.x to version 6.x.x, we recommend that you update to the latest 5.x.x release first. After successfully updating to 5.x.x, you can then update to 6.x.x.
|
||||
|
||||
Pre-release previews—such as Beta and Release Candidate versions—are indicated by appending a dash and a beta or rc identifier, such as version 5.2.9-rc.3.
|
||||
|
||||
{@a frequency}
|
||||
## Release frequency
|
||||
|
||||
We work toward a regular schedule of releases, so that you can plan and coordinate your updates with the continuing evolution of Angular.
|
||||
|
||||
In general, you can expect the following release cycle:
|
||||
|
||||
* A major release every 6 months
|
||||
|
||||
* 1-3 minor releases for each major release
|
||||
|
||||
* A patch release almost every week
|
||||
|
||||
We bake quality into our releases—and let you preview what's coming next—by providing Beta releases and release candidates (RCs) for each major and minor release.
|
||||
|
||||
This cadence of releases gives you access to new beta features as soon as they are ready, while maintaining the stability and reliability of the platform for production users.
|
||||
|
||||
|
||||
{@a schedule}
|
||||
## Release schedule
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
Disclaimer: The dates are offered as general guidance and may be adjusted by us when necessary to ensure delivery of a high-quality platform.
|
||||
|
||||
</div>
|
||||
|
||||
The following table contains our current target release dates for the next two major versions of Angular:
|
||||
|
||||
Date | Stable Release | Compatibility
|
||||
---------------------- | -------------- | ----------------
|
||||
September/October 2018 | 7.0.0 | ^6.0.0
|
||||
March/April 2019 | 8.0.0 | ^7.0.0
|
||||
|
||||
Compatiblity note: The primary goal of the backwards compatibility promise is to ensure that changes in the core framework and tooling don't break the existing ecosystem of components and applications and don't put undue upgrade/migration burden on Angular application and component authors.
|
||||
|
||||
|
||||
{@a lts}
|
||||
{@a support}
|
||||
## Support policy
|
||||
|
||||
All of our major releases are supported for 18 months.
|
||||
|
||||
* 6 months of active support, during which regularly-scheduled updates and patches are released, as described above in [Release frequency](#frequency "Release frequency").
|
||||
|
||||
* 12 months of long-term support (LTS). During the LTS period, only critical fixes and security patches will be released.
|
||||
|
||||
The following table provides the support status and key dates for Angular version 4.0.0 and higher.
|
||||
|
||||
<style>
|
||||
|
||||
td, th {vertical-align: top}
|
||||
|
||||
</style>
|
||||
|
||||
<table>
|
||||
|
||||
<tr>
|
||||
<th>Version</th>
|
||||
<th>Status</th>
|
||||
<th>Release Date</th>
|
||||
<th>LTS Start Date</th>
|
||||
<th>LTS End Date</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>^4.0.0</td>
|
||||
<td>LTS</td>
|
||||
<td>March 23, 2017</td>
|
||||
<td>September 23, 2017</td>
|
||||
<td>September 23, 2018</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>^5.0.0</td>
|
||||
<td>LTS</td>
|
||||
<td>November 1, 2017</td>
|
||||
<td>May 1, 2018</td>
|
||||
<td>May 1, 2019</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>^6.0.0</td>
|
||||
<td>Active</td>
|
||||
<td>May 3, 2018</td>
|
||||
<td>November 3, 2018</td>
|
||||
<td>November 3, 2019</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
{@a deprecation}
|
||||
## Deprecation practices
|
||||
|
||||
Sometimes "breaking changes", such as the removal of support for select APIs and features, are necessary to innovate and stay current with new best practices, changing dependencies, or changes in the (web) platform itself.
|
||||
|
||||
To make these transitions as easy as possible, we make two commitments to you:
|
||||
|
||||
* We work hard to minimize the number of breaking changes and to provide migration tools when possible.
|
||||
|
||||
* We follow the deprecation policy described here, so you have time to update your apps to the latest APIs and best practices.
|
||||
|
||||
To help ensure that you have sufficient time and a clear path to update, this is our deprecation policy:
|
||||
|
||||
* We announce deprecated features in the [change log](https://github.com/angular/angular/blob/master/CHANGELOG.md "Angular change log").
|
||||
|
||||
* When we announce a deprecation, we also announce a recommended update path.
|
||||
|
||||
* We support existing use of a stable API during the deprecation period, so your code will keep working during that period.
|
||||
|
||||
* We support each deprecated API for at least two subsequent major releases, which means at least 12 months after deprecation.
|
||||
|
||||
* We only make peer dependency updates that require changes to your apps in a major release. In minor releases, we update peer dependencies by expanding the supported versions, but we do not require projects to update these dependencies until a future major version.
|
||||
|
||||
|
||||
{@a public-api}
|
||||
## Public API surface
|
||||
|
||||
Angular is a collection of many packages, sub-projects, and tools. To prevent accidental use of private APIs—and so that you can clearly understand what is covered by the practices described here—we document what is and is not considered our public API surface. For details, see [Supported Public API Surface of Angular](https://github.com/angular/angular/blob/master/docs/PUBLIC_API.md "Supported Public API Surface of Angular").
|
||||
|
||||
Any changes to the public API surface will be done using the versioning, support, and depreciation policies describe above.
|
||||
|
||||
{@a labs}
|
||||
## Angular Labs
|
||||
|
||||
Angular Labs is an initiative to cultivate new features and iterate on them quickly. Angular Labs provides a safe place for exploration and experimentation by the Angular team.
|
||||
|
||||
Angular Labs projects are not ready for production use, and no commitment is made to bring them to production. The policies and practices that are described in this document do not apply to Angular Labs projects.
|
@ -3656,7 +3656,7 @@ Lazy loading has multiple benefits.
|
||||
* You can speed up load time for users that only visit certain areas of the application.
|
||||
* You can continue expanding lazy loaded feature areas without increasing the size of the initial load bundle.
|
||||
|
||||
You're already part of the way there.
|
||||
You're already made part way there.
|
||||
By organizing the application into modules—`AppModule`,
|
||||
`HeroesModule`, `AdminModule` and `CrisisCenterModule`—you
|
||||
have natural candidates for lazy loading.
|
||||
|
@ -70,7 +70,6 @@ interface AssetGroup {
|
||||
updateMode?: 'prefetch' | 'lazy';
|
||||
resources: {
|
||||
files?: string[];
|
||||
/** @deprecated As of v6 `versionedFiles` and `files` options have the same behavior. Use `files` instead. */
|
||||
versionedFiles?: string[];
|
||||
urls?: string[];
|
||||
};
|
||||
@ -103,7 +102,7 @@ This section describes the resources to cache, broken up into three groups.
|
||||
|
||||
* `files` lists patterns that match files in the distribution directory. These can be single files or glob-like patterns that match a number of files.
|
||||
|
||||
* `versionedFiles` has been deprecated. As of v6 `versionedFiles` and `files` options have the same behavior. Use `files` instead.
|
||||
* `versionedFiles` is like `files` but should be used for build artifacts that already include a hash in the filename, which is used for cache busting. The Angular service worker can optimize some aspects of its operation if it can assume file contents are immutable.
|
||||
|
||||
* `urls` includes both URLs and URL patterns that will be matched at runtime. These resources are not fetched directly and do not have content hashes, but they will be cached according to their HTTP headers. This is most useful for CDNs such as the Google Fonts service.<br>
|
||||
_(Negative glob patterns are not supported.)_
|
||||
|
@ -23,14 +23,10 @@ ng add @angular/pwa --project *project-name*
|
||||
|
||||
The above command completes the following actions:
|
||||
|
||||
1. Adds the `@angular/service-worker` package to your project.
|
||||
1. Adds the `@angular/service-worker` package.
|
||||
2. Enables service worker build support in the CLI.
|
||||
3. Imports and registers the service worker in the app module.
|
||||
4. Updates the `index.html` file:
|
||||
* Includes a link to add the `manifest.json` file.
|
||||
* Adds meta tags for `theme-color`.
|
||||
5. Installs icon files to support the installed Progressive Web App (PWA).
|
||||
6. Creates the service worker configuration file called [`ngsw-config.json`](/guide/service-worker-config), which specifies the caching behaviors and other settings.
|
||||
4. Creates the service worker configuration file called `ngsw-config.json` which specifies the caching behaviors and other settings.
|
||||
|
||||
|
||||
Now, build the project:
|
||||
|
3
aio/content/guide/testing-observables.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Testing
|
||||
|
||||
TBD. Original content [here](https://docs.google.com/document/d/1gGP5sqWNCHAWWV_GLdZQ1XyMO4K-CHksUxux0BFtVxk/edit#heading=h.ohqykkhzdhb2).
|
@ -204,7 +204,8 @@ The test consumes that spy in the same way it did earlier.
|
||||
Most test suites in this guide call `beforeEach()` to set the preconditions for each `it()` test
|
||||
and rely on the `TestBed` to create classes and inject services.
|
||||
|
||||
There's another school of testing that never calls `beforeEach()` and prefers to create classes explicitly rather than use the `TestBed`.
|
||||
There's another school of testing that never calls `beforeEach()` and
|
||||
and prefers to create classes explicitly rather than use the `TestBed`.
|
||||
|
||||
Here's how you might rewrite one of the `MasterService` tests in that style.
|
||||
|
||||
@ -346,7 +347,7 @@ It appears within the template of a parent component,
|
||||
which binds a _hero_ to the `@Input` property and
|
||||
listens for an event raised through the _selected_ `@Output` property.
|
||||
|
||||
You can test that the class code works without creating the `DashboardHeroComponent`
|
||||
You can test that the class code works without creating the the `DashboardHeroComponent`
|
||||
or its parent component.
|
||||
|
||||
<code-example
|
||||
@ -2366,9 +2367,9 @@ The [override metadata object](#metadata-override-object) is a generic defined a
|
||||
|
||||
<code-example format="." language="javascript">
|
||||
type MetadataOverride<T> = {
|
||||
add?: Partial<T>;
|
||||
remove?: Partial<T>;
|
||||
set?: Partial<T>;
|
||||
add?: T;
|
||||
remove?: T;
|
||||
set?: T;
|
||||
};
|
||||
</code-example>
|
||||
|
||||
@ -2724,9 +2725,9 @@ appropriate to the method, that is, the parameter of an `@NgModule`,
|
||||
|
||||
<code-example format="." language="javascript">
|
||||
type MetadataOverride<T> = {
|
||||
add?: Partial<T>;
|
||||
remove?: Partial<T>;
|
||||
set?: Partial<T>;
|
||||
add?: T;
|
||||
remove?: T;
|
||||
set?: T;
|
||||
};
|
||||
</code-example>
|
||||
|
||||
@ -3378,11 +3379,11 @@ next to their corresponding helper files.
|
||||
{@a q-e2e}
|
||||
#### Why not rely on E2E tests of DOM integration?
|
||||
|
||||
The component DOM tests described in this guide often require extensive setup and
|
||||
advanced techniques whereas the [unit tests](#component-class-testing)
|
||||
are comparatively simple.
|
||||
The component DOM tests describe in this guide often require extensive setup and
|
||||
advanced techniques where as the [class-only test](#component-class-testing)
|
||||
were comparatively simple.
|
||||
|
||||
#### Why not defer DOM integration tests to end-to-end (E2E) testing?
|
||||
Why not defer DOM integration tests to end-to-end (E2E) testing?
|
||||
|
||||
E2E tests are great for high-level validation of the entire system.
|
||||
But they can't give you the comprehensive test coverage that you'd expect from unit tests.
|
||||
@ -3399,4 +3400,4 @@ accidental corruption of remote resources.
|
||||
It can even be hard to navigate to the component you want to test.
|
||||
|
||||
Because of these many obstacles, you should test DOM interaction
|
||||
with unit testing techniques as much as possible.
|
||||
with unit testing techniques as much as possible.
|
@ -180,7 +180,7 @@ npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-lo
|
||||
|
||||
{@a transition}
|
||||
|
||||
## Modify the client app
|
||||
### Modify the client app
|
||||
|
||||
A Universal app can act as a dynamic, content-rich "splash screen" that engages the user.
|
||||
It gives the appearance of a near-instant application.
|
||||
@ -190,9 +190,7 @@ Once loaded, Angular transitions from the static server-rendered page to the dyn
|
||||
|
||||
You must make a few changes to your application code to support both server-side rendering and the transition to the client app.
|
||||
|
||||
{@a root-app-module}
|
||||
|
||||
### The root `AppModule`
|
||||
#### The root `AppModule`
|
||||
|
||||
Open file `src/app/app.module.ts` and find the `BrowserModule` import in the `NgModule` metadata.
|
||||
Replace that import with this one:
|
||||
@ -208,29 +206,9 @@ You can get runtime information about the current platform and the `appId` by in
|
||||
<code-example path="universal/src/app/app.module.ts" region="platform-detection" title="src/app/app.module.ts (platform detection)">
|
||||
</code-example>
|
||||
|
||||
{@a cli-output}
|
||||
|
||||
### Build Destination
|
||||
|
||||
A Universal app is distributed in two parts: the server-side code that serves up the initial application, and the client-side code that's loaded in dynamically.
|
||||
|
||||
The Angular CLI outputs the client-side code in the `dist` directory by default, so you modify the `outputPath` for the __build__ target in the `angular.json` to keep the client-side build outputs separate from the server-side code. The client-side build output will be served by the Express server.
|
||||
|
||||
```
|
||||
...
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/browser",
|
||||
...
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
{@a http-urls}
|
||||
|
||||
### Absolute HTTP URLs
|
||||
#### Absolute HTTP URLs
|
||||
|
||||
The tutorial's `HeroService` and `HeroSearchService` delegate to the Angular `HttpClient` module to fetch application data.
|
||||
These services send requests to _relative_ URLs such as `api/heroes`.
|
||||
@ -284,19 +262,6 @@ The `ModuleMapLoaderModule` is a server-side module that allows lazy-loading of
|
||||
|
||||
This is also the place to register providers that are specific to running your app under Universal.
|
||||
|
||||
{@a app-server-entry-point}
|
||||
|
||||
### App server entry point
|
||||
|
||||
The `Angular CLI` uses the `AppServerModule` to build the server-side bundle.
|
||||
|
||||
Create a `main.server.ts` file in the `src/` directory that exports the `AppServerModule`:
|
||||
|
||||
<code-example path="universal/src/main.server.ts" title="src/main.server.ts">
|
||||
</code-example>
|
||||
|
||||
The `main.server.ts` will be referenced later to add a `server` target to the `Angular CLI` configuration.
|
||||
|
||||
{@a web-server}
|
||||
|
||||
### Universal web server
|
||||
@ -456,8 +421,6 @@ This config extends from the root's `tsconfig.json` file. Certain settings are n
|
||||
* The `angularCompilerOptions` section guides the AOT compiler:
|
||||
* `entryModule` - the root module of the server application, expressed as `path/to/file#ClassName`.
|
||||
|
||||
{@a universal-webpack-configuration}
|
||||
|
||||
### Universal Webpack configuration
|
||||
|
||||
Universal applications doesn't need any extra Webpack configuration, the CLI takes care of that for you,
|
||||
@ -470,47 +433,22 @@ Create a `webpack.server.config.js` file in the project root directory with the
|
||||
|
||||
**Webpack configuration** is a rich topic beyond the scope of this guide.
|
||||
|
||||
{@a universal-cli-configuration}
|
||||
|
||||
### Angular CLI configuration
|
||||
|
||||
The CLI provides builders for different types of __targets__. Commonly known targets such as `build` and `serve` already exist in the `angular.json` configuration. To target a server-side build, add a `server` target to the `architect` configuration object.
|
||||
|
||||
* The `outputPath` tells where the resulting build will be created.
|
||||
* The `main` provides the main entry point to the previously created `main.server.ts`
|
||||
* The `tsConfig` uses the `tsconfig.server.json` as configuration for the TypeScript and AOT compilation.
|
||||
|
||||
```
|
||||
"architect": {
|
||||
...
|
||||
"server": {
|
||||
"builder": "@angular-devkit/build-angular:server",
|
||||
"options": {
|
||||
"outputPath": "dist/server",
|
||||
"main": "src/main.server.ts",
|
||||
"tsConfig": "src/tsconfig.server.json"
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Build and run with universal
|
||||
|
||||
Now that you've created the TypeScript and Webpack config files and configured the Angular CLI, you can build and run the Universal application.
|
||||
Now that you've created the TypeScript and Webpack config files, you can build and run the Universal application.
|
||||
|
||||
First add the _build_ and _serve_ commands to the `scripts` section of the `package.json`:
|
||||
|
||||
```
|
||||
<code-example format="." language="ts">
|
||||
"scripts": {
|
||||
...
|
||||
"build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server",
|
||||
"serve:ssr": "node dist/server",
|
||||
"build:client-and-server-bundles": "ng build --prod && ng run angular.io-example:server",
|
||||
"build:universal": "npm run build:client-and-server-bundles && npm run webpack:server",
|
||||
"serve:universal": "node dist/server.js",
|
||||
"build:client-and-server-bundles": "ng build --prod && ng build --prod --app 1 --output-hashing=false",
|
||||
"webpack:server": "webpack --config webpack.server.config.js --progress --colors"
|
||||
...
|
||||
}
|
||||
```
|
||||
</code-example>
|
||||
|
||||
{@a build}
|
||||
|
||||
@ -519,7 +457,7 @@ First add the _build_ and _serve_ commands to the `scripts` section of the `pack
|
||||
From the command prompt, type
|
||||
|
||||
<code-example format="." language="bash">
|
||||
npm run build:ssr
|
||||
npm run build:universal
|
||||
</code-example>
|
||||
|
||||
The Angular CLI compiles and bundles the universal app into two different folders, `browser` and `server`.
|
||||
@ -531,7 +469,7 @@ Webpack transpiles the `server.ts` file into Javascript.
|
||||
After building the application, start the server.
|
||||
|
||||
<code-example format="." language="bash">
|
||||
npm run serve:ssr
|
||||
npm run serve:universal
|
||||
</code-example>
|
||||
|
||||
The console window should say
|
||||
|
@ -1,69 +0,0 @@
|
||||
# Updating your Angular projects
|
||||
|
||||
Just like Web and the entire web ecosystem, Angular is continuously improving. Angular balances continuous improvement with a strong focus on stability and making updates easy. Keeping your Angular app up-to-date enables you to take advantage of leading-edge new features, as well as optimizations and bug fixes.
|
||||
|
||||
This document contains information and resources to help you keep your Angular apps and libraries up-to-date.
|
||||
|
||||
For information about our versioning policy and practices—including
|
||||
support and deprecation practices, as well as the release schedule—see [Angular versioning and releases](guide/releases "Angular versioning and releases").
|
||||
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
If you are currently using AngularJS, see [Upgrading from AngularJS](guide/upgrade "Upgrading from Angular JS"). _AngularJS_ is the name for all v1.x versions of Angular.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{@a announce}
|
||||
## Getting notified of new releases
|
||||
|
||||
To be notified when new releases are available, follow [@angular](https://twitter.com/angular "@angular on Twitter") on Twitter or subscribe to the [Angular blog](https://blog.angular.io "Angular blog").
|
||||
|
||||
{@a learn}
|
||||
## Learning about new features
|
||||
|
||||
What's new? What's changed? We share the most important things you need to know on the Angular blog in [release announcements]( https://blog.angular.io/tagged/release%20notes "Angular blog - release announcements").
|
||||
|
||||
To review a complete list of changes, organized by version, see the [Angular change log](https://github.com/angular/angular/blob/master/CHANGELOG.md "Angular change log").
|
||||
|
||||
|
||||
{@a checking-version-app}
|
||||
## Checking your version of Angular
|
||||
|
||||
To check your app's version of Angular: From within your project directory, use the `ng version` command.
|
||||
|
||||
|
||||
{@a checking-version-angular}
|
||||
## Finding the current version of Angular
|
||||
|
||||
The most recent stable released version of Angular appears in the [Angular documentation](https://angular.io/docs "Angular documentation") at the bottom of the left side navigation. For example, `stable (v5.2.9)`.
|
||||
|
||||
You can also find the most current version of Angular by using the [CLI command `ng update`](https://github.com/angular/angular-cli/wiki/update "Angular CLI update documentation"). By default, `ng update` (without additional arguments) lists the updates that are available to you.
|
||||
|
||||
|
||||
{@a updating}
|
||||
## Updating your environment and apps
|
||||
|
||||
To make updating easy, we provide complete instructions in the interactive [Angular Update Guide](https://update.angular.io/ "Angular Update Guide").
|
||||
|
||||
The Angular Update Guide provides customized update instructions, based on the current and target versions that you specify. It includes basic and advanced update paths, to match the complexity of your applications. It also includes troubleshooting information and any recommended manual changes to help you get the most out of the new release.
|
||||
|
||||
For simple updates, the [CLI command `ng update`](https://github.com/angular/angular-cli/wiki/update "Angular CLI update documentation") is all you need. Without additional arguments, `ng update` lists the updates that are available to you and provides recommended steps to update your application to the most current version.
|
||||
|
||||
{@a resources}
|
||||
## Resource summary
|
||||
|
||||
* Release announcements: [Angular blog - release announcements](https://blog.angular.io/tagged/release%20notes "Angular blog announcements about recent releases")
|
||||
|
||||
* Release announcements (older): [Angular blog - announcements about releases prior to August 2017](https://blog.angularjs.org/search?q=available&by-date=true "Angular blog announcements about releases prior to August 2017")
|
||||
|
||||
* Release details: [Angular change log](https://github.com/angular/angular/blob/master/CHANGELOG.md "Angular change log")
|
||||
|
||||
* Update instructions: [Angular Update Guide](https://update.angular.io/ "Angular Update Guide")
|
||||
|
||||
* Update command reference: [Angular CLI update documentation](https://github.com/angular/angular-cli/wiki/update "Angular CLI update documentation")
|
||||
|
||||
* Versioning, release, support, and deprecation practices: [Angular versioning and releases](guide/releases "Angular versioning and releases")
|
||||
|
||||
* Release schedule: [Angular versioning and releases](guide/releases#schedule "Angular versioning and releases")
|
@ -1,4 +1,4 @@
|
||||
# Upgrading from AngularJS to Angular
|
||||
# Upgrading from AngularJS
|
||||
|
||||
_Angular_ is the name for the Angular of today and tomorrow.
|
||||
_AngularJS_ is the name for all v1.x versions of Angular.
|
||||
|
Before Width: | Height: | Size: 350 KiB |
Before Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 207 KiB |
Before Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 28 KiB |
@ -591,59 +591,5 @@
|
||||
"website": "http://ngnir.life/",
|
||||
"bio": "Nir is a Principal Frontend Consultant & Head of the Angular department at 500Tech, Google Developer Expert and community leader. He organizes the largest Angular meetup group in Israel (Angular-IL), talks and teaches about front-end technologies around the world. He is also the author of two books about Angular and the founder of the 'Frontend Band'.",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
"achautard": {
|
||||
"name": "Alain Chautard",
|
||||
"picture": "alainchautard.png",
|
||||
"twitter": "AlainChautard",
|
||||
"website": "http://www.angulartraining.com",
|
||||
"bio": "Alain Chautard is a Google Developer Expert in Web Technologies / Angular. He started working with Angular JS in 2011. Since then he has worked with all Angular versions on a daily basis, both as a developer and as a technical trainer. He is the organizer of the Sacramento Angular Meetup group, co-organizer of the Google Developer Group chapter in Sacramento, California, and published author of the Packt video course 'Getting Started with Angular'",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
"coryrylan": {
|
||||
"name": "Cory Rylan",
|
||||
"picture": "cory-rylan.jpg",
|
||||
"twitter": "coryrylan",
|
||||
"website": "https://coryrylan.com",
|
||||
"bio": "Cory is a full time front end web developer. He works full time building responsive web applications and progressive web apps. When not building web apps he is busy teaching Angular and other web technologies in workshops and conferences. He loves the web and is optimistic of the places it can take us.",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
"mhartington": {
|
||||
"name": "Mike Hartington",
|
||||
"picture": "mhartington.png",
|
||||
"twitter": "mhartington",
|
||||
"website": "https://mhartington.io",
|
||||
"bio": "Mike is a Developer Advocate for the Ionic Framework and a GDE in Angular. He spends most of his time making fast PWAs and exploring emerging web standards. When not behind a keyboard, you'll probably find him with a guitar and beer.",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
"juristr": {
|
||||
"name": "Juri Strumpflohner",
|
||||
"picture": "juristr.jpg",
|
||||
"twitter": "juristr",
|
||||
"website": "https://juristr.com",
|
||||
"bio": "Juri is a software engineer and freelance trainer and consultant currently mostly focusing on the frontend side using JavaScript, TypeScript and Angular. He has a passion for teaching and sharing his knowledge and experiences with others. This mostly happens by writing tech articles for his personal blog, by creating video courses for Egghead.io, during on-site workshops at companies or by speaking at conferences. In his free time he enjoys practicing Yoseikan Budo, a martial art where he currently owns the 3rd DAN black belt.",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
"mashhoodr": {
|
||||
"name": "Mashhood Rastgar",
|
||||
"picture": "mashhood.jpg",
|
||||
"twitter": "mashhoodr",
|
||||
"website": "http://imars.info/",
|
||||
"bio": "Mashhood is the principal technical consultant at Recurship and a Google Developer Expert. He works with different startups in US and EU to helps them crawl through the technical maze and quickly build amazing products focused around the problems they are trying to solve. He specializes in using the latest web technologies available to execute the best possible solutions.",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
"kimmaida": {
|
||||
"name": "Kim Maida",
|
||||
"picture": "kimmaida.jpg",
|
||||
"twitter": "KimMaida",
|
||||
"website": "https://kmaida.io/",
|
||||
"bio": "Kim is an an Angular consultant, developer, speaker, writer, and Google Developer Expert. She is passionate about learning from and sharing knowledge with other developers through blogging, speaking, workshops, and open source.",
|
||||
"group": "GDE"
|
||||
}
|
||||
}
|
||||
|
@ -55,12 +55,6 @@
|
||||
<td>Tokyo, Japan</td>
|
||||
<td>Jun 16, 2018</td>
|
||||
</tr>
|
||||
<!-- Angular Conf Australia-->
|
||||
<tr>
|
||||
<th><a href="https://www.angularconf.com.au/" title="Angular Conf Australia">Angular Conf Australia</a></th>
|
||||
<td>Melbourne, Australia</td>
|
||||
<td>Jun 22, 2018</td>
|
||||
</tr>
|
||||
<!-- AngularConnect-->
|
||||
<tr>
|
||||
<th><a href="http://angularconnect.com" title="AngularConnect">AngularConnect</a></th>
|
||||
|
@ -78,440 +78,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div>
|
||||
<h2>BRAND ICONS</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/animations.png" alt="Animations Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">ANIMATIONS</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Animations Icon (png) - <a href="assets/images/logos/concept-icons/animations.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Animations Icon (svg) - <a href="assets/images/logos/concept-icons/animations.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/augury.png" alt="Augury Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">AUGURY</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Augury Icon (png) - <a href="assets/images/logos/concept-icons/augury.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Augury Icon (svg) - <a href="assets/images/logos/concept-icons/augury.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/cdk.png" alt="CDK Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">COMPONENT DEV KIT (CDK)</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>CDK Icon (png) - <a href="assets/images/logos/concept-icons/cdk.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>CDK Icon (svg) - <a href="assets/images/logos/concept-icons/cdk.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/cli.png" alt="CLI Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">CLI</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>CLI Icon (png) - <a href="assets/images/logos/concept-icons/cli.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>CLI Icon (svg) - <a href="assets/images/logos/concept-icons/cli.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/compiler.png" alt="Compiler Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">COMPILER</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Compiler Icon (png) - <a href="assets/images/logos/concept-icons/compiler.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Compiler Icon (svg) - <a href="assets/images/logos/concept-icons/compiler.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/components.png" alt="Components Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">WEB COMPONENTS</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Web Components Icon (png) - <a href="assets/images/logos/concept-icons/components.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Web Components Icon (svg) - <a href="assets/images/logos/concept-icons/components.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/forms.png" alt="Forms Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">FORMS</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Forms Icon (png) - <a href="assets/images/logos/concept-icons/forms.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Forms Icon (svg) - <a href="assets/images/logos/concept-icons/forms.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/http.png" alt="HTTP Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">HTTP</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>HTTP Icon (png) - <a href="assets/images/logos/concept-icons/http.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>HTTP Icon (svg) - <a href="assets/images/logos/concept-icons/http.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/i18n.png" alt="i18n Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">i18n</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>HTTP Icon (png) - <a href="assets/images/logos/concept-icons/i18n.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>HTTP Icon (svg) - <a href="assets/images/logos/concept-icons/i18n.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/karma.png" alt="Karma Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">KARMA</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Karma Icon (png) - <a href="assets/images/logos/concept-icons/karma.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Karma Icon (svg) - <a href="assets/images/logos/concept-icons/karma.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/labs.png" alt="Labs Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">LABS</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Labs Icon (png) - <a href="assets/images/logos/concept-icons/labs.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Labs Icon (svg) - <a href="assets/images/logos/concept-icons/labs.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/language-services.png" alt="Language Services Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">LANGUAGE SERVICES</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Language Services Icon (png) - <a href="assets/images/logos/concept-icons/language-services.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Language Services Icon (svg) - <a href="assets/images/logos/concept-icons/language-services.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/material.png" alt="Material Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">MATERIAL</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Material Icon (png) - <a href="assets/images/logos/concept-icons/material.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Material Icon (svg) - <a href="assets/images/logos/concept-icons/material.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/protractor.png" alt="Protractor Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">PROTRACTOR</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Protractor Icon (png) - <a href="assets/images/logos/concept-icons/protractor.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Protractor Icon (svg) - <a href="assets/images/logos/concept-icons/protractor.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/pwa.png" alt="PWA Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">PWA</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>PWA Icon (png) - <a href="assets/images/logos/concept-icons/pwa.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>PWA Icon (svg) - <a href="assets/images/logos/concept-icons/pwa.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/router.png" alt="Router Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">ROUTER</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Router Icon (png) - <a href="assets/images/logos/concept-icons/router.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Router Icon (svg) - <a href="assets/images/logos/concept-icons/router.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/universal.png" alt="Universal Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">UNIVERSAL</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Universal Icon (png) - <a href="assets/images/logos/concept-icons/universal.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Universal Icon (svg) - <a href="assets/images/logos/concept-icons/universal.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div>
|
||||
<h2>CONCEPT & FEATURE ICONS</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/dependency-injection.png" alt="Dependency Injection Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">DEPENDENCY INJECTION</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Dependency Injection Icon (png) - <a href="assets/images/logos/concept-icons/dependency-injection.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Dependency Injection Icon (svg) - <a href="assets/images/logos/concept-icons/dependency-injection.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/lazy-loading.png" alt="Lazy Loading Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">LAZY LOADING</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Lazy Loading Icon (png) - <a href="assets/images/logos/concept-icons/lazy-loading.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Lazy Loading Icon (svg) - <a href="assets/images/logos/concept-icons/lazy-loading.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/libraries.png" alt="Libraries Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">LIBRARIES</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Libraries Icon (png) - <a href="assets/images/logos/concept-icons/libraries.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Libraries Icon (svg) - <a href="assets/images/logos/concept-icons/libraries.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/performance.png" alt="Performance Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">PERFORMANCE</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Performance Icon (png) - <a href="assets/images/logos/concept-icons/performance.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Performance Icon (svg) - <a href="assets/images/logos/concept-icons/performance.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/templates.png" alt="Templates Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">TEMPLATES</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Templates Icon (png) - <a href="assets/images/logos/concept-icons/templates.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Templates Icon (svg) - <a href="assets/images/logos/concept-icons/templates.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div>
|
||||
|
@ -160,7 +160,7 @@
|
||||
"desc": "Lightweight yet powerful IDE, perfectly equipped for complex client-side development and server-side development with Node.js",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "WebStorm",
|
||||
"title": "Webstorm",
|
||||
"url": "https://www.jetbrains.com/webstorm/"
|
||||
},
|
||||
"ab3": {
|
||||
@ -345,19 +345,6 @@
|
||||
"title": "Angular Material",
|
||||
"url": "https://github.com/angular/material2"
|
||||
},
|
||||
"mcc": {
|
||||
"desc": "Material components made by the community",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Material Community Components",
|
||||
"url": "https://github.com/tiaguinho/material-community-components"
|
||||
},
|
||||
"ngzorro": {
|
||||
"desc": "A set of enterprise-class UI components based on Ant Design and Angular",
|
||||
"rev": true,
|
||||
"title": "Ant Design of Angular (ng-zorro-antd)",
|
||||
"url": "https://ng.ant.design/docs/introduce/en"
|
||||
},
|
||||
"aggrid": {
|
||||
"desc": "A datagrid for Angular with enterprise style features such as sorting, filtering, custom rendering, editing, grouping, aggregation and pivoting.",
|
||||
"rev": true,
|
||||
@ -377,13 +364,6 @@
|
||||
"url": "http://www.amexio.tech/",
|
||||
"logo": "http://www.amexio.org/amexio-logo.png"
|
||||
},
|
||||
"bm": {
|
||||
"desc": "A lightweight Material Design library for Angular, based upon Google's Material Components for the Web",
|
||||
"logo": "https://blox.src.zone/assets/bloxmaterial.03ecfe4fa0147a781487749dc1cc4580.svg",
|
||||
"rev": true,
|
||||
"title": "Blox Material",
|
||||
"url": "https://github.com/src-zone/material"
|
||||
},
|
||||
"essentialjs2": {
|
||||
"desc": "Essential JS 2 for Angular is a collection modern TypeScript based true Angular Components. It has support for Ahead Of Time (AOT) compilation and Tree-Shaking. All the components are developed from the ground up to be lightweight, responsive, modular and touch friendly.",
|
||||
"rev": true,
|
||||
@ -542,6 +522,13 @@
|
||||
"title": "Pluralsight",
|
||||
"url": "https://www.pluralsight.com/search?q=angular+2&categories=all"
|
||||
},
|
||||
"ab": {
|
||||
"desc": "Take this introduction to Angular course, to learn the fundamentals in just two days, free of charge.",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Rangle.io",
|
||||
"url": "https://rangle.io/services/javascript-training/training-angular1-angular2-with-ngupgrade/"
|
||||
},
|
||||
"ab3": {
|
||||
"desc": "Angular courses hosted by Udemy",
|
||||
"logo": "",
|
||||
@ -610,6 +597,12 @@
|
||||
"Workshops & Onsite Training": {
|
||||
"order": 2,
|
||||
"resources": {
|
||||
"-KLIBo3ANF3-1B9wxsoB": {
|
||||
"desc": "Angular Classes from Intertech in Minnesota",
|
||||
"rev": true,
|
||||
"title": "Intertech",
|
||||
"url": "http://www.intertech.com/Training/Web-Dev/AngularJS/AngularJS/Angular-2-Training"
|
||||
},
|
||||
"-KLIBoFWStce29UCwkvY": {
|
||||
"desc": "Private Angular Training and Mentoring",
|
||||
"rev": true,
|
||||
|
@ -495,34 +495,17 @@
|
||||
},
|
||||
|
||||
{
|
||||
"title": "Keeping Up-to-Date",
|
||||
"tooltip": "Angular release practices, planning for updates, and update resources.",
|
||||
"children": [
|
||||
{
|
||||
"url": "guide/updating",
|
||||
"title": "Updating Your Projects",
|
||||
"tooltip": "Information about updating Angular applications and libraries to the latest version."
|
||||
},
|
||||
{
|
||||
"url": "guide/releases",
|
||||
"title": "Angular Releases",
|
||||
"tooltip": "Angular versioning, release, support, and deprecation policies and practices."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"title": "Upgrading from AngularJS",
|
||||
"title": "Upgrading",
|
||||
"tooltip": "Incrementally upgrade an AngularJS application to Angular.",
|
||||
"children": [
|
||||
{
|
||||
"url": "guide/upgrade",
|
||||
"title": "Upgrading Instructions",
|
||||
"title": "Upgrading from AngularJS",
|
||||
"tooltip": "Incrementally upgrade an AngularJS application to Angular."
|
||||
},
|
||||
{
|
||||
"url": "guide/ajs-quick-reference",
|
||||
"title": "AngularJS-Angular Concepts",
|
||||
"title": "Upgrade Cheatsheet",
|
||||
"tooltip": "Learn how AngularJS concepts and techniques map to Angular."
|
||||
}
|
||||
]
|
||||
@ -660,9 +643,9 @@
|
||||
],
|
||||
|
||||
"docVersions": [
|
||||
{ "title": "v5", "url": "https://v5.angular.io" },
|
||||
{ "title": "v4", "url": "https://v4.angular.io" },
|
||||
{ "title": "v4 (LTS)", "url": "https://v4.angular.io" },
|
||||
{ "title": "v2", "url": "https://v2.angular.io" },
|
||||
{ "title": "AngularDart", "url": "https://webdev.dartlang.org/angular" }
|
||||
|
||||
]
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ This information is called _metadata_
|
||||
Some of the metadata is in the `@Component` decorators that you added to your component classes.
|
||||
Other critical metadata is in [`@NgModule`](guide/ngmodules) decorators.
|
||||
|
||||
The most important `@NgModule` decorator annotates the top-level **AppModule** class.
|
||||
The most important `@NgModule`decorator annotates the top-level **AppModule** class.
|
||||
|
||||
The Angular CLI generated an `AppModule` class in `src/app/app.module.ts` when it created the project.
|
||||
This is where you _opt-in_ to the `FormsModule`.
|
||||
|
@ -70,18 +70,18 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "6.0.0",
|
||||
"@angular/cdk": "6.0.0",
|
||||
"@angular/common": "6.0.0",
|
||||
"@angular/core": "6.0.0",
|
||||
"@angular/elements": "6.0.0",
|
||||
"@angular/forms": "6.0.0",
|
||||
"@angular/http": "6.0.0",
|
||||
"@angular/material": "6.0.0",
|
||||
"@angular/platform-browser": "6.0.0",
|
||||
"@angular/platform-browser-dynamic": "6.0.0",
|
||||
"@angular/platform-server": "6.0.0",
|
||||
"@angular/router": "6.0.0",
|
||||
"@angular/animations": "6.0.0-rc.5",
|
||||
"@angular/cdk": "6.0.0-rc.11",
|
||||
"@angular/common": "6.0.0-rc.5",
|
||||
"@angular/core": "6.0.0-rc.5",
|
||||
"@angular/elements": "6.0.0-rc.5",
|
||||
"@angular/forms": "6.0.0-rc.5",
|
||||
"@angular/http": "6.0.0-rc.5",
|
||||
"@angular/material": "6.0.0-rc.11",
|
||||
"@angular/platform-browser": "6.0.0-rc.5",
|
||||
"@angular/platform-browser-dynamic": "6.0.0-rc.5",
|
||||
"@angular/platform-server": "6.0.0-rc.5",
|
||||
"@angular/router": "6.0.0-rc.5",
|
||||
"@angular/service-worker": "^1.0.0-beta.16",
|
||||
"@webcomponents/custom-elements": "^1.0.8",
|
||||
"classlist.js": "^1.1.20150312",
|
||||
@ -95,11 +95,11 @@
|
||||
"zone.js": "^0.8.26"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^0.6.0",
|
||||
"@angular/cli": "^6.0.0",
|
||||
"@angular/compiler": "6.0.0",
|
||||
"@angular/compiler-cli": "6.0.0",
|
||||
"@angular/language-service": "6.0.0",
|
||||
"@angular-devkit/build-angular": "^0.5.6",
|
||||
"@angular/cli": "^6.0.0-rc.4",
|
||||
"@angular/compiler": "6.0.0-rc.5",
|
||||
"@angular/compiler-cli": "6.0.0-rc.5",
|
||||
"@angular/language-service": "6.0.0-rc.5",
|
||||
"@types/jasmine": "^2.5.52",
|
||||
"@types/jasminewd2": "^2.0.3",
|
||||
"@types/node": "~6.0.60",
|
||||
@ -112,7 +112,7 @@
|
||||
"cross-spawn": "^5.1.0",
|
||||
"css-selector-parser": "^1.3.0",
|
||||
"dgeni": "^0.4.7",
|
||||
"dgeni-packages": "^0.26.1",
|
||||
"dgeni-packages": "^0.26.0",
|
||||
"entities": "^1.1.1",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-plugin-jasmine": "^2.2.0",
|
||||
|
@ -2,11 +2,11 @@
|
||||
"aio": {
|
||||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime": 2768,
|
||||
"main": 475855,
|
||||
"runtime": 2689,
|
||||
"main": 478529,
|
||||
"polyfills": 38453,
|
||||
"prettify": 14913
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -67,7 +67,7 @@ case $deployEnv in
|
||||
readonly firebaseToken=$FIREBASE_TOKEN
|
||||
;;
|
||||
archive)
|
||||
readonly projectId=v${majorVersion}-angular-io
|
||||
readonly projectId=angular-io-${majorVersion}
|
||||
readonly deployedUrl=https://v${majorVersion}.angular.io/
|
||||
readonly firebaseToken=$FIREBASE_TOKEN
|
||||
;;
|
||||
|
@ -94,7 +94,7 @@ Deployment URL : https://angular.io/"
|
||||
)
|
||||
expected="Git branch : 2.4.x
|
||||
Build/deploy mode : archive
|
||||
Firebase project : v2-angular-io
|
||||
Firebase project : angular-io-2
|
||||
Deployment URL : https://v2.angular.io/"
|
||||
check "$actual" "$expected"
|
||||
)
|
||||
|
@ -8,13 +8,13 @@
|
||||
<mat-toolbar-row class="notification-container">
|
||||
<aio-notification
|
||||
icon="insert_comment"
|
||||
iconLabel="Announcement"
|
||||
buttonText="Learn More"
|
||||
actionUrl="https://blog.angular.io/version-6-0-0-of-angular-now-available-cc56b0efa7a4"
|
||||
notificationId="angular-v6-announcement"
|
||||
expirationDate="2018-07-01"
|
||||
iconLabel="Survey"
|
||||
buttonText="Go to survey"
|
||||
actionUrl="https://bit.ly/angular-survey-2018"
|
||||
notificationId="survey-january-2018"
|
||||
expirationDate="2018-01-19"
|
||||
(dismissed)="notificationDismissed()">
|
||||
Version 6 of Angular Now Available!
|
||||
Help Angular by taking a <b>1 minute survey</b>!
|
||||
</aio-notification>
|
||||
</mat-toolbar-row>
|
||||
<mat-toolbar-row>
|
||||
@ -58,7 +58,7 @@
|
||||
</mat-sidenav-container>
|
||||
|
||||
<div *ngIf="hasFloatingToc" class="toc-container no-print" [style.max-height.px]="tocMaxHeight" (mousewheel)="restrainScrolling($event)">
|
||||
<aio-lazy-ce selector="aio-toc"></aio-lazy-ce>
|
||||
<aio-toc></aio-toc>
|
||||
</div>
|
||||
|
||||
<footer class="no-print">
|
||||
|
@ -6,7 +6,7 @@ import { HttpClient } from '@angular/common/http';
|
||||
import { MatProgressBar, MatSidenav } from '@angular/material';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { of, timer } from 'rxjs';
|
||||
import { timer } from 'rxjs';
|
||||
import { first, mapTo } from 'rxjs/operators';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
@ -14,7 +14,6 @@ import { AppModule } from './app.module';
|
||||
import { DocumentService } from 'app/documents/document.service';
|
||||
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
|
||||
import { Deployment } from 'app/shared/deployment.service';
|
||||
import { ElementsLoader } from 'app/custom-elements/elements-loader';
|
||||
import { GaService } from 'app/shared/ga.service';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
@ -27,6 +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/layout/toc/toc.component';
|
||||
import { TocItem, TocService } from 'app/shared/toc.service';
|
||||
|
||||
const sideBySideBreakPoint = 992;
|
||||
@ -92,11 +92,11 @@ describe('AppComponent', () => {
|
||||
});
|
||||
|
||||
describe('hasFloatingToc', () => {
|
||||
it('should initially be false', () => {
|
||||
it('should initially be true', () => {
|
||||
const fixture2 = TestBed.createComponent(AppComponent);
|
||||
const component2 = fixture2.componentInstance;
|
||||
|
||||
expect(component2.hasFloatingToc).toBe(false);
|
||||
expect(component2.hasFloatingToc).toBe(true);
|
||||
});
|
||||
|
||||
it('should be false on narrow screens', () => {
|
||||
@ -621,65 +621,55 @@ describe('AppComponent', () => {
|
||||
});
|
||||
|
||||
describe('aio-toc', () => {
|
||||
let tocContainer: HTMLElement|null;
|
||||
let toc: HTMLElement|null;
|
||||
let tocDebugElement: DebugElement;
|
||||
let tocContainer: DebugElement|null;
|
||||
|
||||
const setHasFloatingToc = (hasFloatingToc: boolean) => {
|
||||
component.hasFloatingToc = hasFloatingToc;
|
||||
fixture.detectChanges();
|
||||
|
||||
tocContainer = fixture.debugElement.nativeElement.querySelector('.toc-container');
|
||||
toc = tocContainer && tocContainer.querySelector('aio-toc');
|
||||
tocDebugElement = fixture.debugElement.query(By.directive(TocComponent));
|
||||
tocContainer = tocDebugElement && tocDebugElement.parent;
|
||||
};
|
||||
|
||||
beforeEach(() => setHasFloatingToc(true));
|
||||
|
||||
|
||||
it('should show/hide `<aio-toc>` based on `hasFloatingToc`', () => {
|
||||
expect(tocContainer).toBeFalsy();
|
||||
expect(toc).toBeFalsy();
|
||||
|
||||
setHasFloatingToc(true);
|
||||
expect(tocDebugElement).toBeTruthy();
|
||||
expect(tocContainer).toBeTruthy();
|
||||
expect(toc).toBeTruthy();
|
||||
|
||||
setHasFloatingToc(false);
|
||||
expect(tocDebugElement).toBeFalsy();
|
||||
expect(tocContainer).toBeFalsy();
|
||||
expect(toc).toBeFalsy();
|
||||
|
||||
setHasFloatingToc(true);
|
||||
expect(tocDebugElement).toBeTruthy();
|
||||
expect(tocContainer).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have a non-embedded `<aio-toc>` element', () => {
|
||||
setHasFloatingToc(true);
|
||||
expect(toc!.classList.contains('embedded')).toBe(false);
|
||||
expect(tocDebugElement.classes['embedded']).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should update the TOC container\'s `maxHeight` based on `tocMaxHeight`', () => {
|
||||
setHasFloatingToc(true);
|
||||
|
||||
expect(tocContainer!.style['max-height']).toBe('');
|
||||
expect(tocContainer!.styles['max-height']).toBeNull();
|
||||
|
||||
component.tocMaxHeight = '100';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(tocContainer!.style['max-height']).toBe('100px');
|
||||
expect(tocContainer!.styles['max-height']).toBe('100px');
|
||||
});
|
||||
|
||||
it('should restrain scrolling inside the ToC container', () => {
|
||||
const restrainScrolling = spyOn(component, 'restrainScrolling');
|
||||
const evt = new MouseEvent('mousewheel');
|
||||
const evt = {};
|
||||
|
||||
setHasFloatingToc(true);
|
||||
expect(restrainScrolling).not.toHaveBeenCalled();
|
||||
|
||||
tocContainer!.dispatchEvent(evt);
|
||||
tocContainer!.triggerEventHandler('mousewheel', evt);
|
||||
expect(restrainScrolling).toHaveBeenCalledWith(evt);
|
||||
});
|
||||
|
||||
it('should not be loaded/registered until necessary', () => {
|
||||
const loader: TestElementsLoader = fixture.debugElement.injector.get(ElementsLoader);
|
||||
expect(loader.loadCustomElement).not.toHaveBeenCalled();
|
||||
|
||||
setHasFloatingToc(true);
|
||||
expect(loader.loadCustomElement).toHaveBeenCalledWith('aio-toc');
|
||||
});
|
||||
});
|
||||
|
||||
describe('footer', () => {
|
||||
@ -1290,7 +1280,6 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') {
|
||||
imports: [ AppModule ],
|
||||
providers: [
|
||||
{ provide: APP_BASE_HREF, useValue: '/' },
|
||||
{ provide: ElementsLoader, useClass: TestElementsLoader },
|
||||
{ provide: GaService, useClass: TestGaService },
|
||||
{ provide: HttpClient, useClass: TestHttpClient },
|
||||
{ provide: LocationService, useFactory: () => mockLocationService },
|
||||
@ -1305,14 +1294,6 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') {
|
||||
});
|
||||
}
|
||||
|
||||
class TestElementsLoader {
|
||||
loadContainedCustomElements = jasmine.createSpy('loadContainedCustomElements')
|
||||
.and.returnValue(of(undefined));
|
||||
|
||||
loadCustomElement = jasmine.createSpy('loadCustomElement')
|
||||
.and.returnValue(Promise.resolve());
|
||||
}
|
||||
|
||||
class TestGaService {
|
||||
locationChanged = jasmine.createSpy('locationChanged');
|
||||
}
|
||||
@ -1387,7 +1368,7 @@ class TestHttpClient {
|
||||
const id = match[1]!;
|
||||
// Make up a title for test purposes
|
||||
const title = id.split('/').pop()!.replace(/^([a-z])/, (_, letter) => letter.toUpperCase());
|
||||
const h1 = (id === 'no-title') ? '' : `<h1 class="no-toc">${title}</h1>`;
|
||||
const h1 = (id === 'no-title') ? '' : `<h1>${title}</h1>`;
|
||||
const contents = `${h1}<h2 id="#somewhere">Some heading</h2>`;
|
||||
data = { id, contents };
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ export class AppComponent implements OnInit {
|
||||
topMenuNodes: NavigationNode[];
|
||||
topMenuNarrowNodes: NavigationNode[];
|
||||
|
||||
hasFloatingToc = false;
|
||||
hasFloatingToc = true;
|
||||
private showFloatingToc = new BehaviorSubject(false);
|
||||
private showFloatingTocWidth = 800;
|
||||
tocMaxHeight: string;
|
||||
|
@ -32,6 +32,7 @@ import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { ScrollSpyService } from 'app/shared/scroll-spy.service';
|
||||
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
||||
import { NotificationComponent } from 'app/layout/notification/notification.component';
|
||||
import { TocComponent } from 'app/layout/toc/toc.component';
|
||||
import { TocService } from 'app/shared/toc.service';
|
||||
import { CurrentDateToken, currentDateProvider } from 'app/shared/current-date';
|
||||
import { WindowToken, windowProvider } from 'app/shared/window';
|
||||
@ -110,6 +111,7 @@ export const svgIconProviders = [
|
||||
NavItemComponent,
|
||||
SearchBoxComponent,
|
||||
NotificationComponent,
|
||||
TocComponent,
|
||||
TopMenuComponent,
|
||||
],
|
||||
providers: [
|
||||
@ -131,6 +133,7 @@ export const svgIconProviders = [
|
||||
{ provide: CurrentDateToken, useFactory: currentDateProvider },
|
||||
{ provide: WindowToken, useFactory: windowProvider },
|
||||
],
|
||||
entryComponents: [ TocComponent ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
@ -25,22 +25,20 @@ export interface TabInfo {
|
||||
<!-- Use content projection so that the provided HTML's code-panes can be split into tabs -->
|
||||
<div #content style="display: none"><ng-content></ng-content></div>
|
||||
|
||||
<mat-card>
|
||||
<mat-tab-group class="code-tab-group" disableRipple>
|
||||
<mat-tab style="overflow-y: hidden;" *ngFor="let tab of tabs">
|
||||
<ng-template mat-tab-label>
|
||||
<span class="{{ tab.class }}">{{ tab.title }}</span>
|
||||
</ng-template>
|
||||
<aio-code class="{{ tab.class }}"
|
||||
[language]="tab.language"
|
||||
[linenums]="tab.linenums"
|
||||
[path]="tab.path"
|
||||
[region]="tab.region"
|
||||
[title]="tab.title">
|
||||
</aio-code>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</mat-card>
|
||||
<mat-tab-group class="code-tab-group" disableRipple>
|
||||
<mat-tab style="overflow-y: hidden;" *ngFor="let tab of tabs">
|
||||
<ng-template mat-tab-label>
|
||||
<span class="{{ tab.class }}">{{ tab.title }}</span>
|
||||
</ng-template>
|
||||
<aio-code class="{{ tab.class }}"
|
||||
[language]="tab.language"
|
||||
[linenums]="tab.linenums"
|
||||
[path]="tab.path"
|
||||
[region]="tab.region"
|
||||
[title]="tab.title">
|
||||
</aio-code>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
`,
|
||||
})
|
||||
export class CodeTabsComponent implements OnInit, AfterViewInit {
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { NgModule, Type } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CodeTabsComponent } from './code-tabs.component';
|
||||
import { MatCardModule, MatTabsModule } from '@angular/material';
|
||||
import { MatTabsModule } from '@angular/material';
|
||||
import { CodeModule } from './code.module';
|
||||
import { WithCustomElementComponent } from '../element-registry';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule, MatCardModule, MatTabsModule, CodeModule ],
|
||||
imports: [ CommonModule, MatTabsModule, CodeModule ],
|
||||
declarations: [ CodeTabsComponent ],
|
||||
exports: [ CodeTabsComponent ],
|
||||
entryComponents: [ CodeTabsComponent ]
|
||||
|
@ -6,11 +6,8 @@ import {
|
||||
ELEMENT_MODULE_PATHS_AS_ROUTES,
|
||||
ELEMENT_MODULE_PATHS_TOKEN
|
||||
} from './element-registry';
|
||||
import { LazyCustomElementComponent } from './lazy-custom-element.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [ LazyCustomElementComponent ],
|
||||
exports: [ LazyCustomElementComponent ],
|
||||
providers: [
|
||||
ElementsLoader,
|
||||
{ provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader },
|
||||
|
@ -13,8 +13,8 @@ export const ELEMENT_MODULE_PATHS_AS_ROUTES = [
|
||||
loadChildren: './api/api-list.module#ApiListModule'
|
||||
},
|
||||
{
|
||||
selector: 'aio-contributor-list',
|
||||
loadChildren: './contributor/contributor-list.module#ContributorListModule'
|
||||
selector: 'live-example',
|
||||
loadChildren: './live-example/live-example.module#LiveExampleModule'
|
||||
},
|
||||
{
|
||||
selector: 'aio-file-not-found-search',
|
||||
@ -25,29 +25,25 @@ export const ELEMENT_MODULE_PATHS_AS_ROUTES = [
|
||||
loadChildren: './resource/resource-list.module#ResourceListModule'
|
||||
},
|
||||
{
|
||||
selector: 'aio-toc',
|
||||
loadChildren: './toc/toc.module#TocModule'
|
||||
selector: 'current-location',
|
||||
loadChildren: './current-location/current-location.module#CurrentLocationModule'
|
||||
},
|
||||
{
|
||||
selector: 'code-example',
|
||||
loadChildren: './code/code-example.module#CodeExampleModule'
|
||||
selector: 'aio-contributor-list',
|
||||
loadChildren: './contributor/contributor-list.module#ContributorListModule'
|
||||
},
|
||||
{
|
||||
selector: 'code-tabs',
|
||||
loadChildren: './code/code-tabs.module#CodeTabsModule'
|
||||
},
|
||||
{
|
||||
selector: 'current-location',
|
||||
loadChildren: './current-location/current-location.module#CurrentLocationModule'
|
||||
selector: 'code-example',
|
||||
loadChildren: './code/code-example.module#CodeExampleModule'
|
||||
},
|
||||
{
|
||||
selector: 'expandable-section',
|
||||
loadChildren: './expandable-section/expandable-section.module#ExpandableSectionModule'
|
||||
},
|
||||
{
|
||||
selector: 'live-example',
|
||||
loadChildren: './live-example/live-example.module#LiveExampleModule'
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -4,19 +4,49 @@ import {
|
||||
NgModuleRef,
|
||||
Type
|
||||
} from '@angular/core';
|
||||
import { TestBed, fakeAsync, flushMicrotasks } from '@angular/core/testing';
|
||||
import {TestBed, fakeAsync, tick} from '@angular/core/testing';
|
||||
|
||||
import { ElementsLoader } from './elements-loader';
|
||||
import { ELEMENT_MODULE_PATHS_TOKEN, WithCustomElementComponent } from './element-registry';
|
||||
|
||||
class FakeComponentFactory extends ComponentFactory<any> {
|
||||
selector: string;
|
||||
componentType: Type<any>;
|
||||
ngContentSelectors: string[];
|
||||
inputs = [{propName: this.identifyingInput, templateName: this.identifyingInput}];
|
||||
outputs = [];
|
||||
|
||||
interface Deferred {
|
||||
resolve(): void;
|
||||
reject(err: any): void;
|
||||
constructor(private identifyingInput: string) { super(); }
|
||||
|
||||
create(injector: Injector,
|
||||
projectableNodes?: any[][],
|
||||
rootSelectorOrNode?: string | any,
|
||||
ngModule?: NgModuleRef<any>): ComponentRef<any> {
|
||||
return (jasmine.createSpy('ComponentRef') as any) as ComponentRef<any>;
|
||||
};
|
||||
}
|
||||
|
||||
const FAKE_COMPONENT_FACTORIES = new Map([
|
||||
['element-a-module-path', new FakeComponentFactory('element-a-input')],
|
||||
['element-b-module-path', new FakeComponentFactory('element-b-input')],
|
||||
]);
|
||||
|
||||
describe('ElementsLoader', () => {
|
||||
let elementsLoader: ElementsLoader;
|
||||
let actualCustomElementsDefine;
|
||||
let fakeCustomElementsDefine;
|
||||
|
||||
// ElementsLoader uses the window's customElements API. Provide a fake for this test.
|
||||
beforeEach(() => {
|
||||
actualCustomElementsDefine = window.customElements.define;
|
||||
|
||||
fakeCustomElementsDefine = jasmine.createSpy('define');
|
||||
|
||||
window.customElements.define = fakeCustomElementsDefine;
|
||||
});
|
||||
afterEach(() => {
|
||||
window.customElements.define = actualCustomElementsDefine;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
const injector = TestBed.configureTestingModule({
|
||||
@ -33,196 +63,63 @@ describe('ElementsLoader', () => {
|
||||
elementsLoader = injector.get(ElementsLoader);
|
||||
});
|
||||
|
||||
describe('loadContainedCustomElements()', () => {
|
||||
let loadCustomElementSpy: jasmine.Spy;
|
||||
it('should be able to register an element', fakeAsync(() => {
|
||||
// Verify that the elements loader considered `element-a-selector` to be unregistered.
|
||||
expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeTruthy();
|
||||
|
||||
beforeEach(() => loadCustomElementSpy = spyOn(elementsLoader, 'loadCustomElement'));
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `<element-a-selector></element-a-selector>`;
|
||||
|
||||
it('should attempt to load and register all contained elements', fakeAsync(() => {
|
||||
expect(loadCustomElementSpy).not.toHaveBeenCalled();
|
||||
elementsLoader.loadContainingCustomElements(hostEl);
|
||||
tick();
|
||||
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-a-selector></element-a-selector>
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
const defineArgs = fakeCustomElementsDefine.calls.argsFor(0);
|
||||
expect(defineArgs[0]).toBe('element-a-selector');
|
||||
|
||||
elementsLoader.loadContainedCustomElements(hostEl);
|
||||
flushMicrotasks();
|
||||
// Verify the right component was loaded/created
|
||||
expect(defineArgs[1].observedAttributes[0]).toBe('element-a-input');
|
||||
|
||||
expect(loadCustomElementSpy).toHaveBeenCalledTimes(2);
|
||||
expect(loadCustomElementSpy).toHaveBeenCalledWith('element-a-selector');
|
||||
expect(loadCustomElementSpy).toHaveBeenCalledWith('element-b-selector');
|
||||
}));
|
||||
expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeFalsy();
|
||||
}));
|
||||
|
||||
it('should attempt to load and register only contained elements', fakeAsync(() => {
|
||||
expect(loadCustomElementSpy).not.toHaveBeenCalled();
|
||||
it('should be able to register multiple elements', fakeAsync(() => {
|
||||
// Verify that the elements loader considered `element-a-selector` to be unregistered.
|
||||
expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeTruthy();
|
||||
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-a-selector></element-a-selector>
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
|
||||
elementsLoader.loadContainedCustomElements(hostEl);
|
||||
flushMicrotasks();
|
||||
elementsLoader.loadContainingCustomElements(hostEl);
|
||||
tick();
|
||||
|
||||
expect(loadCustomElementSpy).toHaveBeenCalledTimes(1);
|
||||
expect(loadCustomElementSpy).toHaveBeenCalledWith('element-b-selector');
|
||||
}));
|
||||
const defineElementA = fakeCustomElementsDefine.calls.argsFor(0);
|
||||
expect(defineElementA[0]).toBe('element-a-selector');
|
||||
expect(defineElementA[1].observedAttributes[0]).toBe('element-a-input');
|
||||
expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeFalsy();
|
||||
|
||||
it('should wait for all contained elements to load and register', fakeAsync(() => {
|
||||
const deferreds = returnPromisesFromSpy(loadCustomElementSpy);
|
||||
const defineElementB = fakeCustomElementsDefine.calls.argsFor(1);
|
||||
expect(defineElementB[0]).toBe('element-b-selector');
|
||||
expect(defineElementB[1].observedAttributes[0]).toBe('element-b-input');
|
||||
expect(elementsLoader.elementsToLoad.has('element-b-selector')).toBeFalsy();
|
||||
}));
|
||||
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-a-selector></element-a-selector>
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
it('should only register an element one time', fakeAsync(() => {
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `<element-a-selector></element-a-selector>`;
|
||||
|
||||
const log: any[] = [];
|
||||
elementsLoader.loadContainedCustomElements(hostEl).subscribe(
|
||||
v => log.push(`emitted: ${v}`),
|
||||
e => log.push(`errored: ${e}`),
|
||||
() => log.push('completed'),
|
||||
);
|
||||
elementsLoader.loadContainingCustomElements(hostEl);
|
||||
tick(); // Tick for the module factory loader's async `load` function
|
||||
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual([]);
|
||||
// Call again to to check how many times customElements.define was called.
|
||||
elementsLoader.loadContainingCustomElements(hostEl);
|
||||
tick(); // Tick for the module factory loader's async `load` function
|
||||
|
||||
deferreds[0].resolve();
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual([]);
|
||||
|
||||
deferreds[1].resolve();
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual(['emitted: undefined', 'completed']);
|
||||
}));
|
||||
|
||||
it('should fail if any of the contained elements fails to load and register', fakeAsync(() => {
|
||||
const deferreds = returnPromisesFromSpy(loadCustomElementSpy);
|
||||
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-a-selector></element-a-selector>
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
|
||||
const log: any[] = [];
|
||||
elementsLoader.loadContainedCustomElements(hostEl).subscribe(
|
||||
v => log.push(`emitted: ${v}`),
|
||||
e => log.push(`errored: ${e}`),
|
||||
() => log.push('completed'),
|
||||
);
|
||||
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual([]);
|
||||
|
||||
deferreds[0].resolve();
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual([]);
|
||||
|
||||
deferreds[1].reject('foo');
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual(['errored: foo']);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('loadCustomElement()', () => {
|
||||
let definedSpy: jasmine.Spy;
|
||||
let whenDefinedSpy: jasmine.Spy;
|
||||
let whenDefinedDeferreds: Deferred[];
|
||||
|
||||
beforeEach(() => {
|
||||
// `loadCustomElement()` uses the `window.customElements` API. Provide mocks for these tests.
|
||||
definedSpy = spyOn(window.customElements, 'define');
|
||||
whenDefinedSpy = spyOn(window.customElements, 'whenDefined');
|
||||
whenDefinedDeferreds = returnPromisesFromSpy(whenDefinedSpy);
|
||||
});
|
||||
|
||||
it('should be able to load and register an element', fakeAsync(() => {
|
||||
elementsLoader.loadCustomElement('element-a-selector');
|
||||
flushMicrotasks();
|
||||
|
||||
expect(definedSpy).toHaveBeenCalledTimes(1);
|
||||
expect(definedSpy).toHaveBeenCalledWith('element-a-selector', jasmine.any(Function));
|
||||
|
||||
// Verify the right component was loaded/registered.
|
||||
const Ctor = definedSpy.calls.argsFor(0)[1];
|
||||
expect(Ctor.observedAttributes).toEqual(['element-a-module-path']);
|
||||
}));
|
||||
|
||||
it('should wait until the element is defined', fakeAsync(() => {
|
||||
let state = 'pending';
|
||||
elementsLoader.loadCustomElement('element-b-selector').then(() => state = 'resolved');
|
||||
flushMicrotasks();
|
||||
|
||||
expect(state).toBe('pending');
|
||||
expect(whenDefinedSpy).toHaveBeenCalledTimes(1);
|
||||
expect(whenDefinedSpy).toHaveBeenCalledWith('element-b-selector');
|
||||
|
||||
whenDefinedDeferreds[0].resolve();
|
||||
flushMicrotasks();
|
||||
expect(state).toBe('resolved');
|
||||
}));
|
||||
|
||||
it('should not load and register the same element more than once', fakeAsync(() => {
|
||||
elementsLoader.loadCustomElement('element-a-selector');
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
definedSpy.calls.reset();
|
||||
|
||||
// While loading/registering is still in progress:
|
||||
elementsLoader.loadCustomElement('element-a-selector');
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).not.toHaveBeenCalled();
|
||||
|
||||
definedSpy.calls.reset();
|
||||
whenDefinedDeferreds[0].resolve();
|
||||
|
||||
// Once loading/registering is already completed:
|
||||
let state = 'pending';
|
||||
elementsLoader.loadCustomElement('element-a-selector').then(() => state = 'resolved');
|
||||
flushMicrotasks();
|
||||
expect(state).toBe('resolved');
|
||||
expect(definedSpy).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should fail if defining the the custom element fails', fakeAsync(() => {
|
||||
let state = 'pending';
|
||||
elementsLoader.loadCustomElement('element-b-selector').catch(e => state = `rejected: ${e}`);
|
||||
flushMicrotasks();
|
||||
expect(state).toBe('pending');
|
||||
|
||||
whenDefinedDeferreds[0].reject('foo');
|
||||
flushMicrotasks();
|
||||
expect(state).toBe('rejected: foo');
|
||||
}));
|
||||
|
||||
it('should be able to load and register an element again if previous attempt failed',
|
||||
fakeAsync(() => {
|
||||
elementsLoader.loadCustomElement('element-a-selector');
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
definedSpy.calls.reset();
|
||||
|
||||
// While loading/registering is still in progress:
|
||||
elementsLoader.loadCustomElement('element-a-selector').catch(() => undefined);
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).not.toHaveBeenCalled();
|
||||
|
||||
whenDefinedDeferreds[0].reject('foo');
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).not.toHaveBeenCalled();
|
||||
|
||||
// Once loading/registering has already failed:
|
||||
elementsLoader.loadCustomElement('element-a-selector');
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).toHaveBeenCalledTimes(1);
|
||||
})
|
||||
);
|
||||
});
|
||||
// Should have only been called once, since the second load would not query for element-a
|
||||
expect(window.customElements.define).toHaveBeenCalledTimes(1);
|
||||
}));
|
||||
});
|
||||
|
||||
// TEST CLASSES/HELPERS
|
||||
@ -231,28 +128,11 @@ class FakeCustomElementModule implements WithCustomElementComponent {
|
||||
customElementComponent: Type<any>;
|
||||
}
|
||||
|
||||
class FakeComponentFactory extends ComponentFactory<any> {
|
||||
selector: string;
|
||||
componentType: Type<any>;
|
||||
ngContentSelectors: string[];
|
||||
inputs = [{propName: this.identifyingInput, templateName: this.identifyingInput}];
|
||||
outputs = [];
|
||||
|
||||
constructor(private identifyingInput: string) { super(); }
|
||||
|
||||
create(injector: Injector,
|
||||
projectableNodes?: any[][],
|
||||
rootSelectorOrNode?: string | any,
|
||||
ngModule?: NgModuleRef<any>): ComponentRef<any> {
|
||||
return jasmine.createSpy('ComponentRef') as any;
|
||||
};
|
||||
}
|
||||
|
||||
class FakeComponentFactoryResolver extends ComponentFactoryResolver {
|
||||
constructor(private modulePath) { super(); }
|
||||
|
||||
resolveComponentFactory(component: Type<any>): ComponentFactory<any> {
|
||||
return new FakeComponentFactory(this.modulePath);
|
||||
return FAKE_COMPONENT_FACTORIES.get(this.modulePath)!;
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,9 +168,3 @@ class FakeModuleFactoryLoader extends NgModuleFactoryLoader {
|
||||
return Promise.resolve(fakeModuleFactory);
|
||||
}
|
||||
}
|
||||
|
||||
function returnPromisesFromSpy(spy: jasmine.Spy): Deferred[] {
|
||||
const deferreds: Deferred[] = [];
|
||||
spy.and.callFake(() => new Promise((resolve, reject) => deferreds.push({resolve, reject})));
|
||||
return deferreds;
|
||||
}
|
||||
|
@ -11,13 +11,11 @@ import { createCustomElement } from '@angular/elements';
|
||||
@Injectable()
|
||||
export class ElementsLoader {
|
||||
/** Map of unregistered custom elements and their respective module paths to load. */
|
||||
private elementsToLoad: Map<string, string>;
|
||||
/** Map of custom elements that are in the process of being loaded and registered. */
|
||||
private elementsLoading = new Map<string, Promise<void>>();
|
||||
elementsToLoad: Map<string, string>;
|
||||
|
||||
constructor(private moduleFactoryLoader: NgModuleFactoryLoader,
|
||||
private moduleRef: NgModuleRef<any>,
|
||||
@Inject(ELEMENT_MODULE_PATHS_TOKEN) elementModulePaths: Map<string, string>) {
|
||||
@Inject(ELEMENT_MODULE_PATHS_TOKEN) elementModulePaths) {
|
||||
this.elementsToLoad = new Map(elementModulePaths);
|
||||
}
|
||||
|
||||
@ -26,57 +24,31 @@ export class ElementsLoader {
|
||||
* the browser. Custom elements that are registered will be removed from the list of unregistered
|
||||
* elements so that they will not be queried in subsequent calls.
|
||||
*/
|
||||
loadContainedCustomElements(element: HTMLElement): Observable<void> {
|
||||
const unregisteredSelectors = Array.from(this.elementsToLoad.keys())
|
||||
loadContainingCustomElements(element: HTMLElement): Observable<void> {
|
||||
const selectors: any[] = Array.from(this.elementsToLoad.keys())
|
||||
.filter(s => element.querySelector(s));
|
||||
|
||||
if (!unregisteredSelectors.length) { return of(undefined); }
|
||||
if (!selectors.length) { return of(undefined); }
|
||||
|
||||
// Returns observable that completes when all discovered elements have been registered.
|
||||
const allRegistered = Promise.all(unregisteredSelectors.map(s => this.loadCustomElement(s)));
|
||||
return fromPromise(allRegistered.then(() => undefined));
|
||||
return fromPromise(Promise.all(selectors.map(s => this.register(s))).then(result => undefined));
|
||||
}
|
||||
|
||||
/** Loads and registers the custom element defined on the `WithCustomElement` module factory. */
|
||||
loadCustomElement(selector: string): Promise<void> {
|
||||
if (this.elementsLoading.has(selector)) {
|
||||
// The custom element is in the process of being loaded and registered.
|
||||
return this.elementsLoading.get(selector)!;
|
||||
}
|
||||
/** Registers the custom element defined on the WithCustomElement module factory. */
|
||||
private register(selector: string) {
|
||||
const modulePath = this.elementsToLoad.get(selector)!;
|
||||
return this.moduleFactoryLoader.load(modulePath).then(elementModuleFactory => {
|
||||
if (!this.elementsToLoad.has(selector)) { return; }
|
||||
|
||||
if (this.elementsToLoad.has(selector)) {
|
||||
// Load and register the custom element (for the first time).
|
||||
const modulePath = this.elementsToLoad.get(selector)!;
|
||||
const loadedAndRegistered = this.moduleFactoryLoader
|
||||
.load(modulePath)
|
||||
.then(elementModuleFactory => {
|
||||
const elementModuleRef = elementModuleFactory.create(this.moduleRef.injector);
|
||||
const injector = elementModuleRef.injector;
|
||||
const CustomElementComponent = elementModuleRef.instance.customElementComponent;
|
||||
const CustomElement = createCustomElement(CustomElementComponent, {injector});
|
||||
const elementModuleRef = elementModuleFactory.create(this.moduleRef.injector);
|
||||
const CustomElementComponent = elementModuleRef.instance.customElementComponent;
|
||||
const CustomElement =
|
||||
createCustomElement(CustomElementComponent, {injector: elementModuleRef.injector});
|
||||
|
||||
customElements!.define(selector, CustomElement);
|
||||
return customElements.whenDefined(selector);
|
||||
})
|
||||
.then(() => {
|
||||
// The custom element has been successfully loaded and registered.
|
||||
// Remove from `elementsLoading` and `elementsToLoad`.
|
||||
this.elementsLoading.delete(selector);
|
||||
this.elementsToLoad.delete(selector);
|
||||
})
|
||||
.catch(err => {
|
||||
// The custom element has failed to load and register.
|
||||
// Remove from `elementsLoading`.
|
||||
// (Do not remove from `elementsToLoad` in case it was a temporary error.)
|
||||
this.elementsLoading.delete(selector);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
customElements!.define(selector, CustomElement);
|
||||
this.elementsToLoad.delete(selector);
|
||||
|
||||
this.elementsLoading.set(selector, loadedAndRegistered);
|
||||
return loadedAndRegistered;
|
||||
}
|
||||
|
||||
// The custom element has already been loaded and registered.
|
||||
return Promise.resolve();
|
||||
return customElements.whenDefined(selector);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,67 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { MockLogger } from 'testing/logger.service';
|
||||
import { LazyCustomElementComponent } from './lazy-custom-element.component';
|
||||
import { ElementsLoader } from './elements-loader';
|
||||
|
||||
describe('LazyCustomElementComponent', () => {
|
||||
let mockElementsLoader: jasmine.SpyObj<ElementsLoader>;
|
||||
let mockLogger: MockLogger;
|
||||
let fixture: ComponentFixture<LazyCustomElementComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockElementsLoader = jasmine.createSpyObj<ElementsLoader>('ElementsLoader', [
|
||||
'loadContainedCustomElements',
|
||||
'loadCustomElement',
|
||||
]);
|
||||
|
||||
const injector = TestBed.configureTestingModule({
|
||||
declarations: [ LazyCustomElementComponent ],
|
||||
providers: [
|
||||
{ provide: ElementsLoader, useValue: mockElementsLoader },
|
||||
{ provide: Logger, useClass: MockLogger },
|
||||
],
|
||||
});
|
||||
|
||||
mockLogger = injector.get(Logger);
|
||||
fixture = TestBed.createComponent(LazyCustomElementComponent);
|
||||
});
|
||||
|
||||
it('should set the HTML content based on the selector', () => {
|
||||
const elem = fixture.nativeElement;
|
||||
|
||||
expect(elem.innerHTML).toBe('');
|
||||
|
||||
fixture.componentInstance.selector = 'foo-bar';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(elem.innerHTML).toBe('<foo-bar></foo-bar>');
|
||||
});
|
||||
|
||||
it('should load the specified custom element', () => {
|
||||
expect(mockElementsLoader.loadCustomElement).not.toHaveBeenCalled();
|
||||
|
||||
fixture.componentInstance.selector = 'foo-bar';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(mockElementsLoader.loadCustomElement).toHaveBeenCalledWith('foo-bar');
|
||||
});
|
||||
|
||||
it('should log an error (and abort) if the selector is empty', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(mockElementsLoader.loadCustomElement).not.toHaveBeenCalled();
|
||||
expect(mockLogger.output.error).toEqual([[jasmine.any(Error)]]);
|
||||
expect(mockLogger.output.error[0][0].message).toBe('Invalid selector for \'aio-lazy-ce\': ');
|
||||
});
|
||||
|
||||
it('should log an error (and abort) if the selector is invalid', () => {
|
||||
fixture.componentInstance.selector = 'foo-bar><script></script><foo-bar';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(mockElementsLoader.loadCustomElement).not.toHaveBeenCalled();
|
||||
expect(mockLogger.output.error).toEqual([[jasmine.any(Error)]]);
|
||||
expect(mockLogger.output.error[0][0].message).toBe(
|
||||
'Invalid selector for \'aio-lazy-ce\': foo-bar><script></script><foo-bar');
|
||||
});
|
||||
});
|
@ -1,27 +0,0 @@
|
||||
import { Component, ElementRef, Input, OnInit } from '@angular/core';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { ElementsLoader } from './elements-loader';
|
||||
|
||||
@Component({
|
||||
selector: 'aio-lazy-ce',
|
||||
template: '',
|
||||
})
|
||||
export class LazyCustomElementComponent implements OnInit {
|
||||
@Input() selector = '';
|
||||
|
||||
constructor(
|
||||
private elementRef: ElementRef,
|
||||
private elementsLoader: ElementsLoader,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.selector || /[^\w-]/.test(this.selector)) {
|
||||
this.logger.error(new Error(`Invalid selector for 'aio-lazy-ce': ${this.selector}`));
|
||||
return;
|
||||
}
|
||||
|
||||
this.elementRef.nativeElement.innerHTML = `<${this.selector}></${this.selector}>`;
|
||||
this.elementsLoader.loadCustomElement(this.selector);
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
<!-- Content projection is used to get the content HTML provided to the component. -->
|
||||
<span #content style="display: none"><ng-content></ng-content></span>
|
||||
|
||||
<span [ngSwitch]="mode">
|
||||
<span *ngSwitchCase="'disabled'">{{title}} <em>(not available on this device)</em></span>
|
||||
<span *ngSwitchCase="'embedded'">
|
||||
<div title="{{title}}">
|
||||
<aio-embedded-stackblitz [src]="stackblitz"></aio-embedded-stackblitz>
|
||||
@ -16,7 +14,7 @@
|
||||
<span *ngSwitchDefault>
|
||||
<a [href]="stackblitz" target="_blank" title="{{title}}">{{title}}</a>
|
||||
<span *ngIf="enableDownload">
|
||||
/ <a [href]="zip" download title="Download example">download example</a>
|
||||
/ <a [href]="zip" download title="Download example">download example</a>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -12,6 +12,7 @@ describe('LiveExampleComponent', () => {
|
||||
let liveExampleComponent: LiveExampleComponent;
|
||||
let fixture: ComponentFixture<HostComponent>;
|
||||
let testPath: string;
|
||||
let liveExampleContent: string|null;
|
||||
|
||||
//////// test helpers ////////
|
||||
|
||||
@ -40,7 +41,12 @@ describe('LiveExampleComponent', () => {
|
||||
liveExampleDe = fixture.debugElement.children[0];
|
||||
liveExampleComponent = liveExampleDe.componentInstance;
|
||||
|
||||
// Trigger `ngAfterContentInit()`.
|
||||
// Copy the LiveExample's innerHTML (content)
|
||||
// into the `liveExampleContent` property as the DocViewer does
|
||||
liveExampleDe.nativeElement.liveExampleContent = liveExampleContent;
|
||||
|
||||
fixture.detectChanges();
|
||||
liveExampleComponent.onResize(1033); // wide by default
|
||||
fixture.detectChanges();
|
||||
|
||||
testFn();
|
||||
@ -58,6 +64,7 @@ describe('LiveExampleComponent', () => {
|
||||
.overrideComponent(EmbeddedStackblitzComponent, {set: {template: 'NO IFRAME'}});
|
||||
|
||||
testPath = defaultTestPath;
|
||||
liveExampleContent = null;
|
||||
});
|
||||
|
||||
describe('when not embedded', () => {
|
||||
@ -96,6 +103,15 @@ describe('LiveExampleComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have expected flat-style stackblitz when has `flat-style`', () => {
|
||||
testPath = '/tutorial/toh-pt1';
|
||||
setHostTemplate('<live-example flat-style></live-example>');
|
||||
testComponent(() => {
|
||||
// The file should be "stackblitz.html", not "stackblitz.html"
|
||||
expect(getLiveExampleAnchor().href).toContain('/stackblitz.html');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have expected stackblitz & download hrefs when has example directory (name)', () => {
|
||||
testPath = '/guide/somewhere';
|
||||
setHostTemplate('<live-example name="toh-pt1"></live-example>');
|
||||
@ -134,7 +150,15 @@ describe('LiveExampleComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should not have a download link when `noDownload` attr present', () => {
|
||||
it('should be flat style when flat-style requested', () => {
|
||||
setHostTemplate('<live-example flat-style></live-example>');
|
||||
testComponent(() => {
|
||||
const hrefs = getHrefs();
|
||||
expect(hrefs[0]).toContain(defaultTestPath + '/stackblitz.html');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not have a download link when `noDownload` atty present', () => {
|
||||
setHostTemplate('<live-example noDownload></live-example>');
|
||||
testComponent(() => {
|
||||
const hrefs = getHrefs();
|
||||
@ -143,7 +167,7 @@ describe('LiveExampleComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should only have a download link when `downloadOnly` attr present', () => {
|
||||
it('should only have a download link when `downloadOnly` atty present', () => {
|
||||
setHostTemplate('<live-example downloadOnly>download this</live-example>');
|
||||
testComponent(() => {
|
||||
const hrefs = getHrefs();
|
||||
@ -172,12 +196,12 @@ describe('LiveExampleComponent', () => {
|
||||
});
|
||||
|
||||
it('should add title from <live-example> body', () => {
|
||||
const expectedTitle = 'The Greatest Example';
|
||||
setHostTemplate(`<live-example title="ignore this title">${expectedTitle}</live-example>`);
|
||||
liveExampleContent = 'The Greatest Example';
|
||||
setHostTemplate('<live-example title="ignore this title"></live-example>');
|
||||
testComponent(() => {
|
||||
const anchor = getLiveExampleAnchor();
|
||||
expect(anchor.textContent).toBe(expectedTitle, 'anchor content');
|
||||
expect(anchor.getAttribute('title')).toBe(expectedTitle, 'title');
|
||||
expect(anchor.textContent).toBe(liveExampleContent, 'anchor content');
|
||||
expect(anchor.getAttribute('title')).toBe(liveExampleContent, 'title');
|
||||
});
|
||||
});
|
||||
|
||||
@ -227,4 +251,27 @@ describe('LiveExampleComponent', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when narrow display (mobile)', () => {
|
||||
|
||||
it('should be embedded style when no style defined', () => {
|
||||
setHostTemplate('<live-example></live-example>');
|
||||
testComponent(() => {
|
||||
liveExampleComponent.onResize(600); // narrow
|
||||
fixture.detectChanges();
|
||||
const hrefs = getHrefs();
|
||||
expect(hrefs[0]).toContain(defaultTestPath + '/stackblitz.html');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be embedded style even when flat-style requested', () => {
|
||||
setHostTemplate('<live-example flat-style></live-example>');
|
||||
testComponent(() => {
|
||||
liveExampleComponent.onResize(600); // narrow
|
||||
fixture.detectChanges();
|
||||
const hrefs = getHrefs();
|
||||
expect(hrefs[0]).toContain(defaultTestPath + '/stackblitz.html');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,131 +1,130 @@
|
||||
/* tslint:disable component-selector */
|
||||
import { AfterContentInit, AfterViewInit, Component, ElementRef, Input, ViewChild } from '@angular/core';
|
||||
import { Component, ElementRef, HostListener, Input, OnInit, AfterViewInit, ViewChild } from '@angular/core';
|
||||
import { Location } from '@angular/common';
|
||||
import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
||||
import { AttrMap, boolFromValue, getAttrs, getAttrValue } from 'app/shared/attribute-utils';
|
||||
|
||||
import { boolFromValue, getAttrs, getAttrValue } from 'app/shared/attribute-utils';
|
||||
|
||||
const LIVE_EXAMPLE_BASE = CONTENT_URL_PREFIX + 'live-examples/';
|
||||
const ZIP_BASE = CONTENT_URL_PREFIX + 'zips/';
|
||||
const liveExampleBase = CONTENT_URL_PREFIX + 'live-examples/';
|
||||
const zipBase = CONTENT_URL_PREFIX + 'zips/';
|
||||
|
||||
/**
|
||||
* Angular.io Live Example Embedded Component
|
||||
*
|
||||
* Renders a link to a live/host example of the doc page.
|
||||
*
|
||||
* All attributes and the text content are optional
|
||||
*
|
||||
* Usage:
|
||||
* <live-example
|
||||
* [name="..."] // name of the example directory
|
||||
* [stackblitz="...""] // name of the stackblitz file (becomes part of zip file name as well)
|
||||
* [embedded] // embed the stackblitz in the doc page, else display in new browser tab (default)
|
||||
* [noDownload] // no downloadable zip option
|
||||
* [downloadOnly] // just the zip
|
||||
* [title="..."]> // text for live example link and tooltip
|
||||
* text // higher precedence way to specify text for live example link and tooltip
|
||||
* </live-example>
|
||||
* Example:
|
||||
* <p>Run <live-example>Try the live example</live-example></p>.
|
||||
* // ~/resources/live-examples/{page}/stackblitz.json
|
||||
*
|
||||
* <p>Run <live-example name="toh-pt1">this example</live-example></p>.
|
||||
* // ~/resources/live-examples/toh-pt1/stackblitz.json
|
||||
*
|
||||
* // Link to the default stackblitz in the toh-pt1 sample
|
||||
* // The title overrides default ("live example") with "Tour of Heroes - Part 1"
|
||||
* <p>Run <live-example name="toh-pt1" title="Tour of Heroes - Part 1"></live-example></p>.
|
||||
* // ~/resources/live-examples/toh-pt1/stackblitz.json
|
||||
*
|
||||
* <p>Run <live-example stackblitz="minimal"></live-example></p>.
|
||||
* // ~/resources/live-examples/{page}/minimal.stackblitz.json
|
||||
*
|
||||
* // Embed the current page's default stackblitz
|
||||
* // Text within tag is "live example"
|
||||
* // No title (no tooltip)
|
||||
* <live-example embedded title=""></live-example>
|
||||
* // ~/resources/live-examples/{page}/stackblitz.json
|
||||
*
|
||||
* // Displays within the document page as an embedded style stackblitz editor
|
||||
* <live-example name="toh-pt1" embedded stackblitz="minimal">Tour of Heroes - Part 1</live-example>
|
||||
* // ~/resources/live-examples/toh-pt1/minimal.stackblitz.json
|
||||
*/
|
||||
* Angular.io Live Example Embedded Component
|
||||
*
|
||||
* Renders a link to a live/host example of the doc page.
|
||||
*
|
||||
* All attributes and the text content are optional
|
||||
*
|
||||
* Usage:
|
||||
* <live-example
|
||||
* [name="..."] // name of the example directory
|
||||
* [stackblitz="...""] // name of the stackblitz file (becomes part of zip file name as well)
|
||||
* [embedded] // embed the stackblitz in the doc page, else display in new browser tab (default)
|
||||
* [noDownload] // no downloadable zip option
|
||||
* [downloadOnly] // just the zip
|
||||
* [title="..."]> // text for live example link and tooltip
|
||||
* text // higher precedence way to specify text for live example link and tooltip
|
||||
* </live-example>
|
||||
* Example:
|
||||
* <p>Run <live-example>Try the live example</live-example></p>.
|
||||
* // ~/resources/live-examples/{page}/stackblitz.json
|
||||
*
|
||||
* <p>Run <live-example name="toh-pt1">this example</live-example></p>.
|
||||
* // ~/resources/live-examples/toh-pt1/stackblitz.json
|
||||
*
|
||||
* // Link to the default stackblitz in the toh-pt1 sample
|
||||
* // The title overrides default ("live example") with "Tour of Heroes - Part 1"
|
||||
* <p>Run <live-example name="toh-pt1" title="Tour of Heroes - Part 1"></live-example></p>.
|
||||
* // ~/resources/live-examples/toh-pt1/stackblitz.json
|
||||
*
|
||||
* <p>Run <live-example stackblitz="minimal"></live-example></p>.
|
||||
* // ~/resources/live-examples/{page}/minimal.stackblitz.json
|
||||
*
|
||||
* // Embed the current page's default stackblitz
|
||||
* // Text within tag is "live example"
|
||||
* // No title (no tooltip)
|
||||
* <live-example embedded title=""></live-example>
|
||||
* // ~/resources/live-examples/{page}/stackblitz.json
|
||||
*
|
||||
* // Displays within the document page as an embedded style stackblitz editor
|
||||
* <live-example name="toh-pt1" embedded stackblitz="minimal">Tour of Heroes - Part 1</live-example>
|
||||
* // ~/resources/live-examples/toh-pt1/minimal.stackblitz.json
|
||||
*/
|
||||
@Component({
|
||||
selector: 'live-example',
|
||||
templateUrl: 'live-example.component.html'
|
||||
})
|
||||
export class LiveExampleComponent implements AfterContentInit {
|
||||
export class LiveExampleComponent implements OnInit {
|
||||
|
||||
readonly mode: 'default' | 'embedded' | 'downloadOnly';
|
||||
readonly enableDownload: boolean;
|
||||
readonly stackblitz: string;
|
||||
readonly zip: string;
|
||||
// Will force to embedded-style when viewport width is narrow
|
||||
// "narrow" value was picked based on phone dimensions from http://screensiz.es/phone
|
||||
readonly narrowWidth = 1000;
|
||||
|
||||
attrs: any;
|
||||
enableDownload = true;
|
||||
exampleDir: string;
|
||||
isEmbedded = false;
|
||||
mode = 'disabled';
|
||||
stackblitz: string;
|
||||
stackblitzName: string;
|
||||
title: string;
|
||||
zip: string;
|
||||
zipName: string;
|
||||
|
||||
@ViewChild('content')
|
||||
private content: ElementRef;
|
||||
constructor(
|
||||
private elementRef: ElementRef,
|
||||
location: Location ) {
|
||||
|
||||
constructor(elementRef: ElementRef, location: Location) {
|
||||
const attrs = getAttrs(elementRef);
|
||||
const exampleDir = this.getExampleDir(attrs, location.path(false));
|
||||
const stackblitzName = this.getStackblitzName(attrs);
|
||||
|
||||
this.mode = this.getMode(attrs);
|
||||
this.enableDownload = this.getEnableDownload(attrs);
|
||||
this.stackblitz = this.getStackblitz(exampleDir, stackblitzName, this.mode === 'embedded');
|
||||
this.zip = this.getZip(exampleDir, stackblitzName);
|
||||
this.title = this.getTitle(attrs);
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
// Angular will sanitize this title when displayed, so it should be plain text.
|
||||
const textContent = this.content.nativeElement.textContent.trim();
|
||||
if (textContent) {
|
||||
this.title = textContent;
|
||||
}
|
||||
}
|
||||
|
||||
private getEnableDownload(attrs: AttrMap) {
|
||||
const downloadDisabled = boolFromValue(getAttrValue(attrs, 'noDownload'));
|
||||
return !downloadDisabled;
|
||||
}
|
||||
|
||||
private getExampleDir(attrs: AttrMap, path: string) {
|
||||
let exampleDir = getAttrValue(attrs, 'name');
|
||||
const attrs = this.attrs = getAttrs(this.elementRef);
|
||||
let exampleDir = attrs.name;
|
||||
if (!exampleDir) {
|
||||
// Take the last path segment, excluding query params and hash fragment.
|
||||
const match = path.match(/[^/?#]+(?=\/?(?:\?|#|$))/);
|
||||
exampleDir = match ? match[0] : 'index';
|
||||
// take last segment, excluding hash fragment and query params
|
||||
exampleDir = (location.path(false).match(/[^\/?\#]+(?=\/?(?:$|\#|\?))/) || [])[0];
|
||||
}
|
||||
this.exampleDir = exampleDir.trim();
|
||||
this.zipName = exampleDir.indexOf('/') === -1 ? this.exampleDir : exampleDir.split('/')[0];
|
||||
this.stackblitzName = attrs.stackblitz ? attrs.stackblitz.trim() + '.' : '';
|
||||
this.zip = `${zipBase}${exampleDir}/${this.stackblitzName}${this.zipName}.zip`;
|
||||
|
||||
this.enableDownload = !boolFromValue(getAttrValue(attrs, 'nodownload'));
|
||||
|
||||
if (boolFromValue(getAttrValue(attrs, 'downloadonly'))) {
|
||||
this.mode = 'downloadOnly';
|
||||
}
|
||||
return exampleDir.trim();
|
||||
}
|
||||
|
||||
private getMode(this: LiveExampleComponent, attrs: AttrMap): typeof this.mode {
|
||||
const downloadOnly = boolFromValue(getAttrValue(attrs, 'downloadOnly'));
|
||||
const isEmbedded = boolFromValue(getAttrValue(attrs, 'embedded'));
|
||||
calcStackblitzLink(width: number) {
|
||||
|
||||
return downloadOnly ? 'downloadOnly'
|
||||
: isEmbedded ? 'embedded' :
|
||||
'default';
|
||||
const attrs = this.attrs;
|
||||
const exampleDir = this.exampleDir;
|
||||
let urlQuery = '';
|
||||
|
||||
this.mode = 'default'; // display in another browser tab by default
|
||||
|
||||
this.isEmbedded = boolFromValue(attrs.embedded);
|
||||
|
||||
if (this.isEmbedded) {
|
||||
this.mode = 'embedded'; // display embedded in the doc
|
||||
urlQuery = '?ctl=1';
|
||||
}
|
||||
|
||||
this.stackblitz = `${liveExampleBase}${exampleDir}/${this.stackblitzName}stackblitz.html${urlQuery}`;
|
||||
}
|
||||
|
||||
private getStackblitz(exampleDir: string, stackblitzName: string, isEmbedded: boolean) {
|
||||
const urlQuery = isEmbedded ? '?ctl=1' : '';
|
||||
return `${LIVE_EXAMPLE_BASE}${exampleDir}/${stackblitzName}stackblitz.html${urlQuery}`;
|
||||
ngOnInit() {
|
||||
// The `liveExampleContent` property is set by the DocViewer when it builds this component.
|
||||
// It is the original innerHTML of the host element.
|
||||
// Angular will sanitize this title when displayed so should be plain text.
|
||||
const title = this.elementRef.nativeElement.liveExampleContent;
|
||||
this.title = (title || this.attrs.title || 'live example').trim();
|
||||
this.onResize(window.innerWidth);
|
||||
}
|
||||
|
||||
private getStackblitzName(attrs: AttrMap) {
|
||||
const attrValue = (getAttrValue(attrs, 'stackblitz') || '').trim();
|
||||
return attrValue && `${attrValue}.`;
|
||||
}
|
||||
|
||||
private getTitle(attrs: AttrMap) {
|
||||
return (getAttrValue(attrs, 'title') || 'live example').trim();
|
||||
}
|
||||
|
||||
private getZip(exampleDir: string, stackblitzName: string) {
|
||||
const zipName = exampleDir.split('/')[0];
|
||||
return `${ZIP_BASE}${exampleDir}/${stackblitzName}${zipName}.zip`;
|
||||
@HostListener('window:resize', ['$event.target.innerWidth'])
|
||||
onResize(width: number) {
|
||||
if (this.mode !== 'downloadOnly') {
|
||||
this.calcStackblitzLink(width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,7 +136,7 @@ export class LiveExampleComponent implements AfterContentInit {
|
||||
@Component({
|
||||
selector: 'aio-embedded-stackblitz',
|
||||
template: `<iframe #iframe frameborder="0" width="100%" height="100%"></iframe>`,
|
||||
styles: [ 'iframe { min-height: 400px; }' ]
|
||||
styles: [ 'iframe { min-height: 400px; }']
|
||||
})
|
||||
export class EmbeddedStackblitzComponent implements AfterViewInit {
|
||||
@Input() src: string;
|
||||
|
@ -1,14 +0,0 @@
|
||||
import { NgModule, Type } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { WithCustomElementComponent } from '../element-registry';
|
||||
import { TocComponent } from './toc.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule, MatIconModule ],
|
||||
declarations: [ TocComponent ],
|
||||
entryComponents: [ TocComponent ],
|
||||
})
|
||||
export class TocModule implements WithCustomElementComponent {
|
||||
customElementComponent: Type<any> = TocComponent;
|
||||
}
|
@ -300,7 +300,7 @@ describe('DocViewerComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
const elementsLoader = TestBed.get(ElementsLoader) as MockElementsLoader;
|
||||
loadElementsSpy = elementsLoader.loadContainedCustomElements.and.returnValue(of(undefined));
|
||||
loadElementsSpy = elementsLoader.loadContainingCustomElements.and.returnValue(of(undefined));
|
||||
prepareTitleAndTocSpy = spyOn(docViewer, 'prepareTitleAndToc');
|
||||
swapViewsSpy = spyOn(docViewer, 'swapViews').and.returnValue(of(undefined));
|
||||
});
|
||||
|
@ -136,7 +136,7 @@ export class DocViewerComponent implements OnDestroy {
|
||||
// and is considered to be safe.
|
||||
tap(() => this.nextViewContainer.innerHTML = doc.contents || ''),
|
||||
tap(() => addTitleAndToc = this.prepareTitleAndToc(this.nextViewContainer, doc.id)),
|
||||
switchMap(() => this.elementsLoader.loadContainedCustomElements(this.nextViewContainer)),
|
||||
switchMap(() => this.elementsLoader.loadContainingCustomElements(this.nextViewContainer)),
|
||||
tap(() => this.docReady.emit()),
|
||||
switchMap(() => this.swapViews(addTitleAndToc)),
|
||||
tap(() => this.docRendered.emit()),
|
||||
|
@ -4,8 +4,8 @@ import { By } from '@angular/platform-browser';
|
||||
import { asapScheduler as asap, BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { TocItem, TocService } from 'app/shared/toc.service';
|
||||
import { TocComponent } from './toc.component';
|
||||
import { TocItem, TocService } from 'app/shared/toc.service';
|
||||
|
||||
describe('TocComponent', () => {
|
||||
let tocComponentDe: DebugElement;
|
@ -1,6 +1,6 @@
|
||||
import { ElementRef } from '@angular/core';
|
||||
|
||||
import { AttrMap, getAttrs, getAttrValue, getBoolFromAttribute, boolFromValue } from './attribute-utils';
|
||||
import { getAttrs, getAttrValue, getBoolFromAttribute, boolFromValue } from './attribute-utils';
|
||||
|
||||
describe('Attribute Utilities', () => {
|
||||
let testEl: HTMLElement;
|
||||
@ -32,17 +32,17 @@ describe('Attribute Utilities', () => {
|
||||
});
|
||||
|
||||
describe('getAttrValue', () => {
|
||||
let attrMap: AttrMap;
|
||||
let attrMap: { [index: string]: string };
|
||||
|
||||
beforeEach(() => {
|
||||
attrMap = getAttrs(testEl);
|
||||
});
|
||||
|
||||
it('should return empty string for attribute "a"', () => {
|
||||
it('should return empty string value for attribute "a"', () => {
|
||||
expect(getAttrValue(attrMap, 'a')).toBe('');
|
||||
});
|
||||
|
||||
it('should return empty string for attribute "A"', () => {
|
||||
it('should return empty string value for attribute "A"', () => {
|
||||
expect(getAttrValue(attrMap, 'A')).toBe('');
|
||||
});
|
||||
|
||||
@ -50,7 +50,7 @@ describe('Attribute Utilities', () => {
|
||||
expect(getAttrValue(attrMap, 'b')).toBe('true');
|
||||
});
|
||||
|
||||
it('should return empty string for attribute "d-E"', () => {
|
||||
it('should return empty string value for attribute "d-E"', () => {
|
||||
expect(getAttrValue(attrMap, 'd-E')).toBe('');
|
||||
});
|
||||
|
||||
@ -68,10 +68,12 @@ describe('Attribute Utilities', () => {
|
||||
expect(getAttrValue(attrMap, ['d-e', 'd'])).toBe('');
|
||||
});
|
||||
|
||||
it('should return undefined for non-existent attributes', () => {
|
||||
it('should return undefined value for non-existent attribute "x"', () => {
|
||||
expect(getAttrValue(attrMap, 'x')).toBeUndefined();
|
||||
expect(getAttrValue(attrMap, '')).toBeUndefined();
|
||||
expect(getAttrValue(attrMap, ['', 'x'])).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined if no argument', () => {
|
||||
expect(getAttrValue(attrMap)).toBeUndefined();
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -1,19 +1,17 @@
|
||||
// Utilities for processing HTML element attributes
|
||||
import { ElementRef } from '@angular/core';
|
||||
|
||||
export interface AttrMap {
|
||||
[key: string]: string;
|
||||
}
|
||||
interface StringMap { [index: string]: string; }
|
||||
|
||||
/**
|
||||
* Get attribute map from element or ElementRef `attributes`.
|
||||
* Attribute map keys are forced lowercase for case-insensitive lookup.
|
||||
* @param el The source of the attributes.
|
||||
* @param el The source of the attributes
|
||||
*/
|
||||
export function getAttrs(el: HTMLElement | ElementRef): AttrMap {
|
||||
export function getAttrs(el: HTMLElement | ElementRef): StringMap {
|
||||
const attrs: NamedNodeMap = el instanceof ElementRef ? el.nativeElement.attributes : el.attributes;
|
||||
const attrMap: AttrMap = {};
|
||||
for (const attr of attrs as any as Attr[] /* cast due to https://github.com/Microsoft/TypeScript/issues/2695 */) {
|
||||
const attrMap: StringMap = {};
|
||||
for (const attr of attrs as any /* cast due to https://github.com/Microsoft/TypeScript/issues/2695 */) {
|
||||
attrMap[attr.name.toLowerCase()] = attr.value;
|
||||
}
|
||||
return attrMap;
|
||||
@ -21,29 +19,29 @@ export function getAttrs(el: HTMLElement | ElementRef): AttrMap {
|
||||
|
||||
/**
|
||||
* Return the attribute that matches `attr`.
|
||||
* @param attr Name of the attribute or a string of candidate attribute names.
|
||||
* @param attr Name of the attribute or a string of candidate attribute names
|
||||
*/
|
||||
export function getAttrValue(attrs: AttrMap, attr: string | string[]): string | undefined {
|
||||
const key = (typeof attr === 'string')
|
||||
? attr
|
||||
: attr.find(a => attrs.hasOwnProperty(a.toLowerCase()));
|
||||
|
||||
return (key === undefined) ? undefined : attrs[key.toLowerCase()];
|
||||
export function getAttrValue(attrs: StringMap, attr: string | string[] = ''): string {
|
||||
return attrs[typeof attr === 'string' ?
|
||||
attr.toLowerCase() :
|
||||
attr.find(a => attrs[a.toLowerCase()] !== undefined) || ''
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the boolean state of an attribute value (if supplied)
|
||||
* @param attrValue The string value of some attribute (or undefined if attribute not present).
|
||||
* @param attrValue The string value of some attribute (or undefined if attribute not present)
|
||||
* @param def Default boolean value when attribute is undefined.
|
||||
*/
|
||||
export function boolFromValue(attrValue: string | undefined, def: boolean = false) {
|
||||
return attrValue === undefined ? def : attrValue.trim() !== 'false';
|
||||
export function boolFromValue(attrValue: string|undefined, def: boolean = false) {
|
||||
// tslint:disable-next-line:triple-equals
|
||||
return attrValue == undefined ? def : attrValue.trim() !== 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the boolean state of attribute from an element
|
||||
* @param el The source of the attributes.
|
||||
* @param atty Name of the attribute or a string of candidate attribute names.
|
||||
* @param el The source of the attributes
|
||||
* @param atty Name of the attribute or a string of candidate attribute names
|
||||
* @param def Default boolean value when attribute is undefined.
|
||||
*/
|
||||
export function getBoolFromAttribute(
|
||||
|
BIN
aio/src/assets/images/favicons/apple-touch-icon-144x144.png
Normal file
After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 4.6 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 186 200"><defs><style>.cls-1{fill:#ff7043;}.cls-2{fill:#f4511e;}.cls-3{fill:#ffb8a1;}.cls-4{fill:#ffc6b4;}.cls-5{fill:#ffe2d9;}.cls-6{fill:#fff;}</style></defs><title>animations</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.8 0 92.8 0 92.8 0 0 33.3 14 156.1 92.8 200 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><polygon class="cls-2" points="92.8 0 92.8 22.2 92.8 22.2 92.8 123.2 92.8 123.2 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><rect class="cls-3" x="43" y="71.2" width="68.4" height="68.84" rx="4" ry="4"/><rect class="cls-4" x="57.4" y="62.3" width="68.8" height="68.84" rx="4" ry="4" transform="translate(38.6 -25.6) rotate(20)"/><rect class="cls-5" x="71.3" y="59.2" width="68.8" height="68.84" rx="4" ry="4" transform="translate(66.9 -42.1) rotate(32.5)"/><rect class="cls-6" x="81.6" y="58.3" width="68.8" height="68.84" rx="4" ry="4" transform="translate(99.6 -54.9) rotate(45)"/></g></g></svg>
|
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 5.2 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 185.6 200"><defs><style>.cls-1{fill:#4a3493;}.cls-2{fill:#311b92;}.cls-3{opacity:0.5;}.cls-4{fill:#fff;}</style></defs><title>augury</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.4 0 92.4 0 92.4 0 0 33.3 14 156.1 92.4 200 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-2" points="92.4 0 92.4 22.2 92.4 22.2 92.4 123.2 92.4 123.2 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><g id="Group-2"><g id="Group-2-2" class="cls-3"><path id="Combined-Shape" class="cls-4" d="M142.4,90.2A44.1,44.1,0,1,1,92.7,52.8a40.3,40.3,0,0,1,6-.4,44.8,44.8,0,0,1,15.9,2.9,25.8,25.8,0,1,0,27.8,34.9Z"/></g><path id="Combined-Shape-2" class="cls-4" d="M140.6,87.8A44.1,44.1,0,1,1,90.9,50.4a40.3,40.3,0,0,1,6-.4,45.6,45.6,0,0,1,15.9,2.9,25.8,25.8,0,1,0,27.8,34.9Z"/></g></g></g></svg>
|
Before Width: | Height: | Size: 908 B |
Before Width: | Height: | Size: 2.9 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 185.6 200"><defs><style>.cls-1{fill:#03a9f4;}.cls-2{fill:#039be5;}.cls-3{fill:#fff;}</style></defs><title>cdk</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.4 0 92.4 0 92.4 0 0 33.3 14 156.1 92.4 200 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-2" points="92.4 0 92.4 22.2 92.4 22.2 92.4 123.2 92.4 123.2 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><rect class="cls-3" x="117.8" y="45" width="30" height="30" rx="1" ry="1"/><rect class="cls-3" x="117.8" y="85" width="30" height="30" rx="1" ry="1"/><rect class="cls-3" x="117.8" y="125" width="30" height="30" rx="1" ry="1"/><rect class="cls-3" x="77.8" y="125" width="30" height="30" rx="1" ry="1"/><rect class="cls-3" x="37.8" y="125" width="30" height="30" rx="1" ry="1"/><rect class="cls-3" x="37.8" y="85" width="30" height="30" rx="1" ry="1"/><rect class="cls-3" x="77.8" y="85" width="30" height="30" rx="1" ry="1"/></g></g></svg>
|
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 3.2 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 185.6 200"><defs><style>.cls-1{fill:#37474f;}.cls-2{fill:#263238;}.cls-3{fill:#fff;}</style></defs><title>cli</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.4 0 92.4 0 92.4 0 0 33.3 14 156.1 92.4 200 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-2" points="92.4 0 92.4 22.2 92.4 22.2 92.4 123.2 92.4 123.2 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><path class="cls-3" d="M42.8,55v90h100V55ZM138,139.9H47.5V60.1h90.6v79.8Z"/><polygon class="cls-3" points="60.1 111.4 63.5 114.9 82.8 95.5 63.2 75.9 59.8 79.3 76 95.5 60.1 111.4"/><rect class="cls-3" x="82.8" y="117.2" width="30" height="4"/></g></g></svg>
|
Before Width: | Height: | Size: 757 B |
Before Width: | Height: | Size: 6.1 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 185.6 200"><defs><style>.cls-1{fill:#8bc34a;}.cls-2{fill:#7cb342;}.cls-3{fill:#fff;stroke:#fff;stroke-miterlimit:10;stroke-width:0.08px;}.cls-4{fill:#bed9a1;}</style></defs><title>compiler</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.4 0 92.4 0 92.4 0 0 33.3 14 156.1 92.4 200 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-2" points="92.4 0 92.4 22.2 92.4 22.2 92.4 123.2 92.4 123.2 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><path class="cls-3" d="M113.9,144.1l-1.5-5.5a7,7,0,0,1,2.7-7.4,26.2,26.2,0,0,0,4.8-4,36,36,0,0,0,4.1-4.8,6.7,6.7,0,0,1,7.4-2.7l5.4,1.4a1.7,1.7,0,0,0,1.4-.1c.5-.3.9-.7.9-1.1l3.7-13.8a1.9,1.9,0,0,0-1.4-2.2l-5.4-1.5a7.2,7.2,0,0,1-5.2-6,38.7,38.7,0,0,0-3.2-12.1,7.1,7.1,0,0,1,1.3-7.9l4-3.9a1.9,1.9,0,0,0,0-2.6l-10-10a1.8,1.8,0,0,0-2.5,0l-4,4a7,7,0,0,1-7.8,1.3A37.4,37.4,0,0,0,96.5,62a7.3,7.3,0,0,1-6.1-5.2L89,51.4a1.9,1.9,0,0,0-2.3-1.3L73,53.7a2.3,2.3,0,0,0-1.1.9,3.4,3.4,0,0,0-.2,1.4l1.5,5.4a6.9,6.9,0,0,1-2.7,7.4,38.3,38.3,0,0,0-4.8,4.1,47.2,47.2,0,0,0-4.1,4.8,6.8,6.8,0,0,1-7.4,2.7L48.8,79a1.7,1.7,0,0,0-1.4.1,1.9,1.9,0,0,0-.9,1.1L42.9,93.9a1.9,1.9,0,0,0,1.3,2.3l5.4,1.4a6.2,6.2,0,0,1,3,1.8,6.8,6.8,0,0,1,2.1,4.2,38.7,38.7,0,0,0,3.2,12.1,6.8,6.8,0,0,1-1.3,7.9l-4,3.9a1.9,1.9,0,0,0,0,2.6l10,10a1.9,1.9,0,0,0,2.6,0l3.9-4a6.8,6.8,0,0,1,7.9-1.3A36.8,36.8,0,0,0,89.1,138a7.2,7.2,0,0,1,6,5.2l1.5,5.4a1.7,1.7,0,0,0,2.2,1.3l13.8-3.5a2.3,2.3,0,0,0,1.1-.9A1.8,1.8,0,0,0,113.9,144.1ZM76.5,116.3a23,23,0,1,1,32.5,0A23.2,23.2,0,0,1,76.5,116.3Z"/><ellipse class="cls-4" cx="92.8" cy="100" rx="13.3" ry="13.3"/></g></g></svg>
|
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 4.7 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 185.6 200"><defs><style>.cls-1{fill:#448aff;}.cls-2{fill:#2979ff;}.cls-3{fill:#fff;}.cls-4{fill:#a2c5ff;}</style></defs><title>components</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.4 0 92.4 0 92.4 0 0 33.3 14 156.1 92.4 200 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-2" points="92.4 0 92.4 22.2 92.4 22.2 92.4 123.2 92.4 123.2 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-3" points="73.6 131.4 54.3 98 84.3 46.1 62.8 46.1 32.8 98 62.8 150 84.4 150 73.6 131.4 73.6 131.4"/><polygon class="cls-4" points="152.8 98 122.8 46.1 84.3 46.1 54.3 98 73.6 64.7 112.1 64.7 131.3 98 112.1 131.4 73.6 131.4 84.4 150 122.8 150 152.8 98"/></g></g></svg>
|
Before Width: | Height: | Size: 815 B |
Before Width: | Height: | Size: 4.3 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"><defs><style>.cls-1{fill:#546e7a;}.cls-2{fill:#455a64;}.cls-3,.cls-4{fill:#fff;}.cls-3{opacity:0.5;}</style></defs><title>dependency-injection</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M0,100A100,100,0,0,0,100,200V0A100,100,0,0,0,0,100Z"/><path class="cls-2" d="M100,0V200A100,100,0,0,0,100,0Z"/><polygon class="cls-3" points="131.2 73.2 151 47.7 125.6 67.5 108.6 61.8 59.1 111.3 87.4 139.6 136.9 90.1 131.2 73.2"/><rect class="cls-4" x="74.5" y="74.3" width="40" height="60" transform="translate(101.4 -36.3) rotate(45)"/><rect class="cls-4" x="66.8" y="107" width="20" height="30" transform="translate(108.7 -18.6) rotate(45)"/><rect class="cls-4" x="42.6" y="131.1" width="40" height="10" transform="translate(114.6 -4.4) rotate(45)"/></g></g></svg>
|
Before Width: | Height: | Size: 876 B |
Before Width: | Height: | Size: 3.4 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 186 200"><defs><style>.cls-1{fill:#e57373;}.cls-2{fill:#ef5350;}.cls-3{fill:#fff;}</style></defs><title>forms</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.8 0 92.8 0 92.8 0 0 33.3 14 156.1 92.8 200 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><polygon class="cls-2" points="92.8 0 92.8 22.2 92.8 22.2 92.8 123.2 92.8 123.2 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><path class="cls-3" d="M138.7,90H47c-2.4,0-4-2.3-4-5.6V55.6c0-3.3,1.6-5.6,4-5.6h92.1c2.3,0,3.9,2.3,3.9,5.6V84.4C142.6,87.7,141,90,138.7,90Z"/><path class="cls-3" d="M139.8,120H96.3c-2,0-3.3-1.4-3.3-3.6V103.6c0-2.2,1.3-3.6,3.3-3.6h43.5c1.9,0,3.2,1.4,3.2,3.6v12.8A3.4,3.4,0,0,1,139.8,120Z"/><path class="cls-3" d="M139.8,150H96.3c-2,0-3.3-1.5-3.3-3.8V133.8c0-2.3,1.3-3.8,3.3-3.8h43.5c1.9,0,3.2,1.5,3.2,3.8v12.4C143,148.1,141.7,150,139.8,150Z"/><rect class="cls-3" x="43" y="100" width="40" height="50" rx="4" ry="4"/></g></g></svg>
|
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 4.7 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 185.6 200"><defs><style>.cls-1{fill:#2e7d32;}.cls-2{fill:#1b5e20;}.cls-3,.cls-4,.cls-5{fill:#fff;stroke:#fff;stroke-miterlimit:10;}.cls-3{stroke-width:0.24px;}.cls-4{stroke-width:0.27px;}.cls-5{stroke-width:0.27px;}</style></defs><title>http</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.4 0 92.4 0 92.4 0 0 33.3 14 156.1 92.4 200 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-2" points="92.4 0 92.4 22.2 92.4 22.2 92.4 123.2 92.4 123.2 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-3" points="107.5 150 100.3 148.2 125.7 50 132.9 51.8 107.5 150"/><polygon class="cls-3" points="81.5 150 74.3 148.2 99.7 50 106.8 51.8 81.5 150"/><circle class="cls-4" cx="65.5" cy="82.8" r="7.1"/><circle class="cls-5" cx="65.5" cy="113.1" r="7.1"/></g></g></svg>
|
Before Width: | Height: | Size: 916 B |