Compare commits
107 Commits
Author | SHA1 | Date | |
---|---|---|---|
a73b8a62c8 | |||
2fe8f2b1e0 | |||
f1c08c6918 | |||
bf9de8ca53 | |||
df37c47ac5 | |||
788d19c036 | |||
8492499fe7 | |||
48174010a1 | |||
1460e46ba1 | |||
a7ff7d8d27 | |||
eb970b4626 | |||
18d301258a | |||
6a01fa532c | |||
e190a7e9de | |||
53c6425954 | |||
c198dc6c19 | |||
d1b26dd3a1 | |||
ce7131f3fb | |||
934702892e | |||
8a830a72e2 | |||
2abb54c4a1 | |||
44632bb700 | |||
e33b382ab4 | |||
c4da400db4 | |||
c40e6b2edc | |||
978fc27314 | |||
b9f2bbb7d1 | |||
9654d646ae | |||
7f214499e8 | |||
a3d55a9d27 | |||
e222b8761e | |||
1435c0b3a9 | |||
0a7aebbcf5 | |||
bd7f91feac | |||
3bbc89b958 | |||
52d98e563d | |||
b279f8bd62 | |||
954e34cc91 | |||
e1f6d15387 | |||
cbb3794931 | |||
77a5790a9d | |||
4ca401c394 | |||
3dcd5ebdbc | |||
49307f0595 | |||
762fc28fee | |||
338e58c278 | |||
7e2ed89208 | |||
56a3dcf44c | |||
501bacdcf6 | |||
c5f2979a87 | |||
fe02462b5f | |||
3000d19ad1 | |||
dcf9f05c9a | |||
ba56f3c15b | |||
865ad56e9a | |||
5cda2a041b | |||
6a484853a9 | |||
e6ac289518 | |||
4e8614bb92 | |||
9ace748d3a | |||
08c38a1f99 | |||
9aeef0afdb | |||
8aef446373 | |||
f7ee91a17c | |||
8ae0afd3f8 | |||
be82270493 | |||
4cef2c1236 | |||
26e3615e19 | |||
65f20107fe | |||
5a7bcd1862 | |||
152ea36c80 | |||
ea2adb104b | |||
19caace2ab | |||
ccc1c27461 | |||
97268b95f7 | |||
7c41abe64a | |||
991e138650 | |||
d00b421402 | |||
06ffed5141 | |||
5a15126520 | |||
9e4b2f1a4f | |||
393529d3b5 | |||
81b75590d7 | |||
151f0f4b2c | |||
f88f941008 | |||
0e17958640 | |||
e82b45c8a7 | |||
8422ef2a93 | |||
cdf586d0d2 | |||
3828c89792 | |||
0f352b6350 | |||
06bdecffb9 | |||
99ddec152b | |||
31aba96fcb | |||
facce2c9fd | |||
edaf058548 | |||
4bff2d0c8c | |||
cfc608aaf3 | |||
6d9f5fcddb | |||
b19a05c25a | |||
bd75b3b7b3 | |||
1c67b9063b | |||
9a9ae60e0e | |||
ae6fa9260a | |||
f8fa2f2a6c | |||
f3ee9a6144 | |||
d076c51455 |
@ -4,4 +4,5 @@ aio/content
|
||||
aio/node_modules
|
||||
aio/tools/examples/shared/node_modules
|
||||
integration/bazel
|
||||
integration/bazel-schematics/demo
|
||||
packages/bazel/node_modules
|
||||
|
9
.bazelrc
9
.bazelrc
@ -145,3 +145,12 @@ build:remote --remote_accept_cached=false
|
||||
# Load any settings specific to the current user. Needs to be last statement in this
|
||||
# config, as the user configuration should be able to overwrite flags from this file.
|
||||
try-import .bazelrc.user
|
||||
|
||||
###############################
|
||||
# NodeJS rules settings
|
||||
# These settings are required for rules_nodejs
|
||||
###############################
|
||||
|
||||
# Turn on managed directories feature in Bazel
|
||||
# This allows us to avoid installing a second copy of node_modules
|
||||
common --experimental_allow_incremental_repository_updates
|
||||
|
@ -15,8 +15,8 @@
|
||||
# `CI_CHROMEDRIVER_VERSION_ARG` env var (in `.circleci/env.sh`) points to a ChromeDriver
|
||||
# version that is compatible with the Chrome version in the image.
|
||||
# **NOTE 2**: If you change the version of the docker images, also change the `cache_key` suffix.
|
||||
var_1: &default_docker_image circleci/node:10.12
|
||||
var_2: &browsers_docker_image circleci/node:10.12-browsers
|
||||
var_1: &default_docker_image circleci/node:10.16
|
||||
var_2: &browsers_docker_image circleci/node:10.16-browsers
|
||||
# We don't want to include the current branch name in the cache key because that would prevent
|
||||
# PRs from being able to restore the cache since the branch names are always different for PRs.
|
||||
# The cache key should only consist of dynamic values that change whenever something in the
|
||||
@ -26,7 +26,7 @@ var_2: &browsers_docker_image circleci/node:10.12-browsers
|
||||
# **NOTE 1 **: If you change the cache key prefix, also sync the restore_cache fallback to match.
|
||||
# **NOTE 2 **: Keep the static part of the cache key as prefix to enable correct fallbacks.
|
||||
# See https://circleci.com/docs/2.0/caching/#restoring-cache for how prefixes work in CircleCI.
|
||||
var_3: &cache_key v3-angular-node-10.12-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}-{{ checksum "packages/bazel/package.bzl" }}-{{ checksum "aio/yarn.lock" }}
|
||||
var_3: &cache_key v3-angular-node-10.16-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}-{{ checksum "packages/bazel/package.bzl" }}-{{ checksum "aio/yarn.lock" }}
|
||||
|
||||
# Initializes the CI environment by setting up common environment variables.
|
||||
var_4: &init_environment
|
||||
@ -53,7 +53,10 @@ var_5: &setup_bazel_remote_execution
|
||||
run:
|
||||
name: "Setup bazel RBE remote execution"
|
||||
command: |
|
||||
openssl aes-256-cbc -d -in .circleci/gcp_token -k "$CI_REPO_NAME" -out /home/circleci/.gcp_credentials
|
||||
# We need ensure that the same default digest is used for encoding and decoding
|
||||
# with openssl. Openssl versions might have different default digests which can
|
||||
# cause decryption failures based on the openssl version. https://stackoverflow.com/a/39641378/4317734
|
||||
openssl aes-256-cbc -d -in .circleci/gcp_token -md md5 -k "$CI_REPO_NAME" -out /home/circleci/.gcp_credentials
|
||||
echo "export GOOGLE_APPLICATION_CREDENTIALS=/home/circleci/.gcp_credentials" >> $BASH_ENV
|
||||
sudo bash -c "echo 'build --config=remote' >> /etc/bazel.bazelrc"
|
||||
|
||||
@ -99,7 +102,7 @@ var_10: &restore_cache
|
||||
keys:
|
||||
- *cache_key
|
||||
# This fallback should be the cache_key without variables.
|
||||
- v3-angular-node-10.12-
|
||||
- v3-angular-node-10.16-
|
||||
|
||||
# Branch filter that can be specified for jobs that should only run on publish branches
|
||||
# (e.g. master or the patch branch)
|
||||
@ -250,16 +253,14 @@ jobs:
|
||||
- run: yarn --cwd aio build --progress=false
|
||||
# Lint the code
|
||||
- run: yarn --cwd aio lint
|
||||
# Run PWA-score tests
|
||||
# (Run before unit and e2e tests, which destroy the `dist/` directory.)
|
||||
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
|
||||
# Check the bundle sizes.
|
||||
# (Run before unit and e2e tests, which destroy the `dist/` directory.)
|
||||
- run: yarn --cwd aio payload-size
|
||||
# Run unit tests
|
||||
- run: yarn --cwd aio test --progress=false --watch=false
|
||||
# Run e2e tests
|
||||
- run: yarn --cwd aio e2e --configuration=ci
|
||||
# Run PWA-score tests
|
||||
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
|
||||
# Check the bundle sizes.
|
||||
- run: yarn --cwd aio payload-size
|
||||
# Run unit tests for Firebase redirects
|
||||
- run: yarn --cwd aio redirects-test
|
||||
|
||||
@ -285,13 +286,15 @@ jobs:
|
||||
- *init_environment
|
||||
# Build aio (with local Angular packages)
|
||||
- run: yarn --cwd aio build-local --progress=false
|
||||
# Run PWA-score tests
|
||||
# (Run before unit and e2e tests, which destroy the `dist/` directory.)
|
||||
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
|
||||
# Run unit tests
|
||||
- run: yarn --cwd aio test --progress=false --watch=false
|
||||
# Run e2e tests
|
||||
- run: yarn --cwd aio e2e --configuration=ci
|
||||
# Run PWA-score tests
|
||||
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
|
||||
# Check the bundle sizes.
|
||||
# Temporary disabled due to a problem with patch branch (8.0.x)
|
||||
# - run: yarn --cwd aio payload-size aio-local
|
||||
|
||||
test_aio_local_ivy:
|
||||
<<: *job_defaults
|
||||
@ -303,13 +306,15 @@ jobs:
|
||||
- *init_environment
|
||||
# Build aio with Ivy (using local Angular packages)
|
||||
- run: yarn --cwd aio build-with-ivy --progress=false
|
||||
# Run PWA-score tests
|
||||
# (Run before unit and e2e tests, which destroy the `dist/` directory.)
|
||||
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
|
||||
# Run unit tests
|
||||
- run: yarn --cwd aio test --progress=false --watch=false
|
||||
# Run e2e tests
|
||||
- run: yarn --cwd aio e2e --configuration=ci
|
||||
# Run PWA-score tests
|
||||
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
|
||||
# Check the bundle sizes.
|
||||
# Temporary disabled due to a problem with patch branch (8.0.x)
|
||||
# - run: yarn --cwd aio payload-size aio-local-ivy
|
||||
|
||||
test_aio_tools:
|
||||
<<: *job_defaults
|
||||
@ -500,7 +505,10 @@ jobs:
|
||||
- run: git config --global --unset "url.ssh://git@github.com.insteadof"
|
||||
- run:
|
||||
name: Decrypt github credentials
|
||||
command: 'openssl aes-256-cbc -d -in .circleci/github_token -k "${KEY}" -out ~/.git_credentials'
|
||||
# We need ensure that the same default digest is used for encoding and decoding with
|
||||
# openssl. Openssl versions might have different default digests which can cause
|
||||
# decryption failures based on the installed openssl version. https://stackoverflow.com/a/39641378/4317734
|
||||
command: 'openssl aes-256-cbc -d -in .circleci/github_token -md md5 -k "${KEY}" -out ~/.git_credentials'
|
||||
- run: ./scripts/ci/publish-build-artifacts.sh
|
||||
|
||||
aio_monitoring_stable:
|
||||
|
@ -23,7 +23,7 @@ setPublicVar CI_BRANCH "$CIRCLE_BRANCH";
|
||||
# `.circleci/config.yml`. See http://chromedriver.chromium.org/downloads for a list of versions.
|
||||
# This variable is intended to be passed as an arg to the `webdriver-manager update` command (e.g.
|
||||
# `"postinstall": "webdriver-manager update $CI_CHROMEDRIVER_VERSION_ARG"`).
|
||||
setPublicVar CI_CHROMEDRIVER_VERSION_ARG "--versions.chrome 2.45";
|
||||
setPublicVar CI_CHROMEDRIVER_VERSION_ARG "--versions.chrome 75.0.3770.90";
|
||||
setPublicVar CI_COMMIT "$CIRCLE_SHA1";
|
||||
# `CI_COMMIT_RANGE` will only be available when `CIRCLE_COMPARE_URL` is also available (or can be
|
||||
# retrieved via `get-compare-url.js`), i.e. on push builds (a.k.a. non-PR, non-scheduled builds and
|
||||
|
@ -6,9 +6,9 @@
|
||||
* node get-commit-range <build-number> [<compare-url> [<circle-token>]]
|
||||
* ```
|
||||
*
|
||||
* Returns the value of the `CIRCLE_COMPARE_URL` environment variable (if defined) or, if this is
|
||||
* not a PR build (i.e. `CIRCLE_PR_NUMBER` is not defined), retrieves the equivalent of
|
||||
* `CIRCLE_COMPARE_URL` for jobs that are part of a rerun workflow.
|
||||
* Returns the commit range, either extracting it from `compare-url` (if defined), which is of the
|
||||
* format of the `CIRCLE_COMPARE_URL` environment variable, or by retrieving the equivalent of
|
||||
* `CIRCLE_COMPARE_URL` for jobs that are part of a rerun workflow and extracting it from there.
|
||||
*
|
||||
* **Context:**
|
||||
* CircleCI sets the `CIRCLE_COMPARE_URL` environment variable (from which we can extract the commit
|
||||
|
11
.github/CODEOWNERS
vendored
11
.github/CODEOWNERS
vendored
@ -179,6 +179,7 @@
|
||||
# ===========================================================
|
||||
#
|
||||
# - kara
|
||||
# - jasonaden
|
||||
|
||||
|
||||
# ===========================================================
|
||||
@ -533,6 +534,15 @@
|
||||
/aio/content/examples/interpolation/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/template-syntax/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/images/guide/template-syntax/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/binding-syntax/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/property-binding/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/attribute-binding/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/two-way-binding/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/built-in-directives/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/template-reference-variables/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/inputs-outputs/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/template-expression-operators/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
/aio/content/guide/pipes.md @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/examples/pipes/** @angular/fw-core @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
@ -812,6 +822,7 @@ testing/** @angular/fw-test
|
||||
/aio/content/guide/releases.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/updating.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/workspace-config.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
/aio/content/guide/deprecations.md @angular/fw-docs-packaging @angular/framework-global-approvers @angular/framework-global-approvers-for-docs-only-changes
|
||||
|
||||
|
||||
|
||||
|
26
CHANGELOG.md
26
CHANGELOG.md
@ -1,3 +1,29 @@
|
||||
<a name="8.0.2"></a>
|
||||
## [8.0.2](https://github.com/angular/angular/compare/8.0.1...8.0.2) (2019-06-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** builder workspace should use nodejs v10.16.0 ([#31088](https://github.com/angular/angular/issues/31088)) ([c198dc6](https://github.com/angular/angular/commit/c198dc6))
|
||||
* **core:** temporarily remove [@deprecated](https://github.com/deprecated) jsdoc tag for a TextBedStatic.get overload ([#30714](https://github.com/angular/angular/issues/30714)) ([0a7aebb](https://github.com/angular/angular/commit/0a7aebb)), closes [#30514](https://github.com/angular/angular/issues/30514)
|
||||
* **language-service:** Remove 'any' in getQuickInfoAtPosition ([#31014](https://github.com/angular/angular/issues/31014)) ([7f21449](https://github.com/angular/angular/commit/7f21449))
|
||||
|
||||
|
||||
|
||||
<a name="8.0.1"></a>
|
||||
## [8.0.1](https://github.com/angular/angular/compare/8.0.0...8.0.1) (2019-06-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** do not modify tsconfig.json ([#30984](https://github.com/angular/angular/issues/30984)) ([49307f0](https://github.com/angular/angular/commit/49307f0))
|
||||
* **bazel:** Load global stylesheet in dev and prod ([#30879](https://github.com/angular/angular/issues/30879)) ([5a7bcd1](https://github.com/angular/angular/commit/5a7bcd1))
|
||||
* **common:** expose the `HttpUploadProgressEvent` interface as public API ([#30852](https://github.com/angular/angular/issues/30852)) ([4e8614b](https://github.com/angular/angular/commit/4e8614b)), closes [#30814](https://github.com/angular/angular/issues/30814)
|
||||
* **core:** TypeScript related migrations should cater for BOM ([#30719](https://github.com/angular/angular/issues/30719)) ([26e3615](https://github.com/angular/angular/commit/26e3615)), closes [/github.com/angular/angular-cli/blob/master/packages/angular_devkit/schematics/src/tree/recorder.ts#L72](https://github.com//github.com/angular/angular-cli/blob/master/packages/angular_devkit/schematics/src/tree/recorder.ts/issues/L72) [#30713](https://github.com/angular/angular/issues/30713)
|
||||
* **service-worker:** avoid uncaught rejection warning when registration fails ([#30876](https://github.com/angular/angular/issues/30876)) ([08c38a1](https://github.com/angular/angular/commit/08c38a1))
|
||||
|
||||
|
||||
|
||||
<a name="8.0.0"></a>
|
||||
# [8.0.0](https://github.com/angular/angular/compare/8.0.0-rc.5...8.0.0) (2019-05-28)
|
||||
|
||||
|
@ -23,6 +23,6 @@ guidelines for [contributing][contributing] and then check out one of our issues
|
||||
|
||||
[browserstack]: https://www.browserstack.com/automate/public-build/LzF3RzBVVGt6VWE2S0hHaC9uYllOZz09LS1BVjNTclBKV0x4eVRlcjA4QVY1M0N3PT0=--eb4ce8c8dc2c1c5b2b5352d473ee12a73ac20e06
|
||||
[contributing]: https://github.com/angular/angular/blob/master/CONTRIBUTING.md
|
||||
[quickstart]: https://angular.io/guide/quickstart
|
||||
[quickstart]: https://angular.io/start
|
||||
[changelog]: https://github.com/angular/angular/blob/master/CHANGELOG.md
|
||||
[ng]: https://angular.io
|
||||
|
34
WORKSPACE
34
WORKSPACE
@ -1,4 +1,7 @@
|
||||
workspace(name = "angular")
|
||||
workspace(
|
||||
name = "angular",
|
||||
managed_directories = {"@npm": ["node_modules"]},
|
||||
)
|
||||
|
||||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
|
||||
@ -15,16 +18,15 @@ 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 = "3a3efbf223f6de733475602844ad3a8faa02abda25ab8cfe1d1ed0db134887cf",
|
||||
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/0.27.12/rules_nodejs-0.27.12.tar.gz"],
|
||||
sha256 = "e04a82a72146bfbca2d0575947daa60fda1878c8d3a3afe868a8ec39a6b968bb",
|
||||
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/0.31.1/rules_nodejs-0.31.1.tar.gz"],
|
||||
)
|
||||
|
||||
# Check the bazel version and download npm dependencies
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "check_rules_nodejs_version", "node_repositories", "yarn_install")
|
||||
|
||||
# Bazel version must be at least v0.21.0 because:
|
||||
# - 0.21.0 Using --incompatible_strict_action_env flag fixes cache when running `yarn bazel`
|
||||
# (see https://github.com/angular/angular/issues/27514#issuecomment-451438271)
|
||||
# Bazel version must be at least the following version because:
|
||||
# - 0.26.0 managed_directories feature added which is required for nodejs rules 0.30.0
|
||||
check_bazel_version(
|
||||
message = """
|
||||
You no longer need to install Bazel on your machine.
|
||||
@ -33,22 +35,28 @@ Try running `yarn bazel` instead.
|
||||
(If you did run that, check that you've got a fresh `yarn install`)
|
||||
|
||||
""",
|
||||
minimum_bazel_version = "0.21.0",
|
||||
minimum_bazel_version = "0.26.0",
|
||||
)
|
||||
|
||||
# The NodeJS rules version must be at least v0.15.3 because:
|
||||
# The NodeJS rules version must be at least the following version because:
|
||||
# - 0.15.2 Re-introduced the prod_only attribute on yarn_install
|
||||
# - 0.15.3 Includes a fix for the `jasmine_node_test` rule ignoring target tags
|
||||
# - 0.16.8 Supports npm installed bazel workspaces
|
||||
# - 0.26.0 Fix for data files in yarn_install and npm_install
|
||||
# - 0.27.12 Adds NodeModuleSources provider for transtive npm deps support
|
||||
check_rules_nodejs_version("0.27.12")
|
||||
# - 0.30.0 yarn_install now uses symlinked node_modules with new managed directories Bazel 0.26.0 feature
|
||||
# - 0.31.1 entry_point attribute of nodejs_binary & rollup_bundle is now a label
|
||||
check_rules_nodejs_version(minimum_version_string = "0.31.1")
|
||||
|
||||
# Setup the Node.js toolchain
|
||||
node_repositories(
|
||||
node_version = "10.9.0",
|
||||
node_repositories = {
|
||||
"10.16.0-darwin_amd64": ("node-v10.16.0-darwin-x64.tar.gz", "node-v10.16.0-darwin-x64", "6c009df1b724026d84ae9a838c5b382662e30f6c5563a0995532f2bece39fa9c"),
|
||||
"10.16.0-linux_amd64": ("node-v10.16.0-linux-x64.tar.xz", "node-v10.16.0-linux-x64", "1827f5b99084740234de0c506f4dd2202a696ed60f76059696747c34339b9d48"),
|
||||
"10.16.0-windows_amd64": ("node-v10.16.0-win-x64.zip", "node-v10.16.0-win-x64", "aa22cb357f0fb54ccbc06b19b60e37eefea5d7dd9940912675d3ed988bf9a059"),
|
||||
},
|
||||
node_version = "10.16.0",
|
||||
package_json = ["//:package.json"],
|
||||
preserve_symlinks = True,
|
||||
# yarn 1.13.0 under Bazel has a regression on Windows that causes build errors on rebuilds:
|
||||
# ```
|
||||
# ERROR: Source forest creation failed: C:/.../fyuc5c3n/execroot/angular/external (Directory not empty)
|
||||
@ -71,6 +79,10 @@ yarn_install(
|
||||
package_json = "//:package.json",
|
||||
# Don't install devDependencies, they are large and not used under Bazel
|
||||
prod_only = True,
|
||||
# Temporarily disable node_modules symlinking until the fix for
|
||||
# https://github.com/bazelbuild/bazel/issues/8487 makes it into a
|
||||
# future Bazel release
|
||||
symlink_node_modules = False,
|
||||
yarn_lock = "//:yarn.lock",
|
||||
)
|
||||
|
||||
|
@ -6,8 +6,7 @@
|
||||
# For additional information see: https://developers.google.com/search/docs/guides/rendering
|
||||
|
||||
> 0.5%
|
||||
last 2 versions
|
||||
last 2 major versions
|
||||
Firefox ESR
|
||||
not dead
|
||||
IE 9-11 # For IE 9-11 support.
|
||||
Chrome 41 # For Googlebot support.
|
||||
IE 11
|
||||
|
@ -12,7 +12,7 @@ import { Component } from '@angular/core';
|
||||
// #enddocregion import-core-component
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
selector: 'app-root',
|
||||
template: 'Welcome to Angular'
|
||||
})
|
||||
export class AppComponent {
|
||||
|
@ -0,0 +1,47 @@
|
||||
'use strict'; // necessary for es6 output in node
|
||||
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Attribute binding example', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
it('should display Property Binding with Angular', function () {
|
||||
expect(element(by.css('h1')).getText()).toEqual('Attribute, class, and style bindings');
|
||||
});
|
||||
|
||||
it('should display a table', function() {
|
||||
expect(element.all(by.css('table')).isPresent()).toBe(true);
|
||||
});
|
||||
|
||||
it('should display an Aria button', function () {
|
||||
expect(element.all(by.css('button')).get(0).getText()).toBe('Go for it with Aria');
|
||||
});
|
||||
|
||||
it('should display a blue background on div', function () {
|
||||
expect(element.all(by.css('div')).get(1).getCssValue('background-color')).toEqual('rgba(25, 118, 210, 1)');
|
||||
});
|
||||
|
||||
it('should display a blue div with a red border', function () {
|
||||
expect(element.all(by.css('div')).get(4).getCssValue('border')).toEqual('2px solid rgb(212, 30, 46)');
|
||||
});
|
||||
|
||||
it('should display a div with replaced classes', function () {
|
||||
expect(element.all(by.css('div')).get(5).getAttribute('class')).toEqual('new-class');
|
||||
});
|
||||
|
||||
it('should display four buttons', function() {
|
||||
let redButton = element.all(by.css('button')).get(1);
|
||||
let saveButton = element.all(by.css('button')).get(2);
|
||||
let bigButton = element.all(by.css('button')).get(3);
|
||||
let smallButton = element.all(by.css('button')).get(4);
|
||||
|
||||
expect(redButton.getCssValue('color')).toEqual('rgba(255, 0, 0, 1)');
|
||||
expect(saveButton.getCssValue('background-color')).toEqual('rgba(0, 255, 255, 1)');
|
||||
expect(bigButton.getText()).toBe('Big');
|
||||
expect(smallButton.getText()).toBe('Small');
|
||||
});
|
||||
|
||||
});
|
@ -0,0 +1,22 @@
|
||||
.special {
|
||||
background-color: #1976d2;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.item {
|
||||
font-weight: bold;
|
||||
}
|
||||
.clearance {
|
||||
border: 2px solid #d41e2e;
|
||||
|
||||
}
|
||||
.item-clearance {
|
||||
font-style: italic;
|
||||
|
||||
}
|
||||
|
||||
.new-class {
|
||||
background-color: #ed1b2f;
|
||||
font-style: italic;
|
||||
color: #fff;
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
|
||||
<h1>Attribute, class, and style bindings</h1>
|
||||
<h2>Attribute binding</h2>
|
||||
<!-- #docregion attrib-binding-colspan -->
|
||||
<table border=1>
|
||||
<!-- expression calculates colspan=2 -->
|
||||
<tr><td [attr.colspan]="1 + 1">One-Two</td></tr>
|
||||
|
||||
<!-- ERROR: There is no `colspan` property to set!
|
||||
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
|
||||
-->
|
||||
<!-- #docregion colSpan -->
|
||||
<!-- Notice the colSpan property is camel case -->
|
||||
<tr><td [colSpan]="1 + 1">Three-Four</td></tr>
|
||||
<!-- #enddocregion colSpan -->
|
||||
|
||||
<tr><td>Five</td><td>Six</td></tr>
|
||||
</table>
|
||||
<!-- #enddocregion attrib-binding-colspan -->
|
||||
|
||||
<div>
|
||||
<!-- #docregion attrib-binding-aria -->
|
||||
<!-- create and set an aria attribute for assistive technology -->
|
||||
<button [attr.aria-label]="actionName">{{actionName}} with Aria</button>
|
||||
<!-- #enddocregion attrib-binding-aria -->
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<h2>Class binding</h2>
|
||||
|
||||
<!-- #docregion is-special -->
|
||||
<h3>toggle the "special" class on/off with a property:</h3>
|
||||
<div [class.special]="isSpecial">The class binding is special.</div>
|
||||
|
||||
<h3>binding to class.special overrides the class attribute:</h3>
|
||||
<div class="special" [class.special]="!isSpecial">This one is not so special.</div>
|
||||
|
||||
<h3>Using the bind- syntax:</h3>
|
||||
<div bind-class.special="isSpecial">This class binding is special too.</div>
|
||||
<!-- #enddocregion is-special -->
|
||||
|
||||
<!-- #docregion add-class -->
|
||||
<h3>Add a class:</h3>
|
||||
<div class="item clearance special" [class.item-clearance]="itemClearance">Add another class</div>
|
||||
<!-- #enddocregion add-class -->
|
||||
|
||||
<!-- #docregion class-override -->
|
||||
<h3>Overwrite all existing classes with a new class:</h3>
|
||||
<div class="item clearance special" [attr.class]="resetClasses">Reset all classes at once</div>
|
||||
<!-- #enddocregion class-override -->
|
||||
|
||||
<hr />
|
||||
|
||||
<h2>Style binding</h2>
|
||||
|
||||
<!-- #docregion style-binding-->
|
||||
<button [style.color]="isSpecial ? 'red': 'green'">Red</button>
|
||||
<button [style.background-color]="canSave ? 'cyan': 'grey'" >Save</button>
|
||||
<!-- #enddocregion style-binding -->
|
||||
|
||||
<!-- #docregion style-binding-condition-->
|
||||
<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button>
|
||||
<button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>
|
||||
<!-- #enddocregion style-binding-condition-->
|
@ -0,0 +1,27 @@
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
it('should create the app', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
it(`should have as title 'app'`, async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app.title).toEqual('app');
|
||||
}));
|
||||
it('should render title in a h1 tag', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
|
||||
}));
|
||||
});
|
@ -0,0 +1,15 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
actionName = 'Go for it';
|
||||
isSpecial = true;
|
||||
itemClearance = true;
|
||||
resetClasses = 'new-class';
|
||||
canSave = true;
|
||||
|
||||
}
|
18
aio/content/examples/attribute-binding/src/app/app.module.ts
Normal file
18
aio/content/examples/attribute-binding/src/app/app.module.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
14
aio/content/examples/attribute-binding/src/index.html
Normal file
14
aio/content/examples/attribute-binding/src/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>AttributeBinding</title>
|
||||
<base href="/">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
12
aio/content/examples/attribute-binding/src/main.ts
Normal file
12
aio/content/examples/attribute-binding/src/main.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
.catch(err => console.log(err));
|
10
aio/content/examples/attribute-binding/stackblitz.json
Normal file
10
aio/content/examples/attribute-binding/stackblitz.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"description": "Attribute Binding",
|
||||
"files": [
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[1,2].*"
|
||||
],
|
||||
"file": "src/app/app.component.ts",
|
||||
"tags": ["Attribute Binding"]
|
||||
}
|
76
aio/content/examples/binding-syntax/e2e/src/app.e2e-spec.ts
Normal file
76
aio/content/examples/binding-syntax/e2e/src/app.e2e-spec.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
import { logging } from 'selenium-webdriver';
|
||||
|
||||
describe('Binding syntax e2e tests', () => {
|
||||
|
||||
beforeEach(function () {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
|
||||
// helper function used to test what's logged to the console
|
||||
async function logChecker(button, contents) {
|
||||
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
||||
const message = logs.filter(({ message }) => message.indexOf(contents) !== -1 ? true : false);
|
||||
expect(message.length).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
|
||||
it('should display Binding syntax', function () {
|
||||
expect(element(by.css('h1')).getText()).toEqual('Binding syntax');
|
||||
});
|
||||
|
||||
it('should display Save button', function () {
|
||||
expect(element.all(by.css('button')).get(0).getText()).toBe('Save');
|
||||
});
|
||||
|
||||
it('should display HTML attributes and DOM properties', function () {
|
||||
expect(element.all(by.css('h2')).get(1).getText()).toBe('HTML attributes and DOM properties');
|
||||
});
|
||||
|
||||
it('should display 1. Use the inspector...', function () {
|
||||
expect(element.all(by.css('p')).get(0).getText()).toContain('1. Use the inspector');
|
||||
});
|
||||
|
||||
it('should display Disabled property vs. attribute', function () {
|
||||
expect(element.all(by.css('h3')).get(0).getText()).toBe('Disabled property vs. attribute');
|
||||
});
|
||||
|
||||
|
||||
it('should log a message including Sarah', async () => {
|
||||
let attributeButton = element.all(by.css('button')).get(1);
|
||||
await attributeButton.click();
|
||||
const contents = 'Sarah';
|
||||
logChecker(attributeButton, contents);
|
||||
});
|
||||
|
||||
it('should log a message including Sarah for DOM property', async () => {
|
||||
let DOMPropertyButton = element.all(by.css('button')).get(2);
|
||||
await DOMPropertyButton.click();
|
||||
const contents = 'Sarah';
|
||||
logChecker(DOMPropertyButton, contents);
|
||||
});
|
||||
|
||||
it('should log a message including Sally for DOM property', async () => {
|
||||
let DOMPropertyButton = element.all(by.css('button')).get(2);
|
||||
let input = element(by.css('input'));
|
||||
input.sendKeys('Sally');
|
||||
await DOMPropertyButton.click();
|
||||
const contents = 'Sally';
|
||||
logChecker(DOMPropertyButton, contents);
|
||||
});
|
||||
|
||||
it('should log a message that Test Button works', async () => {
|
||||
let testButton = element.all(by.css('button')).get(3);
|
||||
await testButton.click();
|
||||
const contents = 'Test';
|
||||
logChecker(testButton, contents);
|
||||
});
|
||||
|
||||
it('should toggle Test Button disabled', async () => {
|
||||
let toggleButton = element.all(by.css('button')).get(4);
|
||||
await toggleButton.click();
|
||||
const contents = 'true';
|
||||
logChecker(toggleButton, contents);
|
||||
});
|
||||
});
|
@ -0,0 +1,3 @@
|
||||
div {
|
||||
padding: .25rem 0;
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
|
||||
<div>
|
||||
<h1>Binding syntax</h1>
|
||||
<hr />
|
||||
|
||||
<div>
|
||||
<h2>Button disabled state bound to isUnchanged property</h2>
|
||||
<!-- #docregion disabled-button -->
|
||||
<!-- Bind button disabled state to `isUnchanged` property -->
|
||||
<button [disabled]="isUnchanged">Save</button>
|
||||
<!-- #enddocregion disabled-button -->
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div (keyup)="0">
|
||||
<h2>HTML attributes and DOM properties</h2>
|
||||
<p>1. Use the inspector to see the HTML attribute and DOM property values. Click the buttons to log values to the console.</p>
|
||||
|
||||
<label>HTML Attribute Initializes to "Sarah":
|
||||
<input type="text" value="Sarah" #bindingInput></label>
|
||||
<div>
|
||||
<button (click)="getHTMLAttributeValue()">Get HTML attribute value</button> Won't change.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button (click)="getDOMPropertyValue()">Get DOM property value</button> Changeable. Angular works with these.
|
||||
</div>
|
||||
|
||||
<p>2. Change the name in the input and click the buttons again.</p>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div>
|
||||
<h3>Disabled property vs. attribute</h3>
|
||||
<p>Use the inspector to see the Test Button work and its disabled property toggle.</p>
|
||||
<div>
|
||||
<button id="testButton" (click)="working()">Test Button</button>
|
||||
</div>
|
||||
<div>
|
||||
<button (click)="toggleDisabled()">Toggle disabled property for Test Button</button>
|
||||
</div>
|
||||
|
||||
</div>
|
@ -0,0 +1,27 @@
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
it('should create the app', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
it(`should have as title 'app'`, async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app.title).toEqual('app');
|
||||
}));
|
||||
it('should render title in a h1 tag', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
|
||||
}));
|
||||
});
|
33
aio/content/examples/binding-syntax/src/app/app.component.ts
Normal file
33
aio/content/examples/binding-syntax/src/app/app.component.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { Component, ViewChild, ElementRef } from '@angular/core';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
|
||||
@ViewChild('bindingInput', { static: false }) bindingInput: ElementRef;
|
||||
|
||||
isUnchanged = true;
|
||||
|
||||
getHTMLAttributeValue(): any {
|
||||
console.warn('HTML attribute value: ' + this.bindingInput.nativeElement.getAttribute('value'));
|
||||
}
|
||||
|
||||
getDOMPropertyValue(): any {
|
||||
console.warn('DOM property value: ' + this.bindingInput.nativeElement.value);
|
||||
}
|
||||
|
||||
working(): any {
|
||||
console.warn('Test Button works!');
|
||||
}
|
||||
|
||||
toggleDisabled(): any {
|
||||
|
||||
let testButton = <HTMLInputElement> document.getElementById('testButton');
|
||||
testButton.disabled = !testButton.disabled;
|
||||
console.warn(testButton.disabled);
|
||||
}
|
||||
}
|
18
aio/content/examples/binding-syntax/src/app/app.module.ts
Normal file
18
aio/content/examples/binding-syntax/src/app/app.module.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
14
aio/content/examples/binding-syntax/src/index.html
Normal file
14
aio/content/examples/binding-syntax/src/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!-- #docregion -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<base href="/">
|
||||
<title>Angular binding syntax example</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<app-root>Loading...</app-root>
|
||||
</body>
|
||||
</html>
|
||||
<!-- #enddocregion -->
|
12
aio/content/examples/binding-syntax/src/main.ts
Normal file
12
aio/content/examples/binding-syntax/src/main.ts
Normal file
@ -0,0 +1,12 @@
|
||||
// #docregion
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
10
aio/content/examples/binding-syntax/stackblitz.json
Normal file
10
aio/content/examples/binding-syntax/stackblitz.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"description": "Binding Syntax",
|
||||
"files": [
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[1,2].*"
|
||||
],
|
||||
"file": "src/app/app.component.ts",
|
||||
"tags": ["Binding Syntax"]
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
'use strict';
|
||||
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('Built-in Directives', function () {
|
||||
|
||||
beforeAll(function () {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
it('should have title Built-in Directives', function () {
|
||||
let title = element.all(by.css('h1')).get(0);
|
||||
expect(title.getText()).toEqual('Built-in Directives');
|
||||
});
|
||||
|
||||
it('should change first Teapot header', async () => {
|
||||
let firstLabel = element.all(by.css('p')).get(0);
|
||||
let firstInput = element.all(by.css('input')).get(0);
|
||||
|
||||
expect(firstLabel.getText()).toEqual('Current item name: Teapot');
|
||||
firstInput.sendKeys('abc');
|
||||
expect(firstLabel.getText()).toEqual('Current item name: Teapotabc');
|
||||
});
|
||||
|
||||
|
||||
it('should modify sentence when modified checkbox checked', function () {
|
||||
let modifiedChkbxLabel = element.all(by.css('input[type="checkbox"]')).get(1);
|
||||
let modifiedSentence = element.all(by.css('div')).get(1);
|
||||
|
||||
modifiedChkbxLabel.click();
|
||||
expect(modifiedSentence.getText()).toContain('modified');
|
||||
});
|
||||
|
||||
it('should modify sentence when normal checkbox checked', function () {
|
||||
let normalChkbxLabel = element.all(by.css('input[type="checkbox"]')).get(4);
|
||||
let normalSentence = element.all(by.css('div')).get(7);
|
||||
|
||||
normalChkbxLabel.click();
|
||||
expect(normalSentence.getText()).toContain('normal weight and, extra large');
|
||||
});
|
||||
|
||||
it('should toggle app-item-detail', function () {
|
||||
let toggleButton = element.all(by.css('button')).get(3);
|
||||
let toggledDiv = element.all(by.css('app-item-detail')).get(0);
|
||||
|
||||
toggleButton.click();
|
||||
expect(toggledDiv.isDisplayed()).toBe(true);
|
||||
});
|
||||
|
||||
it('should hide app-item-detail', function () {
|
||||
let hiddenMessage = element.all(by.css('p')).get(11);
|
||||
let hiddenDiv = element.all(by.css('app-item-detail')).get(2);
|
||||
|
||||
expect(hiddenMessage.getText()).toContain('in the DOM');
|
||||
expect(hiddenDiv.isDisplayed()).toBe(true);
|
||||
});
|
||||
|
||||
it('should have 10 lists each containing the string Teapot', function () {
|
||||
let listDiv = element.all(by.cssContainingText('.box', 'Teapot'));
|
||||
expect(listDiv.count()).toBe(10);
|
||||
});
|
||||
|
||||
it('should switch case', function () {
|
||||
let tvRadioButton = element.all(by.css('input[type="radio"]')).get(3);
|
||||
let tvDiv = element(by.css('app-lost-item'));
|
||||
|
||||
let fishbowlRadioButton = element.all(by.css('input[type="radio"]')).get(4);
|
||||
let fishbowlDiv = element(by.css('app-unknown-item'));
|
||||
|
||||
tvRadioButton.click();
|
||||
expect(tvDiv.getText()).toContain('Television');
|
||||
fishbowlRadioButton.click();
|
||||
expect(fishbowlDiv.getText()).toContain('mysterious');
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
@ -0,0 +1,75 @@
|
||||
|
||||
button {
|
||||
font-size: 100%;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
div[ng-reflect-ng-switch], app-unknown-item {
|
||||
margin: .5rem 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#noTrackByCnt,
|
||||
#withTrackByCnt {
|
||||
color: darkred;
|
||||
max-width: 450px;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.box {
|
||||
border: 1px solid black;
|
||||
padding: 6px;
|
||||
max-width: 450px;
|
||||
}
|
||||
|
||||
.child-div {
|
||||
margin-left: 1em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.context {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.parent-div {
|
||||
margin-top: 1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.course {
|
||||
font-weight: bold;
|
||||
font-size: x-large;
|
||||
}
|
||||
|
||||
.helpful {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.saveable {
|
||||
color: limegreen;
|
||||
}
|
||||
|
||||
.study,
|
||||
.modified {
|
||||
font-family: "Brush Script MT";
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.toe {
|
||||
margin-left: 1em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
||||
.to-toc {
|
||||
margin-top: 10px;
|
||||
display: block;
|
||||
}
|
@ -0,0 +1,253 @@
|
||||
<h1>Built-in Directives</h1>
|
||||
|
||||
<h2>Built-in attribute directives</h2>
|
||||
|
||||
<h3 id="ngModel">NgModel (two-way) Binding</h3>
|
||||
|
||||
<fieldset><h4>NgModel examples</h4>
|
||||
<p>Current item name: {{currentItem.name}}</p>
|
||||
<p>
|
||||
<!-- #docregion without-NgModel -->
|
||||
<label for="without">without NgModel:</label>
|
||||
<input [value]="currentItem.name" (input)="currentItem.name=$event.target.value" id="without">
|
||||
<!-- #enddocregion without-NgModel -->
|
||||
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<!-- #docregion NgModel-1 -->
|
||||
<label for="example-ngModel">[(ngModel)]:</label>
|
||||
<input [(ngModel)]="currentItem.name" id="example-ngModel">
|
||||
<!-- #enddocregion NgModel-1 -->
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="example-bindon">bindon-ngModel: </label>
|
||||
<input bindon-ngModel="currentItem.name" id="example-bindon">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<!-- #docregion NgModelChange -->
|
||||
<label for="example-change">(ngModelChange)="...name=$event":</label>
|
||||
<input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change">
|
||||
<!-- #enddocregion NgModelChange -->
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="example-uppercase">(ngModelChange)="setUppercaseName($event)"
|
||||
<!-- #docregion uppercase -->
|
||||
<input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase">
|
||||
<!-- #enddocregion uppercase -->
|
||||
</label>
|
||||
</p>
|
||||
</fieldset>
|
||||
|
||||
<hr><h2 id="ngClass">NgClass Binding</h2>
|
||||
|
||||
<p>currentClasses is {{currentClasses | json}}</p>
|
||||
<!-- #docregion NgClass-1 -->
|
||||
<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div>
|
||||
<!-- #enddocregion NgClass-1 -->
|
||||
<ul>
|
||||
<li>
|
||||
<label for="saveable">saveable</label>
|
||||
<input type="checkbox" [(ngModel)]="canSave" id="saveable">
|
||||
</li>
|
||||
<li>
|
||||
<label for="modified">modified:</label>
|
||||
<input type="checkbox" [value]="!isUnchanged" (change)="isUnchanged=!isUnchanged" id="modified"></li>
|
||||
<li>
|
||||
<label for="special">special: <input type="checkbox" [(ngModel)]="isSpecial" id="special"></label>
|
||||
</li>
|
||||
</ul>
|
||||
<button (click)="setCurrentClasses()">Refresh currentClasses</button>
|
||||
|
||||
<div [ngClass]="currentClasses">
|
||||
This div should be {{ canSave ? "": "not"}} saveable,
|
||||
{{ isUnchanged ? "unchanged" : "modified" }} and
|
||||
{{ isSpecial ? "": "not"}} special after clicking "Refresh".</div>
|
||||
<br><br>
|
||||
<!-- #docregion special-div -->
|
||||
<!-- toggle the "special" class on/off with a property -->
|
||||
<div [ngClass]="isSpecial ? 'special' : ''">This div is special</div>
|
||||
<!-- #enddocregion special-div -->
|
||||
<div class="helpful study course">Helpful study course</div>
|
||||
<div [ngClass]="{'helpful':false, 'study':true, 'course':true}">Study course</div>
|
||||
|
||||
|
||||
<!-- NgStyle binding -->
|
||||
<hr><h3>NgStyle Binding</h3>
|
||||
<!-- #docregion without-ng-style -->
|
||||
<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'">
|
||||
This div is x-large or smaller.
|
||||
</div>
|
||||
<!-- #enddocregion without-ng-style -->
|
||||
|
||||
|
||||
<h4>[ngStyle] binding to currentStyles - CSS property names</h4>
|
||||
<p>currentStyles is {{currentStyles | json}}</p>
|
||||
|
||||
<!-- #docregion NgStyle-2 -->
|
||||
<div [ngStyle]="currentStyles">
|
||||
This div is initially italic, normal weight, and extra large (24px).
|
||||
</div>
|
||||
<!-- #enddocregion NgStyle-2 -->
|
||||
|
||||
|
||||
|
||||
<br>
|
||||
<label>italic: <input type="checkbox" [(ngModel)]="canSave"></label> |
|
||||
<label>normal: <input type="checkbox" [(ngModel)]="isUnchanged"></label> |
|
||||
<label>xlarge: <input type="checkbox" [(ngModel)]="isSpecial"></label>
|
||||
<button (click)="setCurrentStyles()">Refresh currentStyles</button>
|
||||
<br><br>
|
||||
<div [ngStyle]="currentStyles">
|
||||
This div should be {{ canSave ? "italic": "plain"}},
|
||||
{{ isUnchanged ? "normal weight" : "bold" }} and,
|
||||
{{ isSpecial ? "extra large": "normal size"}} after clicking "Refresh".</div>
|
||||
|
||||
<hr>
|
||||
<h2>Built-in structural directives</h2>
|
||||
<h3 id="ngIf">NgIf Binding</h3>
|
||||
<div>
|
||||
<p>If isActive is true, app-item-detail will render: </p>
|
||||
<!-- #docregion NgIf-1 -->
|
||||
<app-item-detail *ngIf="isActive" [item]="item"></app-item-detail>
|
||||
<!-- #enddocregion NgIf-1 -->
|
||||
|
||||
<button (click)="isActiveToggle()">Toggle app-item-detail</button>
|
||||
</div>
|
||||
<p>If currentCustomer isn't null, say hello to Laura:</p>
|
||||
<!-- #docregion NgIf-2 -->
|
||||
<div *ngIf="currentCustomer">Hello, {{currentCustomer.name}}</div>
|
||||
<!-- #enddocregion NgIf-2 -->
|
||||
<p>nullCustomer is null by default. NgIf guards against null. Give it a value to show it:</p>
|
||||
<!-- #docregion NgIf-2b -->
|
||||
<div *ngIf="nullCustomer">Hello, <span>{{nullCustomer}}</span></div>
|
||||
<!-- #enddocregion NgIf-2b -->
|
||||
<button (click)="giveNullCustomerValue()">Give nullCustomer a value</button>
|
||||
|
||||
|
||||
<h4>NgIf binding with template (no *)</h4>
|
||||
|
||||
<ng-template [ngIf]="currentItem">Add {{currentItem.name}} with template</ng-template>
|
||||
<hr>
|
||||
|
||||
<h4>Show/hide vs. NgIf</h4>
|
||||
<!-- #docregion NgIf-3 -->
|
||||
<!-- isSpecial is true -->
|
||||
<div [class.hidden]="!isSpecial">Show with class</div>
|
||||
<div [class.hidden]="isSpecial">Hide with class</div>
|
||||
|
||||
<p>ItemDetail is in the DOM but hidden</p>
|
||||
<app-item-detail [class.hidden]="isSpecial"></app-item-detail>
|
||||
|
||||
<div [style.display]="isSpecial ? 'block' : 'none'">Show with style</div>
|
||||
<div [style.display]="isSpecial ? 'none' : 'block'">Hide with style</div>
|
||||
<!-- #enddocregion NgIf-3 -->
|
||||
|
||||
|
||||
<hr>
|
||||
<h2 id="ngFor">NgFor Binding</h2>
|
||||
|
||||
<div class="box">
|
||||
<!-- #docregion NgFor-1, NgFor-1-2 -->
|
||||
<div *ngFor="let item of items">{{item.name}}</div>
|
||||
<!-- #enddocregion NgFor-1, NgFor-1-2 -->
|
||||
</div>
|
||||
|
||||
<p>*ngFor with ItemDetailComponent element</p>
|
||||
<div class="box">
|
||||
<!-- #docregion NgFor-2, NgFor-1-2 -->
|
||||
<app-item-detail *ngFor="let item of items" [item]="item"></app-item-detail>
|
||||
<!-- #enddocregion NgFor-2, NgFor-1-2 -->
|
||||
</div>
|
||||
|
||||
|
||||
<h4 id="ngFor-index">*ngFor with index</h4>
|
||||
<p>with <i>semi-colon</i> separator</p>
|
||||
<div class="box">
|
||||
<!-- #docregion NgFor-3 -->
|
||||
<div *ngFor="let item of items; let i=index">{{i + 1}} - {{item.name}}</div>
|
||||
<!-- #enddocregion NgFor-3 -->
|
||||
</div>
|
||||
|
||||
<p>with <i>comma</i> separator</p>
|
||||
<div class="box">
|
||||
<div *ngFor="let item of items, let i=index">{{i + 1}} - {{item.name}}</div>
|
||||
</div>
|
||||
|
||||
<h4 id="ngFor-trackBy">*ngFor trackBy</h4>
|
||||
<button (click)="resetList()">Reset items</button>
|
||||
<button (click)="changeIds()">Change ids</button>
|
||||
<button (click)="clearTrackByCounts()">Clear counts</button>
|
||||
|
||||
<p><i>without</i> trackBy</p>
|
||||
<div class="box">
|
||||
<div #noTrackBy *ngFor="let item of items">({{item.id}}) {{item.name}}</div>
|
||||
|
||||
<div id="noTrackByCnt" *ngIf="itemsNoTrackByCount" >
|
||||
Item DOM elements change #{{itemsNoTrackByCount}} without trackBy
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>with trackBy</p>
|
||||
<div class="box">
|
||||
<div #withTrackBy *ngFor="let item of items; trackBy: trackByItems">({{item.id}}) {{item.name}}</div>
|
||||
|
||||
<div id="withTrackByCnt" *ngIf="itemsWithTrackByCount">
|
||||
Item DOM elements change #{{itemsWithTrackByCount}} with trackBy
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br><br><br>
|
||||
|
||||
<p>with trackBy and <i>semi-colon</i> separator</p>
|
||||
<div class="box">
|
||||
<!-- #docregion trackBy -->
|
||||
<div *ngFor="let item of items; trackBy: trackByItems">
|
||||
({{item.id}}) {{item.name}}
|
||||
</div>
|
||||
<!-- #enddocregion trackBy -->
|
||||
</div>
|
||||
|
||||
<p>with trackBy and <i>comma</i> separator</p>
|
||||
<div class="box">
|
||||
<div *ngFor="let item of items, trackBy: trackByItems">({{item.id}}) {{item.name}}</div>
|
||||
</div>
|
||||
|
||||
<p>with trackBy and <i>space</i> separator</p>
|
||||
<div class="box">
|
||||
<div *ngFor="let item of items trackBy: trackByItems">({{item.id}}) {{item.name}}</div>
|
||||
</div>
|
||||
|
||||
<p>with <i>generic</i> trackById function</p>
|
||||
<div class="box">
|
||||
<div *ngFor="let item of items, trackBy: trackById">({{item.id}}) {{item.name}}</div>
|
||||
</div>
|
||||
|
||||
<hr><h2>NgSwitch Binding</h2>
|
||||
|
||||
<p>Pick your favorite item</p>
|
||||
<div>
|
||||
<label *ngFor="let i of items">
|
||||
<div><input type="radio" name="items" [(ngModel)]="currentItem" [value]="i">{{i.name}}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- #docregion NgSwitch -->
|
||||
<div [ngSwitch]="currentItem.feature">
|
||||
<app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item>
|
||||
<app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item>
|
||||
<app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item>
|
||||
<app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item>
|
||||
<!-- #enddocregion NgSwitch -->
|
||||
<!-- #docregion NgSwitch-div -->
|
||||
<div *ngSwitchCase="'bright'"> Are you as bright as {{currentItem.name}}?</div>
|
||||
<!-- #enddocregion NgSwitch-div -->
|
||||
<!-- #docregion NgSwitch -->
|
||||
<app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item>
|
||||
</div>
|
||||
<!-- #enddocregion NgSwitch -->
|
||||
|
@ -0,0 +1,115 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Item } from './item';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
|
||||
canSave = true;
|
||||
isSpecial = true;
|
||||
isUnchanged = true;
|
||||
|
||||
isActive = true;
|
||||
nullCustomer = null;
|
||||
currentCustomer = {
|
||||
name: 'Laura'
|
||||
};
|
||||
|
||||
item: Item; // defined to demonstrate template context precedence
|
||||
items: Item[];
|
||||
|
||||
currentItem: Item;
|
||||
|
||||
|
||||
// trackBy change counting
|
||||
itemsNoTrackByCount = 0;
|
||||
itemsWithTrackByCount = 0;
|
||||
itemsWithTrackByCountReset = 0;
|
||||
itemIdIncrement = 1;
|
||||
|
||||
ngOnInit() {
|
||||
this.resetItems();
|
||||
this.setCurrentClasses();
|
||||
this.setCurrentStyles();
|
||||
this.itemsNoTrackByCount = 0;
|
||||
}
|
||||
|
||||
setUppercaseName(name: string) {
|
||||
this.currentItem.name = name.toUpperCase();
|
||||
}
|
||||
|
||||
// #docregion setClasses
|
||||
currentClasses: {};
|
||||
setCurrentClasses() {
|
||||
// CSS classes: added/removed per current state of component properties
|
||||
this.currentClasses = {
|
||||
'saveable': this.canSave,
|
||||
'modified': !this.isUnchanged,
|
||||
'special': this.isSpecial
|
||||
};
|
||||
}
|
||||
// #enddocregion setClasses
|
||||
|
||||
// #docregion setStyles
|
||||
currentStyles: {};
|
||||
setCurrentStyles() {
|
||||
// CSS styles: set per current state of component properties
|
||||
this.currentStyles = {
|
||||
'font-style': this.canSave ? 'italic' : 'normal',
|
||||
'font-weight': !this.isUnchanged ? 'bold' : 'normal',
|
||||
'font-size': this.isSpecial ? '24px' : '12px'
|
||||
};
|
||||
}
|
||||
// #enddocregion setStyles
|
||||
|
||||
isActiveToggle() {
|
||||
this.isActive = !this.isActive;
|
||||
}
|
||||
|
||||
giveNullCustomerValue() {
|
||||
!(this.nullCustomer = null) ? (this.nullCustomer = 'Kelly') : (this.nullCustomer = null);
|
||||
}
|
||||
|
||||
resetNullItem() {
|
||||
this.nullCustomer = null;
|
||||
}
|
||||
|
||||
resetItems() {
|
||||
this.items = Item.items.map(item => item.clone());
|
||||
this.currentItem = this.items[0];
|
||||
this.item = this.currentItem;
|
||||
}
|
||||
|
||||
resetList() {
|
||||
this.resetItems()
|
||||
this.itemsWithTrackByCountReset = 0;
|
||||
this.itemsNoTrackByCount = ++this.itemsNoTrackByCount;
|
||||
}
|
||||
|
||||
changeIds() {
|
||||
|
||||
this.items.forEach(i => i.id += 1 * this.itemIdIncrement);
|
||||
this.itemsWithTrackByCountReset = -1;
|
||||
this.itemsNoTrackByCount = ++this.itemsNoTrackByCount;
|
||||
this.itemsWithTrackByCount = ++this.itemsWithTrackByCount;
|
||||
}
|
||||
|
||||
clearTrackByCounts() {
|
||||
this.resetItems();
|
||||
this.itemsNoTrackByCount = 0;
|
||||
this.itemsWithTrackByCount = 0;
|
||||
this.itemIdIncrement = 1;
|
||||
}
|
||||
// #docregion trackByItems
|
||||
trackByItems(index: number, item: Item): number { return item.id; }
|
||||
// #enddocregion trackByItems
|
||||
|
||||
trackById(index: number, item: any): number { return item['id']; }
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,34 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
// #docregion import-forms-module
|
||||
import { FormsModule } from '@angular/forms'; // <--- JavaScript import from Angular
|
||||
// #enddocregion import-forms-module
|
||||
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { ItemDetailComponent } from './item-detail/item-detail.component';
|
||||
import { ItemSwitchComponents } from './item-switch.component';
|
||||
|
||||
|
||||
// #docregion import-forms-module
|
||||
@NgModule({
|
||||
// #enddocregion import-forms-module
|
||||
declarations: [
|
||||
AppComponent,
|
||||
ItemDetailComponent,
|
||||
ItemSwitchComponents
|
||||
],
|
||||
// #docregion import-forms-module
|
||||
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule // <--- import into the NgModule
|
||||
],
|
||||
// #enddocregion import-forms-module
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
// #docregion import-forms-module
|
||||
})
|
||||
export class AppModule { }
|
||||
// #enddocregion import-forms-module
|
||||
|
@ -0,0 +1,3 @@
|
||||
<div>
|
||||
<span>{{item?.name}}</span>
|
||||
</div>
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ItemDetailComponent } from './item-detail.component';
|
||||
|
||||
describe('ItemDetailComponent', () => {
|
||||
let component: ItemDetailComponent;
|
||||
let fixture: ComponentFixture<ItemDetailComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ItemDetailComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ItemDetailComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,17 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
import { Item } from '../item';
|
||||
|
||||
@Component({
|
||||
selector: 'app-item-detail',
|
||||
templateUrl: './item-detail.component.html',
|
||||
styleUrls: ['./item-detail.component.css']
|
||||
})
|
||||
export class ItemDetailComponent {
|
||||
|
||||
|
||||
@Input() item: Item;
|
||||
|
||||
constructor() { }
|
||||
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Item } from './item';
|
||||
|
||||
@Component({
|
||||
selector: 'app-stout-item',
|
||||
template: `I'm a little {{item.name}}, short and stout!`
|
||||
})
|
||||
export class StoutItemComponent {
|
||||
@Input() item: Item;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-best-item',
|
||||
template: `This is the brightest {{item.name}} in town.`
|
||||
})
|
||||
export class BestItemComponent {
|
||||
@Input() item: Item;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-device-item',
|
||||
template: `Which is the slimmest {{item.name}}?`
|
||||
})
|
||||
export class DeviceItemComponent {
|
||||
@Input() item: Item;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-lost-item',
|
||||
template: `Has anyone seen my {{item.name}}?`
|
||||
})
|
||||
export class LostItemComponent {
|
||||
@Input() item: Item;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-unknown-item',
|
||||
template: `{{message}}`
|
||||
})
|
||||
export class UnknownItemComponent {
|
||||
@Input() item: Item;
|
||||
get message() {
|
||||
return this.item && this.item.name ?
|
||||
`${this.item.name} is strange and mysterious.` :
|
||||
'A mystery wrapped in a fishbowl.';
|
||||
}
|
||||
}
|
||||
|
||||
export const ItemSwitchComponents =
|
||||
[ StoutItemComponent, BestItemComponent, DeviceItemComponent, LostItemComponent, UnknownItemComponent ];
|
30
aio/content/examples/built-in-directives/src/app/item.ts
Normal file
30
aio/content/examples/built-in-directives/src/app/item.ts
Normal file
@ -0,0 +1,30 @@
|
||||
export class Item {
|
||||
static nextId = 0;
|
||||
|
||||
static items: Item[] = [
|
||||
new Item(
|
||||
null,
|
||||
'Teapot',
|
||||
'stout'
|
||||
),
|
||||
new Item(1, 'Lamp', 'bright'),
|
||||
new Item(2, 'Phone', 'slim' ),
|
||||
new Item(3, 'Television', 'vintage' ),
|
||||
new Item(4, 'Fishbowl')
|
||||
];
|
||||
|
||||
|
||||
constructor(
|
||||
public id?: number,
|
||||
public name?: string,
|
||||
public feature?: string,
|
||||
public url?: string,
|
||||
public rate = 100,
|
||||
) {
|
||||
this.id = id ? id : Item.nextId++;
|
||||
}
|
||||
|
||||
clone(): Item {
|
||||
return Object.assign(new Item(), this);
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 32 KiB |
14
aio/content/examples/built-in-directives/src/index.html
Normal file
14
aio/content/examples/built-in-directives/src/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>BuiltInDirectives</title>
|
||||
<base href="/">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
12
aio/content/examples/built-in-directives/src/main.ts
Normal file
12
aio/content/examples/built-in-directives/src/main.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
.catch(err => console.log(err));
|
10
aio/content/examples/built-in-directives/stackblitz.json
Normal file
10
aio/content/examples/built-in-directives/stackblitz.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"description": "Built-in Directives",
|
||||
"files": [
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[1,2].*"
|
||||
],
|
||||
"file": "src/app/app.component.ts",
|
||||
"tags": ["Built-in Directives"]
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-ctor',
|
||||
selector: 'app-root',
|
||||
template: `
|
||||
<h1>{{title}} [Ctor version]</h1>
|
||||
<h2>My favorite hero is: {{myHero}}</h2>
|
||||
`
|
||||
})
|
||||
// #docregion class
|
||||
export class AppCtorComponent {
|
||||
export class AppComponent {
|
||||
title: string;
|
||||
myHero: string;
|
||||
|
||||
|
@ -36,14 +36,14 @@ export class AdBannerComponent implements OnInit, OnDestroy {
|
||||
|
||||
loadComponent() {
|
||||
this.currentAdIndex = (this.currentAdIndex + 1) % this.ads.length;
|
||||
let adItem = this.ads[this.currentAdIndex];
|
||||
const adItem = this.ads[this.currentAdIndex];
|
||||
|
||||
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(adItem.component);
|
||||
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(adItem.component);
|
||||
|
||||
let viewContainerRef = this.adHost.viewContainerRef;
|
||||
const viewContainerRef = this.adHost.viewContainerRef;
|
||||
viewContainerRef.clear();
|
||||
|
||||
let componentRef = viewContainerRef.createComponent(componentFactory);
|
||||
const componentRef = viewContainerRef.createComponent(componentFactory);
|
||||
(<AdComponent>componentRef.instance).data = adItem.data;
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
<!-- #docregion prices -->
|
||||
|
||||
<div class="cart-item" *ngFor="let item of items">
|
||||
<span>{{ item.name }} </span>
|
||||
<span>{{ item.name }}</span>
|
||||
<span>{{ item.price | currency }}</span>
|
||||
</div>
|
||||
<!-- #enddocregion prices -->
|
||||
<!-- #enddocregion prices -->
|
||||
|
@ -1,6 +1,6 @@
|
||||
<h3>Shipping Prices</h3>
|
||||
|
||||
<div class="shipping-item" *ngFor="let shipping of shippingCosts | async">
|
||||
<span>{{ shipping.type }} </span>
|
||||
<span>{{ shipping.type }}</span>
|
||||
<span>{{ shipping.price | currency }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -16,7 +16,9 @@ export class ShippingComponent {
|
||||
// #enddocregion props
|
||||
|
||||
// #docregion inject-cart-service
|
||||
constructor(private cartService: CartService) {
|
||||
constructor(
|
||||
private cartService: CartService
|
||||
) {
|
||||
// #enddocregion inject-cart-service
|
||||
this.shippingCosts = this.cartService.getShippingPrices();
|
||||
// #docregion inject-cart-service
|
||||
|
29
aio/content/examples/getting-started/tsconfig.0.json
Normal file
29
aio/content/examples/getting-started/tsconfig.0.json
Normal file
@ -0,0 +1,29 @@
|
||||
// This tsconfig is used in the TypeScript
|
||||
// configuration guide (../guide/typescript-configuration.md)
|
||||
// to display the latest default configuration
|
||||
// Note: Update with every major release to the latest default
|
||||
// #docregion
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"importHelpers": true,
|
||||
"target": "es2015",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
],
|
||||
// #docregion lib
|
||||
"lib": [
|
||||
"es2018",
|
||||
"dom"
|
||||
]
|
||||
// #enddocregion lib
|
||||
}
|
||||
}
|
70
aio/content/examples/inputs-outputs/e2e/src/app.e2e-spec.ts
Normal file
70
aio/content/examples/inputs-outputs/e2e/src/app.e2e-spec.ts
Normal file
@ -0,0 +1,70 @@
|
||||
'use strict';
|
||||
|
||||
import { browser, element, by } from 'protractor';
|
||||
import { logging } from 'selenium-webdriver';
|
||||
|
||||
describe('Inputs and Outputs', function () {
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
|
||||
// helper function used to test what's logged to the console
|
||||
async function logChecker(button, contents) {
|
||||
const logs = await browser
|
||||
.manage()
|
||||
.logs()
|
||||
.get(logging.Type.BROWSER);
|
||||
const message = logs.filter(({ message }) =>
|
||||
message.indexOf(contents) !== -1 ? true : false
|
||||
);
|
||||
console.log(message);
|
||||
expect(message.length).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
it('should have title Inputs and Outputs', function () {
|
||||
let title = element.all(by.css('h1')).get(0);
|
||||
expect(title.getText()).toEqual('Inputs and Outputs');
|
||||
});
|
||||
|
||||
it('should add 123 to the parent list', async () => {
|
||||
let addToParentButton = element.all(by.css('button')).get(0);
|
||||
let addToListInput = element.all(by.css('input')).get(0);
|
||||
let addedItem = element.all(by.css('li')).get(4);
|
||||
await addToListInput.sendKeys('123');
|
||||
await addToParentButton.click();
|
||||
expect(addedItem.getText()).toEqual('123');
|
||||
});
|
||||
|
||||
it('should delete item', async () => {
|
||||
let deleteButton = element.all(by.css('button')).get(1);
|
||||
const contents = 'Child';
|
||||
await deleteButton.click();
|
||||
await logChecker(deleteButton, contents);
|
||||
});
|
||||
|
||||
it('should log buy the item', async () => {
|
||||
let buyButton = element.all(by.css('button')).get(2);
|
||||
const contents = 'Child';
|
||||
await buyButton.click();
|
||||
await logChecker(buyButton, contents);
|
||||
});
|
||||
|
||||
it('should save item for later', async () => {
|
||||
let saveButton = element.all(by.css('button')).get(3);
|
||||
const contents = 'Child';
|
||||
await saveButton.click();
|
||||
await logChecker(saveButton, contents);
|
||||
});
|
||||
|
||||
it('should add item to wishlist', async () => {
|
||||
let addToParentButton = element.all(by.css('button')).get(4);
|
||||
let addedItem = element.all(by.css('li')).get(6);
|
||||
await addToParentButton.click();
|
||||
expect(addedItem.getText()).toEqual('Television');
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -0,0 +1,7 @@
|
||||
<p>Save for later item: {{input1}}</p>
|
||||
<button (click)="saveIt()"> Save for later</button>
|
||||
|
||||
|
||||
<p>Item for wishlist: {{input2}}</p>
|
||||
<button (click)="wishForIt()"> Add to wishlist</button>
|
||||
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AliasingComponent } from './aliasing.component';
|
||||
|
||||
describe('AliasingComponent', () => {
|
||||
let component: AliasingComponent;
|
||||
let fixture: ComponentFixture<AliasingComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ AliasingComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AliasingComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,46 @@
|
||||
/* tslint:disable:use-input-property-decorator */
|
||||
/* tslint:disable:use-output-property-decorator */
|
||||
|
||||
/* tslint:disable:no-input-rename */
|
||||
|
||||
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-aliasing',
|
||||
templateUrl: './aliasing.component.html',
|
||||
styleUrls: ['./aliasing.component.css'],
|
||||
// #docregion alias
|
||||
// tslint:disable: no-inputs-metadata-property no-outputs-metadata-property
|
||||
inputs: ['input1: saveForLaterItem'], // propertyName:alias
|
||||
outputs: ['outputEvent1: saveForLaterEvent']
|
||||
// tslint:disable: no-inputs-metadata-property no-outputs-metadata-property
|
||||
// #enddocregion alias
|
||||
|
||||
})
|
||||
export class AliasingComponent {
|
||||
|
||||
input1: string;
|
||||
outputEvent1: EventEmitter<string> = new EventEmitter<string>();
|
||||
|
||||
// #docregion alias-input-output
|
||||
@Input('wishListItem') input2: string; // @Input(alias)
|
||||
@Output('wishEvent') outputEvent2 = new EventEmitter<string>(); // @Output(alias) propertyName = ...
|
||||
// #enddocregion alias-input-output
|
||||
|
||||
|
||||
saveIt() {
|
||||
console.warn('Child says: emiting outputEvent1 with', this.input1);
|
||||
this.outputEvent1.emit(this.input1);
|
||||
}
|
||||
|
||||
wishForIt() {
|
||||
console.warn('Child says: emiting outputEvent2', this.input2);
|
||||
this.outputEvent2.emit(this.input2);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
/* tslint:enable:use-input-property-decorator */
|
||||
/* tslint:enable:use-output-property-decorator */
|
||||
|
@ -0,0 +1,45 @@
|
||||
<h1>Inputs and Outputs</h1>
|
||||
|
||||
<!-- #docregion input-parent -->
|
||||
<app-item-detail [item]="currentItem"></app-item-detail>
|
||||
<!-- #enddocregion input-parent -->
|
||||
<hr>
|
||||
|
||||
<!-- #docregion output-parent -->
|
||||
<app-item-output (newItemEvent)="addItem($event)"></app-item-output>
|
||||
<!-- #enddocregion output-parent -->
|
||||
<h3>Parent component receiving value via @Output()</h3>
|
||||
|
||||
<ul>
|
||||
<li *ngFor="let item of items">{{item}}</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Input and Output together</h2>
|
||||
<p>Open the console to see the EventEmitter at work when you click Delete.</p>
|
||||
|
||||
<!-- #docregion together -->
|
||||
<app-input-output [item]="currentItem" (deleteRequest)="crossOffItem($event)"></app-input-output>
|
||||
<!-- #enddocregion together -->
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Input and Output in the component class metadata</h2>
|
||||
<p>Open the console to see the EventEmitter at work when you click Buy.</p>
|
||||
|
||||
<app-in-the-metadata [clearanceItem]="lastChanceItem" (buyEvent)="buyClearanceItem($event)"></app-in-the-metadata>
|
||||
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Aliasing Inputs and Outputs</h2>
|
||||
<p>See aliasing.component.ts for aliases and the console for the EventEmitter console logs.</p>
|
||||
|
||||
<app-aliasing [saveForLaterItem]="currentItem" (saveForLaterEvent)="saveForLater($event)" [wishListItem]="currentItem" (wishEvent)="addToWishList($event)"></app-aliasing>
|
||||
|
||||
<h2>Wishlist:</h2>
|
||||
<ul>
|
||||
<li *ngFor="let wish of wishlist">{{wish}}</li>
|
||||
</ul>
|
||||
|
@ -0,0 +1,27 @@
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
it('should create the app', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
it(`should have as title 'app'`, async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app.title).toEqual('app');
|
||||
}));
|
||||
it('should render title in a h1 tag', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
|
||||
}));
|
||||
});
|
55
aio/content/examples/inputs-outputs/src/app/app.component.ts
Normal file
55
aio/content/examples/inputs-outputs/src/app/app.component.ts
Normal file
@ -0,0 +1,55 @@
|
||||
|
||||
// #docplaster
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
|
||||
// #docregion parent-property
|
||||
// #docregion add-new-item
|
||||
export class AppComponent {
|
||||
// #enddocregion add-new-item
|
||||
currentItem = 'Television';
|
||||
// #enddocregion parent-property
|
||||
|
||||
lastChanceItem = 'Beanbag';
|
||||
// #docregion add-new-item
|
||||
items = ['item1', 'item2', 'item3', 'item4'];
|
||||
// #enddocregion add-new-item
|
||||
wishlist = ['Drone', 'Computer'];
|
||||
|
||||
// #docregion add-new-item
|
||||
|
||||
addItem(newItem: string) {
|
||||
this.items.push(newItem);
|
||||
}
|
||||
// #enddocregion add-new-item
|
||||
|
||||
|
||||
crossOffItem(item: string) {
|
||||
console.warn(`Parent says: crossing off ${item}.`);
|
||||
}
|
||||
|
||||
buyClearanceItem(item) {
|
||||
console.warn(`Parent says: buying ${item}.`);
|
||||
}
|
||||
|
||||
saveForLater(item) {
|
||||
console.warn(`Parent says: saving ${item} for later.`);
|
||||
}
|
||||
|
||||
addToWishList(wish: string) {
|
||||
console.warn(`Parent says: adding ${this.currentItem} to your wishlist.`);
|
||||
this.wishlist.push(wish);
|
||||
console.warn(this.wishlist);
|
||||
}
|
||||
// #docregion add-new-item
|
||||
// #docregion parent-property
|
||||
}
|
||||
// #enddocregion add-new-item
|
||||
// #enddocregion parent-property
|
||||
|
28
aio/content/examples/inputs-outputs/src/app/app.module.ts
Normal file
28
aio/content/examples/inputs-outputs/src/app/app.module.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { ItemDetailComponent } from './item-detail/item-detail.component';
|
||||
import { ItemOutputComponent } from './item-output/item-output.component';
|
||||
import { InputOutputComponent } from './input-output/input-output.component';
|
||||
import { InTheMetadataComponent } from './in-the-metadata/in-the-metadata.component';
|
||||
import { AliasingComponent } from './aliasing/aliasing.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
ItemDetailComponent,
|
||||
ItemOutputComponent,
|
||||
InputOutputComponent,
|
||||
InTheMetadataComponent,
|
||||
AliasingComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
@ -0,0 +1,3 @@
|
||||
<p>Latest clearance item: {{clearanceItem}}</p>
|
||||
|
||||
<button (click)="buyIt()"> Buy it with an Output!</button>
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { InTheMetadataComponent } from './in-the-metadata.component';
|
||||
|
||||
describe('InTheMetadataComponent', () => {
|
||||
let component: InTheMetadataComponent;
|
||||
let fixture: ComponentFixture<InTheMetadataComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ InTheMetadataComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(InTheMetadataComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,32 @@
|
||||
/* tslint:disable:use-input-property-decorator */
|
||||
/* tslint:disable:use-output-property-decorator */
|
||||
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-in-the-metadata',
|
||||
templateUrl: './in-the-metadata.component.html',
|
||||
styleUrls: ['./in-the-metadata.component.css'],
|
||||
// #docregion metadata
|
||||
// tslint:disable: no-inputs-metadata-property no-outputs-metadata-property
|
||||
inputs: ['clearanceItem'],
|
||||
outputs: ['buyEvent']
|
||||
// tslint:enable: no-inputs-metadata-property no-outputs-metadata-property
|
||||
// #enddocregion metadata
|
||||
|
||||
})
|
||||
export class InTheMetadataComponent {
|
||||
|
||||
|
||||
buyEvent = new EventEmitter<string>();
|
||||
clearanceItem: string;
|
||||
|
||||
buyIt() {
|
||||
console.warn('Child says: emiting buyEvent with', this.clearanceItem);
|
||||
this.buyEvent.emit(this.clearanceItem);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* tslint:enable:use-input-property-decorator */
|
||||
/* tslint:enable:use-output-property-decorator */
|
@ -0,0 +1,2 @@
|
||||
<span [style.text-decoration]="lineThrough">Item: {{item}}</span>
|
||||
<button (click)="delete()">Delete it with an Output!</button>
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { InputOutputComponent } from './input-output.component';
|
||||
|
||||
describe('InputOutputComponent', () => {
|
||||
let component: InputOutputComponent;
|
||||
let fixture: ComponentFixture<InputOutputComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ InputOutputComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(InputOutputComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,25 @@
|
||||
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-input-output',
|
||||
templateUrl: './input-output.component.html',
|
||||
styleUrls: ['./input-output.component.css']
|
||||
})
|
||||
export class InputOutputComponent {
|
||||
// #docregion input-output
|
||||
@Input() item: string;
|
||||
// #docregion output
|
||||
@Output() deleteRequest = new EventEmitter<string>();
|
||||
// #enddocregion output
|
||||
// #enddocregion input-output
|
||||
|
||||
lineThrough = '';
|
||||
|
||||
// #docregion delete-method
|
||||
delete() {
|
||||
console.warn('Child says: emiting item deleteRequest with', this.item);
|
||||
this.deleteRequest.emit(this.item);
|
||||
this.lineThrough = this.lineThrough ? '' : 'line-through';
|
||||
}
|
||||
// #enddocregion delete-method
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<h2>Child component with @Input()</h2>
|
||||
|
||||
<!-- #docregion property-in-template -->
|
||||
<p>
|
||||
Today's item: {{item}}
|
||||
</p>
|
||||
<!-- #enddocregion property-in-template -->
|
||||
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ItemDetailComponent } from './item-detail.component';
|
||||
|
||||
describe('ItemDetailComponent', () => {
|
||||
let component: ItemDetailComponent;
|
||||
let fixture: ComponentFixture<ItemDetailComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ItemDetailComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ItemDetailComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,16 @@
|
||||
// #docplaster
|
||||
// #docregion use-input
|
||||
import { Component, Input } from '@angular/core'; // First, import Input
|
||||
// #enddocregion use-input
|
||||
|
||||
@Component({
|
||||
selector: 'app-item-detail',
|
||||
templateUrl: './item-detail.component.html',
|
||||
styleUrls: ['./item-detail.component.css']
|
||||
})
|
||||
|
||||
// #docregion use-input
|
||||
export class ItemDetailComponent {
|
||||
@Input() item: string; // decorate the property with @Input()
|
||||
}
|
||||
// #enddocregion use-input
|
@ -0,0 +1,6 @@
|
||||
<h2>Child component with @Output()</h2>
|
||||
|
||||
<!-- #docregion child-output -->
|
||||
<label>Add an item: <input #newItem></label>
|
||||
<button (click)="addNewItem(newItem.value)">Add to parent's list</button>
|
||||
<!-- #enddocregion child-output -->
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ItemOutputComponent } from './item-output.component';
|
||||
|
||||
describe('ItemOutputComponent', () => {
|
||||
let component: ItemOutputComponent;
|
||||
let fixture: ComponentFixture<ItemOutputComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ItemOutputComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ItemOutputComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,22 @@
|
||||
// #docregion imports
|
||||
import { Component, Output, EventEmitter } from '@angular/core';
|
||||
// #enddocregion imports
|
||||
|
||||
@Component({
|
||||
selector: 'app-item-output',
|
||||
templateUrl: './item-output.component.html',
|
||||
styleUrls: ['./item-output.component.css']
|
||||
})
|
||||
|
||||
// #docregion item-output-class
|
||||
export class ItemOutputComponent {
|
||||
// #docregion item-output
|
||||
|
||||
@Output() newItemEvent = new EventEmitter<string>();
|
||||
|
||||
// #enddocregion item-output
|
||||
addNewItem(value: string) {
|
||||
this.newItemEvent.emit(value);
|
||||
}
|
||||
}
|
||||
// #enddocregion item-output-class
|
14
aio/content/examples/inputs-outputs/src/index.html
Normal file
14
aio/content/examples/inputs-outputs/src/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Inputs and Outputs</title>
|
||||
<base href="/">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
12
aio/content/examples/inputs-outputs/src/main.ts
Normal file
12
aio/content/examples/inputs-outputs/src/main.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
.catch(err => console.log(err));
|
10
aio/content/examples/inputs-outputs/stackblitz.json
Normal file
10
aio/content/examples/inputs-outputs/stackblitz.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"description": "Inputs and Outputs",
|
||||
"files": [
|
||||
"!**/*.d.ts",
|
||||
"!**/*.js",
|
||||
"!**/*.[1,2].*"
|
||||
],
|
||||
"file": "src/app/app.component.ts",
|
||||
"tags": ["Inputs and Outputs"]
|
||||
}
|
@ -4,7 +4,7 @@ import { Component } from '@angular/core';
|
||||
import { heroes } from './hero';
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
|
@ -6,7 +6,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<body>
|
||||
<my-app>Loading...</my-apps>
|
||||
<app-root>Loading...</app-root>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
@ -0,0 +1,54 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
|
||||
describe('Property binding e2e tests', () => {
|
||||
|
||||
beforeEach(function () {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
it('should display Property Binding with Angular', function () {
|
||||
expect(element(by.css('h1')).getText()).toEqual('Property Binding with Angular');
|
||||
});
|
||||
|
||||
it('should display four phone pictures', function() {
|
||||
expect(element.all(by.css('img')).isPresent()).toBe(true);
|
||||
expect(element.all(by.css('img')).count()).toBe(4);
|
||||
|
||||
});
|
||||
|
||||
it('should display Disabled button', function () {
|
||||
expect(element.all(by.css('button')).get(0).getText()).toBe(`Disabled Button`);
|
||||
});
|
||||
|
||||
it('should display Binding to a property of a directive', function () {
|
||||
expect(element.all(by.css('h2')).get(4).getText()).toBe(`Binding to a property of a directive`);
|
||||
});
|
||||
|
||||
it('should display Your item is: lamp', function () {
|
||||
expect(element.all(by.css('p')).get(0).getText()).toContain(`blue`);
|
||||
});
|
||||
it('should display Your item is: lamp', function () {
|
||||
expect(element.all(by.css('p')).get(1).getText()).toContain(`Your item is: lamp`);
|
||||
});
|
||||
|
||||
it('should display Your item is: parentItem', function () {
|
||||
expect(element.all(by.css('p')).get(2).getText()).toBe(`Your item is: parentItem`);
|
||||
});
|
||||
|
||||
it('should display a ul', function () {
|
||||
expect(element.all(by.css('ul')).get(0).getText()).toContain(`tv`);
|
||||
});
|
||||
|
||||
it('should display a ul containing phone', function () {
|
||||
expect(element.all(by.css('ul')).get(1).getText()).toBe(`21 phone`);
|
||||
});
|
||||
|
||||
it('should display one-time initialized string', function () {
|
||||
expect(element.all(by.css('p')).get(3).getText()).toContain(`one-time initialized`);
|
||||
});
|
||||
|
||||
it('should display Malicious content', function () {
|
||||
expect(element.all(by.css('h2')).get(8).getText()).toBe(`Malicious content`);
|
||||
});
|
||||
});
|
@ -0,0 +1,9 @@
|
||||
div {
|
||||
margin: 1rem auto;
|
||||
width: 90%
|
||||
}
|
||||
.special {
|
||||
background-color: #1976d2;
|
||||
color: #fff;
|
||||
padding: 1rem;
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
|
||||
|
||||
<div>
|
||||
<h1>Property Binding with Angular</h1>
|
||||
<h2>Binding the src property of an image:</h2>
|
||||
<!-- #docregion property-binding -->
|
||||
<img [src]="itemImageUrl">
|
||||
<!-- #enddocregion property-binding -->
|
||||
<h2>Using bind- syntax:</h2>
|
||||
<!-- #docregion bind-prefix -->
|
||||
<img bind-src="itemImageUrl">
|
||||
<!-- #enddocregion bind-prefix -->
|
||||
<hr />
|
||||
|
||||
<h2>Binding to the colSpan property</h2>
|
||||
<table border=1>
|
||||
<tr><td>Column 1</td><td>Column 2</td></tr>
|
||||
<!-- #docregion colSpan -->
|
||||
<!-- Notice the colSpan property is camel case -->
|
||||
<tr><td [colSpan]="2">Span 2 columns</td></tr>
|
||||
<!-- #enddocregion colSpan -->
|
||||
</table>
|
||||
|
||||
|
||||
<hr />
|
||||
<h2>Button disabled state bound to isUnchanged property:</h2>
|
||||
<!-- #docregion disabled-button -->
|
||||
<!-- Bind button disabled state to `isUnchanged` property -->
|
||||
<button [disabled]="isUnchanged">Disabled Button</button>
|
||||
<!-- #enddocregion disabled-button -->
|
||||
<hr />
|
||||
|
||||
<h2>Binding to a property of a directive</h2>
|
||||
<!-- #docregion class-binding -->
|
||||
<p [ngClass]="classes">[ngClass] binding to the classes property making this blue</p>
|
||||
<!-- #enddocregion class-binding -->
|
||||
<hr />
|
||||
|
||||
<h2>Model property of a custom component:</h2>
|
||||
<!-- #docregion model-property-binding -->
|
||||
<app-item-detail [childItem]="parentItem"></app-item-detail>
|
||||
<!-- #enddocregion model-property-binding -->
|
||||
<!-- #docregion no-evaluation -->
|
||||
<app-item-detail childItem="parentItem"></app-item-detail>
|
||||
<!-- #enddocregion no-evaluation -->
|
||||
|
||||
<h3>Pass objects:</h3>
|
||||
<!-- #docregion pass-object -->
|
||||
<app-list-item [items]="currentItem"></app-list-item>
|
||||
<!-- #enddocregion pass-object -->
|
||||
|
||||
<hr />
|
||||
<h2>Initialized string:</h2>
|
||||
<!-- #docregion string-init -->
|
||||
<app-string-init prefix="This is a one-time initialized string."></app-string-init>
|
||||
<!-- #enddocregion string-init -->
|
||||
|
||||
<hr />
|
||||
|
||||
<h2>Property binding and interpolation</h2>
|
||||
<!-- #docregion property-binding-interpolation -->
|
||||
<p><img src="{{itemImageUrl}}"> is the <i>interpolated</i> image.</p>
|
||||
<p><img [src]="itemImageUrl"> is the <i>property bound</i> image.</p>
|
||||
|
||||
<p><span>"{{interpolationTitle}}" is the <i>interpolated</i> title.</span></p>
|
||||
<p>"<span [innerHTML]="propertyTitle"></span>" is the <i>property bound</i> title.</p>
|
||||
<!-- #enddocregion property-binding-interpolation -->
|
||||
|
||||
<hr />
|
||||
|
||||
<h2>Malicious content</h2>
|
||||
|
||||
<!-- #docregion malicious-interpolated -->
|
||||
<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>
|
||||
<!-- #enddocregion malicious-interpolated -->
|
||||
|
||||
<!-- #docregion malicious-content -->
|
||||
<!--
|
||||
Angular generates a warning for the following line as it sanitizes them
|
||||
WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).
|
||||
-->
|
||||
<p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>
|
||||
<!-- #enddocregion malicious-content -->
|
||||
</div>
|
@ -0,0 +1,27 @@
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
it('should create the app', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
}));
|
||||
it(`should have as title 'app'`, async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app.title).toEqual('app');
|
||||
}));
|
||||
it('should render title in a h1 tag', async(() => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
|
||||
}));
|
||||
});
|
@ -0,0 +1,30 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
itemImageUrl = '../assets/phone.png';
|
||||
isUnchanged = true;
|
||||
classes = 'special';
|
||||
// #docregion parent-data-type
|
||||
parentItem = 'lamp';
|
||||
// #enddocregion parent-data-type
|
||||
|
||||
// #docregion pass-object
|
||||
currentItem = [{
|
||||
id: 21,
|
||||
name: 'phone'
|
||||
}];
|
||||
// #enddocregion pass-object
|
||||
|
||||
interpolationTitle = 'Interpolation';
|
||||
propertyTitle = 'Property binding';
|
||||
|
||||
// #docregion malicious-content
|
||||
evilTitle = 'Template <script>alert("evil never sleeps")</script> Syntax';
|
||||
// #enddocregion malicious-content
|
||||
}
|
24
aio/content/examples/property-binding/src/app/app.module.ts
Normal file
24
aio/content/examples/property-binding/src/app/app.module.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { ItemDetailComponent } from './item-detail/item-detail.component';
|
||||
import { ListItemComponent } from './list-item/list-item.component';
|
||||
import { StringInitComponent } from './string-init/string-init.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
ItemDetailComponent,
|
||||
ListItemComponent,
|
||||
StringInitComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
@ -0,0 +1,4 @@
|
||||
<p>Your item is: {{ childItem }} </p>
|
||||
|
||||
|
||||
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ItemDetailComponent } from './item-detail.component';
|
||||
|
||||
describe('ItemDetailComponent', () => {
|
||||
let component: ItemDetailComponent;
|
||||
let fixture: ComponentFixture<ItemDetailComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ItemDetailComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ItemDetailComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,26 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
// import { Item } from '../item';
|
||||
// import { ITEMS } from '../mock-items';
|
||||
|
||||
@Component({
|
||||
selector: 'app-item-detail',
|
||||
templateUrl: './item-detail.component.html',
|
||||
styleUrls: ['./item-detail.component.css']
|
||||
})
|
||||
export class ItemDetailComponent implements OnInit {
|
||||
|
||||
// #docregion input-type
|
||||
@Input() childItem: string;
|
||||
// #enddocregion input-type
|
||||
|
||||
// items = ITEMS;
|
||||
|
||||
|
||||
currentItem = 'bananas in boxes';
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
7
aio/content/examples/property-binding/src/app/item.ts
Normal file
7
aio/content/examples/property-binding/src/app/item.ts
Normal file
@ -0,0 +1,7 @@
|
||||
// #docregion item-class
|
||||
export class Item {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
// #enddocregion item-class
|
||||
|
@ -0,0 +1,11 @@
|
||||
|
||||
<h4>Nested component's list of items:</h4>
|
||||
<ul>
|
||||
<li *ngFor="let item of listItems">{{item.id}} {{item.name}}</li>
|
||||
</ul>
|
||||
|
||||
<h4>Pass an object from parent to nested component:</h4>
|
||||
<ul>
|
||||
<li *ngFor="let item of items">{{item.id}} {{item.name}}</li>
|
||||
</ul>
|
||||
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ListItemComponent } from './list-item.component';
|
||||
|
||||
describe('ItemListComponent', () => {
|
||||
let component: ListItemComponent;
|
||||
let fixture: ComponentFixture<ListItemComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ListItemComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ListItemComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user