Compare commits
153 Commits
Author | SHA1 | Date | |
---|---|---|---|
5db7b4c354 | |||
e08d02145e | |||
dae3a77c43 | |||
02e75df3e7 | |||
f752ab9367 | |||
9c875b30dc | |||
8fa78d10ab | |||
26988f0d62 | |||
6acf117386 | |||
8b731e374d | |||
5378f78e79 | |||
495c40e31c | |||
9963c5d8f7 | |||
13c4a7b1da | |||
70e85d226c | |||
dfb129dbfa | |||
1a1407aef6 | |||
da71f76a6a | |||
0c9b76e9e7 | |||
db34b23ab5 | |||
df0941554b | |||
5221df8e92 | |||
bc35c6a8ed | |||
23fc2b43ac | |||
c16995eb59 | |||
3487d5a13a | |||
1efc75cd72 | |||
c4190e3142 | |||
5d98834585 | |||
529ff73200 | |||
791205005c | |||
2d37e47e95 | |||
27c8066641 | |||
952fd8662c | |||
42be9047d8 | |||
52a0c6b36e | |||
8913aee527 | |||
7628c36f49 | |||
b417dd7b9c | |||
8cf16b4815 | |||
806aed63f4 | |||
eb34037cd7 | |||
ba0df5250f | |||
c4553bbed9 | |||
cc13b37446 | |||
166df5d2ca | |||
a5b474580c | |||
9f132d0d93 | |||
8dee378b3e | |||
0845d1148f | |||
7cfa57a5f7 | |||
f80c22002b | |||
7aa12412f3 | |||
072b707b38 | |||
223b80cb7d | |||
416403fc63 | |||
878e2f0deb | |||
4c30f5135b | |||
6791cd79af | |||
f50313f54d | |||
30433a0710 | |||
86ab9f92b4 | |||
42f9679376 | |||
bee10574d8 | |||
afce0f5038 | |||
bb19e61848 | |||
50e83e2566 | |||
d80c4890be | |||
4f8b716c13 | |||
cb85d69450 | |||
4a446878fa | |||
cb80f46c64 | |||
5199d55d45 | |||
bb11fd9058 | |||
771f7318f0 | |||
5d584b7728 | |||
1b5f6ee7a6 | |||
669e07580c | |||
965249d1da | |||
3a01856e7c | |||
a8134dcfd4 | |||
a1bf0de711 | |||
3dbc076159 | |||
3409efbeb3 | |||
3812f57789 | |||
8292e1cc51 | |||
d8de689080 | |||
d53cfb510c | |||
fb51e10954 | |||
7813529f4e | |||
066fca07f4 | |||
31be06a6f6 | |||
fd795da9d9 | |||
69302adc02 | |||
9891cef6e4 | |||
65d4e7a8af | |||
0fa208f624 | |||
15fa4bbdaf | |||
ed35adbea6 | |||
4645f43c3c | |||
b613639e8a | |||
bc6ff7745e | |||
33aaa9e7d0 | |||
689651b52f | |||
875fcfe5a9 | |||
0d238aa9d9 | |||
398b44640f | |||
5b1c714068 | |||
6df71d52c1 | |||
be25ccb94c | |||
6e994272e8 | |||
79ac811550 | |||
bbe6cf38ff | |||
c95fabf96d | |||
3fad0ffb3a | |||
ef0be182bb | |||
139c5b4eab | |||
1328236810 | |||
5453772648 | |||
7c5f89d2c3 | |||
bc0d140a1d | |||
14ecc9ead2 | |||
62a2fc8981 | |||
8f59e3750b | |||
3d9ebb4a52 | |||
ea141f86d3 | |||
c100dbe860 | |||
9c82e27ab6 | |||
4035e472d0 | |||
d03e2e35cb | |||
b29c32b758 | |||
3e9986871c | |||
617858df61 | |||
68cb77d9ab | |||
5cdf2e4e30 | |||
39bfa349c7 | |||
b3752e6524 | |||
9833b0b31c | |||
2eb8447c95 | |||
78c9972195 | |||
5bf55132fb | |||
50f5827fb4 | |||
e98f11ac9e | |||
1f2c7f6728 | |||
3d2799b73c | |||
685281337b | |||
9e8fa0748f | |||
525af1e5f0 | |||
58f2abef01 | |||
9d918d8e6c | |||
8236904933 | |||
73550967e4 | |||
c6505001a9 |
4
.bazelrc
4
.bazelrc
@ -74,8 +74,8 @@ test --test_output=errors
|
||||
# Trick bazel into treating BUILD files under integration/bazel as being regular files
|
||||
# This lets us glob() up all the files inside this integration test to make them inputs to tests
|
||||
# (Note, we cannot use common --deleted_packages because the bazel version command doesn't support it)
|
||||
build --deleted_packages=integration/bazel,integration/bazel/src,integration/bazel/src/hello-world,integration/bazel/test,integration/bazel/test/e2e
|
||||
query --deleted_packages=integration/bazel,integration/bazel/src,integration/bazel/src/hello-world,integration/bazel/test,integration/bazel/test/e2e
|
||||
build --deleted_packages=integration/bazel,integration/bazel/src,integration/bazel/src/hello-world,integration/bazel/test,integration/bazel/tools,integration/bazel/test/e2e
|
||||
query --deleted_packages=integration/bazel,integration/bazel/src,integration/bazel/src/hello-world,integration/bazel/test,integration/bazel/tools,integration/bazel/test/e2e
|
||||
|
||||
################################
|
||||
# Temporary Settings for Ivy #
|
||||
|
@ -1,3 +1,3 @@
|
||||
3.2.0
|
||||
3.5.1
|
||||
# [NB: this comment has to be after the first line, see https://github.com/bazelbuild/bazelisk/issues/117]
|
||||
# When updating the Bazel version you also need to update the RBE toolchains version in package.bzl
|
||||
|
@ -14,8 +14,8 @@ build --repository_cache=/home/circleci/bazel_repository_cache
|
||||
# Bazel doesn't calculate the memory ceiling correctly when running under Docker.
|
||||
# Limit Bazel to consuming resources that fit in CircleCI "xlarge" class
|
||||
# https://circleci.com/docs/2.0/configuration-reference/#resource_class
|
||||
build --local_cpu_resources=8
|
||||
build --local_ram_resources=14336
|
||||
build --local_cpu_resources=20
|
||||
build --local_ram_resources=32768
|
||||
|
||||
# All build executed remotely should be done using our RBE configuration.
|
||||
build:remote --google_default_credentials
|
||||
|
@ -11,8 +11,8 @@ try-import %workspace%/.circleci/bazel.common.rc
|
||||
build --repository_cache=C:/Users/circleci/bazel_repository_cache
|
||||
|
||||
# Manually set the local resources used in windows CI runs
|
||||
build --local_ram_resources=13500
|
||||
build --local_cpu_resources=4
|
||||
build --local_ram_resources=120000
|
||||
build --local_cpu_resources=32
|
||||
|
||||
# All windows jobs run on master and should use http caching
|
||||
build --remote_http_cache=https://storage.googleapis.com/angular-team-cache
|
||||
|
@ -80,7 +80,7 @@ executors:
|
||||
|
||||
windows-executor:
|
||||
working_directory: ~/ng
|
||||
resource_class: windows.medium
|
||||
resource_class: windows.2xlarge
|
||||
# CircleCI windows VMs do have the GitBash shell available:
|
||||
# https://github.com/CircleCI-Public/windows-preview-docs#shells
|
||||
# But in this specific case we really should not use it because Bazel must not be ran from
|
||||
@ -273,6 +273,7 @@ jobs:
|
||||
- run: yarn -s ng-dev format changed $CI_GIT_BASE_REVISION --check
|
||||
- run: yarn -s ts-circular-deps:check
|
||||
- run: yarn -s ng-dev pullapprove verify
|
||||
- run: yarn -s ng-dev ngbot verify
|
||||
- run: yarn -s ng-dev commit-message validate-range --range $CI_COMMIT_RANGE
|
||||
|
||||
test:
|
||||
@ -857,9 +858,16 @@ workflows:
|
||||
- build-npm-packages
|
||||
- build-ivy-npm-packages
|
||||
- legacy-unit-tests-saucelabs
|
||||
- components-repo-unit-tests:
|
||||
requires:
|
||||
- build-npm-packages
|
||||
# Temporarily disabled components-repo-unit-tests to update rules_nodejs to 2.0.0. Breaking changes in
|
||||
# rules_nodejs create a dependency sandwich between angular/angular & angular/components that are very
|
||||
# difficult and time consuming to resolve and involve patching @angular/bazel in components repo such
|
||||
# as https://github.com/angular/components/commit/9e7ba251207df77164d73d66620e619bcbc4d2ad. It is simpler to
|
||||
# 1) land angular/angular upgrade to rule_nodejs 2.0.0 which has breaking changes
|
||||
# 2) land angular/components upgrade to rules_nodejs 2.0.0 using the @angular/bazel builds snapshot
|
||||
# 3) update angular/angular to the landed components commit and re-enable these tests
|
||||
# - components-repo-unit-tests:
|
||||
# requires:
|
||||
# - build-npm-packages
|
||||
- test_zonejs:
|
||||
requires:
|
||||
- setup
|
||||
|
@ -41,7 +41,8 @@ copy .circleci\bazel.windows.rc ${Env:USERPROFILE}\.bazelrc
|
||||
####################################################################################################
|
||||
# Install specific version of node.
|
||||
####################################################################################################
|
||||
choco install nodejs --version 12.14.1 --no-progress
|
||||
nvm install 12.14.1
|
||||
nvm use 12.14.1
|
||||
|
||||
# These Bazel prereqs aren't needed because the CircleCI image already includes them.
|
||||
# choco install yarn --version 1.16.0 --no-progress
|
||||
|
26
.github/angular-robot.yml
vendored
26
.github/angular-robot.yml
vendored
@ -38,6 +38,7 @@ merge:
|
||||
- "modules/benchmarks/**"
|
||||
- "modules/system.d.ts"
|
||||
- "packages/**"
|
||||
- "dev-infra/benchmark/driver-utilities/**"
|
||||
# list of patterns to ignore for the files changed by the PR
|
||||
exclude:
|
||||
- "packages/*"
|
||||
@ -47,7 +48,10 @@ merge:
|
||||
- "packages/bazel/src/ng_package/**"
|
||||
- "packages/bazel/src/protractor/**"
|
||||
- "packages/bazel/src/schematics/**"
|
||||
- "packages/compiler-cli/src/ngcc/**"
|
||||
- "packages/compiler-cli/linker/**"
|
||||
- "packages/compiler-cli/ngcc/**"
|
||||
- "packages/compiler-cli/src/ngtsc/sourcemaps/**"
|
||||
- "packages/docs/**"
|
||||
- "packages/elements/schematics/**"
|
||||
- "packages/examples/**"
|
||||
@ -55,6 +59,8 @@ merge:
|
||||
- "packages/localize/**"
|
||||
- "packages/private/**"
|
||||
- "packages/service-worker/**"
|
||||
- "packages/common/locales/**"
|
||||
- "packages/http/**"
|
||||
- "**/.gitignore"
|
||||
- "**/.gitkeep"
|
||||
- "**/yarn.lock"
|
||||
@ -136,24 +142,28 @@ triage:
|
||||
# arrays of labels that determine if an issue has been fully triaged
|
||||
l2TriageLabels:
|
||||
-
|
||||
- "type: bug/fix"
|
||||
- "severity*"
|
||||
- "freq*"
|
||||
- "P0"
|
||||
- "comp: *"
|
||||
-
|
||||
- "type: feature"
|
||||
- "P1"
|
||||
- "comp: *"
|
||||
-
|
||||
- "type: refactor"
|
||||
- "P2"
|
||||
- "comp: *"
|
||||
-
|
||||
- "type: RFC / Discussion / question"
|
||||
- "P3"
|
||||
- "comp: *"
|
||||
-
|
||||
- "type: confusing"
|
||||
- "P4"
|
||||
- "comp: *"
|
||||
-
|
||||
- "type: use-case"
|
||||
- "P5"
|
||||
- "comp: *"
|
||||
-
|
||||
- "feature"
|
||||
- "comp: *"
|
||||
-
|
||||
- "discussion"
|
||||
- "comp: *"
|
||||
|
||||
# options for the triage PR plugin
|
||||
|
@ -9,11 +9,11 @@ export const caretaker: CaretakerConfig = {
|
||||
},
|
||||
{
|
||||
name: 'Merge Assistance Queue',
|
||||
query: `is:pr is:open status:success label:"action: merge-assistance"`,
|
||||
query: `is:pr is:open label:"action: merge-assistance"`,
|
||||
},
|
||||
{
|
||||
name: 'Primary Triage Queue',
|
||||
query: `is:open is:issue no:milestone`,
|
||||
name: 'Initial Triage Queue',
|
||||
query: `is:open no:milestone`,
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -3,6 +3,7 @@ import {commitMessage} from './commit-message';
|
||||
import {format} from './format';
|
||||
import {github} from './github';
|
||||
import {merge} from './merge';
|
||||
import {release} from './release';
|
||||
|
||||
module.exports = {
|
||||
commitMessage,
|
||||
@ -10,4 +11,5 @@ module.exports = {
|
||||
github,
|
||||
merge,
|
||||
caretaker,
|
||||
release,
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {DevInfraMergeConfig} from '../dev-infra/pr/merge/config';
|
||||
import {getDefaultTargetLabelConfiguration} from '../dev-infra/pr/merge/defaults';
|
||||
import {github} from './github';
|
||||
import {release} from './release';
|
||||
|
||||
/**
|
||||
* Configuration for the merge tool in `ng-dev`. This sets up the labels which
|
||||
@ -13,7 +14,9 @@ export const merge: DevInfraMergeConfig['merge'] = async api => {
|
||||
mergeReadyLabel: /^action: merge(-assistance)?/,
|
||||
caretakerNoteLabel: 'action: merge-assistance',
|
||||
commitMessageFixupLabel: 'commit message fixup',
|
||||
labels: await getDefaultTargetLabelConfiguration(api, github, '@angular/core'),
|
||||
// We can pick any of the NPM packages as we are in a monorepo where all packages are
|
||||
// published together with the same version and branching.
|
||||
labels: await getDefaultTargetLabelConfiguration(api, github, release),
|
||||
requiredBaseCommits: {
|
||||
// PRs that target either `master` or the patch branch, need to be rebased
|
||||
// on top of the latest commit message validation fix.
|
||||
|
33
.ng-dev/release.ts
Normal file
33
.ng-dev/release.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import {join} from 'path';
|
||||
import {exec} from 'shelljs';
|
||||
import {ReleaseConfig} from '../dev-infra/release/config';
|
||||
|
||||
/** Configuration for the `ng-dev release` command. */
|
||||
export const release: ReleaseConfig = {
|
||||
npmPackages: [
|
||||
'@angular/animations',
|
||||
'@angular/bazel',
|
||||
'@angular/common',
|
||||
'@angular/compiler',
|
||||
'@angular/compiler-cli',
|
||||
'@angular/core',
|
||||
'@angular/elements',
|
||||
'@angular/forms',
|
||||
'@angular/language-service',
|
||||
'@angular/localize',
|
||||
'@angular/platform-browser',
|
||||
'@angular/platform-browser-dynamic',
|
||||
'@angular/platform-server',
|
||||
'@angular/platform-webworker',
|
||||
'@angular/platform-webworker-dynamic',
|
||||
'@angular/router',
|
||||
'@angular/service-worker',
|
||||
'@angular/upgrade',
|
||||
],
|
||||
// TODO: Implement release package building here.
|
||||
buildPackages: async () => [],
|
||||
// TODO: This can be removed once there is an org-wide tool for changelog generation.
|
||||
generateReleaseNotesForHead: async () => {
|
||||
exec('yarn -s gulp changelog', {cwd: join(__dirname, '../')});
|
||||
},
|
||||
};
|
@ -284,7 +284,7 @@ groups:
|
||||
users:
|
||||
- alxhub
|
||||
- crisbeto
|
||||
- devversion
|
||||
# OOO as of 2020-09-28 - devversion
|
||||
|
||||
|
||||
# =========================================================
|
||||
@ -326,6 +326,7 @@ groups:
|
||||
'aio/content/examples/component-interaction/**',
|
||||
'aio/content/images/guide/component-interaction/**',
|
||||
'aio/content/guide/component-styles.md',
|
||||
'aio/content/guide/view-encapsulation.md',
|
||||
'aio/content/examples/component-styles/**',
|
||||
'aio/content/guide/dependency-injection.md',
|
||||
'aio/content/examples/dependency-injection/**',
|
||||
@ -419,7 +420,7 @@ groups:
|
||||
- atscott
|
||||
- ~kara # do not request reviews from Kara, but allow her to approve PRs
|
||||
- mhevery
|
||||
- pkozlowski-opensource
|
||||
# OOO as of 2020-09-28 - pkozlowski-opensource
|
||||
|
||||
|
||||
# =========================================================
|
||||
@ -662,7 +663,7 @@ groups:
|
||||
users:
|
||||
- AndrewKushnir
|
||||
- IgorMinar
|
||||
- pkozlowski-opensource
|
||||
# OOO as of 2020-09-28 - pkozlowski-opensource
|
||||
|
||||
|
||||
# =========================================================
|
||||
@ -679,7 +680,7 @@ groups:
|
||||
reviewers:
|
||||
users:
|
||||
- IgorMinar
|
||||
- pkozlowski-opensource
|
||||
# OOO as of 2020-09-28 - pkozlowski-opensource
|
||||
|
||||
|
||||
# =========================================================
|
||||
@ -697,7 +698,7 @@ groups:
|
||||
users:
|
||||
- IgorMinar
|
||||
- jelbourn
|
||||
- pkozlowski-opensource
|
||||
# OOO as of 2020-09-28 - pkozlowski-opensource
|
||||
|
||||
|
||||
# =========================================================
|
||||
@ -723,7 +724,7 @@ groups:
|
||||
- IgorMinar
|
||||
- mhevery
|
||||
- jelbourn
|
||||
- pkozlowski-opensource
|
||||
# OOO as of 2020-09-28 - pkozlowski-opensource
|
||||
reviews:
|
||||
request: -1 # request reviews from everyone
|
||||
required: 2 # require at least 2 approvals
|
||||
@ -1115,6 +1116,7 @@ groups:
|
||||
'docs/DEBUG.md',
|
||||
'docs/DEBUG_COMPONENTS_REPO_IVY.md',
|
||||
'docs/DEVELOPER.md',
|
||||
'docs/FIXUP_COMMITS.md',
|
||||
'docs/GITHUB_PROCESS.md',
|
||||
'docs/PR_REVIEW.md',
|
||||
'docs/SAVED_REPLIES.md',
|
||||
@ -1150,7 +1152,7 @@ groups:
|
||||
])
|
||||
reviewers:
|
||||
users:
|
||||
- devversion
|
||||
# OOO as of 2020-09-28 - devversion
|
||||
- filipesilva
|
||||
- gkalpak
|
||||
- IgorMinar
|
||||
@ -1184,7 +1186,7 @@ groups:
|
||||
- atscott
|
||||
- jelbourn
|
||||
- petebacondarwin
|
||||
- pkozlowski-opensource
|
||||
# OOO as of 2020-09-28 - pkozlowski-opensource
|
||||
reviews:
|
||||
request: 4 # Request reviews from four people
|
||||
required: 3 # Require that three people approve
|
||||
@ -1212,7 +1214,7 @@ groups:
|
||||
- atscott
|
||||
- jelbourn
|
||||
- petebacondarwin
|
||||
- pkozlowski-opensource
|
||||
# OOO as of 2020-09-28 - pkozlowski-opensource
|
||||
reviews:
|
||||
request: 4 # Request reviews from four people
|
||||
required: 2 # Require that two people approve
|
||||
@ -1240,7 +1242,7 @@ groups:
|
||||
- atscott
|
||||
- jelbourn
|
||||
- petebacondarwin
|
||||
- pkozlowski-opensource
|
||||
# OOO as of 2020-09-28 - pkozlowski-opensource
|
||||
|
||||
|
||||
####################################################################################
|
||||
|
@ -47,3 +47,9 @@ filegroup(
|
||||
"@npm//:node_modules/angular-mocks-1.6/angular-mocks.js",
|
||||
],
|
||||
)
|
||||
|
||||
# Detect if the build is running under --stamp
|
||||
config_setting(
|
||||
name = "stamp",
|
||||
values = {"stamp": "true"},
|
||||
)
|
||||
|
46
CHANGELOG.md
46
CHANGELOG.md
@ -1,3 +1,49 @@
|
||||
<a name="10.1.6"></a>
|
||||
## 10.1.6 (2020-10-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** incorrectly encapsulating [@import](https://github.com/import) containing colons and semicolons ([#38716](https://github.com/angular/angular/issues/38716)) ([52a0c6b](https://github.com/angular/angular/commit/52a0c6b)), closes [#38587](https://github.com/angular/angular/issues/38587)
|
||||
* **compiler-cli:** support namespaced query types in directives ([#38959](https://github.com/angular/angular/issues/38959)) ([#39272](https://github.com/angular/angular/issues/39272)) ([f752ab9](https://github.com/angular/angular/commit/f752ab9))
|
||||
* **elements:** detect matchesSelector prototype without IIFE ([#37799](https://github.com/angular/angular/issues/37799)) ([952fd86](https://github.com/angular/angular/commit/952fd86)), closes [#24551](https://github.com/angular/angular/issues/24551)
|
||||
* **ngcc:** ensure that "inline exports" can be interpreted correctly ([#39272](https://github.com/angular/angular/issues/39272)) ([e08d021](https://github.com/angular/angular/commit/e08d021))
|
||||
* **ngcc:** handle aliases in UMD export declarations ([#38959](https://github.com/angular/angular/issues/38959)) ([#39272](https://github.com/angular/angular/issues/39272)) ([9963c5d](https://github.com/angular/angular/commit/9963c5d)), closes [#38947](https://github.com/angular/angular/issues/38947)
|
||||
* **ngcc:** map `exports` to the current module in UMD files ([#38959](https://github.com/angular/angular/issues/38959)) ([#39272](https://github.com/angular/angular/issues/39272)) ([13c4a7b](https://github.com/angular/angular/commit/13c4a7b))
|
||||
* **ngcc:** support inline export declarations in UMD files ([#38959](https://github.com/angular/angular/issues/38959)) ([#39272](https://github.com/angular/angular/issues/39272)) ([9c875b3](https://github.com/angular/angular/commit/9c875b3)), closes [#38947](https://github.com/angular/angular/issues/38947)
|
||||
|
||||
|
||||
### build
|
||||
|
||||
* upgrade angular build, integration/bazel and [@angular](https://github.com/angular)/bazel package to rule_nodejs 2.2.0 ([#39182](https://github.com/angular/angular/issues/39182)) ([7628c36](https://github.com/angular/angular/commit/7628c36))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **ngcc:** do not rescan program source files when referenced from multiple root files ([#39254](https://github.com/angular/angular/issues/39254)) ([5221df8](https://github.com/angular/angular/commit/5221df8)), closes [#39240](https://github.com/angular/angular/issues/39240)
|
||||
|
||||
|
||||
<a name="10.1.5"></a>
|
||||
## 10.1.5 (2020-10-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **router:** update getRouteGuards to check if the context outlet is activated ([#39049](https://github.com/angular/angular/issues/39049)) ([771f731](https://github.com/angular/angular/commit/771f731)), closes [#39030](https://github.com/angular/angular/issues/39030)
|
||||
* **compiler:** Recover on malformed keyed reads and keyed writes ([#39004](https://github.com/angular/angular/issues/39004)) ([f50313f](https://github.com/angular/angular/commit/f50313f)), closes [#38596](https://github.com/angular/angular/issues/38596)
|
||||
|
||||
|
||||
|
||||
<a name="10.1.4"></a>
|
||||
## 10.1.4 (2020-09-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-cli:** enable [@types](https://github.com/types) discovery in incremental rebuilds ([#39011](https://github.com/angular/angular/issues/39011)) ([6e99427](https://github.com/angular/angular/commit/6e99427)), closes [#38979](https://github.com/angular/angular/issues/38979)
|
||||
|
||||
|
||||
|
||||
<a name="10.1.3"></a>
|
||||
## 10.1.3 (2020-09-23)
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
As contributors and maintainers of the Angular project, we pledge to respect everyone who contributes by posting issues, updating documentation, submitting pull requests, providing feedback in comments, and any other activities.
|
||||
|
||||
Communication through any of Angular's channels (GitHub, Gitter, IRC, mailing lists, Google+, Twitter, etc.) must be constructive and never resort to personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
||||
Communication through any of Angular's channels (GitHub, Discord, Gitter, IRC, mailing lists, Twitter, etc.) must be constructive and never resort to personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
||||
|
||||
We promise to extend courtesy and respect to everyone involved in this project regardless of gender, gender identity, sexual orientation, disability, age, race, ethnicity, religion, or level of experience. We expect anyone contributing to the Angular project to do the same.
|
||||
|
||||
|
@ -32,7 +32,7 @@ Stack Overflow is a much better place to ask questions since:
|
||||
|
||||
To save your and our time, we will systematically close all issues that are requests for general support and redirect people to Stack Overflow.
|
||||
|
||||
If you would like to chat about the question in real-time, you can reach out via [our gitter channel][gitter].
|
||||
If you would like to chat about the question in real-time, you can reach out via [our Discord server][discord].
|
||||
|
||||
|
||||
## <a name="issue"></a> Found a Bug?
|
||||
@ -107,7 +107,7 @@ Before you submit your Pull Request (PR) consider the following guidelines:
|
||||
Adherence to these conventions is necessary because release notes are automatically generated from these messages.
|
||||
|
||||
```shell
|
||||
git commit -a
|
||||
git commit --all
|
||||
```
|
||||
Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files.
|
||||
|
||||
@ -119,20 +119,55 @@ Before you submit your Pull Request (PR) consider the following guidelines:
|
||||
|
||||
11. In GitHub, send a pull request to `angular:master`.
|
||||
|
||||
If we ask for changes via code reviews then:
|
||||
|
||||
* Make the required updates.
|
||||
* Re-run the Angular test suites to ensure tests are still passing.
|
||||
* Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
|
||||
#### Addressing review feedback
|
||||
|
||||
```shell
|
||||
git rebase master -i
|
||||
git push -f
|
||||
```
|
||||
If we ask for changes via code reviews then:
|
||||
|
||||
1. Make the required updates to the code.
|
||||
|
||||
2. Re-run the Angular test suites to ensure tests are still passing.
|
||||
|
||||
3. Create a fixup commit and push to your GitHub repository (this will update your Pull Request):
|
||||
|
||||
```shell
|
||||
git commit --all --fixup HEAD
|
||||
git push
|
||||
```
|
||||
|
||||
For more info on working with fixup commits see [here](docs/FIXUP_COMMITS.md).
|
||||
|
||||
That's it! Thank you for your contribution!
|
||||
|
||||
|
||||
##### Updating the commit message
|
||||
|
||||
A reviewer might often suggest changes to a commit message (for example, to add more context for a change or adhere to our [commit message guidelines](#commit)).
|
||||
In order to update the commit message of the last commit on your branch:
|
||||
|
||||
1. Check out your branch:
|
||||
|
||||
```shell
|
||||
git checkout my-fix-branch
|
||||
```
|
||||
|
||||
2. Amend the last commit and modify the commit message:
|
||||
|
||||
```shell
|
||||
git commit --amend
|
||||
```
|
||||
|
||||
3. Push to your GitHub repository:
|
||||
|
||||
```shell
|
||||
git push --force-with-lease
|
||||
```
|
||||
|
||||
> NOTE:<br />
|
||||
> If you need to update the commit message of an earlier commit, you can use `git rebase` in interactive mode.
|
||||
> See the [git docs](https://git-scm.com/docs/git-rebase#_interactive_mode) for more details.
|
||||
|
||||
|
||||
#### After your pull request is merged
|
||||
|
||||
After your pull request is merged, you can safely delete your branch and pull the changes from the main (upstream) repository:
|
||||
@ -349,7 +384,7 @@ The following documents can help you sort out issues with GitHub accounts and mu
|
||||
[corporate-cla]: http://code.google.com/legal/corporate-cla-v1.0.html
|
||||
[dev-doc]: https://github.com/angular/angular/blob/master/docs/DEVELOPER.md
|
||||
[github]: https://github.com/angular/angular
|
||||
[gitter]: https://gitter.im/angular/angular
|
||||
[discord]: https://discord.gg/angular
|
||||
[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
|
||||
[js-style-guide]: https://google.github.io/styleguide/jsguide.html
|
||||
[jsfiddle]: http://jsfiddle.net
|
||||
|
@ -1,4 +1,5 @@
|
||||
[](https://circleci.com/gh/angular/workflows/angular/tree/master)
|
||||
[](https://discord.gg/angular)
|
||||
[](https://gitter.im/angular/angular?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://www.npmjs.com/@angular/core)
|
||||
|
||||
|
24
WORKSPACE
24
WORKSPACE
@ -8,8 +8,8 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
# Fetch rules_nodejs so we can install our npm dependencies
|
||||
http_archive(
|
||||
name = "build_bazel_rules_nodejs",
|
||||
sha256 = "84abf7ac4234a70924628baa9a73a5a5cbad944c4358cf9abdb4aab29c9a5b77",
|
||||
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/1.7.0/rules_nodejs-1.7.0.tar.gz"],
|
||||
sha256 = "4952ef879704ab4ad6729a29007e7094aef213ea79e9f2e94cbe1c9a753e63ef",
|
||||
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/2.2.0/rules_nodejs-2.2.0.tar.gz"],
|
||||
)
|
||||
|
||||
# Check the rules_nodejs version and download npm dependencies
|
||||
@ -17,7 +17,7 @@ http_archive(
|
||||
# assert on that.
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "check_rules_nodejs_version", "node_repositories", "yarn_install")
|
||||
|
||||
check_rules_nodejs_version(minimum_version_string = "1.7.0")
|
||||
check_rules_nodejs_version(minimum_version_string = "2.2.0")
|
||||
|
||||
# Setup the Node.js toolchain
|
||||
node_repositories(
|
||||
@ -39,23 +39,18 @@ yarn_install(
|
||||
yarn_lock = "//:yarn.lock",
|
||||
)
|
||||
|
||||
# Install all bazel dependencies of the @npm npm packages
|
||||
load("@npm//:install_bazel_dependencies.bzl", "install_bazel_dependencies")
|
||||
|
||||
install_bazel_dependencies()
|
||||
|
||||
# Load angular dependencies
|
||||
load("//packages/bazel:package.bzl", "rules_angular_dev_dependencies")
|
||||
|
||||
rules_angular_dev_dependencies()
|
||||
|
||||
# Load protractor dependencies
|
||||
load("@npm_bazel_protractor//:package.bzl", "npm_bazel_protractor_dependencies")
|
||||
load("@npm//@bazel/protractor:package.bzl", "npm_bazel_protractor_dependencies")
|
||||
|
||||
npm_bazel_protractor_dependencies()
|
||||
|
||||
# Load karma dependencies
|
||||
load("@npm_bazel_karma//:package.bzl", "npm_bazel_karma_dependencies")
|
||||
load("@npm//@bazel/karma:package.bzl", "npm_bazel_karma_dependencies")
|
||||
|
||||
npm_bazel_karma_dependencies()
|
||||
|
||||
@ -68,11 +63,6 @@ load("//dev-infra/browsers:browser_repositories.bzl", "browser_repositories")
|
||||
|
||||
browser_repositories()
|
||||
|
||||
# Setup the rules_typescript tooolchain
|
||||
load("@npm_bazel_typescript//:index.bzl", "ts_setup_workspace")
|
||||
|
||||
ts_setup_workspace()
|
||||
|
||||
# Setup the rules_sass toolchain
|
||||
load("@io_bazel_rules_sass//sass:sass_repositories.bzl", "sass_repositories")
|
||||
|
||||
@ -91,8 +81,8 @@ rbe_autoconfig(
|
||||
# Need to specify a base container digest in order to ensure that we can use the checked-in
|
||||
# platform configurations for the "ubuntu16_04" image. Otherwise the autoconfig rule would
|
||||
# need to pull the image and run it in order determine the toolchain configuration. See:
|
||||
# https://github.com/bazelbuild/bazel-toolchains/blob/3.2.0/configs/ubuntu16_04_clang/versions.bzl
|
||||
base_container_digest = "sha256:5e750dd878df9fcf4e185c6f52b9826090f6e532b097f286913a428290622332",
|
||||
# https://github.com/bazelbuild/bazel-toolchains/blob/3.5.1/configs/ubuntu16_04_clang/versions.bzl
|
||||
base_container_digest = "sha256:f6568d8168b14aafd1b707019927a63c2d37113a03bcee188218f99bd0327ea1",
|
||||
# Note that if you change the `digest`, you might also need to update the
|
||||
# `base_container_digest` to make sure marketplace.gcr.io/google/rbe-ubuntu16-04-webtest:<digest>
|
||||
# and marketplace.gcr.io/google/rbe-ubuntu16-04:<base_container_digest> have
|
||||
|
@ -6,6 +6,7 @@ Everything in this folder is part of the documentation project. This includes
|
||||
* the dgeni configuration for converting source files to rendered files that can be viewed in the web site.
|
||||
* the tooling for setting up examples for development; and generating live-example and zip files from the examples.
|
||||
|
||||
<a name="developer-tasks"></a>
|
||||
## Developer tasks
|
||||
|
||||
We use [Yarn](https://yarnpkg.com) to manage the dependencies and to run build tasks.
|
||||
|
@ -22,7 +22,7 @@ you don't need to specify values for those.
|
||||
The domain name of the server.
|
||||
|
||||
- `AIO_GITHUB_ORGANIZATION`:
|
||||
The GitHub organization whose teams are whitelisted for accepting build artifacts.
|
||||
The GitHub organization whose teams are trusted for accepting build artifacts.
|
||||
See also `AIO_GITHUB_TEAM_SLUGS`.
|
||||
|
||||
- `AIO_GITHUB_REPO`:
|
||||
|
@ -98,7 +98,7 @@ This section describes how each of the aforementioned sub-tasks is accomplished:
|
||||
Such a label can only have been added by a maintainer (with the necessary rights) and
|
||||
designates that they have manually verified the PR contents.
|
||||
2. We can verify (again using the GitHub API) the author's membership in one of the
|
||||
whitelisted/trusted GitHub teams. For this operation, we need a Personal Access Token with the
|
||||
trusted GitHub teams. For this operation, we need a Personal Access Token with the
|
||||
`read:org` scope issued by a user that can "see" the specified GitHub organization.
|
||||
Here too, we use the token by @mary-poppins.
|
||||
|
||||
|
2
aio/content/examples/.gitignore
vendored
2
aio/content/examples/.gitignore
vendored
@ -17,6 +17,7 @@
|
||||
**/e2e/tsconfig.e2e.json
|
||||
**/src/karma.conf.js
|
||||
**/.angular-cli.json
|
||||
**/.browserslistrc
|
||||
**/.editorconfig
|
||||
**/.gitignore
|
||||
**/angular.json
|
||||
@ -30,7 +31,6 @@
|
||||
**/tslint.json
|
||||
**/karma-test-shim.js
|
||||
**/browser-test-shim.js
|
||||
**/browserslist
|
||||
**/node_modules
|
||||
|
||||
# built files
|
||||
|
@ -1,12 +1,8 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
import { browser, by, element } from 'protractor';
|
||||
|
||||
describe('Component Communication Cookbook Tests', () => {
|
||||
|
||||
// Note: '?e2e' which app can read to know it is running in protractor
|
||||
// e.g. `if (!/e2e/.test(location.search)) { ...`
|
||||
beforeAll(() => {
|
||||
browser.get('?e2e');
|
||||
});
|
||||
beforeEach(() => browser.get(browser.baseUrl));
|
||||
|
||||
describe('Parent-to-child communication', () => {
|
||||
// #docregion parent-to-child
|
||||
@ -15,7 +11,7 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
const masterName = 'Master';
|
||||
|
||||
it('should pass properties to children properly', () => {
|
||||
const parent = element.all(by.tagName('app-hero-parent')).get(0);
|
||||
const parent = element(by.tagName('app-hero-parent'));
|
||||
const heroes = parent.all(by.tagName('app-hero-child'));
|
||||
|
||||
for (let i = 0; i < heroNames.length; i++) {
|
||||
@ -35,7 +31,7 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
it('should display trimmed, non-empty names', () => {
|
||||
const nonEmptyNameIndex = 0;
|
||||
const nonEmptyName = '"Dr IQ"';
|
||||
const parent = element.all(by.tagName('app-name-parent')).get(0);
|
||||
const parent = element(by.tagName('app-name-parent'));
|
||||
const hero = parent.all(by.tagName('app-name-child')).get(nonEmptyNameIndex);
|
||||
|
||||
const displayName = hero.element(by.tagName('h3')).getText();
|
||||
@ -45,7 +41,7 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
it('should replace empty name with default name', () => {
|
||||
const emptyNameIndex = 1;
|
||||
const defaultName = '"<no name set>"';
|
||||
const parent = element.all(by.tagName('app-name-parent')).get(0);
|
||||
const parent = element(by.tagName('app-name-parent'));
|
||||
const hero = parent.all(by.tagName('app-name-child')).get(emptyNameIndex);
|
||||
|
||||
const displayName = hero.element(by.tagName('h3')).getText();
|
||||
@ -70,38 +66,36 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
expect(actual.logs.get(0).getText()).toBe(initialLog);
|
||||
});
|
||||
|
||||
it('should set expected values after clicking \'Minor\' twice', () => {
|
||||
it('should set expected values after clicking \'Minor\' twice', async () => {
|
||||
const repoTag = element(by.tagName('app-version-parent'));
|
||||
const newMinorButton = repoTag.all(by.tagName('button')).get(0);
|
||||
|
||||
newMinorButton.click().then(() => {
|
||||
newMinorButton.click().then(() => {
|
||||
const actual = getActual();
|
||||
await newMinorButton.click();
|
||||
await newMinorButton.click();
|
||||
|
||||
const labelAfter2Minor = 'Version 1.25';
|
||||
const logAfter2Minor = 'minor changed from 24 to 25';
|
||||
const actual = getActual();
|
||||
|
||||
expect(actual.label).toBe(labelAfter2Minor);
|
||||
expect(actual.count).toBe(3);
|
||||
expect(actual.logs.get(2).getText()).toBe(logAfter2Minor);
|
||||
});
|
||||
});
|
||||
const labelAfter2Minor = 'Version 1.25';
|
||||
const logAfter2Minor = 'minor changed from 24 to 25';
|
||||
|
||||
expect(actual.label).toBe(labelAfter2Minor);
|
||||
expect(actual.count).toBe(3);
|
||||
expect(actual.logs.get(2).getText()).toBe(logAfter2Minor);
|
||||
});
|
||||
|
||||
it('should set expected values after clicking \'Major\' once', () => {
|
||||
it('should set expected values after clicking \'Major\' once', async () => {
|
||||
const repoTag = element(by.tagName('app-version-parent'));
|
||||
const newMajorButton = repoTag.all(by.tagName('button')).get(1);
|
||||
|
||||
newMajorButton.click().then(() => {
|
||||
const actual = getActual();
|
||||
await newMajorButton.click();
|
||||
const actual = getActual();
|
||||
|
||||
const labelAfterMajor = 'Version 2.0';
|
||||
const logAfterMajor = 'major changed from 1 to 2, minor changed from 25 to 0';
|
||||
const labelAfterMajor = 'Version 2.0';
|
||||
const logAfterMajor = 'major changed from 1 to 2, minor changed from 23 to 0';
|
||||
|
||||
expect(actual.label).toBe(labelAfterMajor);
|
||||
expect(actual.count).toBe(4);
|
||||
expect(actual.logs.get(3).getText()).toBe(logAfterMajor);
|
||||
});
|
||||
expect(actual.label).toBe(labelAfterMajor);
|
||||
expect(actual.count).toBe(2);
|
||||
expect(actual.logs.get(1).getText()).toBe(logAfterMajor);
|
||||
});
|
||||
|
||||
function getActual() {
|
||||
@ -118,110 +112,125 @@ describe('Component Communication Cookbook Tests', () => {
|
||||
}
|
||||
// ...
|
||||
// #enddocregion parent-to-child-onchanges
|
||||
|
||||
});
|
||||
|
||||
describe('Child-to-parent communication', () => {
|
||||
// #docregion child-to-parent
|
||||
// ...
|
||||
it('should not emit the event initially', () => {
|
||||
const voteLabel = element(by.tagName('app-vote-taker'))
|
||||
.element(by.tagName('h3')).getText();
|
||||
expect(voteLabel).toBe('Agree: 0, Disagree: 0');
|
||||
const voteLabel = element(by.tagName('app-vote-taker')).element(by.tagName('h3'));
|
||||
expect(voteLabel.getText()).toBe('Agree: 0, Disagree: 0');
|
||||
});
|
||||
|
||||
it('should process Agree vote', () => {
|
||||
it('should process Agree vote', async () => {
|
||||
const voteLabel = element(by.tagName('app-vote-taker')).element(by.tagName('h3'));
|
||||
const agreeButton1 = element.all(by.tagName('app-voter')).get(0)
|
||||
.all(by.tagName('button')).get(0);
|
||||
agreeButton1.click().then(() => {
|
||||
const voteLabel = element(by.tagName('app-vote-taker'))
|
||||
.element(by.tagName('h3')).getText();
|
||||
expect(voteLabel).toBe('Agree: 1, Disagree: 0');
|
||||
});
|
||||
|
||||
await agreeButton1.click();
|
||||
|
||||
expect(voteLabel.getText()).toBe('Agree: 1, Disagree: 0');
|
||||
});
|
||||
|
||||
it('should process Disagree vote', () => {
|
||||
it('should process Disagree vote', async () => {
|
||||
const voteLabel = element(by.tagName('app-vote-taker')).element(by.tagName('h3'));
|
||||
const agreeButton1 = element.all(by.tagName('app-voter')).get(1)
|
||||
.all(by.tagName('button')).get(1);
|
||||
agreeButton1.click().then(() => {
|
||||
const voteLabel = element(by.tagName('app-vote-taker'))
|
||||
.element(by.tagName('h3')).getText();
|
||||
expect(voteLabel).toBe('Agree: 1, Disagree: 1');
|
||||
});
|
||||
|
||||
await agreeButton1.click();
|
||||
|
||||
expect(voteLabel.getText()).toBe('Agree: 0, Disagree: 1');
|
||||
});
|
||||
// ...
|
||||
// #enddocregion child-to-parent
|
||||
});
|
||||
|
||||
// Can't run timer tests in protractor because
|
||||
// interaction w/ zones causes all tests to freeze & timeout.
|
||||
xdescribe('Parent calls child via local var', () => {
|
||||
countDownTimerTests('countdown-parent-lv');
|
||||
describe('Parent calls child via local var', () => {
|
||||
countDownTimerTests('app-countdown-parent-lv');
|
||||
});
|
||||
|
||||
xdescribe('Parent calls ViewChild', () => {
|
||||
countDownTimerTests('countdown-parent-vc');
|
||||
describe('Parent calls ViewChild', () => {
|
||||
countDownTimerTests('app-countdown-parent-vc');
|
||||
});
|
||||
|
||||
function countDownTimerTests(parentTag: string) {
|
||||
// #docregion countdown-timer-tests
|
||||
// ...
|
||||
it('timer and parent seconds should match', () => {
|
||||
// The tests trigger periodic asynchronous operations (via `setInterval()`), which will prevent
|
||||
// the app from stabilizing. See https://angular.io/api/core/ApplicationRef#is-stable-examples
|
||||
// for more details.
|
||||
// To allow the tests to complete, we will disable automatically waiting for the Angular app to
|
||||
// stabilize.
|
||||
beforeEach(() => browser.waitForAngularEnabled(false));
|
||||
afterEach(() => browser.waitForAngularEnabled(true));
|
||||
|
||||
it('timer and parent seconds should match', async () => {
|
||||
const parent = element(by.tagName(parentTag));
|
||||
const message = parent.element(by.tagName('app-countdown-timer')).getText();
|
||||
browser.sleep(10); // give `seconds` a chance to catchup with `message`
|
||||
const seconds = parent.element(by.className('seconds')).getText();
|
||||
expect(message).toContain(seconds);
|
||||
const startButton = parent.element(by.buttonText('Start'));
|
||||
const seconds = parent.element(by.className('seconds'));
|
||||
const timer = parent.element(by.tagName('app-countdown-timer'));
|
||||
|
||||
await startButton.click();
|
||||
|
||||
// Wait for `<app-countdown-timer>` to be populated with any text.
|
||||
await browser.wait(() => timer.getText(), 2000);
|
||||
|
||||
expect(await timer.getText()).toContain(await seconds.getText());
|
||||
});
|
||||
|
||||
it('should stop the countdown', () => {
|
||||
it('should stop the countdown', async () => {
|
||||
const parent = element(by.tagName(parentTag));
|
||||
const stopButton = parent.all(by.tagName('button')).get(1);
|
||||
const startButton = parent.element(by.buttonText('Start'));
|
||||
const stopButton = parent.element(by.buttonText('Stop'));
|
||||
const timer = parent.element(by.tagName('app-countdown-timer'));
|
||||
|
||||
stopButton.click().then(() => {
|
||||
const message = parent.element(by.tagName('app-countdown-timer')).getText();
|
||||
expect(message).toContain('Holding');
|
||||
});
|
||||
await startButton.click();
|
||||
expect(await timer.getText()).not.toContain('Holding');
|
||||
|
||||
await stopButton.click();
|
||||
expect(await timer.getText()).toContain('Holding');
|
||||
});
|
||||
// ...
|
||||
// #enddocregion countdown-timer-tests
|
||||
}
|
||||
|
||||
|
||||
describe('Parent and children communicate via a service', () => {
|
||||
// #docregion bidirectional-service
|
||||
// ...
|
||||
it('should announce a mission', () => {
|
||||
it('should announce a mission', async () => {
|
||||
const missionControl = element(by.tagName('app-mission-control'));
|
||||
const announceButton = missionControl.all(by.tagName('button')).get(0);
|
||||
announceButton.click().then(() => {
|
||||
const history = missionControl.all(by.tagName('li'));
|
||||
expect(history.count()).toBe(1);
|
||||
expect(history.get(0).getText()).toMatch(/Mission.* announced/);
|
||||
});
|
||||
const history = missionControl.all(by.tagName('li'));
|
||||
|
||||
await announceButton.click();
|
||||
|
||||
expect(history.count()).toBe(1);
|
||||
expect(history.get(0).getText()).toMatch(/Mission.* announced/);
|
||||
});
|
||||
|
||||
it('should confirm the mission by Lovell', () => {
|
||||
testConfirmMission(1, 2, 'Lovell');
|
||||
it('should confirm the mission by Lovell', async () => {
|
||||
await testConfirmMission(1, 'Lovell');
|
||||
});
|
||||
|
||||
it('should confirm the mission by Haise', () => {
|
||||
testConfirmMission(3, 3, 'Haise');
|
||||
it('should confirm the mission by Haise', async () => {
|
||||
await testConfirmMission(3, 'Haise');
|
||||
});
|
||||
|
||||
it('should confirm the mission by Swigert', () => {
|
||||
testConfirmMission(2, 4, 'Swigert');
|
||||
it('should confirm the mission by Swigert', async () => {
|
||||
await testConfirmMission(2, 'Swigert');
|
||||
});
|
||||
|
||||
function testConfirmMission(buttonIndex: number, expectedLogCount: number, astronaut: string) {
|
||||
const confirmedLog = ' confirmed the mission';
|
||||
async function testConfirmMission(buttonIndex: number, astronaut: string) {
|
||||
const missionControl = element(by.tagName('app-mission-control'));
|
||||
const announceButton = missionControl.all(by.tagName('button')).get(0);
|
||||
const confirmButton = missionControl.all(by.tagName('button')).get(buttonIndex);
|
||||
confirmButton.click().then(() => {
|
||||
const history = missionControl.all(by.tagName('li'));
|
||||
expect(history.count()).toBe(expectedLogCount);
|
||||
expect(history.get(expectedLogCount - 1).getText()).toBe(astronaut + confirmedLog);
|
||||
});
|
||||
const history = missionControl.all(by.tagName('li'));
|
||||
|
||||
await announceButton.click();
|
||||
await confirmButton.click();
|
||||
|
||||
expect(history.count()).toBe(2);
|
||||
expect(history.get(1).getText()).toBe(`${astronaut} confirmed the mission`);
|
||||
}
|
||||
// ...
|
||||
// #enddocregion bidirectional-service
|
||||
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"tests": [
|
||||
{
|
||||
"cmd": "yarn",
|
||||
"args": [
|
||||
"e2e",
|
||||
"--protractor-config=e2e/protractor-puppeteer.conf.js",
|
||||
"--no-webdriver-update",
|
||||
"--port={PORT}"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -30,22 +30,21 @@
|
||||
<app-vote-taker></app-vote-taker>
|
||||
</div>
|
||||
<a href="#top" class="to-top">Back to Top</a>
|
||||
<hr>
|
||||
|
||||
<hr>
|
||||
<div id="parent-to-child-local-var">
|
||||
<app-countdown-parent-lv></app-countdown-parent-lv>
|
||||
</div>
|
||||
<a href="#top" class="to-top">Back to Top</a>
|
||||
<hr>
|
||||
|
||||
<hr>
|
||||
<div id="parent-to-view-child">
|
||||
<app-countdown-parent-vc></app-countdown-parent-vc>
|
||||
</div>
|
||||
<a href="#top" class="to-top">Back to Top</a>
|
||||
<hr>
|
||||
|
||||
<hr>
|
||||
<div id="bidirectional-service">
|
||||
<app-mission-control></app-mission-control>
|
||||
</div>
|
||||
<a href="#top" class="to-top">Back to Top</a>
|
||||
<hr>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
@ -15,10 +15,17 @@ import { VersionParentComponent } from './version-parent.component';
|
||||
import { VoterComponent } from './voter.component';
|
||||
import { VoteTakerComponent } from './votetaker.component';
|
||||
|
||||
const directives: any[] = [
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
AstronautComponent,
|
||||
CountdownLocalVarParentComponent,
|
||||
CountdownTimerComponent,
|
||||
CountdownViewChildParentComponent,
|
||||
HeroChildComponent,
|
||||
HeroParentComponent,
|
||||
MissionControlComponent,
|
||||
@ -27,28 +34,8 @@ const directives: any[] = [
|
||||
VersionChildComponent,
|
||||
VersionParentComponent,
|
||||
VoterComponent,
|
||||
VoteTakerComponent
|
||||
];
|
||||
|
||||
const schemas: any[] = [];
|
||||
|
||||
// Include Countdown examples
|
||||
// unless in e2e tests which they break.
|
||||
if (!/e2e/.test(location.search)) {
|
||||
console.log('adding countdown timer examples');
|
||||
directives.push(CountdownLocalVarParentComponent);
|
||||
directives.push(CountdownViewChildParentComponent);
|
||||
} else {
|
||||
// In e2e test use CUSTOM_ELEMENTS_SCHEMA to suppress unknown element errors
|
||||
schemas.push(CUSTOM_ELEMENTS_SCHEMA);
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule
|
||||
VoteTakerComponent,
|
||||
],
|
||||
declarations: directives,
|
||||
bootstrap: [ AppComponent ],
|
||||
schemas
|
||||
})
|
||||
export class AppModule { }
|
||||
|
@ -1,19 +1,16 @@
|
||||
// #docregion
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-countdown-timer',
|
||||
template: '<p>{{message}}</p>'
|
||||
})
|
||||
export class CountdownTimerComponent implements OnInit, OnDestroy {
|
||||
export class CountdownTimerComponent implements OnDestroy {
|
||||
|
||||
intervalId = 0;
|
||||
message = '';
|
||||
seconds = 11;
|
||||
|
||||
clearTimer() { clearInterval(this.intervalId); }
|
||||
|
||||
ngOnInit() { this.start(); }
|
||||
ngOnDestroy() { this.clearTimer(); }
|
||||
|
||||
start() { this.countDown(); }
|
||||
@ -22,6 +19,8 @@ export class CountdownTimerComponent implements OnInit, OnDestroy {
|
||||
this.message = `Holding at T-${this.seconds} seconds`;
|
||||
}
|
||||
|
||||
private clearTimer() { clearInterval(this.intervalId); }
|
||||
|
||||
private countDown() {
|
||||
this.clearTimer();
|
||||
this.intervalId = window.setInterval(() => {
|
||||
|
@ -24,7 +24,7 @@ export class UploaderService {
|
||||
// }
|
||||
|
||||
upload(file: File) {
|
||||
if (!file) { return; }
|
||||
if (!file) { return of<string>(); }
|
||||
|
||||
// COULD HAVE WRITTEN:
|
||||
// return this.http.post('/upload/file', file, {
|
||||
|
@ -0,0 +1,70 @@
|
||||
import { interval } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { backoff } from './backoff';
|
||||
|
||||
describe('backoff()', () => {
|
||||
beforeEach(() => jasmine.clock().install());
|
||||
afterEach(() => jasmine.clock().uninstall());
|
||||
|
||||
it('should retry in case of error', () => {
|
||||
const mockConsole = {log: jasmine.createSpy('log')};
|
||||
const source = interval(10).pipe(
|
||||
tap(i => {
|
||||
if (i > 0) {
|
||||
throw new Error('Test error');
|
||||
}
|
||||
}),
|
||||
backoff(3, 100),
|
||||
);
|
||||
source.subscribe({
|
||||
next: v => mockConsole.log(`Emitted: ${v}`),
|
||||
error: e => mockConsole.log(`Errored: ${e.message || e}`),
|
||||
complete: () => mockConsole.log('Completed'),
|
||||
});
|
||||
|
||||
// Initial try:
|
||||
// Errors on second emission and schedules retrying (with delay).
|
||||
jasmine.clock().tick(10);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([['Emitted: 0']]);
|
||||
|
||||
jasmine.clock().tick(10);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([['Emitted: 0']]);
|
||||
mockConsole.log.calls.reset();
|
||||
|
||||
// First re-attempt after 100ms:
|
||||
// Errors again on second emission and schedules retrying (with larger delay).
|
||||
jasmine.clock().tick(100);
|
||||
expect(mockConsole.log).not.toHaveBeenCalled();
|
||||
|
||||
jasmine.clock().tick(10);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([['Emitted: 0']]);
|
||||
|
||||
jasmine.clock().tick(10);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([['Emitted: 0']]);
|
||||
mockConsole.log.calls.reset();
|
||||
|
||||
// Second re-attempt after 400ms:
|
||||
// Errors again on second emission and schedules retrying (with even larger delay).
|
||||
jasmine.clock().tick(400);
|
||||
expect(mockConsole.log).not.toHaveBeenCalled();
|
||||
|
||||
jasmine.clock().tick(10);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([['Emitted: 0']]);
|
||||
|
||||
jasmine.clock().tick(10);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([['Emitted: 0']]);
|
||||
mockConsole.log.calls.reset();
|
||||
|
||||
// Third re-attempt after 900ms:
|
||||
// Errors again on second emission and gives up (no retrying).
|
||||
jasmine.clock().tick(900);
|
||||
expect(mockConsole.log).not.toHaveBeenCalled();
|
||||
|
||||
jasmine.clock().tick(10);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([['Emitted: 0']]);
|
||||
mockConsole.log.calls.reset();
|
||||
|
||||
jasmine.clock().tick(10);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([['Errored: Test error']]);
|
||||
});
|
||||
});
|
@ -1,23 +1,32 @@
|
||||
// TODO: Add unit tests for this file.
|
||||
import { pipe, range, timer, zip } from 'rxjs';
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { of, pipe, range, throwError, timer, zip } from 'rxjs';
|
||||
import { ajax } from 'rxjs/ajax';
|
||||
import { retryWhen, map, mergeMap } from 'rxjs/operators';
|
||||
import { map, mergeMap, retryWhen } from 'rxjs/operators';
|
||||
|
||||
function backoff(maxTries, ms) {
|
||||
return pipe(
|
||||
retryWhen(attempts => zip(range(1, maxTries), attempts)
|
||||
.pipe(
|
||||
map(([i]) => i * i),
|
||||
mergeMap(i => timer(i * ms))
|
||||
)
|
||||
)
|
||||
);
|
||||
export function backoff(maxTries, delay) {
|
||||
return pipe(
|
||||
retryWhen(attempts =>
|
||||
zip(range(1, maxTries + 1), attempts).pipe(
|
||||
mergeMap(([i, err]) => (i > maxTries) ? throwError(err) : of(i)),
|
||||
map(i => i * i),
|
||||
mergeMap(v => timer(v * delay)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// #enddocregion
|
||||
/*
|
||||
This function declaration is necessary to ensure that it does not get called
|
||||
when running the unit tests. It will not get rendered into the docs.
|
||||
The indentation needs to start in the leftmost level position as well because of how
|
||||
the docplaster combines the different regions together.
|
||||
*/
|
||||
function docRegionAjaxCall() {
|
||||
// #docregion
|
||||
ajax('/api/endpoint')
|
||||
.pipe(backoff(3, 250))
|
||||
.subscribe(data => handleData(data));
|
||||
|
||||
function handleData(data) {
|
||||
// ...
|
||||
.subscribe(function handleData(data) { /* ... */ });
|
||||
// #enddocregion
|
||||
}
|
||||
|
@ -2,7 +2,11 @@
|
||||
"tests": [
|
||||
{
|
||||
"cmd": "yarn",
|
||||
"args": [ "tsc", "--project", "./tsconfig.app.json" ]
|
||||
"args": ["tsc", "--project", "tsconfig.spec.json", "--module", "commonjs"]
|
||||
},
|
||||
{
|
||||
"cmd": "yarn",
|
||||
"args": ["jasmine", "out-tsc/**/*.spec.js"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
46
aio/content/examples/rx-library/src/error-handling.spec.ts
Normal file
46
aio/content/examples/rx-library/src/error-handling.spec.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { Subject, throwError } from 'rxjs';
|
||||
import { docRegionDefault } from './error-handling';
|
||||
|
||||
describe('error-handling', () => {
|
||||
let mockConsole;
|
||||
let ajaxSubject;
|
||||
let ajax;
|
||||
|
||||
beforeEach(() => {
|
||||
mockConsole = {log: jasmine.createSpy('log')};
|
||||
ajaxSubject = new Subject();
|
||||
ajax = jasmine
|
||||
.createSpy('ajax')
|
||||
.and.callFake((url: string) => ajaxSubject);
|
||||
});
|
||||
|
||||
afterEach(() => ajaxSubject.unsubscribe());
|
||||
|
||||
it('should return the response object', () => {
|
||||
docRegionDefault(mockConsole, ajax);
|
||||
|
||||
ajaxSubject.next({response: {foo: 'bar'}});
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([
|
||||
['data: ', {foo: 'bar'}]
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return an empty array when using an object without a `response` property', () => {
|
||||
docRegionDefault(mockConsole, ajax);
|
||||
|
||||
ajaxSubject.next({foo: 'bar'});
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([
|
||||
['data: ', []]
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return an empty array when the ajax observable errors', () => {
|
||||
ajax.and.returnValue(throwError('Test Error'));
|
||||
|
||||
docRegionDefault(mockConsole, ajax);
|
||||
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([
|
||||
['data: ', []]
|
||||
]);
|
||||
});
|
||||
});
|
@ -1,25 +1,36 @@
|
||||
|
||||
import { of } from 'rxjs';
|
||||
|
||||
// #docplaster
|
||||
/*
|
||||
Because of how the code is merged together using the doc regions,
|
||||
we need to indent the imports with the function below.
|
||||
*/
|
||||
/* tslint:disable:no-shadowed-variable */
|
||||
/* tslint:disable:align */
|
||||
// #docregion
|
||||
|
||||
import { ajax } from 'rxjs/ajax';
|
||||
import { map, catchError } from 'rxjs/operators';
|
||||
// Return "response" from the API. If an error happens,
|
||||
// return an empty array.
|
||||
const apiData = ajax('/api/data').pipe(
|
||||
map(res => {
|
||||
if (!res.response) {
|
||||
throw new Error('Value expected!');
|
||||
}
|
||||
return res.response;
|
||||
}),
|
||||
catchError(err => of([]))
|
||||
);
|
||||
|
||||
apiData.subscribe({
|
||||
next(x) { console.log('data: ', x); },
|
||||
error(err) { console.log('errors already caught... will not run'); }
|
||||
});
|
||||
import { of } from 'rxjs';
|
||||
import { ajax } from 'rxjs/ajax';
|
||||
import { map, catchError } from 'rxjs/operators';
|
||||
|
||||
// #enddocregion
|
||||
|
||||
export function docRegionDefault(console, ajax) {
|
||||
// #docregion
|
||||
// Return "response" from the API. If an error happens,
|
||||
// return an empty array.
|
||||
const apiData = ajax('/api/data').pipe(
|
||||
map((res: any) => {
|
||||
if (!res.response) {
|
||||
throw new Error('Value expected!');
|
||||
}
|
||||
return res.response;
|
||||
}),
|
||||
catchError(err => of([]))
|
||||
);
|
||||
|
||||
apiData.subscribe({
|
||||
next(x) { console.log('data: ', x); },
|
||||
error(err) { console.log('errors already caught... will not run'); }
|
||||
});
|
||||
|
||||
// #enddocregion
|
||||
return apiData;
|
||||
}
|
||||
|
14
aio/content/examples/rx-library/src/operators.1.spec.ts
Normal file
14
aio/content/examples/rx-library/src/operators.1.spec.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { docRegionDefault } from './operators.1';
|
||||
|
||||
describe('squareOdd - operators.1.ts', () => {
|
||||
it('should return square odds', () => {
|
||||
const console = {log: jasmine.createSpy('log')};
|
||||
docRegionDefault(console);
|
||||
expect(console.log).toHaveBeenCalledTimes(3);
|
||||
expect(console.log.calls.allArgs()).toEqual([
|
||||
[1],
|
||||
[9],
|
||||
[25],
|
||||
]);
|
||||
});
|
||||
});
|
@ -1,23 +1,30 @@
|
||||
import { of, pipe } from 'rxjs';
|
||||
|
||||
// #docplaster
|
||||
/*
|
||||
Because of how the code is merged together using the doc regions,
|
||||
we need to indent the imports with the function below.
|
||||
*/
|
||||
/* tslint:disable:align */
|
||||
// #docregion
|
||||
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
|
||||
const nums = of(1, 2, 3, 4, 5);
|
||||
|
||||
// Create a function that accepts an Observable.
|
||||
const squareOddVals = pipe(
|
||||
filter((n: number) => n % 2 !== 0),
|
||||
map(n => n * n)
|
||||
);
|
||||
|
||||
// Create an Observable that will run the filter and map functions
|
||||
const squareOdd = squareOddVals(nums);
|
||||
|
||||
// Subscribe to run the combined functions
|
||||
squareOdd.subscribe(x => console.log(x));
|
||||
import { of, pipe } from 'rxjs';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
|
||||
// #enddocregion
|
||||
|
||||
export function docRegionDefault(console) {
|
||||
// #docregion
|
||||
const nums = of(1, 2, 3, 4, 5);
|
||||
|
||||
// Create a function that accepts an Observable.
|
||||
const squareOddVals = pipe(
|
||||
filter((n: number) => n % 2 !== 0),
|
||||
map(n => n * n)
|
||||
);
|
||||
|
||||
// Create an Observable that will run the filter and map functions
|
||||
const squareOdd = squareOddVals(nums);
|
||||
|
||||
// Subscribe to run the combined functions
|
||||
squareOdd.subscribe(x => console.log(x));
|
||||
|
||||
// #enddocregion
|
||||
}
|
||||
|
14
aio/content/examples/rx-library/src/operators.2.spec.ts
Normal file
14
aio/content/examples/rx-library/src/operators.2.spec.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { docRegionDefault } from './operators.2';
|
||||
|
||||
describe('squareOdd - operators.2.ts', () => {
|
||||
it('should return square odds', () => {
|
||||
const console = {log: jasmine.createSpy('log')};
|
||||
docRegionDefault(console);
|
||||
expect(console.log).toHaveBeenCalledTimes(3);
|
||||
expect(console.log.calls.allArgs()).toEqual([
|
||||
[1],
|
||||
[9],
|
||||
[25],
|
||||
]);
|
||||
});
|
||||
});
|
@ -1,16 +1,25 @@
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
// #docplaster
|
||||
/*
|
||||
Because of how the code is merged together using the doc regions,
|
||||
we need to indent the imports with the function below.
|
||||
*/
|
||||
/* tslint:disable:align */
|
||||
// #docregion
|
||||
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
|
||||
const squareOdd = of(1, 2, 3, 4, 5)
|
||||
.pipe(
|
||||
filter(n => n % 2 !== 0),
|
||||
map(n => n * n)
|
||||
);
|
||||
|
||||
// Subscribe to get values
|
||||
squareOdd.subscribe(x => console.log(x));
|
||||
import { of } from 'rxjs';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
|
||||
// #enddocregion
|
||||
|
||||
export function docRegionDefault(console) {
|
||||
// #docregion
|
||||
const squareOdd = of(1, 2, 3, 4, 5)
|
||||
.pipe(
|
||||
filter(n => n % 2 !== 0),
|
||||
map(n => n * n)
|
||||
);
|
||||
|
||||
// Subscribe to get values
|
||||
squareOdd.subscribe(x => console.log(x));
|
||||
|
||||
// #enddocregion
|
||||
}
|
||||
|
14
aio/content/examples/rx-library/src/operators.spec.ts
Normal file
14
aio/content/examples/rx-library/src/operators.spec.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { docRegionDefault } from './operators';
|
||||
|
||||
describe('squaredNums - operators.ts', () => {
|
||||
it('should return square odds', () => {
|
||||
const console = {log: jasmine.createSpy('log')};
|
||||
docRegionDefault(console);
|
||||
expect(console.log).toHaveBeenCalledTimes(3);
|
||||
expect(console.log.calls.allArgs()).toEqual([
|
||||
[1],
|
||||
[4],
|
||||
[9],
|
||||
]);
|
||||
});
|
||||
});
|
@ -1,20 +1,28 @@
|
||||
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
// #docplaster
|
||||
/*
|
||||
Because of how the code is merged together using the doc regions,
|
||||
we need to indent the imports with the function below.
|
||||
*/
|
||||
/* tslint:disable:align */
|
||||
// #docregion
|
||||
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
const nums = of(1, 2, 3);
|
||||
|
||||
const squareValues = map((val: number) => val * val);
|
||||
const squaredNums = squareValues(nums);
|
||||
|
||||
squaredNums.subscribe(x => console.log(x));
|
||||
|
||||
// Logs
|
||||
// 1
|
||||
// 4
|
||||
// 9
|
||||
import { of } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
// #enddocregion
|
||||
|
||||
export function docRegionDefault(console) {
|
||||
// #docregion
|
||||
const nums = of(1, 2, 3);
|
||||
|
||||
const squareValues = map((val: number) => val * val);
|
||||
const squaredNums = squareValues(nums);
|
||||
|
||||
squaredNums.subscribe(x => console.log(x));
|
||||
|
||||
// Logs
|
||||
// 1
|
||||
// 4
|
||||
// 9
|
||||
|
||||
// #enddocregion
|
||||
}
|
||||
|
69
aio/content/examples/rx-library/src/retry-on-error.spec.ts
Normal file
69
aio/content/examples/rx-library/src/retry-on-error.spec.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { of, throwError } from 'rxjs';
|
||||
import { mergeMap, tap } from 'rxjs/operators';
|
||||
import { docRegionDefault } from './retry-on-error';
|
||||
|
||||
describe('retry-on-error', () => {
|
||||
let mockConsole;
|
||||
beforeEach(() => mockConsole = { log: jasmine.createSpy('log') });
|
||||
|
||||
it('should return the response object', () => {
|
||||
const ajax = () => of({ response: { foo: 'bar' } });
|
||||
|
||||
docRegionDefault(mockConsole, ajax);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([
|
||||
['data: ', { foo: 'bar' }],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return an empty array after 3 retries + 1 initial request', () => {
|
||||
const ajax = () => {
|
||||
return of({ noresponse: true }).pipe(tap(() => mockConsole.log('Subscribed to AJAX')));
|
||||
};
|
||||
|
||||
docRegionDefault(mockConsole, ajax);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([
|
||||
['Subscribed to AJAX'],
|
||||
['Error occured.'],
|
||||
['Subscribed to AJAX'],
|
||||
['Error occured.'],
|
||||
['Subscribed to AJAX'],
|
||||
['Error occured.'],
|
||||
['Subscribed to AJAX'],
|
||||
['Error occured.'],
|
||||
['data: ', []],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return the response if the request succeeds upon retrying', () => {
|
||||
// Fail on the first two requests, but succeed from the 3rd onwards.
|
||||
let failCount = 2;
|
||||
const ajax = () => of(null).pipe(
|
||||
tap(() => mockConsole.log('Subscribed to AJAX')),
|
||||
// Fail on the first 2 requests, but succeed from the 3rd onwards.
|
||||
mergeMap(() => {
|
||||
if (failCount > 0) {
|
||||
failCount--;
|
||||
return throwError('Test error');
|
||||
}
|
||||
return of({ response: { foo: 'bar' } });
|
||||
}),
|
||||
);
|
||||
|
||||
docRegionDefault(mockConsole, ajax);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([
|
||||
['Subscribed to AJAX'], // Initial request | 1st attempt overall
|
||||
['Subscribed to AJAX'], // 1st retry attempt | 2nd attempt overall
|
||||
['Subscribed to AJAX'], // 2nd retry attempt | 3rd attempt overall
|
||||
['data: ', { foo: 'bar' }],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return an empty array when the ajax observable throws an error', () => {
|
||||
const ajax = () => throwError('Test Error');
|
||||
|
||||
docRegionDefault(mockConsole, ajax);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([
|
||||
['data: ', []],
|
||||
]);
|
||||
});
|
||||
});
|
@ -1,26 +1,35 @@
|
||||
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
|
||||
// #docplaster
|
||||
/*
|
||||
Because of how the code is merged together using the doc regions,
|
||||
we need to indent the imports with the function below.
|
||||
*/
|
||||
/* tslint:disable:no-shadowed-variable */
|
||||
/* tslint:disable:align */
|
||||
// #docregion
|
||||
|
||||
import { ajax } from 'rxjs/ajax';
|
||||
import { map, retry, catchError } from 'rxjs/operators';
|
||||
|
||||
const apiData = ajax('/api/data').pipe(
|
||||
retry(3), // Retry up to 3 times before failing
|
||||
map(res => {
|
||||
if (!res.response) {
|
||||
throw new Error('Value expected!');
|
||||
}
|
||||
return res.response;
|
||||
}),
|
||||
catchError(err => of([]))
|
||||
);
|
||||
|
||||
apiData.subscribe({
|
||||
next(x) { console.log('data: ', x); },
|
||||
error(err) { console.log('errors already caught... will not run'); }
|
||||
});
|
||||
import { of } from 'rxjs';
|
||||
import { ajax } from 'rxjs/ajax';
|
||||
import { map, retry, catchError } from 'rxjs/operators';
|
||||
|
||||
// #enddocregion
|
||||
|
||||
export function docRegionDefault(console, ajax) {
|
||||
// #docregion
|
||||
const apiData = ajax('/api/data').pipe(
|
||||
map((res: any) => {
|
||||
if (!res.response) {
|
||||
console.log('Error occured.');
|
||||
throw new Error('Value expected!');
|
||||
}
|
||||
return res.response;
|
||||
}),
|
||||
retry(3), // Retry up to 3 times before failing
|
||||
catchError(err => of([]))
|
||||
);
|
||||
|
||||
apiData.subscribe({
|
||||
next(x) { console.log('data: ', x); },
|
||||
error(err) { console.log('errors already caught... will not run'); }
|
||||
});
|
||||
|
||||
// #enddocregion
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
import { of } from 'rxjs';
|
||||
import { docRegionPromise } from './simple-creation.1';
|
||||
|
||||
describe('simple-creation.1', () => {
|
||||
it('should create a promise from an observable and return an empty object', () => {
|
||||
const console = {log: jasmine.createSpy('log')};
|
||||
const fetch = () => of({foo: 42});
|
||||
docRegionPromise(console, fetch);
|
||||
expect(console.log.calls.allArgs()).toEqual([
|
||||
[{foo: 42}],
|
||||
['Completed'],
|
||||
]);
|
||||
});
|
||||
});
|
24
aio/content/examples/rx-library/src/simple-creation.1.ts
Normal file
24
aio/content/examples/rx-library/src/simple-creation.1.ts
Normal file
@ -0,0 +1,24 @@
|
||||
// #docplaster
|
||||
/*
|
||||
Because of how the code is merged together using the doc regions,
|
||||
we need to indent the imports with the function below.
|
||||
*/
|
||||
/* tslint:disable:align */
|
||||
// #docregion promise
|
||||
import { from } from 'rxjs';
|
||||
|
||||
// #enddocregion promise
|
||||
|
||||
export function docRegionPromise(console, fetch) {
|
||||
// #docregion promise
|
||||
// Create an Observable out of a promise
|
||||
const data = from(fetch('/api/endpoint'));
|
||||
// Subscribe to begin listening for async result
|
||||
data.subscribe({
|
||||
next(response) { console.log(response); },
|
||||
error(err) { console.error('Error: ' + err); },
|
||||
complete() { console.log('Completed'); }
|
||||
});
|
||||
|
||||
// #enddocregion promise
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { docRegionInterval } from './simple-creation.2';
|
||||
|
||||
describe('simple-creation.2', () => {
|
||||
beforeEach(() => jasmine.clock().install());
|
||||
afterEach(() => jasmine.clock().uninstall());
|
||||
|
||||
it('should create an Observable that will publish a value on an interval', () => {
|
||||
const console = {log: jasmine.createSpy('log')};
|
||||
const subscription = docRegionInterval(console);
|
||||
jasmine.clock().tick(1000);
|
||||
expect(console.log).toHaveBeenCalledWith('It\'s been 1 seconds since subscribing!');
|
||||
console.log.calls.reset();
|
||||
|
||||
jasmine.clock().tick(999);
|
||||
expect(console.log).not.toHaveBeenCalled();
|
||||
|
||||
jasmine.clock().tick(1);
|
||||
expect(console.log).toHaveBeenCalledWith('It\'s been 2 seconds since subscribing!');
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
});
|
22
aio/content/examples/rx-library/src/simple-creation.2.ts
Normal file
22
aio/content/examples/rx-library/src/simple-creation.2.ts
Normal file
@ -0,0 +1,22 @@
|
||||
// #docplaster
|
||||
/*
|
||||
Because of how the code is merged together using the doc regions,
|
||||
we need to indent the imports with the function below.
|
||||
*/
|
||||
/* tslint:disable:align */
|
||||
// #docregion interval
|
||||
import { interval } from 'rxjs';
|
||||
|
||||
// #enddocregion interval
|
||||
|
||||
export function docRegionInterval(console) {
|
||||
// #docregion interval
|
||||
// Create an Observable that will publish a value on an interval
|
||||
const secondsCounter = interval(1000);
|
||||
// Subscribe to begin publishing values
|
||||
const subscription = secondsCounter.subscribe(n =>
|
||||
console.log(`It's been ${n + 1} seconds since subscribing!`));
|
||||
|
||||
// #enddocregion interval
|
||||
return subscription;
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
import { docRegionEvent } from './simple-creation.3';
|
||||
|
||||
describe('simple-creation.3', () => {
|
||||
let triggerMousemove;
|
||||
let mockConsole;
|
||||
let input;
|
||||
let mockDocument;
|
||||
|
||||
beforeEach(() => {
|
||||
mockConsole = {log: jasmine.createSpy('log')};
|
||||
input = {
|
||||
addEventListener: jasmine
|
||||
.createSpy('addEventListener')
|
||||
.and.callFake((eventName, cb) => {
|
||||
if (eventName === 'mousemove') {
|
||||
triggerMousemove = cb;
|
||||
}
|
||||
}),
|
||||
removeEventListener: jasmine.createSpy('removeEventListener'),
|
||||
};
|
||||
mockDocument = { getElementById: () => input };
|
||||
});
|
||||
|
||||
it('should log coords when subscribing', () => {
|
||||
docRegionEvent(mockConsole, mockDocument);
|
||||
|
||||
expect(mockConsole.log).not.toHaveBeenCalled();
|
||||
|
||||
triggerMousemove({ clientX: 50, clientY: 50 });
|
||||
triggerMousemove({ clientX: 30, clientY: 50 });
|
||||
triggerMousemove({ clientX: 50, clientY: 30 });
|
||||
expect(mockConsole.log).toHaveBeenCalledTimes(3);
|
||||
expect(mockConsole.log.calls.allArgs()).toEqual([
|
||||
['Coords: 50 X 50'],
|
||||
['Coords: 30 X 50'],
|
||||
['Coords: 50 X 30']
|
||||
]);
|
||||
});
|
||||
|
||||
it('should call unsubscribe when clientX and clientY are below < 40 ', () => {
|
||||
docRegionEvent(mockConsole, mockDocument);
|
||||
|
||||
expect(mockConsole.log).not.toHaveBeenCalled();
|
||||
|
||||
// Ensure that we have unsubscribed.
|
||||
triggerMousemove({ clientX: 30, clientY: 30 });
|
||||
expect(input.removeEventListener).toHaveBeenCalledWith('mousemove', triggerMousemove, undefined);
|
||||
mockConsole.log.calls.reset();
|
||||
|
||||
triggerMousemove({ clientX: 50, clientY: 50 });
|
||||
expect(mockConsole.log).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
32
aio/content/examples/rx-library/src/simple-creation.3.ts
Normal file
32
aio/content/examples/rx-library/src/simple-creation.3.ts
Normal file
@ -0,0 +1,32 @@
|
||||
// #docplaster
|
||||
/*
|
||||
Because of how the code is merged together using the doc regions,
|
||||
we need to indent the imports with the function below.
|
||||
*/
|
||||
/* tslint:disable:align */
|
||||
// #docregion event
|
||||
import { fromEvent } from 'rxjs';
|
||||
|
||||
// #enddocregion event
|
||||
|
||||
export function docRegionEvent(console, document) {
|
||||
// #docregion event
|
||||
const el = document.getElementById('my-element');
|
||||
|
||||
// Create an Observable that will publish mouse movements
|
||||
const mouseMoves = fromEvent(el, 'mousemove');
|
||||
|
||||
// Subscribe to start listening for mouse-move events
|
||||
const subscription = mouseMoves.subscribe((evt: MouseEvent) => {
|
||||
// Log coords of mouse movements
|
||||
console.log(`Coords: ${evt.clientX} X ${evt.clientY}`);
|
||||
|
||||
// When the mouse is over the upper-left of the screen,
|
||||
// unsubscribe to stop listening for mouse movements
|
||||
if (evt.clientX < 40 && evt.clientY < 40) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
});
|
||||
|
||||
// #enddocregion event
|
||||
}
|
14
aio/content/examples/rx-library/src/simple-creation.spec.ts
Normal file
14
aio/content/examples/rx-library/src/simple-creation.spec.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { of } from 'rxjs';
|
||||
import { docRegionAjax } from './simple-creation';
|
||||
|
||||
describe('ajax', () => {
|
||||
it('should make a request and console log the status and response', () => {
|
||||
const console = {log: jasmine.createSpy('log')};
|
||||
const ajax = jasmine.createSpy('ajax').and.callFake((url: string) => {
|
||||
return of({status: 200, response: 'foo bar'});
|
||||
});
|
||||
|
||||
docRegionAjax(console, ajax);
|
||||
expect(console.log).toHaveBeenCalledWith(200, 'foo bar');
|
||||
});
|
||||
});
|
@ -1,65 +1,19 @@
|
||||
|
||||
// #docregion promise
|
||||
|
||||
import { from } from 'rxjs';
|
||||
|
||||
// Create an Observable out of a promise
|
||||
const data = from(fetch('/api/endpoint'));
|
||||
// Subscribe to begin listening for async result
|
||||
data.subscribe({
|
||||
next(response) { console.log(response); },
|
||||
error(err) { console.error('Error: ' + err); },
|
||||
complete() { console.log('Completed'); }
|
||||
});
|
||||
|
||||
// #enddocregion promise
|
||||
|
||||
// #docregion interval
|
||||
|
||||
import { interval } from 'rxjs';
|
||||
|
||||
// Create an Observable that will publish a value on an interval
|
||||
const secondsCounter = interval(1000);
|
||||
// Subscribe to begin publishing values
|
||||
secondsCounter.subscribe(n =>
|
||||
console.log(`It's been ${n} seconds since subscribing!`));
|
||||
|
||||
// #enddocregion interval
|
||||
|
||||
|
||||
// #docregion event
|
||||
|
||||
import { fromEvent } from 'rxjs';
|
||||
|
||||
const el = document.getElementById('my-element');
|
||||
|
||||
// Create an Observable that will publish mouse movements
|
||||
const mouseMoves = fromEvent(el, 'mousemove');
|
||||
|
||||
// Subscribe to start listening for mouse-move events
|
||||
const subscription = mouseMoves.subscribe((evt: MouseEvent) => {
|
||||
// Log coords of mouse movements
|
||||
console.log(`Coords: ${evt.clientX} X ${evt.clientY}`);
|
||||
|
||||
// When the mouse is over the upper-left of the screen,
|
||||
// unsubscribe to stop listening for mouse movements
|
||||
if (evt.clientX < 40 && evt.clientY < 40) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
});
|
||||
|
||||
// #enddocregion event
|
||||
|
||||
|
||||
// #docplaster
|
||||
/*
|
||||
Because of how the code is merged together using the doc regions,
|
||||
we need to indent the imports with the function below.
|
||||
*/
|
||||
/* tslint:disable:no-shadowed-variable */
|
||||
/* tslint:disable:align */
|
||||
// #docregion ajax
|
||||
|
||||
import { ajax } from 'rxjs/ajax';
|
||||
import { ajax } from 'rxjs/ajax';
|
||||
|
||||
// Create an Observable that will create an AJAX request
|
||||
const apiData = ajax('/api/data');
|
||||
// Subscribe to create the request
|
||||
apiData.subscribe(res => console.log(res.status, res.response));
|
||||
|
||||
// #enddocregion ajax
|
||||
|
||||
|
||||
export function docRegionAjax(console, ajax) {
|
||||
// #docregion ajax
|
||||
const apiData = ajax('/api/data');
|
||||
// Subscribe to create the request
|
||||
apiData.subscribe(res => console.log(res.status, res.response));
|
||||
// #enddocregion ajax
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
{
|
||||
"extends": "tslint:recommended",
|
||||
"rulesDirectory": [
|
||||
"codelyzer"
|
||||
],
|
||||
"rules": {
|
||||
"align": {
|
||||
"options": [
|
||||
@ -13,22 +16,6 @@
|
||||
"deprecation": {
|
||||
"severity": "warning"
|
||||
},
|
||||
"component-class-suffix": true,
|
||||
"component-selector": [
|
||||
true,
|
||||
"element",
|
||||
// TODO: Fix the code and change the prefix to `"app"` (or whatever makes sense).
|
||||
"",
|
||||
"kebab-case"
|
||||
],
|
||||
"contextual-lifecycle": true,
|
||||
"directive-class-suffix": true,
|
||||
"directive-selector": [
|
||||
true,
|
||||
"attribute",
|
||||
["app", "toh"],
|
||||
"camelCase"
|
||||
],
|
||||
"eofline": true,
|
||||
"import-blacklist": [
|
||||
true,
|
||||
@ -56,6 +43,8 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
// TODO(gkalpak): Fix the code and enable this.
|
||||
// "no-any": true,
|
||||
"no-console": [
|
||||
true,
|
||||
"debug",
|
||||
@ -95,6 +84,11 @@
|
||||
"named": "never"
|
||||
}
|
||||
},
|
||||
// TODO(gkalpak): Fix the code and enable this.
|
||||
// "typedef": [
|
||||
// true,
|
||||
// "call-signature"
|
||||
// ],
|
||||
"typedef-whitespace": {
|
||||
"options": [
|
||||
{
|
||||
@ -130,6 +124,9 @@
|
||||
"check-typecast"
|
||||
]
|
||||
},
|
||||
"component-class-suffix": true,
|
||||
"contextual-lifecycle": true,
|
||||
"directive-class-suffix": true,
|
||||
"no-conflicting-lifecycle": true,
|
||||
"no-host-metadata-property": true,
|
||||
"no-input-rename": true,
|
||||
@ -141,9 +138,19 @@
|
||||
"template-banana-in-box": true,
|
||||
"template-no-negated-async": true,
|
||||
"use-lifecycle-interface": true,
|
||||
"use-pipe-transform-interface": true
|
||||
},
|
||||
"rulesDirectory": [
|
||||
"codelyzer"
|
||||
]
|
||||
"use-pipe-transform-interface": true,
|
||||
"directive-selector": [
|
||||
true,
|
||||
"attribute",
|
||||
["app", "toh"],
|
||||
"camelCase"
|
||||
],
|
||||
"component-selector": [
|
||||
true,
|
||||
"element",
|
||||
// TODO: Fix the code and change the prefix to `"app"` (or whatever makes sense).
|
||||
"",
|
||||
"kebab-case"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
import * as angular from 'angular';
|
||||
import 'angular-route';
|
||||
|
||||
const appName = 'myApp';
|
||||
|
||||
angular.module(appName, [
|
||||
const appModule = angular.module('myApp', [
|
||||
'ngRoute'
|
||||
])
|
||||
.config(['$routeProvider', '$locationProvider',
|
||||
@ -25,5 +23,5 @@ angular.module(appName, [
|
||||
);
|
||||
|
||||
export function bootstrap(el: HTMLElement) {
|
||||
return angular.bootstrap(el, [appName]);
|
||||
return angular.bootstrap(el, [appModule.name]);
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ or in the `@NgModule()` or `@Component()` metadata
|
||||
Registering the provider in the `@Injectable()` metadata also allows Angular to optimize an app
|
||||
by removing the service from the compiled app if it isn't used.
|
||||
|
||||
* When you register a provider with a [specific NgModule](guide/architecture-modules), the same instance of a service is available to all components in that NgModule. To register at this level, use the `providers` property of the `@NgModule()` decorator,
|
||||
* When you register a provider with a [specific NgModule](guide/architecture-modules), the same instance of a service is available to all components in that NgModule. To register at this level, use the `providers` property of the `@NgModule()` decorator.
|
||||
|
||||
```
|
||||
@NgModule({
|
||||
|
@ -119,7 +119,7 @@ The `/deep/` combinator also has the aliases `>>>`, and `::ng-deep`.
|
||||
|
||||
Use `/deep/`, `>>>` and `::ng-deep` only with *emulated* view encapsulation.
|
||||
Emulated is the default and most commonly used view encapsulation. For more information, see the
|
||||
[Controlling view encapsulation](guide/component-styles#view-encapsulation) section.
|
||||
[View Encapsulation](guide/view-encapsulation) section.
|
||||
|
||||
</div>
|
||||
|
||||
@ -267,89 +267,3 @@ as explained in the [CLI wiki](https://github.com/angular/angular-cli/wiki/stori
|
||||
Style strings added to the `@Component.styles` array _must be written in CSS_ because the CLI cannot apply a preprocessor to inline styles.
|
||||
|
||||
</div>
|
||||
|
||||
{@a view-encapsulation}
|
||||
|
||||
## View encapsulation
|
||||
|
||||
As discussed earlier, component CSS styles are encapsulated into the component's view and don't
|
||||
affect the rest of the application.
|
||||
|
||||
To control how this encapsulation happens on a *per
|
||||
component* basis, you can set the *view encapsulation mode* in the component metadata.
|
||||
Choose from the following modes:
|
||||
|
||||
* `ShadowDom` view encapsulation uses the browser's native shadow DOM implementation (see
|
||||
[Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM)
|
||||
on the [MDN](https://developer.mozilla.org) site)
|
||||
to attach a shadow DOM to the component's host element, and then puts the component
|
||||
view inside that shadow DOM. The component's styles are included within the shadow DOM.
|
||||
|
||||
* `Native` view encapsulation uses a now deprecated version of the browser's native shadow DOM implementation - [learn about the changes](https://hayato.io/2016/shadowdomv1/).
|
||||
|
||||
* `Emulated` view encapsulation (the default) emulates the behavior of shadow DOM by preprocessing
|
||||
(and renaming) the CSS code to effectively scope the CSS to the component's view.
|
||||
For details, see [Inspecting generated CSS](guide/component-styles#inspect-generated-css) below.
|
||||
|
||||
* `None` means that Angular does no view encapsulation.
|
||||
Angular adds the CSS to the global styles.
|
||||
The scoping rules, isolations, and protections discussed earlier don't apply.
|
||||
This is essentially the same as pasting the component's styles into the HTML.
|
||||
|
||||
To set the components encapsulation mode, use the `encapsulation` property in the component metadata:
|
||||
|
||||
<code-example path="component-styles/src/app/quest-summary.component.ts" region="encapsulation.native" header="src/app/quest-summary.component.ts"></code-example>
|
||||
|
||||
`ShadowDom` view encapsulation only works on browsers that have native support
|
||||
for shadow DOM (see [Shadow DOM v1](https://caniuse.com/#feat=shadowdomv1) on the
|
||||
[Can I use](http://caniuse.com) site). The support is still limited,
|
||||
which is why `Emulated` view encapsulation is the default mode and recommended
|
||||
in most cases.
|
||||
|
||||
{@a inspect-generated-css}
|
||||
|
||||
## Inspecting generated CSS
|
||||
|
||||
When using emulated view encapsulation, Angular preprocesses
|
||||
all component styles so that they approximate the standard shadow CSS scoping rules.
|
||||
|
||||
In the DOM of a running Angular application with emulated view
|
||||
encapsulation enabled, each DOM element has some extra attributes
|
||||
attached to it:
|
||||
|
||||
<code-example format="">
|
||||
<hero-details _nghost-pmm-5>
|
||||
<h2 _ngcontent-pmm-5>Mister Fantastic</h2>
|
||||
<hero-team _ngcontent-pmm-5 _nghost-pmm-6>
|
||||
<h3 _ngcontent-pmm-6>Team</h3>
|
||||
</hero-team>
|
||||
</hero-detail>
|
||||
|
||||
</code-example>
|
||||
|
||||
There are two kinds of generated attributes:
|
||||
|
||||
* An element that would be a shadow DOM host in native encapsulation has a
|
||||
generated `_nghost` attribute. This is typically the case for component host elements.
|
||||
* An element within a component's view has a `_ngcontent` attribute
|
||||
that identifies to which host's emulated shadow DOM this element belongs.
|
||||
|
||||
The exact values of these attributes aren't important. They are automatically
|
||||
generated and you never refer to them in application code. But they are targeted
|
||||
by the generated component styles, which are in the `<head>` section of the DOM:
|
||||
|
||||
<code-example format="">
|
||||
[_nghost-pmm-5] {
|
||||
display: block;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
h3[_ngcontent-pmm-6] {
|
||||
background-color: white;
|
||||
border: 1px solid #777;
|
||||
}
|
||||
</code-example>
|
||||
|
||||
These styles are post-processed so that each selector is augmented
|
||||
with `_nghost` or `_ngcontent` attribute selectors.
|
||||
These extra selectors enable the scoping rules described in this page.
|
||||
|
@ -156,7 +156,7 @@ The library must be rebuilt on every change.
|
||||
When linking a library, make sure that the build step runs in watch mode, and that the library's `package.json` configuration points at the correct entry points.
|
||||
For example, `main` should point at a JavaScript file, not a TypeScript file.
|
||||
|
||||
## Use TypeScript path mapping for peer dependencies
|
||||
### Use TypeScript path mapping for peer dependencies
|
||||
|
||||
Angular libraries should list all `@angular/*` dependencies as peer dependencies.
|
||||
This ensures that when modules ask for Angular, they all get the exact same module.
|
||||
|
@ -387,7 +387,7 @@ List the generated bundles in the `dist/` folder.
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
|
||||
ls dist/*.bundle.js
|
||||
ls dist/*.js
|
||||
|
||||
</code-example>
|
||||
|
||||
@ -396,7 +396,7 @@ The following example displays the graph for the _main_ bundle.
|
||||
|
||||
<code-example language="none" class="code-shell">
|
||||
|
||||
node_modules/.bin/source-map-explorer dist/main.*.bundle.js
|
||||
node_modules/.bin/source-map-explorer dist/main*
|
||||
|
||||
</code-example>
|
||||
|
||||
|
@ -603,18 +603,6 @@ In practical terms, the `package.json` of all `@angular` packages has changed in
|
||||
|
||||
For more information about the npm package format, see the [Angular Package Format spec](https://goo.gl/jB3GVv).
|
||||
|
||||
{@a removed}
|
||||
## Removed APIs
|
||||
|
||||
The following APIs have been removed starting with version 10.0.0*:
|
||||
|
||||
| Package | API | Replacement | Notes |
|
||||
| ---------------- | -------------- | ----------- | ----- |
|
||||
| `@angular/core` | Undecorated base classes that use Angular features | Add Angular decorator | See [migration guide](guide/migration-undecorated-classes) for more info |
|
||||
| `@angular/core` | `ModuleWithProviders` without a generic | `ModuleWithProviders` with a generic | See [migration guide](guide/migration-module-with-providers) for more info |
|
||||
|
||||
*To see APIs removed in version 9, check out this guide on the [version 9 docs site](https://v9.angular.io/guide/deprecations#removed).
|
||||
|
||||
{@a style-sanitization}
|
||||
### Style Sanitization for `[style]` and `[style.prop]` bindings
|
||||
Angular used to sanitize `[style]` and `[style.prop]` bindings to prevent malicious code from being inserted through `javascript:` expressions in CSS `url()` entries. However, most modern browsers no longer support the usage of these expressions, so sanitization was only maintained for the sake of IE 6 and 7. Given that Angular does not support either IE 6 or 7 and sanitization has a performance cost, we will no longer sanitize style bindings as of version 10 of Angular.
|
||||
|
@ -397,7 +397,7 @@ The following code example binds an observable of message strings
|
||||
## Caching HTTP requests
|
||||
|
||||
To [communicate with backend services using HTTP](/guide/http "Communicating with backend services using HTTP"), the `HttpClient` service uses observables and offers the `HTTPClient.get()` method to fetch data from a server.
|
||||
The aynchronous method sends an HTTP request, and returns an observable that emits the requested data for the response.
|
||||
The asynchronous method sends an HTTP request, and returns an observable that emits the requested data for the response.
|
||||
|
||||
As shown in the previous section, you can use the impure `AsyncPipe` to accept an observable as input and subscribe to the input automatically.
|
||||
You can also create an impure pipe to make and cache an HTTP request.
|
||||
|
@ -180,7 +180,7 @@ A form group tracks the status and changes for each of its controls, so if one o
|
||||
|
||||
<code-example path="reactive-forms/src/app/profile-editor/profile-editor.component.1.html" region="formgroup" header="src/app/profile-editor/profile-editor.component.html (template form group)"></code-example>
|
||||
|
||||
Note that just as a form group contains a group of controls, the *profile form* `FormGroup` is bound to the `form` element with the `FormGroup` directive, creating a communication layer between the model and the form containing the inputs. The `formControlName` input provided by the `FormControlName` directive binds each individual input to the form control defined in `FormGroup`. The form controls communicate with their respective elements. They also communicate changes to the form group instance, which provides the source of truth for the model value.
|
||||
Note that just as a form group contains a group of controls, the *profileForm* `FormGroup` is bound to the `form` element with the `FormGroup` directive, creating a communication layer between the model and the form containing the inputs. The `formControlName` input provided by the `FormControlName` directive binds each individual input to the form control defined in `FormGroup`. The form controls communicate with their respective elements. They also communicate changes to the form group instance, which provides the source of truth for the model value.
|
||||
|
||||
**Save form data**
|
||||
|
||||
|
@ -15,11 +15,11 @@ RxJS provides an implementation of the `Observable` type, which is needed until
|
||||
RxJS offers a number of functions that can be used to create new observables. These functions can simplify the process of creating observables from things such as events, timers, promises, and so on. For example:
|
||||
|
||||
|
||||
<code-example path="rx-library/src/simple-creation.ts" region="promise" header="Create an observable from a promise"></code-example>
|
||||
<code-example path="rx-library/src/simple-creation.1.ts" region="promise" header="Create an observable from a promise"></code-example>
|
||||
|
||||
<code-example path="rx-library/src/simple-creation.ts" region="interval" header="Create an observable from a counter"></code-example>
|
||||
<code-example path="rx-library/src/simple-creation.2.ts" region="interval" header="Create an observable from a counter"></code-example>
|
||||
|
||||
<code-example path="rx-library/src/simple-creation.ts" region="event" header="Create an observable from an event"></code-example>
|
||||
<code-example path="rx-library/src/simple-creation.3.ts" region="event" header="Create an observable from an event"></code-example>
|
||||
|
||||
<code-example path="rx-library/src/simple-creation.ts" region="ajax" header="Create an observable that creates an AJAX request"></code-example>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Template statements
|
||||
|
||||
A template **statement** responds to an **event** raised by a binding target
|
||||
such as an element, component, or directive.
|
||||
Template statements are methods or properties that you can use in your HTML to respond to user events.
|
||||
With template statements, your application can engage users through actions such as displaying dynamic content or submitting forms.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
@ -10,24 +10,30 @@ the syntax and code snippets in this guide.
|
||||
|
||||
</div>
|
||||
|
||||
The following template statement appears in quotes to the right of the `=` symbol as in `(event)="statement"`.
|
||||
In the following example, the template statement `deleteHero()` appears in quotes to the right of the `=` symbol as in `(event)="statement"`.
|
||||
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="context-component-statement" header="src/app/app.component.html"></code-example>
|
||||
|
||||
A template statement *has a side effect*.
|
||||
That's the whole point of an event.
|
||||
It's how you update application state from user action.
|
||||
When the user clicks the **Delete hero** button, Angular calls the `deleteHero()` function in the component class.
|
||||
|
||||
Responding to events is the other side of Angular's "unidirectional data flow".
|
||||
You're free to change anything, anywhere, during this turn of the event loop.
|
||||
You can use template statements with elements, components, or directives in response to events.
|
||||
|
||||
Like template expressions, template *statements* use a language that looks like JavaScript.
|
||||
The template statement parser differs from the template expression parser and
|
||||
specifically supports both basic assignment (`=`) and chaining expressions with <code>;</code>.
|
||||
<div class="alert is-helpful">
|
||||
|
||||
However, certain JavaScript and template expression syntax is not allowed:
|
||||
Responding to events is an aspect of Angular's [unidirectional data flow](guide/glossary#unidirectional-data-flow).
|
||||
You can change anything in your application during a single event loop.
|
||||
|
||||
* <code>new</code>
|
||||
</div>
|
||||
|
||||
## Syntax
|
||||
|
||||
Like [template expressions](guide/interpolation), template statements use a language that looks like JavaScript.
|
||||
However, the parser for template statements differs from the parser for template expressions.
|
||||
In addition, the template statements parser specifically supports both basic assignment, `=`, and chaining expressions with semicolons, `;`.
|
||||
|
||||
The following JavaScript and template expression syntax is not allowed:
|
||||
|
||||
* `new`
|
||||
* increment and decrement operators, `++` and `--`
|
||||
* operator assignment, such as `+=` and `-=`
|
||||
* the bitwise operators, such as `|` and `&`
|
||||
@ -35,31 +41,32 @@ However, certain JavaScript and template expression syntax is not allowed:
|
||||
|
||||
## Statement context
|
||||
|
||||
As with expressions, statements can refer only to what's in the statement context
|
||||
such as an event handling method of the component instance.
|
||||
Statements have a context—a particular part of the application to which the statement belongs.
|
||||
|
||||
The *statement context* is typically the component instance.
|
||||
The *deleteHero* in `(click)="deleteHero()"` is a method of the data-bound component.
|
||||
Statements can refer only to what's in the statement context, which is typically the component instance.
|
||||
For example, `deleteHero()` of `(click)="deleteHero()"` is a method of the component in the following snippet.
|
||||
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="context-component-statement" header="src/app/app.component.html"></code-example>
|
||||
|
||||
The statement context may also refer to properties of the template's own context.
|
||||
In the following examples, the template `$event` object,
|
||||
a [template input variable](guide/built-in-directives#template-input-variable) (`let hero`),
|
||||
and a [template reference variable](guide/template-reference-variables) (`#heroForm`)
|
||||
are passed to an event handling method of the component.
|
||||
In the following example, the component's event handling method, `onSave()` takes the template's own `$event` object as an argument.
|
||||
On the next two lines, the `deleteHero()` method takes a [template input variable](guide/built-in-directives#template-input-variable), `hero`, and `onSubmit()` takes a [template reference variable](guide/template-reference-variables), `#heroForm`.
|
||||
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="context-var-statement" header="src/app/app.component.html"></code-example>
|
||||
|
||||
In this example, the context of the `$event` object, `hero`, and `#heroForm` is the template.
|
||||
|
||||
Template context names take precedence over component context names.
|
||||
In `deleteHero(hero)` above, the `hero` is the template input variable,
|
||||
not the component's `hero` property.
|
||||
In the preceding `deleteHero(hero)`, the `hero` is the template input variable, not the component's `hero` property.
|
||||
|
||||
## Statement guidelines
|
||||
## Statement best practices
|
||||
|
||||
Template statements cannot refer to anything in the global namespace. They
|
||||
can't refer to `window` or `document`.
|
||||
They can't call `console.log` or `Math.max`.
|
||||
* **Conciseness**
|
||||
|
||||
As with expressions, avoid writing complex template statements.
|
||||
A method call or simple property assignment should be the norm.
|
||||
Keep template statements minimal by using method calls or basic property assignments.
|
||||
|
||||
* **Work within the context**
|
||||
|
||||
The context of a template statement can be the component class instance or the template.
|
||||
Because of this, template statements cannot refer to anything in the global namespace such as `window` or `document`.
|
||||
For example, template statements can't call `console.log()` or `Math.max()`.
|
||||
|
@ -479,6 +479,15 @@ using the `downgradeComponent()` method. The result is an AngularJS
|
||||
<code-example path="upgrade-module/src/app/downgrade-static/app.module.ts" region="downgradecomponent" header="app.module.ts">
|
||||
</code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
By default, Angular change detection will also run on the component for every
|
||||
AngularJS `$digest` cycle. If you wish to only have change detection run when
|
||||
the inputs change, you can set `propagateDigest` to `false` when calling
|
||||
`downgradeComponent()`.
|
||||
|
||||
</div>
|
||||
|
||||
Because `HeroDetailComponent` is an Angular component, you must also add it to the
|
||||
`declarations` in the `AppModule`.
|
||||
|
||||
|
83
aio/content/guide/view-encapsulation.md
Normal file
83
aio/content/guide/view-encapsulation.md
Normal file
@ -0,0 +1,83 @@
|
||||
# View encapsulation
|
||||
|
||||
In Angular, component CSS styles are encapsulated into the component's view and don't
|
||||
affect the rest of the application.
|
||||
|
||||
To control how this encapsulation happens on a *per
|
||||
component* basis, you can set the *view encapsulation mode* in the component metadata.
|
||||
Choose from the following modes:
|
||||
|
||||
* `ShadowDom` view encapsulation uses the browser's native shadow DOM implementation (see
|
||||
[Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM)
|
||||
on the [MDN](https://developer.mozilla.org) site)
|
||||
to attach a shadow DOM to the component's host element, and then puts the component
|
||||
view inside that shadow DOM. The component's styles are included within the shadow DOM.
|
||||
|
||||
* `Native` view encapsulation uses a now deprecated version of the browser's native shadow DOM implementation - [learn about the changes](https://hayato.io/2016/shadowdomv1/).
|
||||
|
||||
* `Emulated` view encapsulation (the default) emulates the behavior of shadow DOM by preprocessing
|
||||
(and renaming) the CSS code to effectively scope the CSS to the component's view.
|
||||
For details, see [Inspecting generated CSS](guide/view-encapsulation#inspect-generated-css) below.
|
||||
|
||||
* `None` means that Angular does no view encapsulation.
|
||||
Angular adds the CSS to the global styles.
|
||||
The scoping rules, isolations, and protections discussed earlier don't apply.
|
||||
This is essentially the same as pasting the component's styles into the HTML.
|
||||
|
||||
To set the components encapsulation mode, use the `encapsulation` property in the component metadata:
|
||||
|
||||
<code-example path="component-styles/src/app/quest-summary.component.ts" region="encapsulation.native" header="src/app/quest-summary.component.ts"></code-example>
|
||||
|
||||
`ShadowDom` view encapsulation only works on browsers that have native support
|
||||
for shadow DOM (see [Shadow DOM v1](https://caniuse.com/#feat=shadowdomv1) on the
|
||||
[Can I use](http://caniuse.com) site). The support is still limited,
|
||||
which is why `Emulated` view encapsulation is the default mode and recommended
|
||||
in most cases.
|
||||
|
||||
{@a inspect-generated-css}
|
||||
|
||||
## Inspecting generated CSS
|
||||
|
||||
When using emulated view encapsulation, Angular preprocesses
|
||||
all component styles so that they approximate the standard shadow CSS scoping rules.
|
||||
|
||||
In the DOM of a running Angular application with emulated view
|
||||
encapsulation enabled, each DOM element has some extra attributes
|
||||
attached to it:
|
||||
|
||||
<code-example format="">
|
||||
<hero-details _nghost-pmm-5>
|
||||
<h2 _ngcontent-pmm-5>Mister Fantastic</h2>
|
||||
<hero-team _ngcontent-pmm-5 _nghost-pmm-6>
|
||||
<h3 _ngcontent-pmm-6>Team</h3>
|
||||
</hero-team>
|
||||
</hero-detail>
|
||||
|
||||
</code-example>
|
||||
|
||||
There are two kinds of generated attributes:
|
||||
|
||||
* An element that would be a shadow DOM host in native encapsulation has a
|
||||
generated `_nghost` attribute. This is typically the case for component host elements.
|
||||
* An element within a component's view has a `_ngcontent` attribute
|
||||
that identifies to which host's emulated shadow DOM this element belongs.
|
||||
|
||||
The exact values of these attributes aren't important. They are automatically
|
||||
generated and you never refer to them in application code. But they are targeted
|
||||
by the generated component styles, which are in the `<head>` section of the DOM:
|
||||
|
||||
<code-example format="">
|
||||
[_nghost-pmm-5] {
|
||||
display: block;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
h3[_ngcontent-pmm-6] {
|
||||
background-color: white;
|
||||
border: 1px solid #777;
|
||||
}
|
||||
</code-example>
|
||||
|
||||
These styles are post-processed so that each selector is augmented
|
||||
with `_nghost` or `_ngcontent` attribute selectors.
|
||||
These extra selectors enable the scoping rules described in this page.
|
Binary file not shown.
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 56 KiB |
@ -12,7 +12,7 @@ include the following information:
|
||||
- Node and Angular CLI version (local version only).
|
||||
- How long each command took to initialize and execute.
|
||||
- Command name that was run.
|
||||
- For Schematics commands (add, generate, new and update), a list of whitelisted flags.
|
||||
- For Schematics commands (add, generate, new and update), a list of selected flags.
|
||||
- For build commands (build, serve), the number and size of bundles (initial and lazy),
|
||||
compilation units, the time it took to build and rebuild, and basic Angular-specific
|
||||
API usage.
|
||||
|
@ -458,8 +458,8 @@
|
||||
"name": "Sajeetharan Sinnathurai",
|
||||
"picture": "sajee.jpg",
|
||||
"twitter": "kokkisajee",
|
||||
"website": "https://sajeetharan.herokuapp.com/",
|
||||
"bio": "Sajeetharan is a Developer, Top contributor on stackoverflow for #Angular, ng-SriLanka organizer. He makes use of his extensive knowledge over the past years to contribute to community to make the world a better place.",
|
||||
"website": "https://sajeetharan.com/",
|
||||
"bio": "Sajeetharan is a Cloud Architect, Top contributor on stackoverflow for #Angular, ng-SriLanka organizer. He makes use of his extensive knowledge over the past years to contribute to community to make the world a better place.",
|
||||
"groups": ["GDE"]
|
||||
},
|
||||
"lacolaco": {
|
||||
|
@ -46,7 +46,7 @@ Most Angular code can be written with just the latest JavaScript, using [types](
|
||||
|
||||
<h3>You can sit with us!</h3>
|
||||
|
||||
We want to hear from you. [Report problems or submit suggestions for future docs.](https://github.com/angular/angular/issues/new/choose "Angular GitHub repository new issue form")
|
||||
We want to hear from you. [Report problems or submit suggestions for future docs](https://github.com/angular/angular/issues/new/choose "Angular GitHub repository new issue form").
|
||||
|
||||
Contribute to Angular docs by creating
|
||||
[pull requests](https://github.com/angular/angular/pulls "Angular Github pull requests")
|
||||
|
@ -26,6 +26,15 @@
|
||||
"end": "2020-10-20"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "DevReach",
|
||||
"location": "Online",
|
||||
"linkUrl": "https://www.telerik.com/devreach/online/agenda-thursday#sessions",
|
||||
"date": {
|
||||
"start": "2020-10-19",
|
||||
"end": "2020-10-23"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ngVikings",
|
||||
"location": "Oslo, Norway",
|
||||
|
@ -116,6 +116,11 @@
|
||||
"title": "Component Lifecycle",
|
||||
"tooltip": "Angular calls lifecycle hook methods on directives and components as it creates, changes, and destroys them."
|
||||
},
|
||||
{
|
||||
"url": "guide/view-encapsulation",
|
||||
"title": "View Encapsulation",
|
||||
"tooltip": "Describes how component CSS styles are encapsulated into a component's view."
|
||||
},
|
||||
{
|
||||
"url": "guide/component-interaction",
|
||||
"title": "Component Interaction",
|
||||
@ -126,6 +131,11 @@
|
||||
"title": "Component Styles",
|
||||
"tooltip": "Add CSS styles that are specific to a component."
|
||||
},
|
||||
{
|
||||
"url": "guide/inputs-outputs",
|
||||
"title": "Inputs and Outputs",
|
||||
"tooltip": "Introductory guide to sharing data between parent and child directives or components."
|
||||
},
|
||||
{
|
||||
"url": "guide/dynamic-component-loader",
|
||||
"title": "Dynamic Components",
|
||||
@ -187,11 +197,6 @@
|
||||
"title": "Template reference variables",
|
||||
"tooltip": "Introductory guide to referring to DOM elements within a template."
|
||||
},
|
||||
{
|
||||
"url": "guide/inputs-outputs",
|
||||
"title": "Inputs and Outputs",
|
||||
"tooltip": "Introductory guide to sharing data between parent and child directives or components."
|
||||
},
|
||||
{
|
||||
"url": "guide/template-expression-operators",
|
||||
"title": "Template expression operators",
|
||||
@ -1004,6 +1009,11 @@
|
||||
"title": "Stack Overflow",
|
||||
"tooltip": "Stack Overflow: where the community answers your technical Angular questions."
|
||||
},
|
||||
{
|
||||
"url": "https://discord.gg/angular",
|
||||
"title": "Join Discord",
|
||||
"tooltip": "Join the discussions at Angular Community Discord server."
|
||||
},
|
||||
{
|
||||
"url": "https://gitter.im/angular/angular",
|
||||
"title": "Gitter",
|
||||
|
@ -128,7 +128,8 @@
|
||||
// The below paths are referenced in users projects generated by the CLI
|
||||
{"type": 301, "source": "/config/tsconfig", "destination": "/guide/typescript-configuration"},
|
||||
{"type": 301, "source": "/config/solution-tsconfig", "destination": "https://devblogs.microsoft.com/typescript/announcing-typescript-3-9/#solution-style-tsconfig"},
|
||||
{"type": 301, "source": "/config/app-package-json", "destination": "/guide/strict-mode#non-local-side-effects-in-applications"}
|
||||
{"type": 301, "source": "/config/app-package-json", "destination": "/guide/strict-mode#non-local-side-effects-in-applications"},
|
||||
{"type": 301, "source": "/strict", "destination": "/guide/strict-mode"}
|
||||
],
|
||||
"rewrites": [
|
||||
{
|
||||
|
@ -148,6 +148,7 @@
|
||||
"!/styleguide/**",
|
||||
"!/testing",
|
||||
"!/testing/**",
|
||||
"!/config/**"
|
||||
"!/config/**",
|
||||
"!/strict"
|
||||
]
|
||||
}
|
||||
|
@ -23,7 +23,7 @@
|
||||
"build-local-with-viewengine": "yarn ~~build",
|
||||
"prebuild-local-with-viewengine-ci": "node scripts/switch-to-viewengine && yarn setup-local-ci",
|
||||
"build-local-with-viewengine-ci": "yarn ~~build --progress=false",
|
||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js ef770f1cb",
|
||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js ab97bc382",
|
||||
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",
|
||||
"test": "yarn check-env && ng test",
|
||||
"pree2e": "yarn check-env && yarn update-webdriver",
|
||||
@ -64,7 +64,7 @@
|
||||
"tools-lint": "tslint --config \"tools/tslint.json\" --project \"tools/firebase-test-utils\"",
|
||||
"tools-test": "./scripts/deploy-to-firebase.test.sh && yarn docs-test && yarn boilerplate:test && jasmine tools/ng-packages-installer/index.spec.js && yarn firebase-utils-test",
|
||||
"preserve-and-sync": "yarn docs",
|
||||
"serve-and-sync": "run-p \"start\" \"docs-watch --watch-only\"",
|
||||
"serve-and-sync": "run-p \"docs-watch --watch-only\" \"start {@}\" --",
|
||||
"boilerplate:add": "node ./tools/examples/example-boilerplate add",
|
||||
"boilerplate:add:viewengine": "yarn boilerplate:add --viewengine",
|
||||
"boilerplate:remove": "node ./tools/examples/example-boilerplate remove",
|
||||
@ -87,28 +87,27 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "10.0.2",
|
||||
"@angular/cdk": "10.0.1",
|
||||
"@angular/common": "10.0.2",
|
||||
"@angular/compiler": "10.0.2",
|
||||
"@angular/core": "10.0.2",
|
||||
"@angular/elements": "10.0.2",
|
||||
"@angular/forms": "10.0.2",
|
||||
"@angular/material": "10.0.1",
|
||||
"@angular/platform-browser": "10.0.2",
|
||||
"@angular/platform-browser-dynamic": "10.0.2",
|
||||
"@angular/router": "10.0.2",
|
||||
"@angular/service-worker": "10.0.2",
|
||||
"@angular/animations": "10.1.3",
|
||||
"@angular/cdk": "10.2.2",
|
||||
"@angular/common": "10.1.3",
|
||||
"@angular/compiler": "10.1.3",
|
||||
"@angular/core": "10.1.3",
|
||||
"@angular/elements": "10.1.3",
|
||||
"@angular/forms": "10.1.3",
|
||||
"@angular/material": "10.2.2",
|
||||
"@angular/platform-browser": "10.1.3",
|
||||
"@angular/platform-browser-dynamic": "10.1.3",
|
||||
"@angular/router": "10.1.3",
|
||||
"@angular/service-worker": "10.1.3",
|
||||
"@webcomponents/custom-elements": "1.2.1",
|
||||
"rxjs": "^6.5.3",
|
||||
"tslib": "^1.10.0",
|
||||
"tslib": "^2.0.0",
|
||||
"zone.js": "~0.10.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "0.1000.1",
|
||||
"@angular/cli": "10.0.1",
|
||||
"@angular/compiler-cli": "10.0.2",
|
||||
"@angular/language-service": "10.0.2",
|
||||
"@angular-devkit/build-angular": "0.1001.3",
|
||||
"@angular/cli": "10.1.3",
|
||||
"@angular/compiler-cli": "10.1.3",
|
||||
"@types/jasmine": "^3.4.2",
|
||||
"@types/jasminewd2": "^2.0.8",
|
||||
"@types/lunr": "^2.3.2",
|
||||
@ -119,7 +118,7 @@
|
||||
"canonical-path": "1.0.0",
|
||||
"chalk": "^2.1.0",
|
||||
"cjson": "^0.5.0",
|
||||
"codelyzer": "^6.0.0-next.1",
|
||||
"codelyzer": "^6.0.0",
|
||||
"cross-spawn": "^5.1.0",
|
||||
"css-selector-parser": "^1.3.0",
|
||||
"dgeni": "^0.4.11",
|
||||
@ -136,8 +135,8 @@
|
||||
"html": "^1.0.0",
|
||||
"ignore": "^3.3.3",
|
||||
"image-size": "^0.5.1",
|
||||
"jasmine": "^3.4.0",
|
||||
"jasmine-core": "^3.4.0",
|
||||
"jasmine": "~3.6.0",
|
||||
"jasmine-core": "~3.6.0",
|
||||
"jasmine-spec-reporter": "~5.0.0",
|
||||
"jsdom": "^9.12.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
@ -145,7 +144,7 @@
|
||||
"karma": "~5.0.0",
|
||||
"karma-chrome-launcher": "^3.1.0",
|
||||
"karma-coverage-istanbul-reporter": "^2.1.0",
|
||||
"karma-jasmine": "^3.1.1",
|
||||
"karma-jasmine": "~4.0.0",
|
||||
"karma-jasmine-html-reporter": "^1.4.2",
|
||||
"light-server": "^2.6.2",
|
||||
"lighthouse": "6.1.0",
|
||||
@ -154,7 +153,7 @@
|
||||
"lunr": "^2.1.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"protractor": "~5.4.4",
|
||||
"puppeteer": "3.3.0",
|
||||
"puppeteer": "5.1.0",
|
||||
"rehype": "^6.0.0",
|
||||
"rehype-slug": "^2.0.0",
|
||||
"remark": "^9.0.0",
|
||||
@ -165,7 +164,7 @@
|
||||
"tree-kill": "^1.1.0",
|
||||
"ts-node": "^8.4.1",
|
||||
"tslint": "~6.1.0",
|
||||
"typescript": "~3.9.5",
|
||||
"typescript": "~4.0.2",
|
||||
"uglify-js": "^3.0.15",
|
||||
"unist-util-filter": "^0.2.1",
|
||||
"unist-util-source": "^1.0.1",
|
||||
|
@ -782,7 +782,7 @@ describe('AppComponent', () => {
|
||||
it('should grab focus when the / key is pressed', () => {
|
||||
const searchBox: SearchBoxComponent = fixture.debugElement.query(By.directive(SearchBoxComponent)).componentInstance;
|
||||
spyOn(searchBox, 'focus');
|
||||
window.document.dispatchEvent(new KeyboardEvent('keyup', { 'key': '/' }));
|
||||
window.document.dispatchEvent(new KeyboardEvent('keyup', { key: '/' }));
|
||||
fixture.detectChanges();
|
||||
expect(searchBox.focus).toHaveBeenCalled();
|
||||
});
|
||||
@ -791,7 +791,7 @@ describe('AppComponent', () => {
|
||||
const searchBox: SearchBoxComponent = fixture.debugElement.query(By.directive(SearchBoxComponent)).componentInstance;
|
||||
spyOn(searchBox, 'focus');
|
||||
component.showSearchResults = true;
|
||||
window.document.dispatchEvent(new KeyboardEvent('keyup', { 'key': 'Escape' }));
|
||||
window.document.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape' }));
|
||||
fixture.detectChanges();
|
||||
expect(searchBox.focus).toHaveBeenCalled();
|
||||
});
|
||||
@ -968,23 +968,23 @@ describe('AppComponent', () => {
|
||||
|
||||
// Initially, `isTransitoning` is true.
|
||||
expect(component.isTransitioning).toBe(true);
|
||||
expect(toolbar.classes['transitioning']).toBe(true);
|
||||
expect(toolbar.classes.transitioning).toBe(true);
|
||||
|
||||
triggerDocViewerEvent('docRendered');
|
||||
fixture.detectChanges();
|
||||
expect(component.isTransitioning).toBe(false);
|
||||
expect(toolbar.classes['transitioning']).toBeFalsy();
|
||||
expect(toolbar.classes.transitioning).toBeFalsy();
|
||||
|
||||
// While a document is being rendered, `isTransitoning` is set to true.
|
||||
triggerDocViewerEvent('docReady');
|
||||
fixture.detectChanges();
|
||||
expect(component.isTransitioning).toBe(true);
|
||||
expect(toolbar.classes['transitioning']).toBe(true);
|
||||
expect(toolbar.classes.transitioning).toBe(true);
|
||||
|
||||
triggerDocViewerEvent('docRendered');
|
||||
fixture.detectChanges();
|
||||
expect(component.isTransitioning).toBe(false);
|
||||
expect(toolbar.classes['transitioning']).toBeFalsy();
|
||||
expect(toolbar.classes.transitioning).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should update the sidenav state as soon as a new document is inserted (but not before)', () => {
|
||||
@ -1031,15 +1031,15 @@ describe('AppComponent', () => {
|
||||
|
||||
navigateTo('guide/pipes');
|
||||
expect(component.pageId).toEqual('guide-pipes');
|
||||
expect(container.properties['id']).toEqual('guide-pipes');
|
||||
expect(container.properties.id).toEqual('guide-pipes');
|
||||
|
||||
navigateTo('news');
|
||||
expect(component.pageId).toEqual('news');
|
||||
expect(container.properties['id']).toEqual('news');
|
||||
expect(container.properties.id).toEqual('news');
|
||||
|
||||
navigateTo('');
|
||||
expect(component.pageId).toEqual('home');
|
||||
expect(container.properties['id']).toEqual('home');
|
||||
expect(container.properties.id).toEqual('home');
|
||||
});
|
||||
|
||||
it('should not be affected by changes to the query', () => {
|
||||
@ -1050,7 +1050,7 @@ describe('AppComponent', () => {
|
||||
navigateTo('guide/other?search=http');
|
||||
|
||||
expect(component.pageId).toEqual('guide-other');
|
||||
expect(container.properties['id']).toEqual('guide-other');
|
||||
expect(container.properties.id).toEqual('guide-other');
|
||||
});
|
||||
});
|
||||
|
||||
@ -1125,7 +1125,7 @@ describe('AppComponent', () => {
|
||||
|
||||
function checkHostClass(type: string, value: string) {
|
||||
const host = fixture.debugElement;
|
||||
const classes: string = host.properties['className'];
|
||||
const classes: string = host.properties.className;
|
||||
const classArray = classes.split(' ').filter(c => c.indexOf(`${type}-`) === 0);
|
||||
expect(classArray.length).toBeLessThanOrEqual(1, `"${classes}" should have only one class matching ${type}-*`);
|
||||
expect(classArray).toEqual([`${type}-${value}`], `"${classes}" should contain ${type}-${value}`);
|
||||
@ -1311,42 +1311,42 @@ class TestHttpClient {
|
||||
|
||||
// tslint:disable:quotemark
|
||||
navJson = {
|
||||
"TopBar": [
|
||||
TopBar: [
|
||||
{
|
||||
"url": "features",
|
||||
"title": "Features"
|
||||
url: 'features',
|
||||
title: 'Features',
|
||||
},
|
||||
{
|
||||
"url": "no-title",
|
||||
"title": "No Title"
|
||||
url: 'no-title',
|
||||
title: 'No Title',
|
||||
},
|
||||
],
|
||||
"SideNav": [
|
||||
SideNav: [
|
||||
{
|
||||
"title": "Core",
|
||||
"tooltip": "Learn the core capabilities of Angular",
|
||||
"children": [
|
||||
title: 'Core',
|
||||
tooltip: 'Learn the core capabilities of Angular',
|
||||
children: [
|
||||
{
|
||||
"url": "guide/pipes",
|
||||
"title": "Pipes",
|
||||
"tooltip": "Pipes transform displayed values within a template."
|
||||
url: 'guide/pipes',
|
||||
title: 'Pipes',
|
||||
tooltip: 'Pipes transform displayed values within a template.',
|
||||
},
|
||||
{
|
||||
"url": "guide/bags",
|
||||
"title": "Bags",
|
||||
"tooltip": "Pack your bags for a code adventure."
|
||||
}
|
||||
]
|
||||
url: 'guide/bags',
|
||||
title: 'Bags',
|
||||
tooltip: 'Pack your bags for a code adventure.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"url": "api",
|
||||
"title": "API",
|
||||
"tooltip": "Details of the Angular classes and values."
|
||||
}
|
||||
url: 'api',
|
||||
title: 'API',
|
||||
tooltip: 'Details of the Angular classes and values.',
|
||||
},
|
||||
],
|
||||
"docVersions": TestHttpClient.docVersions,
|
||||
docVersions: TestHttpClient.docVersions,
|
||||
|
||||
"__versionInfo": TestHttpClient.versionInfo,
|
||||
__versionInfo: TestHttpClient.versionInfo,
|
||||
};
|
||||
|
||||
get(url: string) {
|
||||
|
@ -147,7 +147,7 @@ export class AppComponent implements OnInit {
|
||||
// Compute the version picker list from the current version and the versions in the navigation map
|
||||
combineLatest([
|
||||
this.navigationService.versionInfo,
|
||||
this.navigationService.navigationViews.pipe(map(views => views['docVersions'])),
|
||||
this.navigationService.navigationViews.pipe(map(views => views.docVersions)),
|
||||
]).subscribe(([versionInfo, versions]) => {
|
||||
// TODO(pbd): consider whether we can lookup the stable and next versions from the internet
|
||||
const computedVersions: NavigationNode[] = [
|
||||
@ -167,10 +167,10 @@ export class AppComponent implements OnInit {
|
||||
});
|
||||
|
||||
this.navigationService.navigationViews.subscribe(views => {
|
||||
this.footerNodes = views['Footer'] || [];
|
||||
this.sideNavNodes = views['SideNav'] || [];
|
||||
this.topMenuNodes = views['TopBar'] || [];
|
||||
this.topMenuNarrowNodes = views['TopBarNarrow'] || this.topMenuNodes;
|
||||
this.footerNodes = views.Footer || [];
|
||||
this.sideNavNodes = views.SideNav || [];
|
||||
this.topMenuNodes = views.TopBar || [];
|
||||
this.topMenuNarrowNodes = views.TopBarNarrow || this.topMenuNodes;
|
||||
});
|
||||
|
||||
this.navigationService.versionInfo.subscribe(vi => this.versionInfo = vi);
|
||||
|
@ -223,77 +223,77 @@ class TestApiService {
|
||||
// tslint:disable:quotemark
|
||||
const apiSections: ApiSection[] = [
|
||||
{
|
||||
"name": "common",
|
||||
"title": "common",
|
||||
"path": "api/common",
|
||||
"deprecated": false,
|
||||
"items": [
|
||||
name: 'common',
|
||||
title: 'common',
|
||||
path: 'api/common',
|
||||
deprecated: false,
|
||||
items: [
|
||||
{
|
||||
"name": "class_1",
|
||||
"title": "Class 1",
|
||||
"path": "api/common/class_1",
|
||||
"docType": "class",
|
||||
"stability": "experimental",
|
||||
"securityRisk": false,
|
||||
name: 'class_1',
|
||||
title: 'Class 1',
|
||||
path: 'api/common/class_1',
|
||||
docType: 'class',
|
||||
stability: 'experimental',
|
||||
securityRisk: false,
|
||||
},
|
||||
{
|
||||
"name": "class_2",
|
||||
"title": "Class 2",
|
||||
"path": "api/common/class_2",
|
||||
"docType": "class",
|
||||
"stability": "stable",
|
||||
"securityRisk": false,
|
||||
name: 'class_2',
|
||||
title: 'Class 2',
|
||||
path: 'api/common/class_2',
|
||||
docType: 'class',
|
||||
stability: 'stable',
|
||||
securityRisk: false,
|
||||
},
|
||||
{
|
||||
"name": "directive_1",
|
||||
"title": "Directive 1",
|
||||
"path": "api/common/directive_1",
|
||||
"docType": "directive",
|
||||
"stability": "stable",
|
||||
"securityRisk": true,
|
||||
name: 'directive_1',
|
||||
title: 'Directive 1',
|
||||
path: 'api/common/directive_1',
|
||||
docType: 'directive',
|
||||
stability: 'stable',
|
||||
securityRisk: true,
|
||||
},
|
||||
{
|
||||
"name": "pipe_1",
|
||||
"title": "Pipe 1",
|
||||
"path": "api/common/pipe_1",
|
||||
"docType": "pipe",
|
||||
"stability": "stable",
|
||||
"securityRisk": true,
|
||||
name: 'pipe_1',
|
||||
title: 'Pipe 1',
|
||||
path: 'api/common/pipe_1',
|
||||
docType: 'pipe',
|
||||
stability: 'stable',
|
||||
securityRisk: true,
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
"name": "core",
|
||||
"title": "core",
|
||||
"path": "api/core",
|
||||
"deprecated": false,
|
||||
"items": [
|
||||
name: 'core',
|
||||
title: 'core',
|
||||
path: 'api/core',
|
||||
deprecated: false,
|
||||
items: [
|
||||
{
|
||||
"name": "class_3",
|
||||
"title": "Class 3",
|
||||
"path": "api/core/class_3",
|
||||
"docType": "class",
|
||||
"stability": "experimental",
|
||||
"securityRisk": false,
|
||||
name: 'class_3',
|
||||
title: 'Class 3',
|
||||
path: 'api/core/class_3',
|
||||
docType: 'class',
|
||||
stability: 'experimental',
|
||||
securityRisk: false,
|
||||
},
|
||||
{
|
||||
"name": "function_1",
|
||||
"title": "Function 1",
|
||||
"path": "api/core/function 1",
|
||||
"docType": "function",
|
||||
"stability": "deprecated",
|
||||
"securityRisk": true,
|
||||
name: 'function_1',
|
||||
title: 'Function 1',
|
||||
path: 'api/core/function 1',
|
||||
docType: 'function',
|
||||
stability: 'deprecated',
|
||||
securityRisk: true,
|
||||
},
|
||||
{
|
||||
"name": "const_1",
|
||||
"title": "Const 1",
|
||||
"path": "api/core/const_1",
|
||||
"docType": "const",
|
||||
"stability": "stable",
|
||||
"securityRisk": false,
|
||||
}
|
||||
]
|
||||
}
|
||||
name: 'const_1',
|
||||
title: 'Const 1',
|
||||
path: 'api/core/const_1',
|
||||
docType: 'const',
|
||||
stability: 'stable',
|
||||
securityRisk: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
function getApiSections() { return apiSections; }
|
||||
|
@ -67,7 +67,7 @@ export class ApiService implements OnDestroy {
|
||||
* API sections is an array of Angular top modules and metadata about their API documents (items).
|
||||
* Updates `sections` observable
|
||||
*
|
||||
* @param {string} [src] - Name of the api list JSON file
|
||||
* @param [src] - Name of the api list JSON file
|
||||
*/
|
||||
fetchSections(src?: string) {
|
||||
// TODO: get URL by configuration?
|
||||
|
@ -20,13 +20,13 @@ export class PrettyPrinter {
|
||||
}
|
||||
|
||||
private getPrettyPrintOne(): Promise<PrettyPrintOne> {
|
||||
const ppo = (window as any)['prettyPrintOne'];
|
||||
const ppo = (window as any).prettyPrintOne;
|
||||
return ppo ? Promise.resolve(ppo) :
|
||||
// `prettyPrintOne` is not on `window`, which means `prettify.js` has not been loaded yet.
|
||||
// Import it; ad a side-effect it will add `prettyPrintOne` on `window`.
|
||||
import('assets/js/prettify.js' as any)
|
||||
.then(
|
||||
() => (window as any)['prettyPrintOne'],
|
||||
() => (window as any).prettyPrintOne,
|
||||
err => {
|
||||
const msg = `Cannot get prettify.js from server: ${err.message}`;
|
||||
this.logger.error(new Error(msg));
|
||||
@ -37,9 +37,9 @@ export class PrettyPrinter {
|
||||
|
||||
/**
|
||||
* Format code snippet as HTML
|
||||
* @param {string} code - the code snippet to format; should already be HTML encoded
|
||||
* @param {string} [language] - The language of the code to render (could be javascript, html, typescript, etc)
|
||||
* @param {string|number} [linenums] - Whether to display line numbers:
|
||||
* @param code - the code snippet to format; should already be HTML encoded
|
||||
* @param [language] - The language of the code to render (could be javascript, html, typescript, etc)
|
||||
* @param [linenums] - Whether to display line numbers:
|
||||
* - false: don't display
|
||||
* - true: do display
|
||||
* - number: do display but start at the given number
|
||||
|
@ -63,7 +63,7 @@ describe('ContributorListComponent', () => {
|
||||
it('should set the query to the "GDE" group when user selects "GDE"', () => {
|
||||
component = getComponent();
|
||||
component.selectGroup('GDE');
|
||||
expect(locationService.searchResult['group']).toBe('GDE');
|
||||
expect(locationService.searchResult.group).toBe('GDE');
|
||||
});
|
||||
|
||||
it('should set the query to the first group when user selects unknown name', () => {
|
||||
@ -71,7 +71,7 @@ describe('ContributorListComponent', () => {
|
||||
component.selectGroup('GDE'); // a legit group that isn't the first
|
||||
|
||||
component.selectGroup('foo'); // not a legit group name
|
||||
expect(locationService.searchResult['group']).toBe('Angular');
|
||||
expect(locationService.searchResult.group).toBe('Angular');
|
||||
});
|
||||
|
||||
//// Test Helpers ////
|
||||
|
@ -29,7 +29,7 @@ export class ContributorListComponent implements OnInit {
|
||||
private locationService: LocationService) { }
|
||||
|
||||
ngOnInit() {
|
||||
const groupName = this.locationService.search()['group'] || '';
|
||||
const groupName = this.locationService.search().group || '';
|
||||
// no need to unsubscribe because `contributors` completes
|
||||
this.contributorService.contributors
|
||||
.subscribe(grps => {
|
||||
|
@ -63,7 +63,7 @@ describe('ResourceListComponent', () => {
|
||||
it('should set the query to the "education" category when user selects "education"', () => {
|
||||
component = getComponent();
|
||||
component.selectCategory('education');
|
||||
expect(locationService.searchResult['category']).toBe('education');
|
||||
expect(locationService.searchResult.category).toBe('education');
|
||||
});
|
||||
|
||||
it('should set the query to the first category when user selects unknown name', () => {
|
||||
@ -71,7 +71,7 @@ describe('ResourceListComponent', () => {
|
||||
component.selectCategory('education'); // a legit group that isn't the first
|
||||
|
||||
component.selectCategory('foo'); // not a legit group name
|
||||
expect(locationService.searchResult['category']).toBe('development');
|
||||
expect(locationService.searchResult.category).toBe('development');
|
||||
});
|
||||
|
||||
//// Test Helpers ////
|
||||
|
@ -20,7 +20,7 @@ export class ResourceListComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const category = this.locationService.search()['category'] || '';
|
||||
const category = this.locationService.search().category || '';
|
||||
// Not using async pipe because cats appear twice in template
|
||||
// No need to unsubscribe because categories observable completes.
|
||||
this.resourceService.categories.subscribe(cats => {
|
||||
|
@ -86,62 +86,61 @@ describe('ResourceService', () => {
|
||||
});
|
||||
|
||||
function getTestResources() {
|
||||
// tslint:disable:quotemark
|
||||
return {
|
||||
"Cat 3": {
|
||||
"order": 3,
|
||||
"subCategories": {
|
||||
"Cat3 SubCat1": {
|
||||
"order": 2,
|
||||
"resources": {
|
||||
"Cat3 SubCat1 Res1": {
|
||||
"desc": "Meetup in Barcelona, Spain. ",
|
||||
"title": "Angular Beers",
|
||||
"url": "http://www.meetup.com/AngularJS-Beers/"
|
||||
'Cat 3': {
|
||||
order: 3,
|
||||
subCategories: {
|
||||
'Cat3 SubCat1': {
|
||||
order: 2,
|
||||
resources: {
|
||||
'Cat3 SubCat1 Res1': {
|
||||
desc: 'Meetup in Barcelona, Spain. ',
|
||||
title: 'Angular Beers',
|
||||
url: 'http://www.meetup.com/AngularJS-Beers/',
|
||||
},
|
||||
"Cat3 SubCat1 Res2": {
|
||||
"desc": "Angular Camps in Barcelona, Spain.",
|
||||
"title": "Angular Camp",
|
||||
"url": "http://angularcamp.org/"
|
||||
}
|
||||
}
|
||||
'Cat3 SubCat1 Res2': {
|
||||
desc: 'Angular Camps in Barcelona, Spain.',
|
||||
title: 'Angular Camp',
|
||||
url: 'http://angularcamp.org/',
|
||||
},
|
||||
},
|
||||
},
|
||||
"Cat3 SubCat2": {
|
||||
"order": 1,
|
||||
"resources": {
|
||||
"Cat3 SubCat2 Res1": {
|
||||
"desc": "A community index of components and libraries",
|
||||
"title": "Catalog of Angular Components & Libraries",
|
||||
"url": "https://a/b/c"
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Cat 1": {
|
||||
"order": 1,
|
||||
"subCategories": {
|
||||
"Cat1 SubCat1": {
|
||||
"order": 1,
|
||||
"resources": {
|
||||
"S S S": {
|
||||
"desc": "SSS",
|
||||
"title": "Sssss",
|
||||
"url": "http://s/s/s"
|
||||
'Cat3 SubCat2': {
|
||||
order: 1,
|
||||
resources: {
|
||||
'Cat3 SubCat2 Res1': {
|
||||
desc: 'A community index of components and libraries',
|
||||
title: 'Catalog of Angular Components & Libraries',
|
||||
url: 'https://a/b/c',
|
||||
},
|
||||
"A A A": {
|
||||
"desc": "AAA",
|
||||
"title": "Aaaa",
|
||||
"url": "http://a/a/a"
|
||||
},
|
||||
"Z Z Z": {
|
||||
"desc": "ZZZ",
|
||||
"title": "Zzzzz",
|
||||
"url": "http://z/z/z"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
'Cat 1': {
|
||||
order: 1,
|
||||
subCategories: {
|
||||
'Cat1 SubCat1': {
|
||||
order: 1,
|
||||
resources: {
|
||||
'S S S': {
|
||||
desc: 'SSS',
|
||||
title: 'Sssss',
|
||||
url: 'http://s/s/s',
|
||||
},
|
||||
'A A A': {
|
||||
desc: 'AAA',
|
||||
title: 'Aaaa',
|
||||
url: 'http://a/a/a',
|
||||
},
|
||||
'Z Z Z': {
|
||||
desc: 'ZZZ',
|
||||
title: 'Zzzzz',
|
||||
url: 'http://z/z/z',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ describe('NavigationService', () => {
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
navService.navigationViews.subscribe(views => view = views['sideNav']);
|
||||
navService.navigationViews.subscribe(views => view = views.sideNav);
|
||||
httpMock.expectOne({}).flush({sideNav});
|
||||
});
|
||||
|
||||
@ -254,7 +254,7 @@ describe('NavigationService', () => {
|
||||
{...v, ...{ tooltip: v.title + '.'}})
|
||||
);
|
||||
|
||||
navService.navigationViews.subscribe(views => actualDocVersions = views['docVersions']);
|
||||
navService.navigationViews.subscribe(views => actualDocVersions = views.docVersions);
|
||||
});
|
||||
|
||||
it('should extract the docVersions', () => {
|
||||
|
@ -41,7 +41,7 @@ export class SearchBoxComponent implements AfterViewInit {
|
||||
* When we first show this search box we trigger a search if there is a search query in the URL
|
||||
*/
|
||||
ngAfterViewInit() {
|
||||
const query = this.locationService.search()['search'];
|
||||
const query = this.locationService.search().search;
|
||||
if (query) {
|
||||
this.query = this.decodeQuery(query);
|
||||
this.doSearch();
|
||||
|
@ -26,7 +26,7 @@ function createIndex(loadIndexFn: IndexLoader): lunr.Index {
|
||||
// The lunr typings are missing QueryLexer so we have to add them here manually.
|
||||
const queryLexer = (lunr as any as { QueryLexer: { termSeparator: RegExp } }).QueryLexer;
|
||||
queryLexer.termSeparator = lunr.tokenizer.separator = /\s+/;
|
||||
return lunr(/** @this */function() {
|
||||
return lunr(function() {
|
||||
this.ref('path');
|
||||
this.field('topics', { boost: 15 });
|
||||
this.field('titleWords', { boost: 10 });
|
||||
@ -44,7 +44,7 @@ function handleMessage(message: { data: WebWorkerMessage }): void {
|
||||
const payload = message.data.payload;
|
||||
switch (type) {
|
||||
case 'load-index':
|
||||
makeRequest(SEARCH_TERMS_URL, function(searchInfo: PageInfo[]) {
|
||||
makeRequest(SEARCH_TERMS_URL, (searchInfo: PageInfo[]) => {
|
||||
index = createIndex(loadIndex(searchInfo));
|
||||
postMessage({ type, id, payload: true });
|
||||
});
|
||||
@ -94,7 +94,7 @@ function queryIndex(query: string): PageInfo[] {
|
||||
results = index.search(query + ' ' + titleQuery);
|
||||
}
|
||||
// Map the hits into info about each page to be returned as results
|
||||
return results.map(function(hit) { return pages[hit.ref]; });
|
||||
return results.map(hit => pages[hit.ref]);
|
||||
}
|
||||
} catch (e) {
|
||||
// If the search query cannot be parsed the index throws an error
|
||||
|
@ -11,7 +11,7 @@ export class Deployment {
|
||||
* The deployment mode set from the environment provided at build time;
|
||||
* or overridden by the `mode` query parameter: e.g. `...?mode=archive`
|
||||
*/
|
||||
mode: string = this.location.search()['mode'] || environment.mode;
|
||||
mode: string = this.location.search().mode || environment.mode;
|
||||
|
||||
constructor(private location: LocationService) {}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ export class GaService {
|
||||
private previousUrl: string;
|
||||
|
||||
constructor(@Inject(WindowToken) private window: Window) {
|
||||
this.ga('create', environment['gaId'] , 'auto');
|
||||
this.ga('create', environment.gaId , 'auto');
|
||||
}
|
||||
|
||||
locationChanged(url: string) {
|
||||
@ -34,7 +34,7 @@ export class GaService {
|
||||
}
|
||||
|
||||
ga(...args: any[]) {
|
||||
const gaFn = (this.window as any)['ga'];
|
||||
const gaFn = (this.window as any).ga;
|
||||
if (gaFn) {
|
||||
gaFn(...args);
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ export class LocationService {
|
||||
/**
|
||||
* Handle user's anchor click
|
||||
*
|
||||
* @param anchor {HTMLAnchorElement} - the anchor element clicked
|
||||
* @param anchor The anchor element clicked
|
||||
* @param button Number of the mouse button held down. 0 means left or none
|
||||
* @param ctrlKey True if control key held down
|
||||
* @param metaKey True if command or window key held down
|
||||
|
@ -99,7 +99,7 @@ describe('ScrollService', () => {
|
||||
if (original !== undefined) {
|
||||
Object.defineProperty(window.history, 'scrollRestoration', original);
|
||||
} else {
|
||||
delete window.history.scrollRestoration;
|
||||
delete (window.history as any).scrollRestoration;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -13,14 +13,13 @@ import { Logger } from 'app/shared/logger.service';
|
||||
* 1. Checks for available ServiceWorker updates once instantiated.
|
||||
* 2. Re-checks every 6 hours.
|
||||
* 3. Whenever an update is available, it activates the update.
|
||||
*
|
||||
* @property
|
||||
* `updateActivated` {Observable<string>} - Emit the version hash whenever an update is activated.
|
||||
*/
|
||||
@Injectable()
|
||||
export class SwUpdatesService implements OnDestroy {
|
||||
private checkInterval = 1000 * 60 * 60 * 6; // 6 hours
|
||||
private onDestroy = new Subject<void>();
|
||||
|
||||
/** Emit the version hash whenever an update is activated. */
|
||||
updateActivated: Observable<string>;
|
||||
|
||||
constructor(appRef: ApplicationRef, private logger: Logger, private swu: SwUpdate) {
|
||||
|
@ -38,7 +38,11 @@
|
||||
margin: 0;
|
||||
clear: left;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
color: $blue;
|
||||
@include font-size(14);
|
||||
}
|
||||
.show-all {
|
||||
display: initial;
|
||||
}
|
||||
|
@ -105,9 +105,7 @@
|
||||
|
||||
li {
|
||||
box-sizing: border-box;
|
||||
@include font-size(12);
|
||||
@include line-height(16);
|
||||
padding: 3px 0 3px 12px;
|
||||
padding: 7px 0 7px 12px;
|
||||
position: relative;
|
||||
transition: all 0.3s ease-in-out;
|
||||
|
||||
@ -129,6 +127,7 @@
|
||||
color: lighten($darkgray, 10);
|
||||
overflow: visible;
|
||||
@include font-size(12);
|
||||
@include line-height(16);
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
@ -168,11 +167,11 @@
|
||||
}
|
||||
|
||||
&:first-child:before {
|
||||
top: 13px;
|
||||
top: 15px;
|
||||
}
|
||||
|
||||
&:last-child:before {
|
||||
bottom: calc(100% - 14px);
|
||||
bottom: calc(100% - 15px);
|
||||
}
|
||||
|
||||
&:not(.active):hover a:before {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ApiPage } from './api.po';
|
||||
|
||||
describe('Api pages', function() {
|
||||
describe('Api pages', () => {
|
||||
it('should show direct subclasses of a class', () => {
|
||||
const page = new ApiPage('api/forms/AbstractControlDirective');
|
||||
expect(page.getDescendants('class', true)).toEqual(['ControlContainer', 'NgControl']);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { browser, by, element, ElementFinder } from 'protractor';
|
||||
import { SitePage } from './app.po';
|
||||
|
||||
describe('site App', function() {
|
||||
describe('site App', () => {
|
||||
let page: SitePage;
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -3,7 +3,7 @@ import { SitePage } from './app.po';
|
||||
|
||||
/* tslint:disable:max-line-length */
|
||||
|
||||
describe('onerror handler', function() {
|
||||
describe('onerror handler', () => {
|
||||
let page: SitePage;
|
||||
|
||||
beforeAll(() => {
|
||||
@ -186,7 +186,7 @@ createViewNodes@???`);
|
||||
});
|
||||
|
||||
async function callOnError(message: string, url?: string, line?: number, column?: number, error?: Error) {
|
||||
await browser.executeScript(function() {
|
||||
await browser.executeScript(() => {
|
||||
// reset the ga queue
|
||||
(window as any).ga.q.length = 0;
|
||||
// post the error to the handler
|
||||
|
@ -39,31 +39,27 @@ See the [README.md](examples/README.md) for more details.
|
||||
|
||||
## example-zipper
|
||||
|
||||
In the AIO application, we offer the reader the option to download each example as a full self-contained
|
||||
runnable project packaged as a zip file. These zip files are generated by the utility in this folder.
|
||||
In the AIO application, we offer the reader the option to download each example as a full self-contained runnable project packaged as a zip file.
|
||||
These zip files are generated by the utility in this folder.
|
||||
|
||||
See the [README.md](example-zipper/README.md) for more details.
|
||||
|
||||
## stackblitz-builder
|
||||
|
||||
In the AIO application, we can embed a running version of the example as a [Stackblitz](https://stackblitz.com/) session.
|
||||
We can also provide a link to create a runnable version of the example in the [Stackblitz](https://stackblitz.com/)
|
||||
editor.
|
||||
We can also provide a link to create a runnable version of the example in the [Stackblitz](https://stackblitz.com/) editor.
|
||||
|
||||
See the [README.md](stackblitz-builder/README.md) for more details.
|
||||
|
||||
## transforms
|
||||
|
||||
All the content that is rendered by the AIO application, and some of its configuration files, are
|
||||
generated from source files by [Dgeni](https://github.com/angular/dgeni). Dgeni is a general purpose
|
||||
documentation generation tool.
|
||||
All the content that is rendered by the AIO application, and some of its configuration files, are generated from source files by [Dgeni](https://github.com/angular/dgeni).
|
||||
Dgeni is a general purpose documentation generation tool.
|
||||
|
||||
Markdown files in `/aio/content`, code comments in the core Angular source files and example files are
|
||||
processed and transformed into files that are consumed by the AIO application.
|
||||
Markdown files in `/aio/content`, code comments in the core Angular source files and example files are processed and transformed into files that are consumed by the AIO application.
|
||||
|
||||
Dgeni is configured by "packages", which contain services and processors. Some of these packages are
|
||||
installed as `node_modules` from the [dgeni-packages](https://github.com/angular/dgeni-packages) and
|
||||
some are specific to the AIO project.
|
||||
Dgeni is configured by "packages", which contain services and processors.
|
||||
Some of these packages are installed as `node_modules` from the [dgeni-packages](https://github.com/angular/dgeni-packages) and some are specific to the AIO project.
|
||||
|
||||
The project specific packages are stored in the `aio/tools/transforms` folder. See the
|
||||
[README.md](transforms/README.md) for more details.
|
||||
The project specific packages are stored in the `aio/tools/transforms` folder.
|
||||
See the [README.md](transforms/README.md) for more details.
|
||||
|
@ -1,16 +0,0 @@
|
||||
# Docs releases
|
||||
|
||||
This document explains how to update the documentation examples after an Angular release. This is only needed for major and minor versions.
|
||||
|
||||
All the packages for the docs' examples are specified in `/aio/tools/examples/shared/package.json`
|
||||
|
||||
**1)** So within the `shared` folder, you need to issue the following command:
|
||||
|
||||
```
|
||||
$ yarn upgrade-interactive --tilde
|
||||
```
|
||||
|
||||
There, select all the packages that are updated on the new Angular release.
|
||||
|
||||
**2)** Changes to the tsconfig.json? There are several files in `/aio/tools/examples/shared/boilerplate/*/tsconfig.json` (based on the example type).
|
||||
|
@ -1,7 +1,5 @@
|
||||
# Overview
|
||||
|
||||
The AIO application is built using the Angular CLI tool. We are often trialling new features for the CLI, which
|
||||
we apply to the library after it is installed. This folder contains git patch files that contain these new features
|
||||
and a utility to apply those patches to the CLI library.
|
||||
The AIO application is built using the Angular CLI tool. We are often trialling new features for the CLI, which we apply to the library after it is installed. This folder contains git patch files that contain these new features and a utility to apply those patches to the CLI library.
|
||||
|
||||
**Currently, we have no patches to run.**
|
||||
**Currently, we have no patches to run.**
|
||||
|
@ -1,13 +1,14 @@
|
||||
# Overview
|
||||
|
||||
In the AIO application, we offer the reader the option to download each example as a full self-contained
|
||||
runnable project packaged as a zip file. These zip files are generated by the utility in this folder.
|
||||
In the AIO application, we offer the reader the option to download each example as a full self-contained runnable project packaged as a zip file.
|
||||
These zip files are generated by the utility in this folder.
|
||||
|
||||
## Example zipper
|
||||
|
||||
The `exampleZipper.js` tool is very similar to the Stackblitz's `builder.js`. The latter creates an HTML file
|
||||
with all the examples' files and the former creates a zip file instead. They both use the `stackblitz.json` file
|
||||
to flag an example as something to stackblitz or zip. For example:
|
||||
The `exampleZipper.js` tool is very similar to the Stackblitz's `builder.js`.
|
||||
The latter creates an HTML file with all the examples' files and the former creates a zip file instead.
|
||||
They both use the `stackblitz.json` file to flag an example as something to stackblitz or zip.
|
||||
For example:
|
||||
|
||||
```json
|
||||
{
|
||||
@ -41,8 +42,7 @@ The `exampleZipper.js` won't include any `System.js` related files for CLI-based
|
||||
As mentioned, the tool uses the `stackblitz.json` as a flag and also a configuration file for the zipper.
|
||||
The problem is that not all examples have a stackblitz but they could offer a zip instead.
|
||||
|
||||
In those cases, you can create a `zipper.json` file with the same syntax. It will be ignored by the
|
||||
stackblitz tool.
|
||||
In those cases, you can create a `zipper.json` file with the same syntax. It will be ignored by the stackblitz tool.
|
||||
|
||||
## Executing the zip generation
|
||||
|
||||
@ -50,5 +50,4 @@ stackblitz tool.
|
||||
|
||||
Where? At `src/generated/zips/`.
|
||||
|
||||
Then the `<live-example>` embedded component will look at this folder to get the zip it needs for
|
||||
the example.
|
||||
Then the `<live-example>` embedded component will look at this folder to get the zip it needs for the example.
|
||||
|
@ -1,110 +1,157 @@
|
||||
# Overview
|
||||
|
||||
Many of the documentation pages contain snippets of code examples. Extract these snippets from
|
||||
real working example applications, which are stored in subfolders of the `/aio/content/examples`
|
||||
folder. Each example can be built and run independently. Each example also provides e2e specs, which
|
||||
are run as part of our CircleCI legacy build tasks, to verify that the examples continue to work as
|
||||
expected, as changes are made to the core Angular libraries.
|
||||
Many of the documentation pages contain snippets of code examples.
|
||||
These snippets are extracted from real working example applications, which are stored in sub-folders of the [aio/content/examples/](.) folder.
|
||||
Each example can be built and run independently.
|
||||
Each example also provides tests (mostly e2e and occasionally unit tests), which are run as part of our CircleCI `test_docs_examples*` jobs, to verify that the examples continue to work as expected, as changes are made to the core Angular libraries.
|
||||
|
||||
In order to build, run and test these examples independently you need to install dependencies into
|
||||
their sub-folder. Also there are a number of common boilerplate files that are needed to configure
|
||||
each example's project. Maintain these common boilerplate files centrally to reduce the amount
|
||||
of effort if one of them needs to change.
|
||||
In order to build, run and test these examples independently, you need to install dependencies into their sub-folder.
|
||||
Also there are a number of common boilerplate files that are needed to configure each example's project.
|
||||
These common boilerplate files are maintained centrally to reduce the amount of effort if one of them needs to change.
|
||||
|
||||
> **Note for Windows users**
|
||||
>
|
||||
> Setting up the examples involves creating some [symbolic links](https://en.wikipedia.org/wiki/Symbolic_link) (see [here](#symlinked-node_modules) for details). On Windows, this requires to either have [Developer Mode enabled](https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10) (supported on Windows 10 or newer) or run the setup commands as administrator.
|
||||
> Setting up the examples involves creating some [symbolic links](https://en.wikipedia.org/wiki/Symbolic_link) (see [here](#symlinked-node_modules) for details).
|
||||
> On Windows, this requires to either have [Developer Mode enabled](https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10) (supported on Windows 10 or newer) or run the setup commands as administrator.
|
||||
|
||||
|
||||
## Boilerplate overview
|
||||
|
||||
As mentioned, many of the documentation pages contain snippets extracted from real example applications.
|
||||
To achieve that, all those applications needs to contain a basic boilerplate. E.g. a `node_modules`
|
||||
folder, `package.json` with scripts, etc.
|
||||
As mentioned above, many of the documentation pages contain snippets extracted from real example applications.
|
||||
To achieve that, all those applications need to contain some basic boilerplate, such as a `node_modules/` folder, a `package.json` file with scripts and dependencies, etc.
|
||||
|
||||
There are also different project types, each with its own boilerplate.
|
||||
For example, there are projects based on the Angular CLI, projects that use AngularJS, Custom Elements, i18n, server-side rendering, etc.
|
||||
(See the [example configuration section](#example-config) below for more info on how to specify the project type.)
|
||||
|
||||
To avoid having to maintain the boilerplate in each example, we use the [example-boilerplate-js](./example-boilerplate.js) script to provide a set of files that works across all the examples of a specific type.
|
||||
|
||||
No one wants to maintain the boilerplate on each example, so the goal of this tool is to provide a set of files that works across all the examples.
|
||||
|
||||
### Boilerplate files
|
||||
|
||||
Inside `/aio/tools/examples/shared/boilerplate` you will find a set of folders representing each project type.
|
||||
Inside [shared/boilerplate/](./shared/boilerplate) there is a sub-folder with boilerplate files for each of the different project types.
|
||||
|
||||
Currently you will find the next project types:
|
||||
Currently, the following project types are supported:
|
||||
|
||||
* cli - For CLI based examples. This is the default one, to be used in the majority of the examples.
|
||||
* getting-started - CLI-based with its own set of styles.
|
||||
* i18n - CLI-based with additional scripts for internationalization.
|
||||
* schematics - CLI-based with additional scripts for building schematics.
|
||||
* service-worker - CLI-based with additional packages and configuration for service workers.
|
||||
* systemjs - Currently in deprecation, only used in a few examples.
|
||||
* testing - CLI-based with additional styles for jasmine testing.
|
||||
* universal - CLI-based with an extra server target.
|
||||
* viewengine - Additional configuration for running CLI-/SystemJS-based examples with `ViewEngine` (the pre-Ivy compiler/renderer).
|
||||
- `cli`: For example apps based on the Angular CLI. This is the default type and is used in the majority of the examples.
|
||||
- `cli-ajs`: For CLI-based examples that also use AngularJS (but not via `@angular/upgrade`).
|
||||
- `elements`: For CLI-based examples that also use `@angular/elements`.
|
||||
- `getting-started`: For the "Getting started" tutorial. Essentially the same as `cli` but with custom CSS styles.
|
||||
- `i18n`: For CLI-based examples that also use internationalization.
|
||||
- `schematics`: For CLI-based examples that include a library with schematics.
|
||||
- `service-worker`: For CLI-based examples that also use `@angular/service-worker`.
|
||||
- `systemjs`: For non-CLI legacy examples using SystemJS. This is deprecated and only used in few examples.
|
||||
- `testing`: For CLI-based examples that are related to unit testing.
|
||||
- `universal`: For CLI-based examples that also use `@nguniversal/express-engine` for SSR.
|
||||
|
||||
There is also a `common` folder that contains files used in all different examples.
|
||||
There are also the following special folders:
|
||||
- `common`: Contains files used in many examples.
|
||||
(See the [next section](#example-config) for info on how to exclude common files in certain examples.)
|
||||
- `viewengine/cli`: Additional configuration for running CLI-based examples with `ViewEngine` (the pre-Ivy compiler/renderer).
|
||||
This applies to all CLI-based examples, such as `cli-ajs`, `elements`, `getting-started`, etc.
|
||||
- `viewengine/systemjs`: Additional configuration for running SystemJS-based examples with `ViewEngine` (the pre-Ivy compiler/renderer).
|
||||
|
||||
### The example-config.json
|
||||
|
||||
Each example is identified by an **example-config.json** configuration file in its root folder.
|
||||
This configuration file indicates what type of boilerplate this example needs. E.g.
|
||||
<a name="example-config"></a>
|
||||
### The `example-config.json`
|
||||
|
||||
Each example is identified by an `example-config.json` configuration file in its root folder.
|
||||
This configuration file indicates what type of boilerplate this example needs and how to test it.
|
||||
For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"projectType": "cli",
|
||||
"useCommonBoilerplate": true
|
||||
"projectType": "cli"
|
||||
}
|
||||
```
|
||||
|
||||
If the file is empty then the default type of cli is assumed.
|
||||
When the boilerplate tooling runs, it will copy into the example folder all of the appropriate files based on the project type.
|
||||
The file is expected to contain a JSON object with zero or more of the following properties:
|
||||
|
||||
- `projectType: string`: One of the supported project types (see above).
|
||||
Default: `"cli"`
|
||||
- `useCommonBoilerplate: boolean`: Whether to include common boilerplate from the [common/](./shared/boilerplate/common) folder.
|
||||
Default: `true`
|
||||
|
||||
**SystemJS-only properties:**
|
||||
- `build: string`: The npm script to run in order to build the example app.
|
||||
Default: `"build"`
|
||||
- `run: string`: The npm script to run in order to serve the example app (so that e2e test can be run against it).
|
||||
Default `"serve:e2e"`
|
||||
|
||||
**CLI-only properties:**
|
||||
- `tests: object[]`: An array of objects, each specifying a test command. This can be used to run multiple test commands in series (for example, to run unit and e2e tests).
|
||||
The commands are specified as `{cmd: string, args: string[]}` and must be in a format that could be passed to Node.js' `child_process.spawn(cmd, args)`. You can use a special `{PORT}` placeholder, that will be replaced with the port on which the app is served during the actual test.
|
||||
Default:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"cmd": "yarn",
|
||||
"args": [
|
||||
"e2e",
|
||||
"--prod",
|
||||
"--protractor-config=e2e/protractor-puppeteer.conf.js",
|
||||
"--no-webdriver-update",
|
||||
"--port={PORT}"
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
An empty `example-config.json` file is equivalent with `{"projectType": "cli"}`.
|
||||
|
||||
|
||||
<a name="symlinked-node_modules"></a>
|
||||
### A node_modules to share
|
||||
### A `node_modules/` to share
|
||||
|
||||
With all the boilerplate files in place, the only missing piece are the installed packages. For
|
||||
that you have a `/aio/tools/examples/shared/package.json` which contains **all** the packages
|
||||
needed to run all the examples through all different boilerplates.
|
||||
With all the boilerplate files in place, the only missing piece is the installed packages.
|
||||
For that we have [shared/package.json](./shared/package.json), which contains **all** the packages needed to run any example type.
|
||||
|
||||
After installing these dependencies, a `node_modules/` folder will be created at
|
||||
`/aio/tools/examples/shared/node_modules/`. This folder will be **symlinked** into each example.
|
||||
Upon installing these dependencies, a [shared/node_modules/](./shared/node_modules) folder is created.
|
||||
This folder will be **symlinked** into each example.
|
||||
So it is not a copy like the other boilerplate files.
|
||||
|
||||
### End to end tests
|
||||
|
||||
End to end changes between boilerplates.
|
||||
### End-to-end tests
|
||||
|
||||
For CLI applications, create a `app.e2e-spec.ts` inside the `e2e` folder. The tooling will run
|
||||
`ng e2e` for each CLI based examples.
|
||||
End-to-end infrastructure is slightly different between CLI- and SystemJS-based examples.
|
||||
|
||||
For SystemJS, each example contains an `e2e-spec.ts` file. You can find all the related configuration files
|
||||
in the `/aio/tools/examples/shared` folder.
|
||||
For CLI-based examples, create an `app.e2e-spec.ts` file inside the `e2e/` folder.
|
||||
This will be picked up by the default testing command (see the [example configuration section](#example-config) above).
|
||||
If you are using a custom test command, make sure e2e specs are picked up (if applicable).
|
||||
|
||||
### example-boilerplate.js
|
||||
For SystemJS-based examples, create an `e2e-spec.ts` file inside the example root folder.
|
||||
These apps will be tested with the following command:
|
||||
|
||||
This script installs all the dependencies that are shared among all the examples, creates the
|
||||
`node_modules` symlinks and copy all the boilerplate files where needed. It won't do anything
|
||||
about stackblitz nor e2e tests.
|
||||
```sh
|
||||
yarn protractor aio/tools/examples/shared/protractor.config.js \
|
||||
--specs=<example-folder>/e2e-spec.ts \
|
||||
--params.appDir=<example-folder>
|
||||
```
|
||||
|
||||
It also contains a function to remove all the boilerplate. It uses a `git clean -xdf` to do
|
||||
the job. It will remove all files that don't exist in the git repository, **including any
|
||||
new file that you are working on that hasn't been stage yet.** So be sure to save your work
|
||||
before removing the boilerplate.
|
||||
|
||||
### run-example-e2e.js
|
||||
### `example-boilerplate.js`
|
||||
|
||||
This script will find all the `e2e-spec.ts` files and run them.
|
||||
The [example-boilerplate.js](./example-boilerplate.js) script installs the dependencies for all examples, creates the `node_modules/` symlinks and copies the necessary boilerplate files into example folders.
|
||||
|
||||
To not run all tests, you can use the `--filter=name` flag to run the example's e2e that contains
|
||||
that name.
|
||||
It also contains a function to remove all the boilerplate.
|
||||
It uses `git clean -xdf` to do the job.
|
||||
It will remove all files that are not tracked by git, **including any new files that you are working on that haven't been stageg yet.**
|
||||
So, be sure to commit your work before removing the boilerplate.
|
||||
|
||||
It also has an optional `--setup` flag to run the `example-boilerplate.js` script and install
|
||||
the latest `webdriver`.
|
||||
|
||||
It will create a `/aio/protractor-results.txt` file when it finishes running tests.
|
||||
### `run-example-e2e.js`
|
||||
|
||||
The [run-example-e2e.js](./run-example-e2e.js) script will find and run the e2e tests for all examples.
|
||||
Although it only runs e2e tests by default, it can be configured to run any test command (for CLI-based examples) by using the `tests` property of the [example-config.json](#example-config) file.
|
||||
It is named `*-e2e` for historical reasons, but it is not limited to running e2e tests.
|
||||
|
||||
See [aio/README.md](../../README.md#developer-tasks) for the available command-line options.
|
||||
|
||||
Running the script will create an `aio/protractor-results.txt` file with the results of the tests.
|
||||
|
||||
|
||||
### Updating example dependencies
|
||||
|
||||
With every major release, we update the examples to be on the latest version. The following steps to update are:
|
||||
|
||||
* In the `shared/package.json` file, bump all the `@angular/*`, `@angular-devkit/*`, `rxjs`, `typescript`, and `zone.js` package versions to the version that corresponds with the [framework version](../../../package.json).
|
||||
* In the `shared` folder, run `yarn` to update the dependencies for the shared `node_modules` and the `yarn.lock` file.
|
||||
* In the `boilerplate` folder, go through each sub-folder and update the `package.json` dependencies if one is present.
|
||||
* Follow the [update guide](./shared/boilerplate/UPDATING_CLI.md) to update the common files used in the examples based on project type.
|
||||
With every major Angular release, we update the examples to be on the latest version.
|
||||
See [UPDATING.md](./UPDATING.md) for instructions.
|
||||
|
52
aio/tools/examples/UPDATING.md
Normal file
52
aio/tools/examples/UPDATING.md
Normal file
@ -0,0 +1,52 @@
|
||||
# Update example dependencies
|
||||
|
||||
Follow these steps to update the examples to the latest versions of Angular (and related dependencies):
|
||||
|
||||
- In [shared/package.json](./shared/package.json), bump all the `@angular/*` and `@nguniversal/*` package versions to the version you want to update to and update their peer dependencies (such as `@angular-devkit/*`, `rxjs`, `typescript`, `zone.js`) and other dependencies (e.g. `@types/*`) to the latest compatible versions.
|
||||
|
||||
> NOTE:
|
||||
> The [angular-cli-diff](https://github.com/cexbrayat/angular-cli-diff) repo can be a useful resource for discovering what dependency versions are used for a basic CLI app at a specific CLI version.
|
||||
|
||||
- In the [shared/](./shared) folder, run `yarn` to update the dependencies in the [shared/node_modules/](./shared/node_modules) folder and the [shared/yarn.lock](./shared/yarn.lock) file.
|
||||
|
||||
- In the [shared/](./shared) folder, run `yarn sync-deps` to update the dependency versions of the `package.json` files in each sub-folder of [shared/boilerplate/](./shared/boilerplate) to match the ones in [shared/package.json](./shared/package.json).
|
||||
|
||||
- Follow the steps in the following section to update the rest of the boilerplate files.
|
||||
|
||||
|
||||
## Update other boilerplate files
|
||||
|
||||
The Angular CLI default setup is updated using `ng update`.
|
||||
Any necessary changes to boilerplate files will be done automatically through migration schematics.
|
||||
|
||||
> NOTE:
|
||||
> Migrations affecting source code files will not happen automatically, because `ng update` does not know about all the examples in `aio/content/examples/`.
|
||||
> You have to make these changes (if any) manually.
|
||||
> Again, the [angular-cli-diff](https://github.com/cexbrayat/angular-cli-diff) repo can be a useful resource for discovering changes between versions.
|
||||
|
||||
- In the [shared/boilerplate/cli/](./shared/boilerplate/cli) folder, run the following commands to migrate the the project to the current versions of Angular CLI and the Angular framework (updated in previous steps):
|
||||
```sh
|
||||
# Ensure dependencies are installed.
|
||||
yarn install
|
||||
|
||||
# Migrate project to new versions.
|
||||
yarn ng update @angular/cli --migrate-only --from=<previous-cli-version>
|
||||
yarn ng update @angular/core --migrate-only --from=<previous-core-version>
|
||||
```
|
||||
|
||||
> NOTE:
|
||||
> In order for `ng update` to work, there must be a `node_modules/` directory with installed dependencies inside the [shared/boilerplate/cli/](./shared/boilerplate/cli) directory.
|
||||
> This `node_modules/` directory is only needed during the update operation and is otherwise ignored (both by git and by the [example-boilerplate.js](./example-boilerplate.js) script) by means of the [shared/boilerplate/.gitignore](./shared/boilerplate/.gitignore) file.
|
||||
|
||||
- The previous command made any necessary changes to boilerplate files inside the `cli/` folder, but the same changes need to be applied to the other CLI-based boilerplate folders.
|
||||
Inspect the changes in `cli/` and manually apply the necessary ones to other CLI-based boilerplate folders.
|
||||
|
||||
- Ensure any changes to [cli/tslint.json](./shared/boilerplate/cli/tslint.json) are ported over to [systemjs/tslint.json](./shared/boilerplate/systemjs/tslint.json) and also [aio/content/examples/tslint.json](../../content/examples/tslint.json).
|
||||
This last part is important, since this file is used to lint example code on CI.
|
||||
|
||||
- Inspect the changes and determine whether some of them need to be applied to the `systemjs` boilerplate files.
|
||||
|
||||
- Inspect the changes and determine whether any updates to guides are necessary.
|
||||
For example, if a file is renamed or moved, any guides mentioning that file may need updating to refer to the new name/location.
|
||||
|
||||
- Commit all changes to the repository.
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user