Compare commits

..

43 Commits
6.1.7 ... 6.1.8

Author SHA1 Message Date
3988ebf432 release: cut the v6.1.8 release 2018-09-19 14:07:10 -07:00
5099b79545 docs: copy edit (#25582)
PR Close #25582
2018-09-19 10:43:07 -07:00
038d06d2e9 docs: clean up formats, add detail (#25582)
PR Close #25582
2018-09-19 10:43:07 -07:00
9e1aff9fe6 docs: update view-related api doc (#25582)
PR Close #25582
2018-09-19 10:43:07 -07:00
a41f331cb4 docs: add ngmodule api doc (#25618)
PR Close #25618
2018-09-19 10:40:59 -07:00
71628f1837 docs(animations): updated animation docs (#24206)
PR Close #24206
2018-09-19 10:37:31 -07:00
df878a6b60 docs: delete extra sentence (#25984)
PR Close #25984
2018-09-19 09:42:12 -07:00
48d7f4e8b5 fix(bazel): move bazel managed runtime deps for downstream usage (#25690)
PR Close #25690
2018-09-18 15:09:50 -07:00
66f5d27e50 build(bazel): fix typos in comments (#25172)
PR Close #25172
2018-09-18 15:05:29 -07:00
91dd160b21 fix(bazel): correct type concatenated to devmode_js (#25467)
PR Close #25467
2018-09-18 15:04:40 -07:00
1c44b71fd2 feat(ivy): enable .ngfactory.js generation in g3 only (#25392)
This turns on generation of ngfactory.js files when compiling in Ivy
mode in g3. They're not turned on for Bazel users as there appears to
be a strange interaction with the way our tests run in Bazel mode.

PR Close #25392
2018-09-18 15:04:31 -07:00
a5e0ae501d docs(bazel): add skydoc generation (#23544)
PR Close #23544
2018-09-18 15:04:26 -07:00
2d0e642dbe fix(bazel): allow compile_strategy to be (privately) imported (#25080)
compile_strategy() is used to decide whether to build Angular code
using ngc (legacy) or ngtsc (local). In order for g3 BUILD rules
to switch properly and allow testing of Ivy in g3, they need to
import this function.

This commit removes the _ prefix which allows the function to be
imported.

PR Close #25080
2018-09-18 15:00:32 -07:00
9ea656f20e build: use nodejs public api (#25940)
`nodejs_binary` and `nodejs_test` from `@build_bazel_rules_nodejs//:defs.bzl` and `@build_bazel_rules_nodejs//internal/node:node.bzl` are different as the first one uses a macro https://github.com/bazelbuild/rules_nodejs/blob/master/internal/node/node.bzl#L229 to wrap the `nodejs_binary` and `nodejs_test` as an `.exe` for Windows.

PR Close #25940
2018-09-18 14:47:43 -07:00
97ae7aed41 feat(bazel): add additional parameters to ts_api_guardian_test def (#25694)
Added `strip_export_pattern` and `allow_module_identifiers` so that these can be passed from downstream

PR Close #25694
2018-09-18 14:47:35 -07:00
678b4209c8 fix(bazel): specify the package and lock files using the workspace (#25694)
PR Close #25694
2018-09-18 14:47:30 -07:00
b7be4f55be build: add support for running builds outside of sandbox on Mac. (#25870)
Add following to your `~/.bazelrc`. This will run the build faster locally
(outside of sandbox), but continue running the builds with sandboxing
on CI.

```
build --spawn_strategy=standalone --strategy=ESM5=sandboxed
```
PR Close #25870
2018-09-18 14:43:39 -07:00
e7c72ab556 style: reformat bzl files on patch branch to match master 2018-09-18 14:43:05 -07:00
af785f9e91 ci: enforce formatting of .bzl files (#23544)
These are now enforced in google3 so we want to match, so that PRs don't get held up when we sync

PR Close #23544
2018-09-18 14:40:59 -07:00
1ac5d68827 build: bump the com_github_bazelbuild_buildtools version to 0.12.0 (#25917)
This is a preliminary fix to make buildifier work with Bazel 0.16.

See alexeagle/angular-bazel-example/issues/173

PR Close #25917
2018-09-18 14:36:38 -07:00
2c987625ae build(bazel): update to rules_typescript 0.17.0 & rules_nodejs 0.13.4 (#25920)
PR Close #25920
2018-09-18 14:36:28 -07:00
a77f567403 ci: update to bazel 0.17 (#25967)
this includes support for @ character in labels, which we need for fine-grained deps

PR Close #25967
2018-09-18 14:32:34 -07:00
110c81f359 build: ignore aio/docs-infra commits in changelog gulp task (#25838)
PR Close #25838
2018-09-18 13:23:06 -07:00
ef4b5c7e59 build: update conventional-changelog and simplify changelog gulp task (#25838)
The version prefix issue has been fixed with
conventional-changelog/conventional-changelog#179.

PR Close #25838
2018-09-18 13:23:00 -07:00
c69362442d build: update .nvmrc file to correct node version (#25992)
The version was updated in 34ec9244a6
but this file got missed.
PR Close #25992
2018-09-18 13:11:58 -07:00
6c8791ee32 docs(forms): change documentation of the FormGroup patchValue method (#25901)
Improve the grammar of the description to make it more readable.
PR Close #25901
2018-09-18 13:08:06 -07:00
274dc1e972 build(bazel): update to rules_typescript 0.17.0 & rules_nodejs 0.13.4 (#25977)
PR Close #25977
2018-09-18 13:03:37 -07:00
d9bd86050b docs: fix typo in bootstrapping guide (#25939)
Fixes #25938

PR Close #25939
2018-09-14 16:38:18 -07:00
076374ba4f docs: delete old comments from example (#25931)
PR Close #25931
2018-09-13 15:33:33 -07:00
e117b1ffd2 fix(router): mount correct component if router outlet was not instantiated and if using a route reuse strategy (#25313) (#25314)
This unsets 'attachRef' on outlet context if no route is to be reused in route activation.

Closes #25313

PR Close #25314
2018-09-11 16:26:42 -07:00
8d7fbb614b docs(router): change description of parameter usage for navigation functions (#21840)
PR Close #21840
2018-09-11 16:24:23 -07:00
a31cfc521c fix(compiler): Fix look up of entryComponents in AOT Summaries (#24892)
Previously, when you attempted to bootstrap a component that had a
router-outlet using ngsummaries, it would complain that the component
was not provided by any module even if it was. This commit fixes a
mistake (AFAICT) which caused the lookup of the component in the AOT
summaries to fail.

I believe this change is safe. I've run the affected tests within Google
and there were no breakages caused by this change.

PR Close #24892
2018-09-11 16:23:17 -07:00
55a1ce7adf docs: add disableTypeScriptVersionCheck documentation (#25537)
PR Close #25537
2018-09-11 12:25:55 -07:00
9f3da659aa docs(aio): add ng-sq-ui to resources (#25874)
PR Close #25874
2018-09-10 10:31:11 -07:00
8f9aeaaa67 docs: move compiler options to last section of the page (#22353)
PR Close #22353
2018-09-10 10:30:01 -07:00
b9a5ce1c06 docs(service-worker): update http-server command (#25845)
PR Close #25845
2018-09-06 14:59:51 -07:00
f67229efa3 build(docs-infra): ensure any stale generated content is deleted (#25841)
Since `aio/src/generated/` is git-ignored, it is easy for stale content
(e.g. removed images, examples, zips, etc.) to remain there on local
clones and then get copied into the `dist/` directory.

This commit ensures `aio/src/generated/` is cleaned up before generating
the new content.

PR Close #25841
2018-09-06 14:59:29 -07:00
f707f545aa build: update to Node 10 (#25822)
PR Close #25822
2018-09-06 14:58:30 -07:00
62f4ea5f0f test(docs-infra): fix double-slash in URL of aio_monitoring test (#25641) (#25820)
As part of the tests run in the CircleCI `aio_monitoring` job, we need
to retrieve `sitemap.xml` from the site under test. Previously, the URL
used to retrieve that contained a double-slash (`//`). At some point,
Firebase (which is used for hosting the site) stopped normalizing
double-slashes to a single slash, causing the test to fail.

This commit fixes the problem by ensuring that the constructed URLs do
not contain double-slashes.

PR Close #25641

PR Close #25820
2018-09-06 14:58:09 -07:00
ecc3406ca6 test(docs-infra): correctly extract sitemap URLs (#19795) (#25820)
`%%DEPLOYMENT_HOST%%` has been assumed to be the host prefix for sitemap
URLs since bf29936af, but afaict this was never the case.

PR Close #19795

PR Close #25820
2018-09-06 14:58:09 -07:00
e244b5180e build(bazel): update bazel integration to build angular from source (#25774)
PR Close #25774
2018-09-06 14:56:53 -07:00
f85d3d7857 build(bazel): make resolveTypeReferenceDirectives override work with both ts 2.9 & ts 3.0 (#25774)
PR Close #25774
2018-09-06 14:56:53 -07:00
b404d47b16 build(bazel): fix bazel types reference directive resolves (#25774)
PR Close #25774
2018-09-06 14:56:53 -07:00
216 changed files with 5358 additions and 4275 deletions

View File

@ -12,8 +12,8 @@
## IMPORTANT
# If you change the `docker_image` version, also change the `cache_key` suffix and the version of
# `com_github_bazelbuild_buildtools` in the `/WORKSPACE` file.
var_1: &docker_image angular/ngcontainer:0.4.0
var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.4.0
var_1: &docker_image angular/ngcontainer:0.6.0
var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.6.0
# Define common ENV vars
var_3: &define_env_vars
@ -48,7 +48,7 @@ jobs:
# Check BUILD.bazel formatting before we have a node_modules directory
# Then we don't need any exclude pattern to avoid checking those files
- run: 'buildifier -mode=check $(find . -type f \( -name BUILD.bazel -or -name BUILD \)) ||
- run: 'buildifier -mode=check $(find . -type f \( -name "*.bzl" -or -name BUILD.bazel -or -name BUILD \)) ||
(echo "BUILD files not formatted. Please run ''yarn buildifier''" ; exit 1)'
# Run the skylark linter to check our Bazel rules
# deprecated-api is disabled because we use actions.new_file(genfiles_dir)

2
.nvmrc
View File

@ -1 +1 @@
8.9
10.9.0

View File

@ -2,7 +2,7 @@ language: node_js
sudo: false
dist: trusty
node_js:
- '8.9.1'
- '10.9.0'
addons:
# firefox: "38.0"

View File

@ -1,3 +1,24 @@
<a name="6.1.8"></a>
## [6.1.8](https://github.com/angular/angular/compare/6.1.7...6.1.8) (2018-09-19)
### Bug Fixes
* **bazel:** allow compile_strategy to be (privately) imported ([#25080](https://github.com/angular/angular/issues/25080)) ([2d0e642](https://github.com/angular/angular/commit/2d0e642))
* **bazel:** correct type concatenated to devmode_js ([#25467](https://github.com/angular/angular/issues/25467)) ([91dd160](https://github.com/angular/angular/commit/91dd160))
* **bazel:** move bazel managed runtime deps for downstream usage ([#25690](https://github.com/angular/angular/issues/25690)) ([48d7f4e](https://github.com/angular/angular/commit/48d7f4e))
* **bazel:** specify the package and lock files using the workspace ([#25694](https://github.com/angular/angular/issues/25694)) ([678b420](https://github.com/angular/angular/commit/678b420))
* **compiler:** Fix look up of entryComponents in AOT Summaries ([#24892](https://github.com/angular/angular/issues/24892)) ([a31cfc5](https://github.com/angular/angular/commit/a31cfc5))
* **router:** mount correct component if router outlet was not instantiated and if using a route reuse strategy ([#25313](https://github.com/angular/angular/issues/25313)) ([#25314](https://github.com/angular/angular/issues/25314)) ([e117b1f](https://github.com/angular/angular/commit/e117b1f))
### Features
* **bazel:** add additional parameters to `ts_api_guardian_test` def ([#25694](https://github.com/angular/angular/issues/25694)) ([97ae7ae](https://github.com/angular/angular/commit/97ae7ae))
* **ivy:** enable .ngfactory.js generation in g3 only ([#25392](https://github.com/angular/angular/issues/25392)) ([1c44b71](https://github.com/angular/angular/commit/1c44b71))
<a name="6.1.7"></a>
## [6.1.7](https://github.com/angular/angular/compare/6.1.6...6.1.7) (2018-09-06)
@ -8,12 +29,6 @@
* **core:** size regression with closure compiler ([#25531](https://github.com/angular/angular/issues/25531)) ([ebcf762](https://github.com/angular/angular/commit/ebcf762))
* **docs-infra:** show "suggest edits" only for /guide and /tutorial dirs ([#24378](https://github.com/angular/angular/issues/24378)) ([66b7870](https://github.com/angular/angular/commit/66b7870))
* **upgrade:** trigger `$destroy` event on upgraded component element ([#25357](https://github.com/angular/angular/issues/25357)) ([82e0676](https://github.com/angular/angular/commit/82e0676)), closes [#25334](https://github.com/angular/angular/issues/25334)
### Features
* **docs-infra:** add "suggest edits" feature to all docs ([#24378](https://github.com/angular/angular/issues/24378)) ([82088a8](https://github.com/angular/angular/commit/82088a8))
* **docs-infra:** disable "status" selector in API list when displaying only packages ([#25718](https://github.com/angular/angular/issues/25718)) ([6f7df8a](https://github.com/angular/angular/commit/6f7df8a)), closes [#25708](https://github.com/angular/angular/issues/25708)
* **router:** warn if navigation triggered outside Angular zone ([#24959](https://github.com/angular/angular/issues/24959)) ([23a96dc](https://github.com/angular/angular/commit/23a96dc)), closes [#15770](https://github.com/angular/angular/issues/15770) [#15946](https://github.com/angular/angular/issues/15946) [#24728](https://github.com/angular/angular/issues/24728)

101
WORKSPACE
View File

@ -3,58 +3,58 @@ workspace(name = "angular")
#
# Download Bazel toolchain dependencies as needed by build actions
#
http_archive(
name = "build_bazel_rules_nodejs",
urls = ["https://github.com/bazelbuild/rules_nodejs/archive/0.12.0.zip"],
strip_prefix = "rules_nodejs-0.12.0",
sha256 = "2977cdbc8ae0eed7d4186385af56a50a3321a549e2136a959998bba89d2edb6e",
)
http_archive(
name = "bazel_skylib",
urls = ["https://github.com/bazelbuild/bazel-skylib/archive/0.3.1.zip"],
strip_prefix = "bazel-skylib-0.3.1",
sha256 = "95518adafc9a2b656667bbf517a952e54ce7f350779d0dd95133db4eb5c27fb1",
)
http_archive(
name = "io_bazel_rules_webtesting",
url = "https://github.com/bazelbuild/rules_webtesting/archive/0.2.1.zip",
strip_prefix = "rules_webtesting-0.2.1",
sha256 = "7d490aadff9b5262e5251fa69427ab2ffd1548422467cb9f9e1d110e2c36f0fa",
)
http_archive(
name = "build_bazel_rules_typescript",
url = "https://github.com/bazelbuild/rules_typescript/archive/0.16.0.zip",
strip_prefix = "rules_typescript-0.16.0",
sha256 = "e65c5639a42e2f6d3f9d2bda62487d6b42734830dda45be1620c3e2b1115070c",
url = "https://github.com/bazelbuild/rules_typescript/archive/0.17.0.zip",
strip_prefix = "rules_typescript-0.17.0",
sha256 = "1626ee2cc9770af6950bfc77dffa027f9aedf330fe2ea2ee7e504428927bd95d",
)
load("@build_bazel_rules_typescript//:package.bzl", "rules_typescript_dependencies")
rules_typescript_dependencies()
http_archive(
name = "bazel_toolchains",
urls = [
"https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/archive/5124557861ebf4c0b67f98180bff1f8551e0b421.tar.gz",
"https://github.com/bazelbuild/bazel-toolchains/archive/5124557861ebf4c0b67f98180bff1f8551e0b421.tar.gz",
],
strip_prefix = "bazel-toolchains-5124557861ebf4c0b67f98180bff1f8551e0b421",
sha256 = "c3b08805602cd1d2b67ebe96407c1e8c6ed3d4ce55236ae2efe2f1948f38168d",
)
http_archive(
name = "io_bazel_rules_go",
url = "https://github.com/bazelbuild/rules_go/releases/download/0.10.3/rules_go-0.10.3.tar.gz",
sha256 = "feba3278c13cde8d67e341a837f69a029f698d7a27ddbb2a202be7a10b22142a",
name = "io_bazel_rules_sass",
url = "https://github.com/bazelbuild/rules_sass/archive/1.11.0.zip",
strip_prefix = "rules_sass-1.11.0",
sha256 = "dbe9fb97d5a7833b2a733eebc78c9c1e3880f676ac8af16e58ccf2139cbcad03",
)
# This commit matches the version of buildifier in angular/ngcontainer
# If you change this, also check if it matches the version in the angular/ngcontainer
# version in /.circleci/config.yml
BAZEL_BUILDTOOLS_VERSION = "82b21607e00913b16fe1c51bec80232d9d6de31c"
BAZEL_BUILDTOOLS_VERSION = "49a6c199e3fbf5d94534b2771868677d3f9c6de9"
http_archive(
name = "com_github_bazelbuild_buildtools",
url = "https://github.com/bazelbuild/buildtools/archive/%s.zip" % BAZEL_BUILDTOOLS_VERSION,
strip_prefix = "buildtools-%s" % BAZEL_BUILDTOOLS_VERSION,
sha256 = "edb24c2f9c55b10a820ec74db0564415c0cf553fa55e9fc709a6332fb6685eff",
sha256 = "edf39af5fc257521e4af4c40829fffe8fba6d0ebff9f4dd69a6f8f1223ae047b",
)
# Fetching the Bazel source code allows us to compile the Skylark linter
http_archive(
name = "io_bazel",
url = "https://github.com/bazelbuild/bazel/archive/968f87900dce45a7af749a965b72dbac51b176b3.zip",
strip_prefix = "bazel-968f87900dce45a7af749a965b72dbac51b176b3",
sha256 = "e373d2ae24955c1254c495c9c421c009d88966565c35e4e8444c082cb1f0f48f",
url = "https://github.com/bazelbuild/bazel/archive/0.17.1.zip",
strip_prefix = "bazel-0.17.1",
sha256 = "ace8cced3b21e64a8fdad68508e9b0644201ec848ad583651719841d567fc66d",
)
http_archive(
name = "io_bazel_skydoc",
# TODO: switch to upstream when https://github.com/bazelbuild/skydoc/pull/103 is merged
url = "https://github.com/alexeagle/skydoc/archive/fe2e9f888d28e567fef62ec9d4a93c425526d701.zip",
strip_prefix = "skydoc-fe2e9f888d28e567fef62ec9d4a93c425526d701",
sha256 = "7bfb5545f59792a2745f2523b9eef363f9c3e7274791c030885e7069f8116016",
)
# We have a source dependency on the Devkit repository, because it's built with
@ -72,9 +72,9 @@ http_archive(
http_archive(
name = "org_brotli",
url = "https://github.com/google/brotli/archive/f9b8c02673c576a3e807edbf3a9328e9e7af6d7c.zip",
strip_prefix = "brotli-f9b8c02673c576a3e807edbf3a9328e9e7af6d7c",
sha256 = "8a517806d2b7c8505ba5c53934e7d7c70d341b68ffd268e9044d35b564a48828",
url = "https://github.com/google/brotli/archive/v1.0.5.zip",
strip_prefix = "brotli-1.0.5",
sha256 = "774b893a0700b0692a76e2e5b7e7610dbbe330ffbe3fe864b4b52ca718061d5a",
)
#
@ -97,16 +97,18 @@ local_repository(
# Load and install our dependencies downloaded above.
#
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install")
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories")
check_bazel_version("0.16.0", """
check_bazel_version("0.17.0", """
If you are on a Mac and using Homebrew, there is a breaking change to the installation in Bazel 0.16
See https://blog.bazel.build/2018/08/22/bazel-homebrew.html
""")
node_repositories(
package_json = ["//:package.json"],
preserve_symlinks = True,
package_json = ["//:package.json"],
preserve_symlinks = True,
node_version = "10.9.0",
yarn_version = "1.9.2",
)
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
@ -130,20 +132,11 @@ load("@angular//:index.bzl", "ng_setup_workspace")
ng_setup_workspace()
#
# Ask Bazel to manage these toolchain dependencies for us.
# Bazel will run `yarn install` when one of these toolchains is requested during
# a build.
#
##################################
# Skylark documentation generation
yarn_install(
name = "ts-api-guardian_runtime_deps",
package_json = "//tools/ts-api-guardian:package.json",
yarn_lock = "//tools/ts-api-guardian:yarn.lock",
)
load("@io_bazel_rules_sass//sass:sass_repositories.bzl", "sass_repositories")
sass_repositories()
yarn_install(
name = "http-server_runtime_deps",
package_json = "//tools/http-server:package.json",
yarn_lock = "//tools/http-server:yarn.lock",
)
load("@io_bazel_skydoc//skylark:skylark.bzl", "skydoc_repositories")
skydoc_repositories()

View File

@ -1,351 +1,259 @@
'use strict'; // necessary for es6 output in node
import { browser, element, by, ElementFinder } from 'protractor';
import { logging, promise } from 'selenium-webdriver';
import { browser } from 'protractor';
import { logging } from 'selenium-webdriver';
import * as openClose from './open-close.po';
import * as statusSlider from './status-slider.po';
import * as toggle from './toggle.po';
import * as enterLeave from './enter-leave.po';
import * as auto from './auto.po';
import * as filterStagger from './filter-stagger.po';
import * as heroGroups from './hero-groups';
import { getLinkById, sleepFor } from './util';
/**
* The tests here basically just checking that the end styles
* of each animation are in effect.
*
* Relies on the Angular testability only becoming stable once
* animation(s) have finished.
*
* Ideally we'd use https://developer.mozilla.org/en-US/docs/Web/API/Document/getAnimations
* but they're not supported in Chrome at the moment. The upcoming nganimate polyfill
* may also add some introspection support.
*/
describe('Animation Tests', () => {
const openCloseHref = getLinkById('open-close');
const statusSliderHref = getLinkById('status');
const toggleHref = getLinkById('toggle');
const enterLeaveHref = getLinkById('enter-leave');
const autoHref = getLinkById('auto');
const filterHref = getLinkById('heroes');
const heroGroupsHref = getLinkById('hero-groups');
const INACTIVE_COLOR = 'rgba(238, 238, 238, 1)';
const ACTIVE_COLOR = 'rgba(207, 216, 220, 1)';
const NO_TRANSFORM_MATRIX_REGEX = /matrix\(1,\s*0,\s*0,\s*1,\s*0,\s*0\)/;
beforeEach(() => {
beforeAll(() => {
browser.get('');
});
describe('basic states', () => {
describe('Open/Close Component', () => {
let host: ElementFinder;
beforeEach(() => {
host = element(by.css('app-hero-list-basic'));
beforeAll(async () => {
await openCloseHref.click();
sleepFor();
});
it('animates between active and inactive', () => {
addInactiveHero();
it('should be open', async () => {
let text = await openClose.getComponentText();
const toggleButton = openClose.getToggleButton();
const container = openClose.getComponentContainer();
let li = host.element(by.css('li'));
if (text.includes('Closed')) {
await toggleButton.click();
sleepFor();
}
expect(getScaleX(li)).toBe(1.0);
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
text = await openClose.getComponentText();
const containerHeight = await container.getCssValue('height');
li.click();
browser.driver.sleep(300);
expect(getScaleX(li)).toBe(1.1);
expect(li.getCssValue('backgroundColor')).toBe(ACTIVE_COLOR);
li.click();
browser.driver.sleep(300);
expect(getScaleX(li)).toBe(1.0);
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
expect(text).toContain('The box is now Open!');
expect(containerHeight).toBe('200px');
});
});
it('should be closed', async () => {
let text = await openClose.getComponentText();
const toggleButton = openClose.getToggleButton();
const container = openClose.getComponentContainer();
describe('styles inline in transitions', () => {
if (text.includes('Open')) {
await toggleButton.click();
sleepFor();
}
let host: ElementFinder;
text = await openClose.getComponentText();
const containerHeight = await container.getCssValue('height');
beforeEach(function() {
host = element(by.css('app-hero-list-inline-styles'));
expect(text).toContain('The box is now Closed!');
expect(containerHeight).toBe('100px');
});
it('are not kept after animation', () => {
addInactiveHero();
it('should log animation events', async () => {
const toggleButton = openClose.getToggleButton();
const loggingCheckbox = openClose.getLoggingCheckbox();
await loggingCheckbox.click();
await toggleButton.click();
let li = host.element(by.css('li'));
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
li.click();
browser.driver.sleep(300);
expect(getScaleX(li)).toBe(1.0);
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
});
const animationMessages = logs.filter(({ message }) => message.indexOf('Animation') !== -1 ? true : false);
});
describe('combined transition syntax', () => {
let host: ElementFinder;
beforeEach(() => {
host = element(by.css('app-hero-list-combined-transitions'));
});
it('animates between active and inactive', () => {
addInactiveHero();
let li = host.element(by.css('li'));
expect(getScaleX(li)).toBe(1.0);
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
li.click();
browser.driver.sleep(300);
expect(getScaleX(li)).toBe(1.1);
expect(li.getCssValue('backgroundColor')).toBe(ACTIVE_COLOR);
li.click();
browser.driver.sleep(300);
expect(getScaleX(li)).toBe(1.0);
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
});
});
describe('two-way transition syntax', () => {
let host: ElementFinder;
beforeEach(() => {
host = element(by.css('app-hero-list-twoway'));
});
it('animates between active and inactive', () => {
addInactiveHero();
let li = host.element(by.css('li'));
expect(getScaleX(li)).toBe(1.0);
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
li.click();
browser.driver.sleep(300);
expect(getScaleX(li)).toBe(1.1);
expect(li.getCssValue('backgroundColor')).toBe(ACTIVE_COLOR);
li.click();
browser.driver.sleep(300);
expect(getScaleX(li)).toBe(1.0);
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
});
});
describe('enter & leave', () => {
let host: ElementFinder;
beforeEach(() => {
host = element(by.css('app-hero-list-enter-leave'));
});
it('adds and removes element', () => {
addInactiveHero();
let li = host.element(by.css('li'));
expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX);
removeHero();
expect(li.isPresent()).toBe(false);
});
});
describe('enter & leave & states', () => {
let host: ElementFinder;
beforeEach(function() {
host = element(by.css('app-hero-list-enter-leave-states'));
});
it('adds and removes and animates between active and inactive', () => {
addInactiveHero();
let li = host.element(by.css('li'));
expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX);
li.click();
browser.driver.sleep(300);
expect(getScaleX(li)).toBe(1.1);
li.click();
browser.driver.sleep(300);
expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX);
removeHero();
expect(li.isPresent()).toBe(false);
});
});
describe('auto style calc', () => {
let host: ElementFinder;
beforeEach(function() {
host = element(by.css('app-hero-list-auto'));
});
it('adds and removes element', () => {
addInactiveHero();
let li = host.element(by.css('li'));
expect(li.getCssValue('height')).toBe('50px');
removeHero();
expect(li.isPresent()).toBe(false);
});
});
describe('different timings', () => {
let host: ElementFinder;
beforeEach(() => {
host = element(by.css('app-hero-list-timings'));
});
it('adds and removes element', () => {
addInactiveHero();
let li = host.element(by.css('li'));
expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX);
expect(li.getCssValue('opacity')).toMatch('1');
removeHero();
expect(li.isPresent()).toBe(false);
});
});
describe('multiple keyframes', () => {
let host: ElementFinder;
beforeEach(() => {
host = element(by.css('app-hero-list-multistep'));
});
it('adds and removes element', () => {
addInactiveHero();
let li = host.element(by.css('li'));
expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX);
expect(li.getCssValue('opacity')).toMatch('1');
removeHero();
expect(li.isPresent()).toBe(false);
});
});
describe('parallel groups', () => {
let host: ElementFinder;
beforeEach(() => {
host = element(by.css('app-hero-list-groups'));
});
it('adds and removes element', () => {
addInactiveHero();
let li = host.element(by.css('li'));
expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX);
expect(li.getCssValue('opacity')).toMatch('1');
removeHero(700);
expect(li.isPresent()).toBe(false);
});
});
describe('adding active heroes', () => {
let host: ElementFinder;
beforeEach(() => {
host = element(by.css('app-hero-list-basic'));
});
it('animates between active and inactive', () => {
addActiveHero();
let li = host.element(by.css('li'));
expect(getScaleX(li)).toBe(1.1);
expect(li.getCssValue('backgroundColor')).toBe(ACTIVE_COLOR);
li.click();
browser.driver.sleep(300);
expect(getScaleX(li)).toBe(1.0);
expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR);
li.click();
browser.driver.sleep(300);
expect(getScaleX(li)).toBe(1.1);
expect(li.getCssValue('backgroundColor')).toBe(ACTIVE_COLOR);
expect(animationMessages.length).toBeGreaterThan(0);
});
});
describe('callbacks', () => {
it('fires a callback on start and done', () => {
addActiveHero();
browser.manage().logs().get(logging.Type.BROWSER)
.then((logs: logging.Entry[]) => {
const animationMessages = logs.filter((log) => {
return log.message.indexOf('Animation') !== -1 ? true : false;
});
describe('Status Slider Component', () => {
const activeColor = 'rgba(255, 165, 0, 1)';
const inactiveColor = 'rgba(0, 0, 255, 1)';
expect(animationMessages.length).toBeGreaterThan(0);
});
beforeAll(async () => {
await statusSliderHref.click();
sleepFor(2000);
});
it('should be inactive with an orange background', async () => {
let text = await statusSlider.getComponentText();
const toggleButton = statusSlider.getToggleButton();
const container = statusSlider.getComponentContainer();
if (text === 'Active') {
await toggleButton.click();
sleepFor(2000);
}
text = await statusSlider.getComponentText();
const bgColor = await container.getCssValue('backgroundColor');
expect(text).toBe('Inactive');
expect(bgColor).toBe(inactiveColor);
});
it('should be active with a blue background', async () => {
let text = await statusSlider.getComponentText();
const toggleButton = statusSlider.getToggleButton();
const container = statusSlider.getComponentContainer();
if (text === 'Inactive') {
await toggleButton.click();
sleepFor(2000);
}
text = await statusSlider.getComponentText();
const bgColor = await container.getCssValue('backgroundColor');
expect(text).toBe('Active');
expect(bgColor).toBe(activeColor);
});
});
function addActiveHero(sleep?: number) {
sleep = sleep || 500;
element(by.buttonText('Add active hero')).click();
browser.driver.sleep(sleep);
}
function addInactiveHero(sleep?: number) {
sleep = sleep || 500;
element(by.buttonText('Add inactive hero')).click();
browser.driver.sleep(sleep);
}
function removeHero(sleep?: number) {
sleep = sleep || 500;
element(by.buttonText('Remove hero')).click();
browser.driver.sleep(sleep);
}
function getScaleX(el: ElementFinder) {
return Promise.all([
getBoundingClientWidth(el),
getOffsetWidth(el)
]).then(function(promiseResolutions) {
let clientWidth = promiseResolutions[0];
let offsetWidth = promiseResolutions[1];
return clientWidth / offsetWidth;
describe('Toggle Animations Component', () => {
beforeAll(async () => {
await toggleHref.click();
sleepFor();
});
}
function getBoundingClientWidth(el: ElementFinder) {
return browser.executeScript(
'return arguments[0].getBoundingClientRect().width',
el.getWebElement()
) as PromiseLike<number>;
}
it('should disabled animations on the child element', async () => {
const toggleButton = toggle.getToggleAnimationsButton();
function getOffsetWidth(el: ElementFinder) {
return browser.executeScript(
'return arguments[0].offsetWidth',
el.getWebElement()
) as PromiseLike<number>;
}
await toggleButton.click();
const container = toggle.getComponentContainer();
const cssClasses = await container.getAttribute('class');
expect(cssClasses).toContain('ng-animate-disabled');
});
});
describe('Enter/Leave Component', () => {
beforeAll(async () => {
await enterLeaveHref.click();
sleepFor(100);
});
it('should attach a flyInOut trigger to the list of items', async () => {
const heroesList = enterLeave.getHeroesList();
const hero = heroesList.get(0);
const cssClasses = await hero.getAttribute('class');
const transform = await hero.getCssValue('transform');
expect(cssClasses).toContain('ng-trigger-flyInOut');
expect(transform).toBe('matrix(1, 0, 0, 1, 0, 0)');
});
it('should remove the hero from the list when clicked', async () => {
const heroesList = enterLeave.getHeroesList();
const total = await heroesList.count();
const hero = heroesList.get(0);
await hero.click();
await sleepFor(100);
const newTotal = await heroesList.count();
expect(newTotal).toBeLessThan(total);
});
});
describe('Auto Calculation Component', () => {
beforeAll(async () => {
await autoHref.click();
sleepFor(0);
});
it('should attach a shrinkOut trigger to the list of items', async () => {
const heroesList = auto.getHeroesList();
const hero = heroesList.get(0);
const cssClasses = await hero.getAttribute('class');
expect(cssClasses).toContain('ng-trigger-shrinkOut');
});
it('should remove the hero from the list when clicked', async () => {
const heroesList = auto.getHeroesList();
const total = await heroesList.count();
const hero = heroesList.get(0);
await hero.click();
await sleepFor(250);
const newTotal = await heroesList.count();
expect(newTotal).toBeLessThan(total);
});
});
describe('Filter/Stagger Component', () => {
beforeAll(async () => {
await filterHref.click();
sleepFor();
});
it('should attach a filterAnimations trigger to the list container', async () => {
const heroesList = filterStagger.getComponentContainer();
const cssClasses = await heroesList.getAttribute('class');
expect(cssClasses).toContain('ng-trigger-filterAnimation');
});
it('should filter down the list when a search is performed', async () => {
const heroesList = filterStagger.getHeroesList();
const total = await heroesList.count();
const formInput = filterStagger.getFormInput();
await formInput.sendKeys('Mag');
await sleepFor(500);
const newTotal = await heroesList.count();
expect(newTotal).toBeLessThan(total);
expect(newTotal).toBe(2);
});
});
describe('Hero Groups Component', () => {
beforeAll(async () => {
await heroGroupsHref.click();
sleepFor(300);
});
it('should attach a flyInOut trigger to the list of items', async () => {
const heroesList = heroGroups.getHeroesList();
const hero = heroesList.get(0);
const cssClasses = await hero.getAttribute('class');
const transform = await hero.getCssValue('transform');
const opacity = await hero.getCssValue('opacity');
expect(cssClasses).toContain('ng-trigger-flyInOut');
expect(transform).toBe('matrix(1, 0, 0, 1, 0, 0)');
expect(opacity).toBe('1');
});
it('should remove the hero from the list when clicked', async () => {
const heroesList = heroGroups.getHeroesList();
const total = await heroesList.count();
const hero = heroesList.get(0);
await hero.click();
await sleepFor(300);
const newTotal = await heroesList.count();
expect(newTotal).toBeLessThan(total);
});
});
});

View File

@ -0,0 +1,19 @@
import { by } from 'protractor';
import { locate } from './util';
export function getPage() {
return by.css('app-hero-list-auto-page');
}
export function getComponent() {
return by.css('app-hero-list-auto');
}
export function getComponentContainer() {
const findContainer = () => by.css('ul');
return locate(getComponent(), findContainer());
}
export function getHeroesList() {
return getComponentContainer().all(by.css('li'));
}

View File

@ -0,0 +1,19 @@
import { by } from 'protractor';
import { locate } from './util';
export function getPage() {
return by.css('app-hero-list-enter-leave-page');
}
export function getComponent() {
return by.css('app-hero-list-enter-leave');
}
export function getComponentContainer() {
const findContainer = () => by.css('ul');
return locate(getComponent(), findContainer());
}
export function getHeroesList() {
return getComponentContainer().all(by.css('li'));
}

View File

@ -0,0 +1,20 @@
import { by } from 'protractor';
import { locate } from './util';
export function getPage() {
return by.css('app-hero-list-page');
}
export function getComponentContainer() {
const findContainer = () => by.css('ul');
return locate(getPage(), findContainer());
}
export function getHeroesList() {
return getComponentContainer().all(by.css('li'));
}
export function getFormInput() {
const formInput = () => by.css('form > input');
return locate(getPage(), formInput());
}

View File

@ -0,0 +1,19 @@
import { by } from 'protractor';
import { locate } from './util';
export function getPage() {
return by.css('app-hero-list-groups-page');
}
export function getComponent() {
return by.css('app-hero-list-groups');
}
export function getComponentContainer() {
const findContainer = () => by.css('ul');
return locate(getComponent(), findContainer());
}
export function getHeroesList() {
return getComponentContainer().all(by.css('li'));
}

View File

@ -0,0 +1,33 @@
import { by } from 'protractor';
import { locate } from './util';
export function getPage() {
return by.css('app-open-close-page');
}
export function getComponent() {
return by.css('app-open-close');
}
export function getToggleButton() {
const toggleButton = () => by.buttonText('Toggle Open/Close');
return locate(getComponent(), toggleButton());
}
export function getLoggingCheckbox() {
const loggingCheckbox = () => by.css('section > input[type="checkbox"]');
return locate(getPage(), loggingCheckbox());
}
export function getComponentContainer() {
const findContainer = () => by.css('div');
return locate(getComponent(), findContainer());
}
export async function getComponentText() {
const findContainerText = () => by.css('div');
const contents = locate(getComponent(), findContainerText());
const componentText = await contents.getText();
return componentText;
}

View File

@ -0,0 +1,28 @@
import { by } from 'protractor';
import { locate } from './util';
export function getPage() {
return by.css('app-status-slider-page');
}
export function getComponent() {
return by.css('app-status-slider');
}
export function getToggleButton() {
const toggleButton = () => by.buttonText('Toggle Status');
return locate(getComponent(), toggleButton());
}
export function getComponentContainer() {
const findContainer = () => by.css('div');
return locate(getComponent(), findContainer());
}
export async function getComponentText() {
const findContainerText = () => by.css('div');
const contents = locate(getComponent(), findContainerText());
const componentText = await contents.getText();
return componentText;
}

View File

@ -0,0 +1,25 @@
import { by } from 'protractor';
import { locate } from './util';
export function getPage() {
return by.css('app-toggle-animations-child-page');
}
export function getComponent() {
return by.css('app-open-close-toggle');
}
export function getToggleButton() {
const toggleButton = () => by.buttonText('Toggle Open/Closed');
return locate(getComponent(), toggleButton());
}
export function getToggleAnimationsButton() {
const toggleAnimationsButton = () => by.buttonText('Toggle Animations');
return locate(getComponent(), toggleAnimationsButton());
}
export function getComponentContainer() {
const findContainer = () => by.css('div');
return locate(getComponent()).all(findContainer()).get(0);
}

View File

@ -0,0 +1,19 @@
import { Locator, ElementFinder, browser, by, element } from 'protractor';
/**
*
* locate(finder1, finder2) => element(finder1).element(finder2).element(finderN);
*/
export function locate(locator: Locator, ...locators: Locator[]) {
return locators.reduce((current: ElementFinder, next: Locator) => {
return current.element(next);
}, element(locator)) as ElementFinder;
}
export async function sleepFor(time = 1000) {
return await browser.sleep(time);
}
export function getLinkById(id: string) {
return element(by.css(`a[id=${id}]`));
}

View File

@ -0,0 +1,3 @@
<p>
Angular's animations library makes it easy to define and apply animation effects such as page and list transitions.
</p>

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-about',
templateUrl: './about.component.html',
styleUrls: ['./about.component.css']
})
export class AboutComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@ -0,0 +1,11 @@
// #docregion
import { animation, style, animate } from '@angular/animations';
export const transAnimation = animation([
style({
height: '{{ height }}',
opacity: '{{ opacity }}',
backgroundColor: '{{ backgroundColor }}'
}),
animate('{{ time }}')
]);

View File

@ -0,0 +1,74 @@
// #docregion reusable
import {
animation, trigger, animateChild, group,
transition, animate, style, query
} from '@angular/animations';
export const transAnimation = animation([
style({
height: '{{ height }}',
opacity: '{{ opacity }}',
backgroundColor: '{{ backgroundColor }}'
}),
animate('{{ time }}')
]);
// #enddocregion reusable
// Routable animations
// #docregion route-animations
export const slideInAnimation =
// #docregion style-view
trigger('routeAnimations', [
transition('HomePage <=> AboutPage', [
style({ position: 'relative' }),
query(':enter, :leave', [
style({
position: 'absolute',
top: 0,
left: 0,
width: '100%'
})
]),
// #enddocregion style-view
// #docregion query
query(':enter', [
style({ left: '-100%'})
]),
query(':leave', animateChild()),
group([
query(':leave', [
animate('300ms ease-out', style({ left: '100%'}))
]),
query(':enter', [
animate('300ms ease-out', style({ left: '0%'}))
])
]),
query(':enter', animateChild()),
]),
transition('* <=> FilterPage', [
style({ position: 'relative' }),
query(':enter, :leave', [
style({
position: 'absolute',
top: 0,
left: 0,
width: '100%'
})
]),
query(':enter', [
style({ left: '-100%'})
]),
query(':leave', animateChild()),
group([
query(':leave', [
animate('200ms ease-out', style({ left: '100%'}))
]),
query(':enter', [
animate('300ms ease-out', style({ left: '0%'}))
])
]),
query(':enter', animateChild()),
])
// #enddocregion query
]);
// #enddocregion route-animations

View File

@ -0,0 +1,35 @@
// #docplaster
// #docregion imports
import { Component, HostBinding } from '@angular/core';
import {
trigger,
state,
style,
animate,
transition,
// ...
} from '@angular/animations';
// #enddocregion imports
// #docregion decorator, toggle-app-animations
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.css'],
animations: [
// animation triggers go here
]
})
// #enddocregion decorator
export class AppComponent {
@HostBinding('@.disabled')
public animationsDisabled = false;
// #enddocregion toggle-app-animations
toggleAnimations() {
this.animationsDisabled = !this.animationsDisabled;
}
// #docregion toggle-app-animations
}
// #enddocregion toggle-app-animations

View File

@ -0,0 +1,7 @@
:host {
display: block;
}
section {
margin-top: 100px;
}

View File

@ -0,0 +1,21 @@
<h1>Animations</h1>
Toggle All Animations <input type="checkbox" [checked]="!animationsDisabled" (click)="toggleAnimations()"/>
<nav>
<a id="home" routerLink="/home" routerLinkActive="active">Home</a>
<a id="about" routerLink="/about" routerLinkActive="active">About</a>
<a id="open-close" routerLink="/open-close" routerLinkActive="active">Open/Close</a>
<a id="status" routerLink="/status" routerLinkActive="active">Status Slider</a>
<a id="toggle" routerLink="/toggle" routerLinkActive="active">Toggle Animations</a>
<a id="enter-leave" routerLink="/enter-leave" routerLinkActive="active">Enter/Leave</a>
<a id="auto" routerLink="/auto" routerLinkActive="active">Auto Calculation</a>
<a id="heroes" routerLink="/heroes" routerLinkActive="active">Filter/Stagger</a>
<a id="hero-groups" routerLink="/hero-groups" routerLinkActive="active">Hero Groups</a>
</nav>
<!-- #docregion route-animations-outlet -->
<div [@routeAnimations]="prepareRoute(outlet)" >
<router-outlet #outlet="outlet"></router-outlet>
</div>
<!-- #enddocregion route-animations-outlet -->

View File

@ -0,0 +1,47 @@
// #docplaster
// #docregion imports
import { Component, HostBinding } from '@angular/core';
import {
trigger,
state,
style,
animate,
transition,
// ...
} from '@angular/animations';
// #enddocregion imports
import { RouterOutlet } from '@angular/router';
import { slideInAnimation } from './animations';
// #docregion decorator, toggle-app-animations, define
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.css'],
animations: [
// #enddocregion decorator
slideInAnimation
// #docregion decorator
// animation triggers go here
]
})
// #enddocregion decorator, define
export class AppComponent {
@HostBinding('@.disabled')
public animationsDisabled = false;
// #enddocregion toggle-app-animations
// #docregion prepare-router-outlet
prepareRoute(outlet: RouterOutlet) {
return outlet && outlet.activatedRouteData && outlet.activatedRouteData['animation'];
}
// #enddocregion prepare-router-outlet
toggleAnimations() {
this.animationsDisabled = !this.animationsDisabled;
}
// #docregion toggle-app-animations
}
// #enddocregion toggle-app-animations

View File

@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@NgModule({
imports: [
BrowserModule,
BrowserAnimationsModule
],
declarations: [ ],
bootstrap: [ ]
})
export class AppModule { }

View File

@ -1,43 +1,63 @@
// #docplaster
// #docregion route-animation-data
import { NgModule } from '@angular/core';
// #docregion animations-module
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
// #enddocregion animations-module
import { HeroTeamBuilderComponent } from './hero-team-builder.component';
import { HeroListBasicComponent } from './hero-list-basic.component';
import { HeroListInlineStylesComponent } from './hero-list-inline-styles.component';
import { HeroListEnterLeaveComponent } from './hero-list-enter-leave.component';
import { HeroListEnterLeaveStatesComponent } from './hero-list-enter-leave-states.component';
import { HeroListCombinedTransitionsComponent } from './hero-list-combined-transitions.component';
import { HeroListTwowayComponent } from './hero-list-twoway.component';
import { HeroListAutoComponent } from './hero-list-auto.component';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { OpenCloseComponent } from './open-close.component';
import { OpenClosePageComponent } from './open-close-page.component';
import { OpenCloseChildComponent } from './open-close.component.4';
import { ToggleAnimationsPageComponent } from './toggle-animations-page.component';
import { StatusSliderComponent } from './status-slider.component';
import { StatusSliderPageComponent } from './status-slider-page.component';
import { HeroListPageComponent } from './hero-list-page.component';
import { HeroListGroupPageComponent } from './hero-list-group-page.component';
import { HeroListGroupsComponent } from './hero-list-groups.component';
import { HeroListMultistepComponent } from './hero-list-multistep.component';
import { HeroListTimingsComponent } from './hero-list-timings.component';
// #docregion animations-module
import { HeroListEnterLeavePageComponent } from './hero-list-enter-leave-page.component';
import { HeroListEnterLeaveComponent } from './hero-list-enter-leave.component';
import { HeroListAutoCalcPageComponent } from './hero-list-auto-page.component';
import { HeroListAutoComponent } from './hero-list-auto.component';
import { HomeComponent } from './home.component';
import { AboutComponent } from './about.component';
@NgModule({
imports: [ BrowserModule, BrowserAnimationsModule ],
// ... more stuff ...
// #enddocregion animations-module
declarations: [
HeroTeamBuilderComponent,
HeroListBasicComponent,
HeroListInlineStylesComponent,
HeroListCombinedTransitionsComponent,
HeroListTwowayComponent,
HeroListEnterLeaveComponent,
HeroListEnterLeaveStatesComponent,
HeroListAutoComponent,
HeroListTimingsComponent,
HeroListMultistepComponent,
HeroListGroupsComponent
imports: [
BrowserModule,
BrowserAnimationsModule,
RouterModule.forRoot([
{ path: '', pathMatch: 'full', redirectTo: '/enter-leave' },
{ path: 'open-close', component: OpenClosePageComponent },
{ path: 'status', component: StatusSliderPageComponent },
{ path: 'toggle', component: ToggleAnimationsPageComponent },
{ path: 'heroes', component: HeroListPageComponent, data: {animation: 'FilterPage'} },
{ path: 'hero-groups', component: HeroListGroupPageComponent },
{ path: 'enter-leave', component: HeroListEnterLeavePageComponent },
{ path: 'auto', component: HeroListAutoCalcPageComponent },
{ path: 'home', component: HomeComponent, data: {animation: 'HomePage'} },
{ path: 'about', component: AboutComponent, data: {animation: 'AboutPage'} },
])
],
bootstrap: [ HeroTeamBuilderComponent ]
// #docregion animations-module
// #enddocregion route-animation-data
declarations: [
AppComponent,
StatusSliderComponent,
OpenCloseComponent,
OpenCloseChildComponent,
OpenClosePageComponent,
StatusSliderPageComponent,
ToggleAnimationsPageComponent,
HeroListPageComponent,
HeroListGroupsComponent,
HeroListGroupPageComponent,
HeroListEnterLeavePageComponent,
HeroListEnterLeaveComponent,
HeroListAutoCalcPageComponent,
HeroListAutoComponent,
HomeComponent,
AboutComponent
],
bootstrap: [AppComponent]
})
export class AppModule { }
// #enddocregion animations-module

View File

@ -0,0 +1,20 @@
import { Component } from '@angular/core';
import { HEROES } from './mock-heroes';
@Component({
selector: 'app-hero-list-auto-page',
template: `
<section>
<h2>Automatic Calculation</h2>
<app-hero-list-auto [heroes]="heroes" (remove)="onRemove($event)"></app-hero-list-auto>
</section>
`
})
export class HeroListAutoCalcPageComponent {
heroes = HEROES.slice();
onRemove(id: number) {
this.heroes = this.heroes.filter(hero => hero.id !== id);
}
}

View File

@ -0,0 +1,9 @@
<ul class="heroes">
<li *ngFor="let hero of heroes"
[@shrinkOut]="'in'" (click)="removeHero(hero.id)">
<div class="inner">
<span class="badge">{{ hero.id }}</span>
<span>{{ hero.name }}</span>
</div>
</li>
</ul>

View File

@ -1,6 +1,8 @@
import {
Component,
Input
Input,
Output,
EventEmitter
} from '@angular/core';
import {
trigger,
@ -10,38 +12,30 @@ import {
transition
} from '@angular/animations';
import { Hero } from './hero.service';
import { Hero } from './hero';
@Component({
selector: 'app-hero-list-auto',
// #docregion template
template: `
<ul>
<li *ngFor="let hero of heroes"
[@shrinkOut]="'in'">
{{hero.name}}
</li>
</ul>
`,
// #enddocregion template
styleUrls: ['./hero-list.component.css'],
/* When the element leaves (transition "in => void" occurs),
* get the element's current computed height and animate
* it down to 0.
*/
// #docregion animationdef
templateUrl: 'hero-list-auto.component.html',
styleUrls: ['./hero-list-page.component.css'],
// #docregion auto-calc
animations: [
trigger('shrinkOut', [
state('in', style({height: '*'})),
state('in', style({ height: '*' })),
transition('* => void', [
style({height: '*'}),
animate(250, style({height: 0}))
style({ height: '*' }),
animate(250, style({ height: 0 }))
])
])
]
// #enddocregion animationdef
// #enddocregion auto-calc
})
export class HeroListAutoComponent {
@Input() heroes: Hero[];
@Output() remove = new EventEmitter<number>();
removeHero(id: number) {
this.remove.emit(id);
}
}

View File

@ -1,70 +0,0 @@
// #docplaster
// #docregion
// #docregion imports
import {
Component,
Input
} from '@angular/core';
import {
trigger,
state,
style,
animate,
transition
} from '@angular/animations';
// #enddocregion imports
import { Hero } from './hero.service';
@Component({
selector: 'app-hero-list-basic',
// #enddocregion
/* The click event calls hero.toggleState(), which
* causes the state of that hero to switch from
* active to inactive or vice versa.
*/
// #docregion
// #docregion template
template: `
<ul>
<li *ngFor="let hero of heroes"
[@heroState]="hero.state"
(click)="hero.toggleState()">
{{hero.name}}
</li>
</ul>
`,
// #enddocregion template
styleUrls: ['./hero-list.component.css'],
// #enddocregion
/**
* Define two states, "inactive" and "active", and the end
* styles that apply whenever the element is in those states.
* Then define animations for transitioning between the states,
* one in each direction
*/
// #docregion
// #docregion animationdef
animations: [
trigger('heroState', [
// #docregion states
state('inactive', style({
backgroundColor: '#eee',
transform: 'scale(1)'
})),
state('active', style({
backgroundColor: '#cfd8dc',
transform: 'scale(1.1)'
})),
// #enddocregion states
// #docregion transitions
transition('inactive => active', animate('100ms ease-in')),
transition('active => inactive', animate('100ms ease-out'))
// #enddocregion transitions
])
]
// #enddocregion animationdef
})
export class HeroListBasicComponent {
@Input() heroes: Hero[];
}

View File

@ -1,59 +0,0 @@
// #docregion
// #docregion imports
import {
Component,
Input
} from '@angular/core';
import {
trigger,
state,
style,
animate,
transition
} from '@angular/animations';
// #enddocregion imports
import { Hero } from './hero.service';
@Component({
selector: 'app-hero-list-combined-transitions',
// #docregion template
template: `
<ul>
<li *ngFor="let hero of heroes"
[@heroState]="hero.state"
(click)="hero.toggleState()">
{{hero.name}}
</li>
</ul>
`,
// #enddocregion template
styleUrls: ['./hero-list.component.css'],
/*
* Define two states, "inactive" and "active", and the end
* styles that apply whenever the element is in those states.
* Then define an animated transition between these two
* states, in *both* directions.
*/
// #docregion animationdef
animations: [
trigger('heroState', [
state('inactive', style({
backgroundColor: '#eee',
transform: 'scale(1)'
})),
state('active', style({
backgroundColor: '#cfd8dc',
transform: 'scale(1.1)'
})),
// #docregion transitions
transition('inactive => active, active => inactive',
animate('100ms ease-out'))
// #enddocregion transitions
])
]
// #enddocregion animationdef
})
export class HeroListCombinedTransitionsComponent {
@Input() heroes: Hero[];
}

View File

@ -0,0 +1,20 @@
import { Component } from '@angular/core';
import { HEROES } from './mock-heroes';
@Component({
selector: 'app-hero-list-enter-leave-page',
template: `
<section>
<h2>Enter/Leave</h2>
<app-hero-list-enter-leave [heroes]="heroes" (remove)="onRemove($event)"></app-hero-list-enter-leave>
</section>
`
})
export class HeroListEnterLeavePageComponent {
heroes = HEROES.slice();
onRemove(id: number) {
this.heroes = this.heroes.filter(hero => hero.id !== id);
}
}

View File

@ -1,63 +0,0 @@
import {
Component,
Input
} from '@angular/core';
import {
trigger,
state,
style,
animate,
transition
} from '@angular/animations';
import { Hero } from './hero.service';
@Component({
selector: 'app-hero-list-enter-leave-states',
// #docregion template
template: `
<ul>
<li *ngFor="let hero of heroes"
(click)="hero.toggleState()"
[@heroState]="hero.state">
{{hero.name}}
</li>
</ul>
`,
// #enddocregion template
styleUrls: ['./hero-list.component.css'],
/* The elements here have two possible states based
* on the hero state, "active", or "inactive". We animate
* six transitions: Between the two states in both directions,
* and between each state and void. With this we can animate
* the enter and leave of elements differently based on which
* state they are in when they are added and removed.
*/
// #docregion animationdef
animations: [
trigger('heroState', [
state('inactive', style({transform: 'translateX(0) scale(1)'})),
state('active', style({transform: 'translateX(0) scale(1.1)'})),
transition('inactive => active', animate('100ms ease-in')),
transition('active => inactive', animate('100ms ease-out')),
transition('void => inactive', [
style({transform: 'translateX(-100%) scale(1)'}),
animate(100)
]),
transition('inactive => void', [
animate(100, style({transform: 'translateX(100%) scale(1)'}))
]),
transition('void => active', [
style({transform: 'translateX(0) scale(0)'}),
animate(200)
]),
transition('active => void', [
animate(200, style({transform: 'translateX(0) scale(0)'}))
])
])
]
// #enddocregion animationdef
})
export class HeroListEnterLeaveStatesComponent {
@Input() heroes: Hero[];
}

View File

@ -1,6 +1,8 @@
import {
Component,
Input
Input,
Output,
EventEmitter
} from '@angular/core';
import {
trigger,
@ -10,42 +12,45 @@ import {
transition
} from '@angular/animations';
import { Hero } from './hero.service';
import { Hero } from './hero';
@Component({
selector: 'app-hero-list-enter-leave',
// #docregion template
template: `
<ul>
<ul class="heroes">
<li *ngFor="let hero of heroes"
[@flyInOut]="'in'">
{{hero.name}}
[@flyInOut]="'in'" (click)="removeHero(hero.id)">
<div class="inner">
<span class="badge">{{ hero.id }}</span>
<span>{{ hero.name }}</span>
</div>
</li>
</ul>
`,
// #enddocregion template
styleUrls: ['./hero-list.component.css'],
/* The element here always has the state "in" when it
* is present. We animate two transitions: From void
* to in and from in to void, to achieve an animated
* enter and leave transition. The element enters from
* the left and leaves to the right using translateX.
*/
styleUrls: ['./hero-list-page.component.css'],
// #docregion animationdef
animations: [
trigger('flyInOut', [
state('in', style({transform: 'translateX(0)'})),
state('in', style({ transform: 'translateX(0)' })),
transition('void => *', [
style({transform: 'translateX(-100%)'}),
style({ transform: 'translateX(-100%)' }),
animate(100)
]),
transition('* => void', [
animate(100, style({transform: 'translateX(100%)'}))
animate(100, style({ transform: 'translateX(100%)' }))
])
])
]
// #enddocregion animationdef
})
export class HeroListEnterLeaveComponent {
@Input() heroes: Hero[];
@Input() heroes: Hero[];
@Output() remove = new EventEmitter<number>();
removeHero(id: number) {
this.remove.emit(id);
}
}

View File

@ -0,0 +1,20 @@
import { Component } from '@angular/core';
import { HEROES } from './mock-heroes';
@Component({
selector: 'app-hero-list-groups-page',
template: `
<section>
<h2>Hero List Group</h2>
<app-hero-list-groups [heroes]="heroes" (remove)="onRemove($event)"></app-hero-list-groups>
</section>
`
})
export class HeroListGroupPageComponent {
heroes = HEROES.slice();
onRemove(id: number) {
this.heroes = this.heroes.filter(hero => hero.id !== id);
}
}

View File

@ -1,6 +1,8 @@
import {
Component,
Input
Input,
Output,
EventEmitter
} from '@angular/core';
import {
trigger,
@ -11,45 +13,31 @@ import {
group
} from '@angular/animations';
import { Hero } from './hero.service';
import { Hero } from './hero';
@Component({
selector: 'app-hero-list-groups',
template: `
<ul>
<ul class="heroes">
<li *ngFor="let hero of heroes"
[@flyInOut]="'in'">
{{hero.name}}
[@flyInOut]="'in'" (click)="removeHero(hero.id)">
<div class="inner">
<span class="badge">{{ hero.id }}</span>
<span>{{ hero.name }}</span>
</div>
</li>
</ul>
`,
styleUrls: ['./hero-list.component.css'],
styles: [`
li {
padding: 0 !important;
text-align: center;
}
`],
/* The element here always has the state "in" when it
* is present. We animate two transitions: From void
* to in and from in to void, to achieve an animated
* enter and leave transition.
*
* The transitions have *parallel group* that allow
* animating several properties at the same time but
* with different timing configurations. On enter
* (void => *) we start the opacity animation 0.1s
* earlier than the translation/width animation.
* On leave (* => void) we do the opposite -
* the translation/width animation begins immediately
* and the opacity animation 0.1s later.
*/
styleUrls: ['./hero-list-page.component.css'],
// #docregion animationdef
animations: [
trigger('flyInOut', [
state('in', style({width: 120, transform: 'translateX(0)', opacity: 1})),
state('in', style({
width: 120,
transform: 'translateX(0)', opacity: 1
})),
transition('void => *', [
style({width: 10, transform: 'translateX(50px)', opacity: 0}),
style({ width: 10, transform: 'translateX(50px)', opacity: 0 }),
group([
animate('0.3s 0.1s ease', style({
transform: 'translateX(0)',
@ -77,4 +65,10 @@ import { Hero } from './hero.service';
})
export class HeroListGroupsComponent {
@Input() heroes: Hero[];
@Output() remove = new EventEmitter<number>();
removeHero(id: number) {
this.remove.emit(id);
}
}

View File

@ -1,60 +0,0 @@
// #docregion
// #docregion imports
import {
Component,
Input,
} from '@angular/core';
import {
trigger,
style,
animate,
transition
} from '@angular/animations';
// #enddocregion imports
import { Hero } from './hero.service';
@Component({
selector: 'app-hero-list-inline-styles',
// #docregion template
template: `
<ul>
<li *ngFor="let hero of heroes"
[@heroState]="hero.state"
(click)="hero.toggleState()">
{{hero.name}}
</li>
</ul>
`,
// #enddocregion template
styleUrls: ['./hero-list.component.css'],
/**
* Define two states, "inactive" and "active", and the end
* styles that apply whenever the element is in those states.
* Then define an animation for the inactive => active transition.
* This animation has no end styles, but only styles that are
* defined inline inside the transition and thus are only kept
* as long as the animation is running.
*/
// #docregion animationdef
animations: [
trigger('heroState', [
// #docregion transitions
transition('inactive => active', [
style({
backgroundColor: '#cfd8dc',
transform: 'scale(1.3)'
}),
animate('80ms ease-in', style({
backgroundColor: '#eee',
transform: 'scale(1)'
}))
]),
// #enddocregion transitions
])
]
// #enddocregion animationdef
})
export class HeroListInlineStylesComponent {
@Input() heroes: Hero[];
}

View File

@ -1,71 +0,0 @@
import {
Component,
Input,
} from '@angular/core';
import {
trigger,
state,
style,
animate,
transition,
keyframes,
AnimationEvent
} from '@angular/animations';
import { Hero } from './hero.service';
@Component({
selector: 'app-hero-list-multistep',
// #docregion template
template: `
<ul>
<li *ngFor="let hero of heroes"
(@flyInOut.start)="animationStarted($event)"
(@flyInOut.done)="animationDone($event)"
[@flyInOut]="'in'">
{{hero.name}}
</li>
</ul>
`,
// #enddocregion template
styleUrls: ['./hero-list.component.css'],
/* The element here always has the state "in" when it
* is present. We animate two transitions: From void
* to in and from in to void, to achieve an animated
* enter and leave transition. Each transition is
* defined in terms of multiple keyframes, to give it
* a bounce effect.
*/
// #docregion animationdef
animations: [
trigger('flyInOut', [
state('in', style({transform: 'translateX(0)'})),
transition('void => *', [
animate(300, keyframes([
style({opacity: 0, transform: 'translateX(-100%)', offset: 0}),
style({opacity: 1, transform: 'translateX(15px)', offset: 0.3}),
style({opacity: 1, transform: 'translateX(0)', offset: 1.0})
]))
]),
transition('* => void', [
animate(300, keyframes([
style({opacity: 1, transform: 'translateX(0)', offset: 0}),
style({opacity: 1, transform: 'translateX(-15px)', offset: 0.7}),
style({opacity: 0, transform: 'translateX(100%)', offset: 1.0})
]))
])
])
]
// #enddocregion animationdef
})
export class HeroListMultistepComponent {
@Input() heroes: Hero[];
animationStarted(event: AnimationEvent) {
console.warn('Animation started: ', event);
}
animationDone(event: AnimationEvent) {
console.warn('Animation done: ', event);
}
}

View File

@ -0,0 +1,94 @@
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
position: relative;
height: 2.3em;
overflow:hidden;
margin: .5em;
}
.heroes li > .inner {
cursor: pointer;
background-color: #EEE;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
width: 19em;
}
.heroes li:hover > .inner {
color: #607D8B;
background-color: #DDD;
transform: translateX(.1em);
}
.heroes a {
color: #888;
text-decoration: none;
position: relative;
display: block;
width: 250px;
}
.heroes a:hover {
color:#607D8B;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #607D8B;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
min-width: 16px;
text-align: right;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
.button {
background-color: #eee;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
cursor: hand;
font-family: Arial;
}
button:hover {
background-color: #cfd8dc;
}
button.delete {
position: relative;
left: 24em;
top: -32px;
background-color: gray !important;
color: white;
display: inherit;
padding: 5px 8px;
width: 2em;
}
input {
font-size: 100%;
margin-bottom: 2px;
width: 11em;
}
.heroes input {
position: relative;
top: -3px;
width: 12em;
}

View File

@ -0,0 +1,19 @@
<!-- #docplaster -->
<h2>Filter/Stagger</h2>
<form>
<input #criteria (input)="updateCriteria(criteria.value)" placeholder="Search Heroes" />
</form>
<!-- #docregion filter-animations -->
<ul class="heroes" [@filterAnimation]="heroTotal">
<!-- #enddocregion filter-animations -->
<li *ngFor="let hero of heroes" class="hero">
<div class="inner">
<span class="badge">{{ hero.id }}</span>
<span>{{ hero.name }}</span>
</div>
</li>
<!-- #docregion filter-animations -->
</ul>
<!-- #enddocregion filter-animations -->

View File

@ -0,0 +1,81 @@
// #docplaster
import { Component, HostBinding, OnInit } from '@angular/core';
import { trigger, transition, animate, style, query, stagger } from '@angular/animations';
import { HEROES } from './mock-heroes';
// #docregion filter-animations
@Component({
// #enddocregion filter-animations
selector: 'app-hero-list-page',
templateUrl: 'hero-list-page.component.html',
styleUrls: ['hero-list-page.component.css'],
// #docregion page-animations, filter-animations
animations: [
// #enddocregion filter-animations
trigger('pageAnimations', [
transition(':enter', [
query('.hero, form', [
style({opacity: 0, transform: 'translateY(-100px)'}),
stagger(-30, [
animate('500ms cubic-bezier(0.35, 0, 0.25, 1)', style({ opacity: 1, transform: 'none' }))
])
])
])
]),
// #enddocregion page-animations
// #docregion increment
// #docregion filter-animations
trigger('filterAnimation', [
transition(':enter, * => 0, * => -1', []),
transition(':increment', [
query(':enter', [
style({ opacity: 0, width: '0px' }),
stagger(50, [
animate('300ms ease-out', style({ opacity: 1, width: '*' })),
]),
], { optional: true })
]),
transition(':decrement', [
query(':leave', [
stagger(50, [
animate('300ms ease-out', style({ opacity: 0, width: '0px' })),
]),
])
]),
]),
// #enddocregion increment
// #docregion page-animations
]
})
export class HeroListPageComponent implements OnInit {
// #enddocregion filter-animations
@HostBinding('@pageAnimations')
public animatePage = true;
_heroes = [];
// #docregion filter-animations
heroTotal = -1;
// #enddocregion filter-animations
get heroes() {
return this._heroes;
}
ngOnInit() {
this._heroes = HEROES;
}
updateCriteria(criteria: string) {
criteria = criteria ? criteria.trim() : '';
this._heroes = HEROES.filter(hero => hero.name.toLowerCase().includes(criteria.toLowerCase()));
const newTotal = this.heroes.length;
if (this.heroTotal !== newTotal) {
this.heroTotal = newTotal;
} else if (!criteria) {
this.heroTotal = -1;
}
}
// #docregion filter-animations
}
// #enddocregion filter-animations

View File

@ -1,58 +0,0 @@
import {
Component,
Input
} from '@angular/core';
import {
trigger,
state,
style,
animate,
transition
} from '@angular/animations';
import { Hero } from './hero.service';
@Component({
selector: 'app-hero-list-timings',
template: `
<ul>
<li *ngFor="let hero of heroes"
[@flyInOut]="'in'"
(click)="hero.toggleState()">
{{hero.name}}
</li>
</ul>
`,
styleUrls: ['./hero-list.component.css'],
/* The element here always has the state "in" when it
* is present. We animate two transitions: From void
* to in and from in to void, to achieve an animated
* enter and leave transition. The element enters from
* the left and leaves to the right using translateX,
* and fades in/out using opacity. We use different easings
* for enter and leave.
*/
// #docregion animationdef
animations: [
trigger('flyInOut', [
state('in', style({opacity: 1, transform: 'translateX(0)'})),
transition('void => *', [
style({
opacity: 0,
transform: 'translateX(-100%)'
}),
animate('0.2s ease-in')
]),
transition('* => void', [
animate('0.2s 0.1s ease-out', style({
opacity: 0,
transform: 'translateX(100%)'
}))
])
])
]
// #enddocregion animationdef
})
export class HeroListTimingsComponent {
@Input() heroes: Hero[];
}

View File

@ -1,58 +0,0 @@
// #docregion
// #docregion imports
import {
Component,
Input
} from '@angular/core';
import {
trigger,
state,
style,
animate,
transition
} from '@angular/animations';
// #enddocregion imports
import { Hero } from './hero.service';
@Component({
selector: 'app-hero-list-twoway',
// #docregion template
template: `
<ul>
<li *ngFor="let hero of heroes"
[@heroState]="hero.state"
(click)="hero.toggleState()">
{{hero.name}}
</li>
</ul>
`,
// #enddocregion template
styleUrls: ['./hero-list.component.css'],
/*
* Define two states, "inactive" and "active", and the end
* styles that apply whenever the element is in those states.
* Then define an animated transition between these two
* states, in *both* directions.
*/
// #docregion animationdef
animations: [
trigger('heroState', [
state('inactive', style({
backgroundColor: '#eee',
transform: 'scale(1)'
})),
state('active', style({
backgroundColor: '#cfd8dc',
transform: 'scale(1.1)'
})),
// #docregion transitions
transition('inactive <=> active', animate('100ms ease-out'))
// #enddocregion transitions
])
]
// #enddocregion animationdef
})
export class HeroListTwowayComponent {
@Input() heroes: Hero[];
}

View File

@ -1,99 +0,0 @@
import { Component } from '@angular/core';
import { Hero, HeroService } from './hero.service';
@Component({
selector: 'app-root',
template: `
<div class="buttons">
<button [disabled]="!heroService.canAdd()" (click)="heroService.addInactive()">Add inactive hero</button>
<button [disabled]="!heroService.canAdd()" (click)="heroService.addActive()">Add active hero</button>
<button [disabled]="!heroService.canRemove()" (click)="heroService.remove()">Remove hero</button>
</div>
<div class="columns">
<div class="column">
<h4>Basic State</h4>
<p>Switch between active/inactive on click.</p>
<app-hero-list-basic [heroes]="heroes"></app-hero-list-basic>
</div>
<div class="column">
<h4>Styles inline in transitions</h4>
<p>Animated effect on click, no persistend end styles.</p>
<app-hero-list-inline-styles [heroes]="heroes"></app-hero-list-inline-styles>
</div>
<div class="column">
<h4>Combined transition syntax</h4>
<p>Switch between active/inactive on click. Define just one transition used in both directions.</p>
<app-hero-list-combined-transitions [heroes]="heroes"></app-hero-list-combined-transitions>
</div>
<div class="column">
<h4>Two-way transition syntax</h4>
<p>Switch between active/inactive on click. Define just one transition used in both directions using the <=> syntax.</p>
<app-hero-list-twoway [heroes]="heroes"></app-hero-list-twoway>
</div>
<div class="column">
<h4>Enter & Leave</h4>
<p>Enter and leave animations using the void state.</p>
<app-hero-list-enter-leave [heroes]="heroes"></app-hero-list-enter-leave>
</div>
</div>
<div class="columns">
<div class="column">
<h4>Enter & Leave & States</h4>
<p>
Enter and leave animations combined with active/inactive state animations.
Different enter and leave transitions depending on state.
</p>
<app-hero-list-enter-leave-states [heroes]="heroes"></app-hero-list-enter-leave-states>
</div>
<div class="column">
<h4>Auto Style Calc</h4>
<p>Leave animation from the current computed height using the auto-style value *.</p>
<app-hero-list-auto [heroes]="heroes"></app-hero-list-auto>
</div>
<div class="column">
<h4>Different Timings</h4>
<p>Enter and leave animations with different easings, ease-in for enter, ease-out for leave.</p>
<app-hero-list-timings [heroes]="heroes"></app-hero-list-timings>
</div>
<div class="column">
<h4>Multiple Keyframes</h4>
<p>Enter and leave animations with three keyframes in each, to give the transition some bounce.</p>
<app-hero-list-multistep [heroes]="heroes"></app-hero-list-multistep>
</div>
<div class="column">
<h4>Parallel Groups</h4>
<p>Enter and leave animations with multiple properties animated in parallel with different timings.</p>
<app-hero-list-groups [heroes]="heroes"></app-hero-list-groups>
</div>
</div>
`,
styles: [`
.buttons {
text-align: center;
}
button {
padding: 1.5em 3em;
}
.columns {
display: flex;
flex-direction: row;
}
.column {
flex: 1;
padding: 10px;
}
.column p {
min-height: 6em;
}
`],
providers: [HeroService]
})
export class HeroTeamBuilderComponent {
heroes: Hero[];
constructor(private heroService: HeroService) {
this.heroes = heroService.heroes;
}
}

View File

@ -1,54 +0,0 @@
import { Injectable } from '@angular/core';
// #docregion hero
export class Hero {
constructor(public name: string, public state = 'inactive') { }
toggleState() {
this.state = this.state === 'active' ? 'inactive' : 'active';
}
}
// #enddocregion hero
const ALL_HEROES = [
'Windstorm',
'RubberMan',
'Bombasto',
'Magneta',
'Dynama',
'Narco',
'Celeritas',
'Dr IQ',
'Magma',
'Tornado',
'Mr. Nice'
].map(name => new Hero(name));
@Injectable()
export class HeroService {
heroes: Hero[] = [];
canAdd() {
return this.heroes.length < ALL_HEROES.length;
}
canRemove() {
return this.heroes.length > 0;
}
addActive(active = true) {
let hero = ALL_HEROES[this.heroes.length];
hero.state = active ? 'active' : 'inactive';
this.heroes.push(hero);
}
addInactive() {
this.addActive(false);
}
remove() {
this.heroes.length -= 1;
}
}

View File

@ -0,0 +1,4 @@
export class Hero {
id: number;
name: string;
}

View File

@ -0,0 +1,3 @@
<p>
Welcome to Animations in Angular!
</p>

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@ -0,0 +1,12 @@
:host {
display: block;
}
.insert-remove-container {
border: 1px solid #dddddd;
margin-top: 1em;
padding: 20px 20px 0px 20px;
color: #000000;
font-weight: bold;
font-size: 20px;
}

View File

@ -0,0 +1,10 @@
<!-- #docplaster -->
<nav>
<button (click)="toggle()">Toggle Insert/Remove</button>
</nav>
<!-- #docregion insert-remove-->
<div @myInsertRemoveTrigger *ngIf="isShown" class="insert-remove-container">
<p>The box is inserted</p>
</div>
<!-- #enddocregion insert-remove-->

View File

@ -0,0 +1,29 @@
// #docplaster
import { Component } from '@angular/core';
import { trigger, transition, animate, style } from '@angular/animations';
@Component({
selector: 'app-insert-remove',
animations: [
// #docregion enter-leave-trigger
trigger('myInsertRemoveTrigger', [
transition(':enter', [
style({ opacity: 0 }),
animate('5s', style({ opacity: 1 })),
]),
transition(':leave', [
animate('5s', style({ opacity: 0 }))
])
]),
// #enddocregion enter-leave-trigger
],
templateUrl: 'insert-remove.component.html',
styleUrls: ['insert-remove.component.css']
})
export class InsertRemoveComponent {
isShown = false;
toggle() {
this.isShown = !this.isShown;
}
}

View File

@ -0,0 +1,15 @@
// #docregion
import { Hero } from './hero';
export const HEROES: Hero[] = [
{ id: 11, name: 'Mr. Nice' },
{ id: 12, name: 'Narco' },
{ id: 13, name: 'Bombasto' },
{ id: 14, name: 'Celeritas' },
{ id: 15, name: 'Magneta' },
{ id: 16, name: 'RubberMan' },
{ id: 17, name: 'Dynama' },
{ id: 18, name: 'Dr IQ' },
{ id: 19, name: 'Magma' },
{ id: 20, name: 'Tornado' }
];

View File

@ -0,0 +1,20 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-open-close-page',
template: `
<section>
<h2>Open Close Component</h2>
<input type="checkbox" [checked]="logging" (click)="toggleLogging()"/> Console Log Animation Events
<app-open-close [logging]="logging"></app-open-close>
</section>
`
})
export class OpenClosePageComponent {
logging = false;
toggleLogging() {
this.logging = !this.logging;
}
}

View File

@ -0,0 +1,10 @@
<!-- #docplaster -->
<nav>
<button (click)="toggle()">Toggle Open/Close</button>
</nav>
<!-- #docregion compare, trigger -->
<div [@openClose]="isOpen ? 'open' : 'closed'" class="open-close-container">
<p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
</div>
<!-- #enddocregion compare, trigger -->

View File

@ -0,0 +1,40 @@
import { Component } from '@angular/core';
import { trigger, transition, state, animate, style, keyframes } from '@angular/animations';
@Component({
selector: 'app-open-close',
animations: [
// #docregion trigger
trigger('openClose', [
state('open', style({
height: '200px',
opacity: 1,
backgroundColor: 'yellow'
})),
state('close', style({
height: '100px',
opacity: 0.5,
backgroundColor: 'green'
})),
// ...
transition('* => *', [
animate('1s', keyframes ( [
style({ opacity: 0.1, offset: 0.1 }),
style({ opacity: 0.6, offset: 0.2 }),
style({ opacity: 1, offset: 0.5 }),
style({ opacity: 0.2, offset: 0.7 })
]))
])
])
// #enddocregion trigger
],
templateUrl: 'open-close.component.html',
styleUrls: ['open-close.component.css']
})
export class OpenCloseKeyframeComponent {
isOpen = false;
toggle() {
this.isOpen = !this.isOpen;
}
}

View File

@ -0,0 +1,12 @@
<!-- #docplaster -->
<nav>
<button (click)="toggle()">Toggle Boolean/Close</button>
</nav>
<!-- #docregion trigger-boolean -->
<div [@openClose]="isOpen ? true : false" class="open-close-container">
<!-- #enddocregion trigger-boolean -->
<p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
<!-- #docregion trigger-boolean -->
</div>
<!-- #enddocregion trigger-boolean -->

View File

@ -0,0 +1,24 @@
import { Component } from '@angular/core';
import { trigger, transition, state, animate, style } from '@angular/animations';
@Component({
selector: 'app-open-close-boolean',
// #docregion trigger-boolean
animations: [
trigger('openClose', [
state('true', style({ height: '*' })),
state('false', style({ height: '0px' })),
transition('false <=> true', animate(500))
])
],
// #enddocregion trigger-boolean
templateUrl: 'open-close.component.2.html',
styleUrls: ['open-close.component.css']
})
export class OpenCloseBooleanComponent {
isOpen = false;
toggle() {
this.isOpen = !this.isOpen;
}
}

View File

@ -0,0 +1,15 @@
<!-- #docplaster -->
<nav>
<button (click)="toggle()">Toggle Open/Close</button>
</nav>
<!-- #docregion callbacks -->
<div [@openClose]="isOpen ? 'open' : 'closed'"
(@openClose.start)="onAnimationEvent($event)"
(@openClose.done)="onAnimationEvent($event)"
class="open-close-container">
<!-- #enddocregion callbacks -->
<p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
<!-- #docregion callbacks -->
</div>
<!-- #enddocregion callbacks -->

View File

@ -0,0 +1,48 @@
// #docplaster
// #docregion reusable
import { Component } from '@angular/core';
import { useAnimation, transition, trigger, style, animate } from '@angular/animations';
import { transAnimation } from './animations';
@Component({
// #enddocregion reusable
selector: 'app-open-close-reusable',
// #docregion runtime
animations: [
transition('open => closed', [
style({
height: '200 px',
opacity: '{{ opacity }}',
backgroundcolor: 'yelow'
}),
animate('{{ time }}'),
], {
params: {
time: '1s',
opacity: '1'
}
}),
// #enddocregion runtime
// #docregion reusable
trigger('openClose', [
transition('open => closed', [
useAnimation(transAnimation, {
params: {
height: 0,
opacity: 1,
backgroundColor: 'red',
time: '1s'
}
})
])
])
// #docregion runtime
],
// #enddocregion runtime
// #enddocregion reusable
templateUrl: 'open-close.component.html',
styleUrls: ['open-close.component.css']
// #docregion reusable
})
// #enddocregion reusable
export class OpenCloseReusableComponent { }

View File

@ -0,0 +1,12 @@
<nav>
<button (click)="toggleAnimations()">Toggle Animations</button>
<button (click)="toggle()">Toggle Open/Closed</button>
</nav>
<!-- #docregion toggle-animation -->
<div [@.disabled]="isDisabled">
<div [@childAnimation]="isOpen ? 'open' : 'closed'"
class="open-close-container">
<p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
</div>
</div>
<!-- #enddocregion toggle-animation -->

View File

@ -0,0 +1,47 @@
// #docplaster
// #docregion
import { Component } from '@angular/core';
import { trigger, transition, state, animate, style } from '@angular/animations';
// #docregion toggle-animation
@Component({
// #enddocregion toggle-animation
selector: 'app-open-close-toggle',
templateUrl: 'open-close.component.4.html',
styleUrls: ['open-close.component.css'],
// #docregion toggle-animation
animations: [
trigger('childAnimation', [
// ...
// #enddocregion toggle-animation
state('open', style({
width: '250px',
opacity: 1,
backgroundColor: 'yellow'
})),
state('closed', style({
width: '100px',
opacity: 0.5,
backgroundColor: 'green'
})),
transition('* => *', [
animate('1s')
]),
// #docregion toggle-animation
]),
],
})
export class OpenCloseChildComponent {
isDisabled = false;
isOpen = false;
// #enddocregion toggle-animation
toggleAnimations() {
this.isDisabled = !this.isDisabled;
}
toggle() {
this.isOpen = !this.isOpen;
}
// #docregion toggle-animation
}
// #enddocregion toggle-animation

View File

@ -0,0 +1,12 @@
:host {
display: block;
}
.open-close-container {
border: 1px solid #dddddd;
margin-top: 1em;
padding: 20px 20px 0px 20px;
color: #000000;
font-weight: bold;
font-size: 20px;
}

View File

@ -0,0 +1,13 @@
<!-- #docplaster -->
<nav>
<button (click)="toggle()">Toggle Open/Close</button>
</nav>
<!-- #docregion compare, trigger -->
<div [@openClose]="isOpen ? 'open' : 'closed'"
(@openClose.start)="onAnimationEvent($event)"
(@openClose.done)="onAnimationEvent($event)"
class="open-close-container">
<p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
</div>
<!-- #enddocregion compare, trigger -->

View File

@ -0,0 +1,110 @@
// #docplaster
import { Component, Input } from '@angular/core';
import { trigger, transition, state, animate, style, AnimationEvent } from '@angular/animations';
// #docregion component, events1
@Component({
selector: 'app-open-close',
// #docregion trigger, trigger-wildcard1, trigger-transition
animations: [
trigger('openClose', [
// #enddocregion events1
// #docregion state1, events1
// ...
// #enddocregion events1
state('open', style({
height: '200px',
opacity: 1,
backgroundColor: 'yellow'
})),
// #enddocregion state1
// #docregion state2
state('closed', style({
height: '100px',
opacity: 0.5,
backgroundColor: 'green'
})),
// #enddocregion state2, trigger-wildcard1
// #docregion transition1
transition('open => closed', [
animate('1s')
]),
// #enddocregion transition1
// #docregion transition2
transition('closed => open', [
animate('0.5s')
]),
// #enddocregion trigger, component
// #enddocregion transition2
// #docregion trigger-wildcard1
transition('* => closed', [
animate('1s')
]),
transition('* => open', [
animate('0.5s')
]),
// #enddocregion trigger-wildcard1
// #docregion trigger-wildcard2
transition('open <=> closed', [
animate('0.5s')
]),
// #enddocregion trigger-wildcard2
// #docregion transition4
transition ('* => open', [
animate ('1s',
style ({ opacity: '*' }),
),
]),
// #enddocregion transition4
// #docregion transition3
transition('* => *', [
animate('1s')
]),
// #enddocregion transition3, trigger-transition
// #docregion trigger, component, trigger-wildcard1, events1
]),
],
// #enddocregion trigger, trigger-wildcard1
templateUrl: 'open-close.component.html',
styleUrls: ['open-close.component.css']
})
// #docregion events
export class OpenCloseComponent {
// #enddocregion events1, events
isOpen = true;
toggle() {
this.isOpen = !this.isOpen;
}
// #enddocregion component
@Input() logging = false;
// #docregion events1, events
onAnimationEvent ( event: AnimationEvent ) {
// #enddocregion events1, events
if (!this.logging) {
return;
}
// #docregion events
// openClose is trigger name in this example
console.warn(`Animation Trigger: ${event.triggerName}`);
// phaseName is start or done
console.warn(`Phase: ${event.phaseName}`);
// in our example, totalTime is 1000 or 1 second
console.warn(`Total time: ${event.totalTime}`);
// in our example, fromState is either open or closed
console.warn(`From: ${event.fromState}`);
// in our example, toState either open or closed
console.warn(`To: ${event.toState}`);
// the HTML element itself, the button in this case
console.warn(`Element: ${event.element}`);
// #docregion events1
}
// #docregion component
}
// #enddocregion component

View File

@ -0,0 +1,12 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-status-slider-page',
template: `
<section>
<h2>Status Slider</h2>
<app-status-slider></app-status-slider>
</section>
`
})
export class StatusSliderPageComponent {}

View File

@ -0,0 +1,13 @@
:host {
display: block;
}
.box {
width: 300px;
border: 5px solid black;
display: block;
line-height: 300px;
text-align: center;
font-size: 50px;
color: white;
}

View File

@ -0,0 +1,7 @@
<nav>
<button (click)="toggle()">Toggle Status</button>
</nav>
<div [@slideStatus]="status" class="box">
{{ status == 'active' ? 'Active' : 'Inactive' }}
</div>

View File

@ -0,0 +1,52 @@
import { Component } from '@angular/core';
import { trigger, transition, state, animate, style, keyframes } from '@angular/animations';
@Component({
selector: 'app-status-slider',
templateUrl: 'status-slider.component.html',
styleUrls: ['status-slider.component.css'],
animations: [
trigger('slideStatus', [
state('inactive', style({ backgroundColor: 'blue' })),
state('active', style({ backgroundColor: 'orange' })),
// #docregion keyframesWithOffsets
transition('* => active', [
animate('2s', keyframes([
style({ backgroundColor: 'blue', offset: 0}),
style({ backgroundColor: 'red', offset: 0.8}),
style({ backgroundColor: 'orange', offset: 1.0})
])),
]),
transition('* => inactive', [
animate('2s', keyframes([
style({ backgroundColor: 'orange', offset: 0}),
style({ backgroundColor: 'red', offset: 0.2}),
style({ backgroundColor: 'blue', offset: 1.0})
]))
]),
// #enddocregion keyframesWithOffsets
// #docregion keyframes
transition('* => active', [
animate('2s', keyframes([
style({ backgroundColor: 'blue' }),
style({ backgroundColor: 'red' }),
style({ backgroundColor: 'orange' })
]))
// #enddocregion keyframes
]),
])
]
})
export class StatusSliderComponent {
status: 'active' | 'inactive' = 'inactive';
toggle() {
if (this.status === 'active') {
this.status = 'inactive';
} else {
this.status = 'active';
}
}
}

View File

@ -0,0 +1,13 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-toggle-animations-child-page',
template: `
<section>
<h2>Toggle Animations</h2>
<app-open-close-toggle></app-open-close-toggle>
</section>
`
})
export class ToggleAnimationsPageComponent {}

View File

@ -8,12 +8,7 @@
</head>
<body>
<h1 style="visibility: hidden;">External H1 Title for E2E test</h1>
<app-root></app-root>
<button style="visibility: hidden;">External button for E2E test</button>
<ul style="visibility: hidden;">
<li>External list for E2E test</li>
</ul>
</body>
</html>

View File

@ -1,7 +1,9 @@
{
"description": "Angular Animations",
"description": "Angular Animations Guide",
"files":[
"!**/*.d.ts",
"!**/*.js"
]
"!**/*.js",
"!**/*.[1,2,3].*"
],
"tags": ["animations"]
}

View File

@ -173,51 +173,4 @@ describe('NgModule-example', function () {
});
});
// describe('index.0.html', function() {
// beforeEach(function () {
// browser.get('index.0.html');
// });
// it('has a title', function () {
// const title = element.all(by.tagName('h1')).get(0);
// expect(title.getText()).toBe('Minimal NgModule');
// });
// });
// describe('index.1.html', function () {
// beforeEach(function () {
// browser.get('index.1.html');
// });
// describe('app-title', appTitleTests(powderblue));
// });
// describe('index.1b.html', function () {
// beforeEach(function () {
// browser.get('index.1b.html');
// });
// describe('app-title', appTitleTests(powderblue));
// describe('contact', contactTests(powderblue));
// });
// describe('index.2.html', function () {
// beforeEach(function () {
// browser.get('index.2.html');
// });
// describe('app-title', appTitleTests(gold));
// describe('contact', contactTests(powderblue));
// });
// describe('index.3.html', function () {
// beforeEach(function () {
// browser.get('index.3.html');
// });
// describe('app-title', appTitleTests(gold));
// });
});

View File

@ -1,354 +1,334 @@
# Animations
# Introduction to Angular animations
Motion is an important aspect in the design of modern web applications. Good
user interfaces transition smoothly between states with engaging animations
that call attention where it's needed. Well-designed animations can make a UI not only
more fun but also easier to use.
Animation provides the illusion of motion: HTML elements change styling over time. Well-designed animations can make your application more fun and easier to use, but they aren't just cosmetic. Animations can improve your app and user experience in a number of ways:
## Overview
* Without animations, web page transitions can seem abrupt and jarring.
Angular's animation system lets you build animations that run with the same kind of native
performance found in pure CSS animations. You can also tightly integrate your
animation logic with the rest of your application code, for ease of control.
* Motion greatly enhances the user experience, so animations give users a chance to detect the application's response to their actions.
<div class="alert is-helpful">
* Good animations intuitively call the user's attention to where it is needed.
Angular animations are built on top of the standard [Web Animations API](https://w3c.github.io/web-animations/)
and run natively on [browsers that support it](http://caniuse.com/#feat=web-animation).
Typically, animations involve multiple style *transformations* over time. An HTML element can move, change color, grow or shrink, fade, or slide off the page. These changes can occur simultaneously or sequentially. You can control the timing of each transformation.
As of Angular 6, If the Web Animations API is not supported natively by the browser, then Angular will use CSS
keyframes as a fallback instead (automatically). This means that the polyfill is no longer required unless any
code uses [AnimationBuilder](/api/animations/AnimationBuilder). If your code does use AnimationBuilder, then
uncomment the `web-animations-js` polyfill from the `polyfills.ts` file generated by Angular CLI.
</div>
Angular's animation system is built on CSS functionality, which means you can animate any property that the browser considers animatable. This includes positions, sizes, transforms, colors, borders, and more. The W3C maintains a list of animatable properties on its [CSS Transitions](https://www.w3.org/TR/css-transitions-1/) page.
<div class="alert is-helpful">
The examples in this page are available as a <live-example></live-example>.
## About this guide
</div>
This guide covers the basic Angular animation features to get you started on adding Angular animations to your project.
## Setup
The features described in this guide &mdash; and the more advanced features described in the related Angular animations guides &mdash; are demonstrated in an example app available as a <live-example></live-example>.
Before you can add animations to your application, you need
to import a few animation-specific modules and functions to the root application module.
#### Prerequisites
<code-example path="animations/src/app/app.module.ts" region="animations-module" title="app.module.ts (animation module import excerpt)" linenums="false"></code-example>
The guide assumes that you're familiar with building basic Angular apps, as described in the following sections:
#### Example basics
* [Tutorial](tutorial)
* [Architecture Overview](guide/architecture)
The animations examples in this guide animate a list of heroes.
A `Hero` class has a `name` property, a `state` property that indicates if the hero is active or not,
and a `toggleState()` method to switch between the states.
## Getting started
<code-example path="animations/src/app/hero.service.ts" region="hero" title="hero.service.ts (Hero class)" linenums="false"></code-example>
The main Angular modules for animations are `@angular/animations` and `@angular/platform-browser`. When you create a new project using the CLI, these dependencies are automatically added to your project.
Across the top of the screen (`app.hero-team-builder.component.ts`)
are a series of buttons that add and remove heroes from the list (via the `HeroService`).
The buttons trigger changes to the list that all of the example components see at the same time.
To get started with adding Angular animations to your project, import the animation-specific modules along with standard Angular functionality.
{@a example-transitioning-between-states}
### Step 1: Enabling the animations module
## Transitioning between two states
Import `BrowserAnimationsModule`, which introduces the animation capabilities into your Angular root application module.
<img src="generated/images/guide/animations/animation_basic_click.gif" alt="A simple transition animation" class="right">
You can build a simple animation that transitions an element between two states
driven by a model attribute.
Animations can be defined inside `@Component` metadata.
<code-example path="animations/src/app/hero-list-basic.component.ts" region="imports" title="hero-list-basic.component.ts" linenums="false"></code-example>
With these, you can define an *animation trigger* called `heroState` in the component
metadata. It uses animations to transition between two states: `active` and `inactive`. When a
hero is active, the element appears in a slightly larger size and lighter color.
<code-example path="animations/src/app/hero-list-basic.component.ts" region="animationdef" title="hero-list-basic.component.ts (@Component excerpt)" linenums="false"></code-example>
<div class="alert is-helpful">
In this example, you are defining animation styles (color and transform) inline in the
animation metadata.
</div>
Now, using the `[@triggerName]` syntax, attach the animation that you just defined to
one or more elements in the component's template.
<code-example path="animations/src/app/hero-list-basic.component.ts" region="template" title="hero-list-basic.component.ts (excerpt)" linenums="false"></code-example>
Here, the animation trigger applies to every element repeated by an `ngFor`. Each of
the repeated elements animates independently. The value of the
attribute is bound to the expression `hero.state` and is always either `active` or `inactive`.
With this setup, an animated transition appears whenever a hero object changes state.
Here's the full component implementation:
<code-example path="animations/src/app/hero-list-basic.component.ts" title="hero-list-basic.component.ts"></code-example>
## States and transitions
Angular animations are defined as logical **states** and **transitions**
between states.
An animation state is a string value that you define in your application code. In the example
above, the states `'active'` and `'inactive'` are based on the logical state of
hero objects. The source of the state can be a simple object attribute, as it was in this case,
or it can be a value computed in a method. The important thing is that you can read it into the
component's template.
You can define *styles* for each animation state:
<code-example path="animations/src/app/hero-list-basic.component.ts" region="states" title="src/app/hero-list-basic.component.ts" linenums="false"></code-example>
These `state` definitions specify the *end styles* of each state.
They are applied to the element once it has transitioned to that state, and stay
*as long as it remains in that state*. In effect, you're defining what styles the element has in different states.
After you define states, you can define *transitions* between the states. Each transition
controls the timing of switching between one set of styles and the next:
<code-example path="animations/src/app/hero-list-basic.component.ts" region="transitions" title="src/app/hero-list-basic.component.ts" linenums="false"></code-example>
<figure>
<img src="generated/images/guide/animations/ng_animate_transitions_inactive_active.png" alt="In Angular animations you define states and transitions between states" width="400">
</figure>
If several transitions have the same timing configuration, you can combine
them into the same `transition` definition:
<code-example path="animations/src/app/hero-list-combined-transitions.component.ts" region="transitions" title="src/app/hero-list-combined-transitions.component.ts" linenums="false"></code-example>
When both directions of a transition have the same timing, as in the previous
example, you can use the shorthand syntax `<=>`:
<code-example path="animations/src/app/hero-list-twoway.component.ts" region="transitions" title="src/app/hero-list-twoway.component.ts" linenums="false"></code-example>
You can also apply a style during an animation but not keep it around
after the animation finishes. You can define such styles inline, in the `transition`. In this example,
the element receives one set of styles immediately and is then animated to the next.
When the transition finishes, none of these styles are kept because they're not
defined in a `state`.
<code-example path="animations/src/app/hero-list-inline-styles.component.ts" region="transitions" title="src/app/hero-list-inline-styles.component.ts" linenums="false"></code-example>
### The wildcard state `*`
The `*` ("wildcard") state matches *any* animation state. This is useful for defining styles and
transitions that apply regardless of which state the animation is in. For example:
* The `active => *` transition applies when the element's state changes from `active` to anything else.
* The `* => *` transition applies when *any* change between two states takes place.
<figure>
<img src="generated/images/guide/animations/ng_animate_transitions_inactive_active_wildcards.png" alt="The wildcard state can be used to match many different transitions at once" width="400">
</figure>
### The `void` state
The special state called `void` can apply to any animation. It applies
when the element is *not* attached to a view, perhaps because it has not yet been
added or because it has been removed. The `void` state is useful for defining enter and
leave animations.
For example the `* => void` transition applies when the element leaves the view,
regardless of what state it was in before it left.
<figure>
<img src="generated/images/guide/animations/ng_animate_transitions_void_in.png" alt="The void state can be used for enter and leave transitions" width="400">
</figure>
The wildcard state `*` also matches `void`.
## Example: Entering and leaving
<img src="generated/images/guide/animations/animation_enter_leave.gif" alt="Enter and leave animations" class="right" width="250">
Using the `void` and `*` states you can define transitions that animate the
entering and leaving of elements:
* Enter: `void => *`
* Leave: `* => void`
For example, in the `animations` array below there are two transitions that use
the `void => *` and `* => void` syntax to animate the element in and out of the view.
<code-example path="animations/src/app/hero-list-enter-leave.component.ts" region="animationdef" title="hero-list-enter-leave.component.ts (excerpt)" linenums="false"></code-example>
Note that in this case the styles are applied to the void state directly in the
transition definitions, and not in a separate `state(void)` definition. Thus, the transforms
are different on enter and leave: the element enters from the left
and leaves to the right.
<div class="alert is-helpful">
These two common animations have their own aliases:
<code-example language="typescript">
transition(':enter', [ ... ]); // void => *
transition(':leave', [ ... ]); // * => void
<code-example path="animations/src/app/app.module.1.ts" title="src/app/app.module.ts" language="typescript" linenums="false">
</code-example>
<div class="alert is-helpful">
**Note:** When you use the CLI to create your app, the root application module `app.module.ts` is placed in the `src/app` folder.
</div>
## Example: Entering and leaving from different states
### Step 2: Importing animation functions into component files
<img src="generated/images/guide/animations/animation_enter_leave_states.gif" alt="Enter and leave animations combined with state animations" class="right" width="200">
If you plan to use specific animation functions in component files, import those functions from `@angular/animations`.
You can also combine this animation with the earlier state transition animation by
using the hero state as the animation state. This lets you configure
different transitions for entering and leaving based on what the state of the hero
is:
<code-example path="animations/src/app/app.component.ts" title="src/app/app.component.ts" region="imports" language="typescript">
</code-example>
* Inactive hero enter: `void => inactive`
* Active hero enter: `void => active`
* Inactive hero leave: `inactive => void`
* Active hero leave: `active => void`
<div class="alert is-helpful">
This gives you fine-grained control over each transition:
**Note:** See a [summary of available animation functions](guide/animations#animation-api-summary) at the end of this guide.
</div>
### Step 3: Adding the animation metadata property
In the component file, add a metadata property called `animations:` within the `@Component()` decorator. You put the trigger that defines an animation within the `animations` metadata property.
<code-example path="animations/src/app/app.component.ts" title="src/app/app.component.ts" region="decorator" language="typescript">
</code-example>
## Animating a simple transition
Let's animate a simple transition that changes a single HTML element from one state to another. For example, you can specify that a button displays either **Open** or **Closed** based on the user's last action. When the button is in the `open` state, it's visible and yellow. When it's the `closed` state, it's transparent and green.
In HTML, these attributes are set using ordinary CSS styles such as color and opacity. In Angular, use the `style()` function to specify a set of CSS styles for use with animations. You can collect a set of styles in an animation state, and give the state a name, such as `open` or `closed`.
<figure>
<img src="generated/images/guide/animations/ng_animate_transitions_inactive_active_void.png" alt="This example transitions between active, inactive, and void states" width="400">
<img src="generated/images/guide/animations/open-closed.png" alt="open and closed states">
</figure>
<code-example path="animations/src/app/hero-list-enter-leave-states.component.ts" region="animationdef" title="hero-list-enter-leave.component.ts (excerpt)" linenums="false"></code-example>
### Animation state and styles
## Animatable properties and units
Use Angular's `state()` function to define different states to call at the end of each transition. This function takes two arguments: a unique name like `open` or `closed` and a `style()` function.
Since Angular's animation support builds on top of Web Animations, you can animate any property
that the browser considers *animatable*. This includes positions, sizes, transforms, colors,
borders, and many others. The W3C maintains
[a list of animatable properties](https://www.w3.org/TR/css3-transitions/#animatable-properties)
on its [CSS Transitions page](https://www.w3.org/TR/css3-transitions).
Use the `style()` function to define a set of styles to associate with a given state name. Note that the style attributes must be in [*camelCase*](guide/glossary#case-conventions).
For positional properties that have a numeric value, you can define a unit by providing
the value as a string with the appropriate suffix:
Let's see how Angular's `state()` function works with the `style­()` function to set CSS style attributes. In this code snippet, multiple style attributes are set at the same time for the state. In the `open` state, the button has a height of 200 pixels, an opacity of 1, and a background color of yellow.
* `'50px'`
* `'3em'`
* `'100%'`
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" region="state1" language="typescript">
</code-example>
If you don't provide a unit when specifying dimension, Angular assumes the default of `px`:
In the `closed` state, shown below, the button has a height of 100 pixels, an opacity of 0.5, and a background color of green.
* `50` is the same as saying `'50px'`
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" region="state2" language="typescript">
</code-example>
## Automatic property calculation
### Transitions and timing
<img src="generated/images/guide/animations/animation_auto.gif" alt="Animation with automated height calculation" class="right" width="220">
In Angular, you can set multiple styles without any animation. However, without further refinement, the button instantly transforms with no fade, no shrinkage, or other visible indicator that a change is occurring.
Sometimes you don't know the value of a dimensional style property until runtime.
For example, elements often have widths and heights that
depend on their content and the screen size. These properties are often tricky
to animate with CSS.
To make the change less abrupt, we need to define an animation *transition* to specify the changes that occur between one state and another over a period of time. The `transition()` function accepts two arguments: the first argument accepts an expression that defines the direction between two transition states, and the second argument accepts an `animate()` function.
In these cases, you can use a special `*` property value so that the value of the
property is computed at runtime and then plugged into the animation.
In this example, the leave animation takes whatever height the element has before it
leaves and animates from that height to zero:
Use the `animate()` function to define the length, delay, and easing of a transition, and to designate the style function for defining styles while transitions are taking place. You can also use the `animate()` function to define the `keyframes()` function for multi-step animations. These definitions are placed in the second argument of the `animate()` function.
<code-example path="animations/src/app/hero-list-auto.component.ts" region="animationdef" title="src/app/hero-list-auto.component.ts" linenums="false"></code-example>
#### Animation metadata: duration, delay, and easing
## Animation timing
The `animate()` function (second argument of the transition function) accepts the `timings` and `styles` input parameters.
There are three timing properties you can tune for every animated transition:
the duration, the delay, and the easing function. They are all combined into
a single transition *timing string*.
The `timings` parameter takes a string defined in three parts.
### Duration
>`animate ('duration delay easing')`
The duration controls how long the animation takes to run from start to finish.
You can define a duration in three ways:
The first part, `duration`, is required. The duration can be expressed in milliseconds as a simple number without quotes, or in seconds with quotes and a time specifier. For example, a duration of a tenth of a second can be expressed as follows:
* As a plain number, in milliseconds: `100`
* In a string, as milliseconds: `'100ms'`
* In a string, as seconds: `'0.1s'`
### Delay
The delay controls the length of time between the animation trigger and the beginning
of the transition. You can define one by adding it to the same string
following the duration. It also has the same format options as the duration:
The second argument, `delay`, has the same syntax as `duration`. For example:
* Wait for 100ms and then run for 200ms: `'0.2s 100ms'`
### Easing
The third argument, `easing`, controls how the animation [accelerates and decelerates](http://easings.net/) during its runtime. For example, `ease-in` causes the animation to begin slowly, and to pick up speed as it progresses.
The [easing function](http://easings.net/) controls how the animation accelerates
and decelerates during its runtime. For example, an `ease-in` function causes
the animation to begin relatively slowly but pick up speed as it progresses. You
can control the easing by adding it as a *third* value in the string after the duration
and the delay (or as the *second* value when there is no delay):
* Wait for 100ms, run for 200ms. Use a deceleration curve to start out fast and slowly decelerate to a resting point: `'0.2s 100ms ease-out'`
* Wait for 100ms and then run for 200ms, with easing: `'0.2s 100ms ease-out'`
* Run for 200ms, with easing: `'0.2s ease-in-out'`
* Run for 200ms, with no delay. Use a standard curve to start slow, accelerate in the middle, and then decelerate slowly at the end: `'0.2s ease-in-out'`
<img src="generated/images/guide/animations/animation_timings.gif" alt="Animations with specific timings" class="right" width="220">
* Start immediately, run for 200ms. Use a acceleration curve to start slow and end at full velocity: `'0.2s ease-in'`
### Example
<div class="alert is-helpful">
Here are a couple of custom timings in action. Both enter and leave last for
200 milliseconds, that is `0.2s`, but they have different easings. The leave begins after a
slight delay of 10 milliseconds as specified in `'0.2s 10 ease-out'`:
**Note:** See the Angular Material Design website's topic on [Natural easing curves](https://material.io/design/motion/speed.html#easing) for general information on easing curves.
</div>
<code-example path="animations/src/app/hero-list-timings.component.ts" region="animationdef" title="hero-list-timings.component.ts (excerpt)" linenums="false"></code-example>
This example provides a state transition from `open` to `closed` with a one second transition between states.
## Multi-step animations with keyframes
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" language="typescript"
region="transition1">
</code-example>
<img src="generated/images/guide/animations/animation_multistep.gif" alt="Animations with some bounce implemented with keyframes" class="right" width="220">
In the code snippet above, the `=>` operator indicates unidirectional transitions, and `<=>` is bidirectional. Within the transition, `animate()` specifies how long the transition takes. In this case, the state change from `open` to `closed` takes one second, expressed here as `1s`.
Animation *keyframes* go beyond a simple transition to a more intricate animation
that goes through one or more intermediate styles when transitioning between two sets of styles.
This example adds a state transition from the `closed` state to the `open` state with a 0.5 second transition animation arc.
For each keyframe, you specify an *offset* that defines at which point
in the animation that keyframe applies. The offset is a number between zero,
which marks the beginning of the animation, and one, which marks the end.
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" language="typescript"
region="transition2">
</code-example>
This example adds some "bounce" to the enter and leave animations with
keyframes:
<div class="alert is-helpful">
<code-example path="animations/src/app/hero-list-multistep.component.ts" region="animationdef" title="hero-list-multistep.component.ts (excerpt)" linenums="false"></code-example>
**Note:** Some additional notes on using styles within `state` and `transition` functions.
Note that the offsets are *not* defined in terms of absolute time. They are relative
measures from zero to one. The final timeline of the animation is based on the combination
of keyframe offsets, duration, delay, and easing.
* Use `state()` to define styles that are applied at the end of each transition, they persist after the animation has completed.
Defining offsets for keyframes is optional. If you omit them, offsets with even
spacing are automatically assigned. For example, three keyframes without predefined
offsets receive offsets `0`, `0.5`, and `1`.
* Use `transition()` to define intermediate styles, which create the illusion of motion during the animation.
## Parallel animation groups
* When animations are disabled, `transition()` styles can be skipped, but `state()` styles can't.
<img src="generated/images/guide/animations/animation_groups.gif" alt="Parallel animations with different timings, implemented with groups" class="right" width="220px">
* You can include multiple state pairs within the same `transition()` argument:<br/> `transition( 'on => off, off => void' )`.
</div>
You've seen how to animate multiple style properties at the same time:
just put all of them into the same `style()` definition.
### Triggering the animation
But you may also want to configure different *timings* for animations that happen
in parallel. For example, you may want to animate two CSS properties but use a
different easing function for each one.
An animation requires a *trigger*, so that it knows when to start. The `trigger()` function collects the states and transitions, and gives the animation a name, so that you can attach it to the triggering element in the HTML template.
For this you can use animation *groups*. In this example, using groups both on
enter and leave allows for two different timing configurations. Both
are applied to the same element in parallel, but run independently of each other:
The `trigger()` function describes the property name to watch for changes. When a change occurs, the trigger initiates the actions included in its definition. These actions can be transitions or other functions, as we'll see later on.
<code-example path="animations/src/app/hero-list-groups.component.ts" region="animationdef" title="hero-list-groups.component.ts (excerpt)" linenums="false"></code-example>
In this example, we'll name the trigger `openClose`, and attach it to the `button` element. The trigger describes the open and closed states, and the timings for the two transitions.
One group animates the element transform and width; the other group animates the opacity.
<figure>
<img src="generated/images/guide/animations/triggering-the-animation.png" alt="triggering the animation">
</figure>
## Animation callbacks
<div class="alert is-helpful">
A callback is fired when an animation is started and also when it is done.
**Note:** Within each `trigger()` function call, an element can only be in one state at any given time. However, it's possible for multiple triggers to be active at once.
</div>
In the keyframes example, you have a `trigger` called `@flyInOut`. You can hook
those callbacks like this:
### Defining animations and attaching them to the HTML template
<code-example path="animations/src/app/hero-list-multistep.component.ts" region="template" title="hero-list-multistep.component.ts (excerpt)" linenums="false"></code-example>
Animations are defined in the metadata of the component that controls the HTML element to be animated. Put the code that defines your animations under the `animations:` property within the `@Component()` decorator.
The callbacks receive an `AnimationEvent` that contains useful properties such as
`fromState`, `toState` and `totalTime`.
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" language="typescript"
region="component" linenums="false">
</code-example>
Those callbacks will fire whether or not an animation is picked up.
When you've defined an animation trigger for a component, you can attach it to an element in that component's template by wrapping the trigger name in brackets and preceding it with an `@` symbol. Then, you can bind the trigger to a template expression using standard Angular property binding syntax as shown below, where `triggerName` is the name of the trigger, and `expression` evaluates to a defined animation state.
```
<div [@triggerName]="expression">...</div>;
```
The animation is executed or triggered when the expression value changes to a new state.
The following code snippet binds the trigger to the value of the `isOpen` property.
<code-example path="animations/src/app/open-close.component.1.html" title="src/app/open-close.component.html"
region="compare">
</code-example>
In this example, when the `isOpen` expression evaluates to a defined state of `open` or `closed`, it notifies the trigger `openClose` of a state change. Then it's up to the `openClose` code to handle the state change and kick off a state change animation.
For elements entering or leaving a page (inserted or removed from the DOM), you can make the animations conditional. For example, use `*ngIf` with the animation trigger in the HTML template.
<div class="alert is-helpful">
**Note:** In the component file, set the trigger that defines the animations as the value of the `animations:` property in the `@Component()` decorator.
In the HTML template file, use the trigger name to attach the defined animations to the HTML element to be animated.
</div>
### Code review
Here are the code files discussed in the transition example.
<code-tabs>
<code-pane title="src/app/open-close.component.ts" path="animations/src/app/open-close.component.ts" language="typescript"
region="component">
</code-pane>
<code-pane title="src/app/open-close.component.html" path="animations/src/app/open-close.component.1.html"
region="trigger">
</code-pane>
<code-pane title="src/app/open-close.component.css" path="animations/src/app/open-close.component.css">
</code-pane>
</code-tabs>
### Summary
You learned to add animation to a simple transition between two states, using `style()` and `state()` along with `animate()` for the timing.
You can learn about more advanced features in Angular animations under the Animation section, beginning with advanced techniques in [transition and triggers](guide/transition-and-triggers).
{@a animation-api-summary}
## Animations API summary
The functional API provided by the `@angular/animations` module provides a domain-specific language (DSL) for creating and controlling animations in Angular applications. See the [API reference](api/animations) for a complete listing and syntax details of the core functions and related data structures.
<table>
<tr>
<th style="vertical-align: top">
Function name
</th>
<th style="vertical-align: top">
What it does
</th>
</tr>
<tr>
<td><code>trigger()</code></td>
<td>Kicks off the animation and serves as a container for all other animation function calls. HTML template binds to <code>triggerName</code>. Use the first argument to declare a unique trigger name. Uses array syntax.</td>
</tr>
<tr>
<td><code>style()</code></td>
<td>Defines one or more CSS styles to use in animations. Controls the visual appearance of HTML elements during animations. Uses object syntax.</td>
</tr>
<tr>
<td><code>state()</code></td>
<td>Creates a named set of CSS styles that should be applied on successful transition to a given state. The state can then be referenced by name within other animation functions.</td>
</tr>
<tr>
<td><code>animate()</code></td>
<td>Specifies the timing information for a transition. Optional values for <code>delay</code> and <code>easing</code>. Can contain <code>style()</code> calls within.</td>
</tr>
<tr>
<td><code>transition()</code></td>
<td>Defines the animation sequence between two named states. Uses array syntax.</td>
</tr>
<tr>
<td><code>keyframes()</code></td>
<td>Allows a sequential change between styles within a specified time interval. Use within <code>animate()</code>. Can include multiple <code>style()</code> calls within each <code>keyframe()</code>. Uses array syntax.</td>
</tr>
<tr>
<td><code>group()</code></td>
<td>Specifies a group of animation steps (<em>inner animations</em>) to be run in parallel. Animation continues only after all inner animation steps have completed. Used within <code>sequence()</code> or <code>transition().</code></td>
</tr>
<tr>
<td><code>query()</code></td>
<td>Use to find one or more inner HTML elements within the current element. </td>
</tr>
<tr>
<td><code>sequence()</code></td>
<td>Specifies a list of animation steps that are run sequentially, one by one.</td>
</tr>
<tr>
<td><code>stagger()</code></td>
<td>Staggers the starting time for animations for multiple elements.</td>
</tr>
<tr>
<td><code>animation()</code></td>
<td>Produces a reusable animation that can be invoked from elsewhere. Used together with <code>useAnimation()</code>.</td>
</tr>
<tr>
<td><code>useAnimation()</code></td>
<td>Activates a reusable animation. Used with <code>animation()</code>.</td>
</tr>
<tr>
<td><code>animateChild()</code></td>
<td>Allows animations on child components to be run within the same timeframe as the parent.</td>
</tr>
</table>
## More on Angular animations
You may also be interested in the following:
* [Transition and triggers](guide/transition-and-triggers)
* [Complex animation sequences](guide/complex-animation-sequences)
* [Reusable animations](guide/reusable-animations)
* [Route transition animations](guide/route-animations)
<div class="alert is-helpful">
Check out this full animation [demo](http://animationsftw.in/#/) with accompanying [presentation](https://www.youtube.com/watch?v=JhNo3Wvj6UQ&feature=youtu.be&t=2h47m53s), shown at the AngularConnect conference in November 2017.
</div>

View File

@ -1,10 +1,12 @@
# The Ahead-of-Time (AOT) Compiler
# The Ahead-of-Time (AOT) compiler
The Angular Ahead-of-Time (AOT) compiler converts your Angular HTML and TypeScript code into efficient JavaScript code during the build phase _before_ the browser downloads and runs that code.
An Angular application consists mainly of components and their HTML templates. Because the components and templates provided by Angular cannot be understood by the browser directly, Angular applications require a compilation process before they can run in a browser.
This guide explains how to build with the AOT compiler using different compiler options and how to write Angular metadata that AOT can compile.
The Angular Ahead-of-Time (AOT) compiler converts your Angular HTML and TypeScript code into efficient JavaScript code during the build phase _before_ the browser downloads and runs that code. Compiling your application during the build process provides a faster rendering in the browser.
<div class="alert is-helpful>
This guide explains how to specify metadata and apply available compiler options to compile your applications efficiently using the AOT compiler.
<div class="alert is-helpful"
<a href="https://www.youtube.com/watch?v=kW9cJsvcsGo">Watch compiler author Tobias Bosch explain the Angular Compiler</a> at AngularConnect 2016.
@ -14,13 +16,9 @@ This guide explains how to build with the AOT compiler using different compiler
## Angular compilation
An Angular application consists largely of components and their HTML templates.
Before the browser can render the application,
the components and templates must be converted to executable JavaScript by an _Angular compiler_.
Angular offers two ways to compile your application:
1. **_Just-in-Time_ (JIT)**, which compiles your app in the browser at runtime
1. **_Just-in-Time_ (JIT)**, which compiles your app in the browser at runtime.
1. **_Ahead-of-Time_ (AOT)**, which compiles your app at build time.
JIT compilation is the default when you run the _build-only_ or the _build-and-serve-locally_ CLI commands:
@ -41,7 +39,7 @@ For AOT compilation, append the `--aot` flags to the _build-only_ or the _build-
<div class="alert is-helpful">
The `--prod` meta-flag compiles with AOT by default.
The `ng build` command with the `--prod` meta-flag (`ng build --prod`) compiles with AOT by default.
See the [CLI documentation](https://github.com/angular/angular-cli/wiki) for details, especially the [`build` topic](https://github.com/angular/angular-cli/wiki/build).
@ -77,186 +75,24 @@ AOT compiles HTML templates and components into JavaScript files long before the
With no templates to read and no risky client-side HTML or JavaScript evaluation,
there are fewer opportunities for injection attacks.
{@a compiler-options}
## Controlling app compilation
## Angular Compiler Options
When you use the Angular AOT compiler, you can control your app compilation in two ways:
You can control your app compilation by providing template compiler options in the `tsconfig.json` file along with the options supplied to the TypeScript compiler. The template compiler options are specified as members of
`"angularCompilerOptions"` object as shown below:
* By providing template compiler options in the `tsconfig.json` file.
```json
{
"compilerOptions": {
"experimentalDecorators": true,
...
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"preserveWhitespaces": true,
...
}
}
```
### *enableResourceInlining*
This options tell the compiler to replace the `templateUrl` and `styleUrls` property in all `@Component` decorators with inlined contents in `template` and `styles` properties.
When enabled, the `.js` output of ngc will have no lazy-loaded `templateUrl` or `styleUrls`.
For more information, see [Angular template compiler options](#compiler-options).
### *skipMetadataEmit*
This option tells the compiler not to produce `.metadata.json` files.
The option is `false` by default.
`.metadata.json` files contain information needed by the template compiler from a `.ts`
file that is not included in the `.d.ts` file produced by the TypeScript compiler. This information contains,
for example, the content of annotations (such as a component's template) which TypeScript
emits to the `.js` file but not to the `.d.ts` file.
This option should be set to `true` if using TypeScript's `--outFile` option, as the metadata files
are not valid for this style of TypeScript output. It is not recommeded to use `--outFile` with
Angular. Use a bundler, such as [webpack](https://webpack.js.org/), instead.
This option can also be set to `true` when using factory summaries as the factory summaries
include a copy of the information that is in the `.metadata.json` file.
### *strictMetadataEmit*
This option tells the template compiler to report an error to the `.metadata.json`
file if `"skipMetadataEmit"` is `false` . This option is `false` by default. This should only be used when `"skipMetadataEmit"` is `false` and `"skipTemplateCodegen"` is `true`.
It is intended to validate the `.metadata.json` files emitted for bundling with an `npm` package. The validation is overly strict and can emit errors for metadata that would never produce an error when used by the template compiler. You can choose to suppress the error emitted by this option for an exported symbol by including `@dynamic` in the comment documenting the symbol.
It is valid for `.metadata.json` files to contain errors. The template compiler reports these errors
if the metadata is used to determine the contents of an annotation. The metadata
collector cannot predict the symbols that are designed to use in an annotation, so it will preemptively
include error nodes in the metadata for the exported symbols. The template compiler can then use the error
nodes to report an error if these symbols are used. If the client of a library intends to use a symbol in an annotation, the template compiler will not normally report
this until the client uses the symbol. This option allows detecting these errors during the build phase of
the library and is used, for example, in producing Angular libraries themselves.
### *skipTemplateCodegen*
This option tells the compiler to suppress emitting `.ngfactory.js` and `.ngstyle.js` files. When set,
this turns off most of the template compiler and disables reporting template diagnostics.
This option can be used to instruct the
template compiler to produce `.metadata.json` files for distribution with an `npm` package while
avoiding the production of `.ngfactory.js` and `.ngstyle.js` files that cannot be distributed to
`npm`.
### *strictInjectionParameters*
When set to `true`, this options tells the compiler to report an error for a parameter supplied
whose injection type cannot be determined. When this value option is not provided or is `false`, constructor parameters of classes marked with `@Injectable` whose type cannot be resolved will
produce a warning.
*Note*: It is recommended to change this option explicitly to `true` as this option will default to `true` in the future.
### *flatModuleOutFile*
When set to `true`, this option tells the template compiler to generate a flat module
index of the given file name and the corresponding flat module metadata. Use this option when creating
flat modules that are packaged similarly to `@angular/core` and `@angular/common`. When this option
is used, the `package.json` for the library should refer
to the generated flat module index instead of the library index file. With this
option only one `.metadata.json` file is produced that contains all the metadata necessary
for symbols exported from the library index. In the generated `.ngfactory.js` files, the flat
module index is used to import symbols that includes both the public API from the library index
as well as shrowded internal symbols.
By default the `.ts` file supplied in the `files` field is assumed to be library index.
If more than one `.ts` file is specified, `libraryIndex` is used to select the file to use.
If more than one `.ts` file is supplied without a `libraryIndex`, an error is produced. A flat module
index `.d.ts` and `.js` will be created with the given `flatModuleOutFile` name in the same
location as the library index `.d.ts` file. For example, if a library uses
`public_api.ts` file as the library index of the module, the `tsconfig.json` `files` field
would be `["public_api.ts"]`. The `flatModuleOutFile` options could then be set to, for
example `"index.js"`, which produces `index.d.ts` and `index.metadata.json` files. The
library's `package.json`'s `module` field would be `"index.js"` and the `typings` field
would be `"index.d.ts"`.
### *flatModuleId*
This option specifies the preferred module id to use for importing a flat module.
References generated by the template compiler will use this module name when importing symbols
from the flat module.
This is only meaningful when `flatModuleOutFile` is also supplied. Otherwise the compiler ignores
this option.
### *generateCodeForLibraries*
This option tells the template compiler to generate factory files (`.ngfactory.js` and `.ngstyle.js`)
for `.d.ts` files with a corresponding `.metadata.json` file. This option defaults to
`true`. When this option is `false`, factory files are generated only for `.ts` files.
This option should be set to `false` when using factory summaries.
### *fullTemplateTypeCheck*
This option tells the compiler to enable the [binding expression validation](#binding-expression-validation)
phase of the template compiler which uses TypeScript to validate binding expressions.
This option is `false` by default.
*Note*: It is recommended to set this to `true` as this option will default to `true` in the future.
### *annotateForClosureCompiler*
This option tells the compiler to use [Tsickle](https://github.com/angular/tsickle) to annotate the emitted
JavaScript with [JsDoc](http://usejsdoc.org/) comments needed by the
[Closure Compiler](https://github.com/google/closure-compiler). This option defaults to `false`.
### *annotationsAs*
Use this option to modify how the Angular specific annotations are emitted to improve tree-shaking. Non-Angular
annotations and decorators are unaffected. Default is `static fields`.
value | description
----------------|-------------------------------------------------------------
`decorators` | Leave the Decorators in-place. This makes compilation faster. TypeScript will emit calls to the __decorate helper. Use `--emitDecoratorMetadata` for runtime reflection. However, the resulting code will not properly tree-shake.
`static fields` | Replace decorators with a static field in the class. Allows advanced tree-shakers like [Closure Compiler](https://github.com/google/closure-compiler) to remove unused classes.
### *trace*
This tells the compiler to print extra information while compiling templates.
### *disableExpressionLowering*
The Angular template compiler transforms code that is used, or could be used, in an annotation
to allow it to be imported from template factory modules. See
[metadata rewriting](#metadata-rewriting) for more information.
Setting this option to `false` disables this rewriting, requiring the rewriting to be
done manually.
### *preserveWhitespaces*
This option tells the compiler whether to remove blank text nodes from compiled templates.
As of v6, this option is `false` by default, which results in smaller emitted template factory modules.
### *allowEmptyCodegenFiles*
Tells the compiler to generate all the possible generated files even if they are empty. This option is
`false` by default. This is an option used by `bazel` build rules and is needed to simplify
how `bazel` rules track file dependencies. It is not recommended to use this option outside of the `bazel`
rules.
### *enableIvy*
Tells the compiler to generate definitions using the Render3 style code generation. This option defaults to `false`.
Not all features are supported with this option enabled. It is only supported
for experimentation and testing of Render3 style code generation.
*Note*: Is it not recommended to use this option as it is not yet feature complete with the Render2 code generation.
* By [specifying Angular metadata](#metadata-aot).
## Angular Metadata and AOT
The Angular **AOT compiler** extracts and interprets **metadata** about the parts of the application that Angular is supposed to manage.
{@a metadata-aot}
## Specifying Angular metadata
Angular metadata tells Angular how to construct instances of your application classes and interact with them at runtime.
The Angular **AOT compiler** extracts **metadata** to interpret the parts of the application that Angular is supposed to manage.
You specify the metadata with **decorators** such as `@Component()` and `@Input()`.
You also specify metadata implicitly in the constructor declarations of these decorated classes.
You can specify the metadata with **decorators** such as `@Component()` and `@Input()` or implicitly in the constructor declarations of these decorated classes.
In the following example, the `@Component()` metadata object and the class constructor tell Angular how to create and display an instance of `TypicalComponent`.
@ -310,25 +146,84 @@ describes the JSON format as a collection of TypeScript interfaces.
The _collector_ only understands a subset of JavaScript.
Define metadata objects with the following limited syntax:
Syntax | Example
-----------------------------------|-----------------------------------
Literal object | `{cherry: true, apple: true, mincemeat: false}`
Literal array | `['cherries', 'flour', 'sugar']`
Spread in literal array | `['apples', 'flour', ...the_rest]`
Calls | `bake(ingredients)`
New | `new Oven()`
Property access | `pie.slice`
Array index | `ingredients[0]`
Identifier reference | `Component`
A template string | <code>&#96;pie is ${multiplier} times better than cake&#96;</code>
Literal string | `'pi'`
Literal number | `3.14153265`
Literal boolean | `true`
Literal null | `null`
Supported prefix operator | `!cake`
Supported Binary operator | `a + b`
Conditional operator | `a ? b : c`
Parentheses | `(a + b)`
<style>
td, th {vertical-align: top}
</style>
<table>
<tr>
<th>Syntax</th>
<th>Example</th>
</tr>
<tr>
<td>Literal object </td>
<td><code>{cherry: true, apple: true, mincemeat: false}</code></td>
</tr>
<tr>
<td>Literal array </td>
<td><code>['cherries', 'flour', 'sugar']</code></td>
</tr>
<tr>
<td>Spread in literal array</td>
<td><code>['apples', 'flour', ...the_rest]</code></td>
</tr>
<tr>
<td>Calls</td>
<td><code>bake(ingredients)</code></td>
</tr>
<tr>
<td>New</td>
<td><code>new Oven()</code></td>
</tr>
<tr>
<td>Property access</td>
<td><code>pie.slice</code></td>
</tr>
<tr>
<td>Array index</td>
<td><code>ingredients[0]</code></td>
</tr>
<tr>
<td>Identity reference</td>
<td><code>Component</code></td>
</tr>
<tr>
<td>A template string</td>
<td><code>`pie is ${multiplier} times better than cake`</code></td>
<tr>
<td>Literal string</td>
<td><code>pi</code></td>
</tr>
<tr>
<td>Literal number</td>
<td><code>3.14153265</code></td>
</tr>
<tr>
<td>Literal boolean</td>
<td><code>true</code></td>
</tr>
<tr>
<td>Literal null</td>
<td><code>null</code></td>
</tr>
<tr>
<td>Supported prefix operator </td>
<td><code>!cake</code></td>
</tr>
<tr>
<td>Supported binary operator </td>
<td><code>a+b</code></td>
</tr>
<tr>
<td>Conditional operator</td>
<td><code>a ? b : c</code></td>
</tr>
<tr>
<td>Parentheses</td>
<td><code>(a+b)</code></td>
</tr>
</table>
If an expression uses unsupported syntax, the _collector_ writes an error node to the `.metadata.json` file. The compiler later reports the error if it needs that
piece of metadata to generate the application code.
@ -461,26 +356,89 @@ The _collector_ reduces this expression to its equivalent _folded_ string:
The following table describes which expressions the _collector_ can and cannot fold:
Syntax | Foldable
-----------------------------------|-----------------------------------
Literal object | yes
Literal array | yes
Spread in literal array | no
Calls | no
New | no
Property access | yes, if target is foldable
Array index | yes, if target and index are foldable
Identifier reference | yes, if it is a reference to a local
A template with no substitutions | yes
A template with substitutions | yes, if the substitutions are foldable
Literal string | yes
Literal number | yes
Literal boolean | yes
Literal null | yes
Supported prefix operator | yes, if operand is foldable
Supported binary operator | yes, if both left and right are foldable
Conditional operator | yes, if condition is foldable
Parentheses | yes, if the expression is foldable
<style>
td, th {vertical-align: top}
</style>
<table>
<tr>
<th>Syntax</th>
<th>Foldable</th>
</tr>
<tr>
<td>Literal object </td>
<td>Yes</td>
</tr>
<tr>
<td>Literal array </td>
<td>Yes</td>
</tr>
<tr>
<td>Spread in literal array</td>
<td>no</td>
</tr>
<tr>
<td>Calls</td>
<td>no</td>
</tr>
<tr>
<td>New</td>
<td>no</td>
</tr>
<tr>
<td>Property access</td>
<td>yes, if target is foldable</td>
</tr>
<tr>
<td>Array index</td>
<td> yes, if target and index are foldable</td>
</tr>
<tr>
<td>Identity reference</td>
<td>yes, if it is a reference to a local</td>
</tr>
<tr>
<td>A template with no substitutions</td>
<td>yes</td>
</tr>
<tr>
<td>A template with substitutions</td>
<td>yes, if the substitutions are foldable</td>
</tr>
<tr>
<td>Literal string</td>
<td>yes</td>
</tr>
<tr>
<td>Literal number</td>
<td>yes</td>
</tr>
<tr>
<td>Literal boolean</td>
<td>yes</td>
</tr>
<tr>
<td>Literal null</td>
<td>yes</td>
</tr>
<tr>
<td>Supported prefix operator </td>
<td>yes, if operand is foldable</td>
</tr>
<tr>
<td>Supported binary operator </td>
<td>yes, if both left and right are foldable</td>
</tr>
<tr>
<td>Conditional operator</td>
<td>yes, if condition is foldable </td>
</tr>
<tr>
<td>Parentheses</td>
<td>yes, if the expression is foldable</td>
</tr>
</table>
If an expression is not foldable, the collector writes it to `.metadata.json` as an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) for the compiler to resolve.
@ -522,26 +480,90 @@ The compiler only allows metadata that create instances of the class `InjectionT
The compiler only supports metadata for these Angular decorators.
Decorator | Module
------------------|--------------
`Attribute` | `@angular/core`
`Component` | `@angular/core`
`ContentChild` | `@angular/core`
`ContentChildren` | `@angular/core`
`Directive` | `@angular/core`
`Host` | `@angular/core`
`HostBinding` | `@angular/core`
`HostListener` | `@angular/core`
`Inject` | `@angular/core`
`Injectable` | `@angular/core`
`Input` | `@angular/core`
`NgModule` | `@angular/core`
`Optional` | `@angular/core`
`Output` | `@angular/core`
`Pipe` | `@angular/core`
`Self` | `@angular/core`
`SkipSelf` | `@angular/core`
`ViewChild` | `@angular/core`
<style>
td, th {vertical-align: top}
</style>
<table>
<tr>
<th>Decorator</th>
<th>Module</th>
</tr>
<tr>
<td><code>Attribute</code></td>
<td><code>@angular/core</code></td>
</tr>
<tr>
<td><code>Component</code></td>
<td><code>@angular/core</code></td>
</tr>
<tr>
<td><code>ContentChild</code></td>
<td><code>@angular/core</code></td>
</tr>
<tr>
<td><code>ContentChildren</code></td>
<td><code>@angular/core</code></td>
</tr>
<tr>
<td><code>Directive</code></td>
<td><code>@angular/core</code></td>
</tr>
<tr>
<td><code>Host</code></td>
<td><code>@angular/core</code></td>
</tr>
<tr>
<td><code>HostBinding</code></td>
<td><code>@angular/core</code></td>
</tr>
<tr>
<td><code>HostListner</code></td>
<td><code>@angular/core</code></td>
</tr>
<tr>
<td><code>Inject</code></td>
<td><code>@angular/core</code></td>
</tr>
<tr>
<td><code>Injectable</code></td>
<td><code>@angular/core</code></td>
</tr>
<tr>
<td><code>Input</code></td>
<td><code>@angular/core</code></td>
</tr>
<tr>
<td><code>NgModule</code></td>
<td><code>@angular/core</code></td>
</tr>
<tr>
<td><code>Optional</code></td>
<td><code>@angular/core</code></td>
</tr>
<tr>
<td><code>Output</code></td>
<td><code>@angular/core</code></td>
</tr>
<tr>
<td><code>Pipe</code></td>
<td><code>@angular/core</code></td>
</tr>
<tr>
<td><code>Self</code></td>
<td><code>@angular/core</code></td>
</tr>
<tr>
<td><code>SkipSelf</code></td>
<td><code>@angular/core</code></td>
</tr>
<tr>
<td><code>ViewChild</code></td>
<td><code>@angular/core</code></td>
</tr>
</table>
### Macro-functions and macro-static methods
@ -629,7 +651,7 @@ factory without having to know what the value of `ɵ0` contains.
The compiler does the rewriting during the emit of the `.js` file. This doesn't rewrite the `.d.ts` file, however, so TypeScript doesn't recognize it as being an export. Thus, it does not pollute the ES module's exported API.
## Metadata Errors
## Metadata errors
The following are metadata errors you may encounter, with explanations and suggested corrections.
@ -1136,8 +1158,8 @@ This error can occur if you use an expression in the `extends` clause of a class
Chuck: After reviewing your PR comment I'm still at a loss. See [comment there](https://github.com/angular/angular/pull/17712#discussion_r132025495).
-->
{@a binding-expression-validation}
{@a binding-expression-validation}
## Phase 3: binding expression validation
In the validation phase, the Angular template compiler uses the TypeScript compiler to validate the
@ -1285,12 +1307,198 @@ Chuck: After reviewing your PR comment I'm still at a loss. See [comment there](
person?: Person;
}
```
## Summary
* What the AOT compiler does and why it is important.
* Why metadata must be written in a subset of JavaScript.
* What that subset is.
* Other restrictions on metadata definition.
* Macro-functions and macro-static methods.
* Compiler errors related to metadata.
* Validation of binding expressions
{@a compiler-options}
## Angular template compiler options
The template compiler options are specified as members of the `"angularCompilerOptions"` object in the `tsconfig.json` file. Specify template compiler options along with the options supplied to the TypeScript compiler as shown here:
```json
{
"compilerOptions": {
"experimentalDecorators": true,
...
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"preserveWhitespaces": true,
...
}
}
```
The following section describes the Angular's template compiler options.
### *enableResourceInlining*
This option instructs the compiler to replace the `templateUrl` and `styleUrls` property in all `@Component` decorators with inlined contents in `template` and `styles` properties.
When enabled, the `.js` output of `ngc` will have no lazy-loaded `templateUrl` or `styleUrls`.
### *skipMetadataEmit*
This option tells the compiler not to produce `.metadata.json` files.
The option is `false` by default.
`.metadata.json` files contain information needed by the template compiler from a `.ts`
file that is not included in the `.d.ts` file produced by the TypeScript compiler. This information contains,
for example, the content of annotations (such as a component's template), which TypeScript
emits to the `.js` file but not to the `.d.ts` file.
This option should be set to `true` if you are using TypeScript's `--outFile` option, because the metadata files
are not valid for this style of TypeScript output. It is not recommeded to use `--outFile` with
Angular. Use a bundler, such as [webpack](https://webpack.js.org/), instead.
This option can also be set to `true` when using factory summaries because the factory summaries
include a copy of the information that is in the `.metadata.json` file.
### *strictMetadataEmit*
This option tells the template compiler to report an error to the `.metadata.json`
file if `"skipMetadataEmit"` is `false`. This option is `false` by default. This should only be used when `"skipMetadataEmit"` is `false` and `"skipTemplateCodeGen"` is `true`.
This option is intended to validate the `.metadata.json` files emitted for bundling with an `npm` package. The validation is strict and can emit errors for metadata that would never produce an error when used by the template compiler. You can choose to suppress the error emitted by this option for an exported symbol by including `@dynamic` in the comment documenting the symbol.
It is valid for `.metadata.json` files to contain errors. The template compiler reports these errors
if the metadata is used to determine the contents of an annotation. The metadata
collector cannot predict the symbols that are designed for use in an annotation, so it will preemptively
include error nodes in the metadata for the exported symbols. The template compiler can then use the error
nodes to report an error if these symbols are used. If the client of a library intends to use a symbol in an annotation, the template compiler will not normally report
this until the client uses the symbol. This option allows detecting these errors during the build phase of
the library and is used, for example, in producing Angular libraries themselves.
### *skipTemplateCodegen*
This option tells the compiler to suppress emitting `.ngfactory.js` and `.ngstyle.js` files. When set,
this turns off most of the template compiler and disables reporting template diagnostics.
This option can be used to instruct the
template compiler to produce `.metadata.json` files for distribution with an `npm` package while
avoiding the production of `.ngfactory.js` and `.ngstyle.js` files that cannot be distributed to
`npm`.
### *strictInjectionParameters*
When set to `true`, this options tells the compiler to report an error for a parameter supplied
whose injection type cannot be determined. When this option is not provided or is `false`, constructor parameters of classes marked with `@Injectable` whose type cannot be resolved will
produce a warning.
*Note*: It is recommended to change this option explicitly to `true` as this option will default to `true` in the future.
### *flatModuleOutFile*
When set to `true`, this option tells the template compiler to generate a flat module
index of the given file name and the corresponding flat module metadata. Use this option when creating
flat modules that are packaged similarly to `@angular/core` and `@angular/common`. When this option
is used, the `package.json` for the library should refer
to the generated flat module index instead of the library index file. With this
option only one `.metadata.json` file is produced, which contains all the metadata necessary
for symbols exported from the library index. In the generated `.ngfactory.js` files, the flat
module index is used to import symbols that includes both the public API from the library index
as well as shrowded internal symbols.
By default the `.ts` file supplied in the `files` field is assumed to be the library index.
If more than one `.ts` file is specified, `libraryIndex` is used to select the file to use.
If more than one `.ts` file is supplied without a `libraryIndex`, an error is produced. A flat module
index `.d.ts` and `.js` will be created with the given `flatModuleOutFile` name in the same
location as the library index `.d.ts` file. For example, if a library uses the
`public_api.ts` file as the library index of the module, the `tsconfig.json` `files` field
would be `["public_api.ts"]`. The `flatModuleOutFile` options could then be set to, for
example `"index.js"`, which produces `index.d.ts` and `index.metadata.json` files. The
library's `package.json`'s `module` field would be `"index.js"` and the `typings` field
would be `"index.d.ts"`.
### *flatModuleId*
This option specifies the preferred module id to use for importing a flat module.
References generated by the template compiler will use this module name when importing symbols
from the flat module.
This is only meaningful when `flatModuleOutFile` is also supplied. Otherwise the compiler ignores
this option.
### *generateCodeForLibraries*
This option tells the template compiler to generate factory files (`.ngfactory.js` and `.ngstyle.js`)
for `.d.ts` files with a corresponding `.metadata.json` file. This option defaults to
`true`. When this option is `false`, factory files are generated only for `.ts` files.
This option should be set to `false` when using factory summaries.
### *fullTemplateTypeCheck*
This option tells the compiler to enable the [binding expression validation](#binding-expression-validation)
phase of the template compiler which uses TypeScript to validate binding expressions.
This option is `false` by default.
*Note*: It is recommended to set this to `true` because this option will default to `true` in the future.
### *annotateForClosureCompiler*
This option tells the compiler to use [Tsickle](https://github.com/angular/tsickle) to annotate the emitted
JavaScript with [JSDoc](http://usejsdoc.org/) comments needed by the
[Closure Compiler](https://github.com/google/closure-compiler). This option defaults to `false`.
### *annotationsAs*
Use this option to modify how the Angular specific annotations are emitted to improve tree-shaking. Non-Angular
annotations and decorators are unnaffected. Default is `static fields`.
<style>
td, th {vertical-align: top}
</style>
<table>
<tr>
<th>Value</th>
<th>Description</th>
</tr>
<tr>
<td><code>decorators</code></td>
<td>Leave the decorators in place. This makes compilation faster. TypeScript will emit calls to the __decorate helper. Use <code>--emitDecoratorMetadata</code> for runtime reflection. However, the resulting code will not properly tree-shake.</td>
</tr>
<tr>
<td><code>static fields</code></td>
<td>Replace decorators with a static field in the class. Allows advanced tree-shakers like
<a href="https://github.com/google/closure-compiler">Closure compiler</a> to remove unused classes.</td>
</tr>
</table>
### *trace*
This tells the compiler to print extra information while compiling templates.
### *enableLegacyTemplate*
Use of the `<template>` element was deprecated starting in Angular 4.0 in favor of using
`<ng-template>` to avoid colliding with the DOM's element of the same name. Setting this option to
`true` enables the use of the deprecated `<template>` element. This option
is `false` by default. This option might be required by some third-party Angular libraries.
### *disableExpressionLowering*
The Angular template compiler transforms code that is used, or could be used, in an annotation
to allow it to be imported from template factory modules. See
[metadata rewriting](#metadata-rewriting) for more information.
Setting this option to `false` disables this rewriting, requiring the rewriting to be
done manually.
### *disableTypeScriptVersionCheck*
When `true`, this option tells the compiler not to check the TypeScript version.
The compiler will skip checking and will not error out when an unsupported version of TypeScript is used.
Setting this option to `true` is not recommended because unsupported versions of TypeScript might have undefined behaviour.
This option is `false` by default.
### *preserveWhitespaces*
This option tells the compiler whether to remove blank text nodes from compiled templates.
As of v6, this option is `false` by default, which results in smaller emitted template factory modules.
### *allowEmptyCodegenFiles*
Tells the compiler to generate all the possible generated files even if they are empty. This option is
`false` by default. This is an option used by the Bazel build rules and is needed to simplify
how Bazel rules track file dependencies. It is not recommended to use this option outside of the Bazel
rules.

View File

@ -143,9 +143,6 @@ A component template can reference another component, directive,
or pipe when the referenced class is declared in this module or
the class was imported from another module.
You don't have any services to provide yet.
But you will create some before long and you may chose to provide many of them here.
{@a bootstrap-array}
## The `providers` array

View File

@ -0,0 +1,104 @@
# Complex animation sequences
#### Prerequisites
A basic understanding of the following concepts:
* [Introduction to Angular animations](guide/animations)
* [Transition and triggers](guide/transition-and-triggers)
<hr>
So far, we've learned simple animations of single HTML elements. Angular also lets you animate coordinated sequences, such as an entire grid or list of elements as they enter and leave a page. You can choose to run multiple animations in parallel, or run discrete animations sequentially, one following another.
Functions that control complex animation sequences are as follows:
* `query()` finds one or more inner HTML elements.
* `stagger()` applies a cascading delay to animations for multiple elements.
* `group()` runs multiple animation steps in parallel.
* `sequence()` runs animation steps one after another.
{@a complex-sequence}
## Animate multiple elements using query() and stagger() functions
The `query()` function allows you to find inner elements within the element that is being animated. This function targets specific HTML elements within a parent component and applies animations to each element individually. Angular intelligently handles setup, teardown, and cleanup as it coordinates the elements across the page.
The `stagger()` function allows you to define a timing gap between each queried item that is animated and thus animates elements with a delay between them.
The Filter/Stagger tab in the live example shows a list of heroes with an introductory sequence. The entire list of heroes cascades in, with a slight delay from top to bottom.
The following example demonstrates how to use `query()` and `stagger()` functions on the entry of an animated element.
* Use `query()` to look for any element entering or leaving the page. The query specifies elements meeting certain CSS class criteria.
* For each of these elements, use `style()` to set the same initial style for the element. Make it invisible and use `transform` to move it out of position so that it can slide into place.
* Use `stagger()` to delay each animation by 30 milliseconds.
* Animate each element on screen for 0.5 seconds using a custom-defined easing curve, simultaneously fading it in and un-transforming it.
<code-example path="animations/src/app/hero-list-page.component.ts" title="src/app/hero-list-page.component.ts" region="page-animations" language="typescript" linenums="false"></code-example>
## Parallel animation using group() function
You've seen how to add a delay between each successive animation. But you may also want to configure animations that happen in parallel. For example, you may want to animate two CSS properties of the same element but use a different `easing` function for each one. For this, you can use the animation `group()` function.
<div class="alert is-helpful">
**Note:** The `group()` function is used to group animation *steps*, rather than animated elements.
</div>
In the following example, using groups on both `:enter` and `:leave` allow for two different timing configurations. They're applied to the same element in parallel, but run independently.
<code-example path="animations/src/app/hero-list-groups.component.ts" region="animationdef" title="src/app/hero-list-groups.component.ts (excerpt)" language="typescript" linenums="false"></code-example>
## Sequential vs. parallel animations
Complex animations can have many things happening at once. But what if you want to create an animation involving several animations happening one after the other? Earlier we used `group()` to run multiple animations all at the same time, in parallel.
A second function called `sequence()` lets you run those same animations one after the other. Within `sequence()`, the animation steps consist of either `style()` or `animate()` function calls.
* Use `style()` to apply the provided styling data immediately.
* Use `animate()` to apply styling data over a given time interval.
## Filter animation example
Let's take a look at another animation on the live example page. Under the Filter/Stagger tab, enter some text into the **Search Heroes** text box, such as `Magnet` or `tornado`.
The filter works in real time as you type. Elements leave the page as you type each new letter and the filter gets progressively stricter. The heroes list gradually re-enters the page as you delete each letter in the filter box.
The HTML template contains a trigger called `filterAnimation`.
<code-example path="animations/src/app/hero-list-page.component.html" title="src/app/hero-list-page.component.html" region="filter-animations"></code-example>
The component file contains three transitions.
<code-example path="animations/src/app/hero-list-page.component.ts" title="src/app/hero-list-page.component.ts" region="filter-animations" language="typescript" linenums="false"></code-example>
The animation does the following:
* Ignores any animations that are performed when the user first opens or navigates to this page. The filter narrows what is already there, so it assumes that any HTML elements to be animated already exist in the DOM.
* Performs a filter match for matches.
For each match:
* Hides the element by making it completely transparent and infinitely narrow, by setting its opacity and width to 0.
* Animates in the element over 300 milliseconds. During the animation, the element assumes its default width and opacity.
* If there are multiple matching elements, staggers in each element starting at the top of the page, with a 50-millisecond delay between each element.
## Animation sequence summary
Angular functions for animating multiple elements start with `query()` to find inner elements, for example gathering all images within a `<div>`. The remaining functions, `stagger()`, `group()`, and `sequence()`, apply cascades or allow you to control how multiple animation steps are applied.
## More on Angular animations
You may also be interested in the following:
* [Introduction to Angular animations](guide/animations)
* [Transition and triggers](guide/transition-and-triggers)
* [Reusable animations](guide/reusable-animations)
* [Route transition animations](guide/route-animations)

View File

@ -0,0 +1,40 @@
# Reusable animations
#### Prerequisites
A basic understanding of the following concepts:
* [Introduction to Angular animations](guide/animations)
* [Transition and triggers](guide/transition-and-triggers)
<hr>
The [AnimationOptions](https://angular.io/api/animations/AnimationOptions) interface in Angular animations enables you to create animations that you can reuse across different components.
## Creating reusable animations
To create a reusable animation, use the [`animation()`](https://angular.io/api/animations/animation) method to define an animation in a separate `.ts` file and declare this animation definition as a `const` export variable. You can then import and reuse this animation in any of your app components using the [`useAnimation()`](https://angular.io/api/animations/useAnimation) API.
<code-example path="animations/src/app/animations.ts" title="src/app/animations.ts" region="reusable" language="typescript" linenums="false">
</code-example>
In the above code snippet, `transAnimation` is made reusable by declaring it as an export variable.
<div class="alert is-helpful">
**Note:** The `height`, `opacity`, `backgroundColor`, and `time` inputs are replaced during runtime.
</div>
You can import the reusable `transAnimation` variable in your component class and reuse it using the `useAnimation()` method as shown below.
<code-example path="animations/src/app/open-close.component.3.ts" title="src/app/open-close.component.ts" region="reusable" language="typescript" linenums="false">
</code-example>
## More on Angular animations
You may also be interested in the following:
* [Introduction to Angular animations](guide/animations)
* [Transition and triggers](guide/transition-and-triggers)
* [Complex animation Sequences](guide/complex-animation-sequences)
* [Route transition animations](guide/route-animations)

View File

@ -0,0 +1,139 @@
# Route transition animations
#### Prerequisites
A basic understanding of the following concepts:
* [Introduction to Angular animations](guide/animations)
* [Transition and triggers](guide/transition-and-triggers)
* [Reusable animations](guide/reusable-animations)
<hr>
Routing enables users to navigate between different routes in an application. When a user navigates from one route to another, the Angular router maps the URL path to a relevant component and displays its view. Animating this route transition can greatly enhance the user experience.
The Angular router comes with high-level animation functions that let you animate the transitions between views when a route changes. To produce an animation sequence when switching between routes, you need to define nested animation sequences. Start with the top-level component that hosts the view, and nest additional animations in the components that host the embedded views.
To enable routing transition animation, do the following:
1. Import the routing module into the application and create a routing configuration that defines the possible routes.
2. Add a router outlet to tell the Angular router where to place the activated components in the DOM.
3. Define the animation.
Let's illustrate a router transition animation by navigating between two routes, *Home* and *About* associated with the `HomeComponent` and `AboutComponent` views respectively. Both of these component views are children of the top-most view, hosted by `AppComponent`. We'll implement a router transition animation that slides in the new view to the right and slides out the old view when the user navigates between the two routes.
</br>
<figure>
<img src="generated/images/guide/animations/route-animation.gif" alt="Animations in action" width="440">
</figure>
## Route configuration
To begin, configure a set of routes using methods available in the `RouterModule` class. This route configuration tells the router how to navigate.
Use the `RouterModule.forRoot` method to define a set of routes. Also, import this `RouterModule` to the `imports` array of the main module, `AppModule`.
<div class="alert is-helpful">
**Note:** Use the `RouterModule.forRoot` method in the root module, `AppModule`, to register top-level application routes and providers. For feature modules, call the `RouterModule.forChild` method to register additional routes.
</div>
The following configuration defines the possible routes for the application.
<code-example path="animations/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts" region="route-animation-data" language="typescript">
</code-example>
The `home` and `about` paths are associated with the `HomeComponent` and `AboutComponent` views. The route configuration tells the Angular router to instantiate the `HomeComponent` and `AboutComponent` views when the navigation matches the corresponding path.
In addition to `path` and `component`, the `data` property of each route defines the key animation-specific configuration associated with a route. The `data` property value is passed into `AppComponent` when the route changes. You can also pass additional data in route config that is consumed within the animation. The data property value has to match the transitions defined in the `routeAnimation` trigger, which we'll define later.
<div class="alert is-helpful">
**Note:** The `data` property names that you use can be arbitrary. For example, the name *animation* used in the example above is an arbitrary choice.
</div>
## Router outlet
After configuring the routes, tell the Angular router where to render the views when matched with a route. You can set a router outlet by inserting a `<router-outlet>` container inside the root `AppComponent` template.
The `<router-outlet>` container has an attribute directive that contains data about active routes and their states, based on the `data` property that we set in the route configuration.
<code-example path="animations/src/app/app.component.html" title="src/app/app.component.html" region="route-animations-outlet">
</code-example>
`AppComponent` defines a method that can detect when a view changes. The method assigns an animation state value to the animation trigger (`@routeAnimation`) based on the route configuration `data` property value. Here's an example of an `AppComponent` method that detects when a route change happens.
<code-example path="animations/src/app/app.component.ts" linenums="false" title="src/app/app.component.ts" region="prepare-router-outlet" language="typescript">
</code-example>
Here, the `prepareRoute()` method takes the value of the output directive (established through `#outlet="outlet"`) and returns a string value representing the state of the animation based on the custom data of the current active route. You can use this data to control which transition to execute for each route.
## Animation definition
Animations can be defined directly inside your components. For this example we are defining the animations in a separate file, which allows us to re-use the animations.
The following code snippet defines a reusable animation named `slideInAnimation`.
<code-example path="animations/src/app/animations.ts" linenums="false" title="src/app/animations.ts" region="route-animations" language="typescript">
</code-example>
The animation definition does several things:
* Defines two transitions. A single trigger can define multiple states and transitions.
* Adjusts the styles of the host and child views to control their relative positions during the transition.
* Uses `query()` to determine which child view is entering and which is leaving the host view.
A route change activates the animation trigger, and a transition matching the state change is applied.
<div class="alert is-helpful">
**Note:** The transition states must match the `data` property value defined in the route configuration.
</div>
Make the animation definition available in your application by adding the reusable animation (`slideInAnimation`) to the `animations` metadata of the `AppComponent`.
<code-example path="animations/src/app/app.component.ts" linenums="false" title="src/app/app.component.ts" region="define" language="typescript">
</code-example>
### Styling the host and child components
During a transition, a new view is inserted directly after the old one and both elements appear on screen at the same time. To prevent this, apply additional styling to the host view, and to the removed and inserted child views. The host view must use relative positioning, and the child views must use absolute positioning. Adding styling to the views animates the containers in place, without the DOM moving things around.
<code-example path="animations/src/app/animations.ts" linenums="false" title="src/app/animations.ts" region="style-view" language="typescript">
</code-example>
### Querying the view containers
Use the `query()` method to find and animate elements within the current host component. The `query(":enter")` statement returns the view that is being inserted, and `query(":leave")` returns the view that is being removed.
Let's assume that we are routing from the *Home => About*.
<code-example path="animations/src/app/animations.ts" linenums="false" title="src/app/animations.ts" region="query" language="typescript" linenums="false">
</code-example>
The animation code does the following after styling the views:
* `query(':enter style({ left: '-100%'})` matches the view that is added and hides the newly added view by positioning it to the far left.
* Calls `animateChild()` on the view that is leaving, to run its child animations.
* Uses `group()` function to make the inner animations run in parallel.
* Within the `group()` function:
* Queries the view that is removed and animates it to slide far to the right.
* Slides in the new view by animating the view with an easing function and duration. </br>
This animation results in the `about` view sliding from the left to right.
* Calls the `animateChild()` method on the new view to run its child animations after the main animation completes.
You now have a basic routable animation that animates routing from one view to another.
## More on Angular animations
You may also be interested in the following:
* [Introduction to Angular animations](guide/animations)
* [Transition and triggers](guide/transition-and-triggers)
* [Complex animation sequences](guide/complex-animation-sequences)
* [Reusable animations](guide/reusable-animations)

View File

@ -49,13 +49,12 @@ using an example application.
### Serving with `http-server`
Because `ng serve` does not work with service workers, you must use a separate HTTP server to test your project locally. You can use any HTTP server. The example below uses the [http-server](https://www.npmjs.com/package/http-server) package from npm. To reduce the possibility of conflicts, test on a dedicated port.
Because `ng serve` does not work with service workers, you must use a separate HTTP server to test your project locally. You can use any HTTP server. The example below uses the [http-server](https://www.npmjs.com/package/http-server) package from npm. To reduce the possibility of conflicts and avoid serving stale content, test on a dedicated port and disable caching.
To serve with `http-server`, change to the directory containing your web files and start the web server:
To serve the directory containing your web files with `http-server`, run the following command:
```sh
cd dist/*project-name*
http-server -p 8080
http-server -p 8080 -c-1 dist/<project-name>
```
### Initial load
@ -121,8 +120,7 @@ next step is understanding how updates work.
```sh
ng build --prod
cd dist
http-server -p 8080
http-server -p 8080 -c-1 dist/<project-name>
```
### Updating your application in the browser

View File

@ -0,0 +1,308 @@
# Animations transitions and triggers
You learned the basics of Angular animations in the [introduction](guide/animations) page.
In this guide, we go into greater depth on special transition states such as `*` (wildcard) and `void`, and show how these special states are used for elements entering and leaving a view. The chapter also explores on multiple animation triggers, animation callbacks and sequence-based animation using keyframes.
## Predefined states and wildcard matching
In Angular, transition states can be defined explicitly through the `state()` function, or using the predefined `*` (wildcard) and `void` states.
### Wildcard state
An asterisk `*` or *wildcard* matches any animation state. This is useful for defining transitions that apply regardless of the HTML element's start or end state.
For example, a transition of `open => *` applies when the element's state changes from open to anything else.
<figure>
<img src="generated/images/guide/animations/wildcard-state-500.png" alt="wildcard state expressions">
</figure>
Here's another code sample using the wildcard state together with our previous example using the `open` and `closed` states. Instead of defining each state-to-state transition pair, we're now saying that any transition to `closed` takes 1 second, and any transition to `open` takes 0.5 seconds.
This allows us to add new states without having to include separate transitions for each one.
<code-example title="src/app/open-close.component.ts" path="animations/src/app/open-close.component.ts" region="trigger-wildcard1" language="typescript" linenums="false">
</code-example>
Use a double arrow syntax to specify state-to-state transitions in both directions.
<code-example title="src/app/open-close.component.ts" path="animations/src/app/open-close.component.ts" region="trigger-wildcard2" language="typescript" linenums="false">
</code-example>
### Using wildcard state with multiple transition states
In our two-state button example, the wildcard isn't that useful because there are only two possible states, `open` and `closed`. Wildcard states are better when an element in one particular state has multiple potential states that it can change to. If our button can change from `open` to either `closed` or something like `inProgress`, using a wildcard state could reduce the amount of coding needed.
<figure>
<img src="generated/images/guide/animations/wildcard-3-states.png" alt="wildcard state with 3 states">
</figure>
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" linenums="false"
region="trigger-transition" language="typescript">
</code-example>
The `* => *` transition applies when any change between two states takes place.
Transitions are matched in the order in which they are defined. Thus, you can apply other transitions on top of the `* => *` (any-to-any) transition. For example, define style changes or animations that would apply just to `open => closed`, or just to `closed => open`, and then use `* => *` as a fallback for state pairings that aren't otherwise called out.
To do this, list the more specific transitions *before* `* => *`.
### Using wildcards with styles
Use the wildcard `*` with a style to tell the animation to use whatever the current style value is, and animate with that. Wildcard is a fallback value that's used if the state being animated isn't declared within the trigger.
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" linenums="false"
region="transition4" language="typescript">
</code-example>
### Void state
You can use the `void` state to configure transitions for an element that is entering or leaving a page. See [Animating entering and leaving a view](#enter-leave-view).
### Combining wildcard and void states
You can combine wildcard and void states in a transition to trigger animations that enter and leave the page:
* A transition of `* => void` applies when the element leaves a view, regardless of what state it was in before it left.
* A transition of `void => *` applies when the element enters a view, regardless of what state it assumes when entering.
* The wildcard state `*` matches to *any* state, including `void`.
## Animating entering and leaving a view
This section shows how to animate elements entering or leaving a page.
<div class="alert is-helpful">
**Note:** For our purposes, an element entering or leaving a view is equivalent to being inserted or removed from the DOM.
</div>
Now we'll add a new behavior:
* When you add a hero to the list of heroes, it appears to fly onto the page from the left.
* When you remove a hero from the list, it appears to fly out to the right.
<code-example path="animations/src/app/hero-list-enter-leave.component.ts" title="src/app/hero-list-enter-leave.component.ts" region="animationdef" language="typescript" linenums="false">
</code-example>
In the above code, we applied the `void` state when the HTML element isn't attached to a view.
{@a enter-leave-view}
## :enter and :leave aliases
`:enter` and `:leave` are aliases for the `void => *` and `* => void` transitions. These aliases are used by several animation functions.
<code-example hideCopy language="typescript">
transition ( ':enter', [ ... ] ); // alias for void => *
transition ( ':leave', [ ... ] ); // alias for * => void
</code-example>
It's harder to target an element that is entering a view because it isn't in the DOM yet.
So, use the aliases `:enter` and `:leave` to target HTML elements that are inserted or removed from a view.
### Use of \*ngIf and \*ngFor with :enter and :leave
The `:enter` transition runs when any `*ngIf` or `*ngFor` views are placed on the page, and `:leave` runs when those views are removed from the page.
In this example, we have a special trigger for the enter and leave animation called `myInsertRemoveTrigger`. The HTML template contains the following code.
<code-example path="animations/src/app/insert-remove.component.html" title="src/app/insert-remove.component.html" region="insert-remove" language="typescript">
</code-example>
In the component file, the `:enter` transition sets an initial opacity of 0, and then animates it to change that opacity to 1 as the element is inserted into the view.
<code-example path="animations/src/app/insert-remove.component.ts" title="src/app/insert-remove.component.ts" region="enter-leave-trigger" language="typescript">
</code-example>
Note that this example doesn't need to use `state()`.
## :increment and :decrement in transitions
The `transition()` function takes additional selector values, `:increment` and `:decrement`. Use these to kick off a transition when a numeric value has increased or decreased in value.
<div class="alert is-helpful">
**Note:** The following example uses `query()` and `stagger()` methods, which is discussed in the [complex sequences](guide/complex-animation-sequences#complex-sequence) page.
</div>
<code-example path="animations/src/app/hero-list-page.component.ts" title="src/app/hero-list-page.component.ts" region="increment" language="typescript" linenums="false"></code-example>
## Boolean values in transitions
If a trigger contains a boolean value as a binding value, then this value can be matched using a `transition()` expression that compares `true` and `false`, or `1` and `0`.
<code-example path="animations/src/app/open-close.component.2.html" title="src/app/open-close.component.html" region="trigger-boolean">
</code-example>
In the code snippet above, the HTML template binds a `<div>` element to a trigger named `openClose` with a status expression of `isOpen`, and with possible values of `true` and `false`. This is an alternative to the practice of creating two named states of `open` and `close`.
In the component code, in the `@Component` metadata under the `animations:` property, when the state evaluates to `true` (meaning "open" here), the associated HTML element's height is a wildcard style or default. In this case, use whatever height the element already had before the animation started. When the element is "closed," the element animates to a height of 0, which makes it invisible.
<code-example path="animations/src/app/open-close.component.2.ts" title="src/app/open-close.component.ts" region="trigger-boolean" language="typescript">
</code-example>
## Multiple animation triggers
You can define more than one animation trigger for a component. You can attach animation triggers to different elements, and the parent-child relationships among the elements affect how and when the animations run.
### Parent-child animations
Each time an animation is triggered in Angular, the parent animation always get priority and child animations are blocked. In order for a child animation to run, the parent animation must query each of the elements containing child animations and then allow the animations to run using the [`animateChild()`](https://angular.io/api/animations/animateChild) function.
#### Disabling an animation on an HTML element
A special animation control binding called `@.disabled` can be placed on an HTML element to disable animations on that element, as well as any nested elements. When true, the `@.disabled` binding prevents all animations from rendering.
The code sample below shows how to use this feature.
<code-tabs>
<code-pane path="animations/src/app/open-close.component.4.html" title="src/app/open-close.component.html" region="toggle-animation">
</code-pane>
<code-pane path="animations/src/app/open-close.component.4.ts" title="src/app/open-close.component.ts" region="toggle-animation" language="typescript">
</code-pane>
</code-tabs>
When the `@.disabled` binding is true, the `@childAnimation` trigger doesn't kick off.
When an element within an HTML template has animations disabled using the `@.disabled` host binding, animations are disabled on all inner elements as well. You can't selectively disable multiple animations on a single element.
However, selective child animations can still be run on a disabled parent in one of the following ways:
* A parent animation can use the [`query()`](https://angular.io/api/animations/query) function to collect inner elements located in disabled areas of the HTML template. Those elements can still animate.
* A subanimation can be queried by a parent and then later animated with the `animateChild()` function.
#### Disabling all animations
To disable all animations for an Angular app, place the `@.disabled` host binding on the topmost Angular component.
<code-example path="animations/src/app/app.component.ts" title="src/app/app.component.ts" region="toggle-app-animations" language="typescript" linenums="false">
</code-example>
<div class="alert is-helpful">
**Note:** Disabling animations application-wide is useful during end-to-end (E2E) testing.
</div>
## Animation callbacks
The animation `trigger()` function emits *callbacks* when it starts and when it finishes. In the example below we have a component that contains an `openClose` trigger.
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" region="events1" language="typescript" linenums="false">
</code-example>
In the HTML template, the animation event is passed back via `$event`, as `@trigger.start` and `@trigger.done`, where `trigger` is the name of the trigger being used. In our example, the trigger `openClose` appears as follows.
<code-example path="animations/src/app/open-close.component.3.html" title="src/app/open-close.component.html" region="callbacks">
</code-example>
A potential use for animation callbacks could be to cover for a slow API call, such as a database lookup. For example, you could set up the **InProgress** button to have its own looping animation where it pulsates or does some other visual motion while the backend system operation finishes.
Then, another animation can be called when the current animation finishes. For example, the button goes from the `inProgress` state to the `closed` state when the API call is completed.
An animation can influence an end user to *perceive* the operation as faster, even when it isn't. Thus, a simple animation can be a cost-effective way to keep users happy, rather than seeking to improve the speed of a server call and having to compensate for circumstances beyond your control, such as an unreliable network connection.
Callbacks can serve as a debugging tool, for example in conjunction with `console.warn()` to view the application's progress in a browser's Developer JavaScript Console. The following code snippet creates console log output for our original example, a button with the two states of `open` and `closed`.
<code-example path="animations/src/app/open-close.component.ts" title="src/app/open-close.component.ts" region="events" language="typescript" linenums="false">
</code-example>
{@a keyframes}
## Keyframes
In the previous section, we saw a simple two-state transition. Now we'll create an animation with multiple steps run in sequence using *keyframes*.
Angular's `keyframe()` function is similar to keyframes in CSS. Keyframes allow several style changes within a single timing segment. For example, our button, instead of fading, could change color several times over a single 2-second timespan.
<figure>
<img src="generated/images/guide/animations/keyframes-500.png" alt="keyframes">
</figure>
The code for this color change might look like this.
<code-example path="animations/src/app/status-slider.component.ts" title="src/app/status-slider.component.ts" region="keyframes" language="typescript" linenums="false">
</code-example>
### Offset
Keyframes include an *offset* that defines the point in the animation where each style change occurs. Offsets are relative measures from zero to one, marking the beginning and end of the animation, respectively.
Defining offsets for keyframes is optional. If you omit them, evenly spaced offsets are automatically assigned. For example, three keyframes without predefined offsets receive offsets of 0, 0.5, and 1. Specifying an offset of 0.8 for the middle transition in the above example might look like this.
<figure>
<img src="generated/images/guide/animations/keyframes-offset-500.png" alt="keyframes with offset">
</figure>
The code with offsets specified would be as follows.
<code-example path="animations/src/app/status-slider.component.ts" title="src/app/status-slider.component.ts" region="keyframesWithOffsets" language="typescript">
</code-example>
You can combine keyframes with `duration`, `delay`, and `easing` within a single animation.
### Keyframes with a pulsation
Use ketframes to create a pulse effect in your animations by defining styles at specific offset throughout the animation.
Here's an example of using keyframes to create a pulse effect:
* The original `open` and `closed` states, with the original changes in height, color, and opacity, occurring over a timeframe of 1 second
* A keyframes sequence inserted in the middle that causes the button to appear to pulsate irregularly over the course of that same 1-second timeframe
<figure>
<img src="generated/images/guide/animations/keyframes-pulsation.png" alt="keyframes with irregular pulsation">
</figure>
The code snippet for this animation might look like this.
<code-example path="animations/src/app/open-close.component.1.ts" title="src/app/open-close.component.ts" region="trigger" language="typescript" linenums="false">
</code-example>
### Animatable properties and units
Angular's animation support builds on top of web animations, so you can animate any property that the browser considers animatable. This includes positions, sizes, transforms, colors, borders, and more. The W3C maintains a list of animatable properties on its [CSS Transitions](https://www.w3.org/TR/css-transitions-1/) page.
For positional properties with a numeric value, define a unit by providing the value as a string, in quotes, with the appropriate suffix:
* 50 pixels: `'50px'`
* Relative font size: `'3em'`
* Percentage: `'100%'`
If you don't provide a unit when specifying dimension, Angular assumes a default unit of pixels, or px. Expressing 50 pixels as `50` is the same as saying `'50px'`.
### Automatic property calculation with wildcards
Sometimes you don't know the value of a dimensional style property until runtime. For example, elements often have widths and heights that depend on their content and the screen size. These properties are often challenging to animate using CSS.
In these cases, you can use a special wildcard `*` property value under `style()`, so that the value of that particular style property is computed at runtime and then plugged into the animation.
In this example, we have a trigger called `shrinkOut`, used when an HTML element leaves the page. The animation takes whatever height the element has before it leaves, and animates from that height to zero.
<code-example path="animations/src/app/hero-list-auto.component.ts" title="src/app/hero-list-auto.component.ts" region="auto-calc" language="typescript" linenums="false"></code-example>
### Keyframes summary
The `keyframes()` function in Angular allows you to specify multiple interim styles within a single transition, with an optional offset to define the point in the animation where each style change occurs.
## More on Angular animations
You may also be interested in the following:
* [Introduction to Angular animations](guide/animations)
* [Complex animation sequences](guide/complex-animation-sequences)
* [Reusable animations](guide/reusable-animations)
* [Route transition animations](guide/route-animations)

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -414,6 +414,13 @@
"rev": true,
"title": "Truly UI",
"url": "http://truly-ui.com"
},
"ngsqui": {
"desc": "Simple Quality UI (SQ-UI) is a flexible and easily customizable UI-kit, aiming to provide maximum efficiency with as little overhead as possible. Driven by the idea that it should be strictly \"for developers by developers\", every new feature release includes functionalities demanded by the developers who are using it.",
"logo": "https://sq-ui.github.io/ng-sq-ui/_media/sq-ui-logo.png",
"rev": true,
"title": "Simple Quality UI",
"url": "https://sq-ui.github.io/ng-sq-ui/#/"
}
}
}

View File

@ -202,14 +202,10 @@
"url": "guide/pipes",
"title": "Pipes",
"tooltip": "Pipes transform displayed values within a template."
},
{
"url": "guide/animations",
"title": "Animations",
"tooltip": "A guide to Angular's animation system."
}
]
},
{
"title": "Forms",
"tooltip": "Angular Forms",
@ -387,6 +383,38 @@
"title": "Routing & Navigation",
"tooltip": "Discover the basics of screen navigation with the Angular Router."
},
{
"title": "Animations",
"tooltip": "A guide to Angular's animation system.",
"children": [
{
"url": "guide/animations",
"title": "Introduction",
"tooltip": "Basic techniques in Angular animations."
},
{
"url": "guide/transition-and-triggers",
"title": "Transition and Triggers",
"tooltip": "Advanced techniques in transition and triggers."
},
{
"url": "guide/complex-animation-sequences",
"title": "Complex Sequences",
"tooltip": "Complex Angular animation sequences."
},
{
"url": "guide/reusable-animations",
"title": "Reusable Animations",
"tooltip": "Creating reusable animations."
},
{
"url": "guide/route-animations",
"title": "Route Transition Animations",
"tooltip": "Animate route transitions."
}
]
},
{
"url": "guide/testing",
"title": "Testing",

View File

@ -22,7 +22,7 @@
"test": "yarn check-env && ng test",
"pree2e": "yarn check-env && yarn update-webdriver",
"e2e": "ng e2e --no-webdriver-update",
"presetup": "yarn --cwd .. install && yarn install --frozen-lockfile && yarn ~~check-env && yarn boilerplate:remove",
"presetup": "yarn --cwd .. install && yarn install --frozen-lockfile && yarn ~~check-env && yarn ~~clean-generated && yarn boilerplate:remove",
"setup": "yarn aio-use-npm && yarn example-use-npm",
"postsetup": "yarn boilerplate:add && yarn build-ie-polyfills && yarn docs",
"presetup-local": "yarn presetup",
@ -61,12 +61,13 @@
"build-ie-polyfills": "yarn webpack-cli src/ie-polyfills.js -o src/generated/ie-polyfills.min.js --mode production",
"update-webdriver": "webdriver-manager update --standalone false --gecko false $CHROMEDRIVER_VERSION_ARG",
"~~check-env": "node scripts/check-environment",
"~~clean-generated": "node --eval \"require('shelljs').rm('-rf', 'src/generated')\"",
"~~build": "ng build",
"post~~build": "yarn build-404-page && yarn sw-manifest && yarn sw-copy"
},
"engines": {
"node": ">=8.9.1 <9.0.0",
"yarn": ">=1.3.2 <2.0.0"
"node": ">=10.9.0 <11.0.0",
"yarn": ">=1.9.2 <2.0.0"
},
"private": true,
"dependencies": {

View File

@ -3,6 +3,9 @@ import { SitePage } from './site.po';
describe(browser.baseUrl, () => {
const page = new SitePage();
const getCurrentUrl = () => browser.getCurrentUrl().then(stripQuery).then(stripTrailingSlash);
const stripQuery = (url: string) => url.replace(/\?.*$/, '');
const stripTrailingSlash = (url: string) => url.replace(/\/$/, '');
beforeAll(done => page.init().then(done));
@ -14,8 +17,8 @@ describe(browser.baseUrl, () => {
it(`should not redirect '${url}' (${i + 1}/${page.sitemapUrls.length})`, async () => {
await page.goTo(url);
const expectedUrl = browser.baseUrl + url;
const actualUrl = (await browser.getCurrentUrl()).replace(/\?.*$/, '');
const expectedUrl = stripTrailingSlash(page.baseUrl + url);
const actualUrl = await getCurrentUrl();
expect(actualUrl).toBe(expectedUrl);
});
@ -27,8 +30,8 @@ describe(browser.baseUrl, () => {
it(`should redirect '${fromUrl}' to '${toUrl}' (${i + 1}/${page.legacyUrls.length})`, async () => {
await page.goTo(fromUrl);
const expectedUrl = (/^http/.test(toUrl) ? '' : browser.baseUrl.replace(/\/$/, '')) + toUrl;
const actualUrl = (await browser.getCurrentUrl()).replace(/\?.*$/, '');
const expectedUrl = stripTrailingSlash(/^http/.test(toUrl) ? toUrl : page.baseUrl + toUrl);
const actualUrl = await getCurrentUrl();
expect(actualUrl).toBe(expectedUrl);
});
@ -64,8 +67,8 @@ describe(browser.baseUrl, () => {
expect(homeNavLink.isPresent()).toBe(true);
await homeNavLink.click();
const expectedUrl = browser.baseUrl;
const actualUrl = await browser.getCurrentUrl();
const expectedUrl = page.baseUrl;
const actualUrl = await getCurrentUrl();
expect(actualUrl).toBe(expectedUrl);
});

View File

@ -1,6 +1,9 @@
import { browser, by, element, ExpectedConditions } from 'protractor';
export class SitePage {
/** The base URL with the trailing `/` stripped off (if any). */
baseUrl = browser.baseUrl.replace(/\/$/, '');
/** All URLs found in the app's `sitemap.xml` (i.e. valid URLs tha should not be redirected). */
sitemapUrls: string[] = browser.params.sitemapUrls;
@ -44,7 +47,7 @@ export class SitePage {
.then(regs => Promise.all(regs.map(reg => reg.unregister())))
.then(cb);
await browser.get(url || browser.baseUrl);
await browser.get(url || this.baseUrl);
await browser.executeScript('document.body.classList.add(\'no-animations\')');
await browser.executeAsyncScript(unregisterServiceWorker);
await browser.waitForAngular();

View File

@ -32,6 +32,7 @@ export function loadLocalSitemapUrls() {
}
export async function loadRemoteSitemapUrls(host: string) {
host = host.replace(/\/$/, '');
const urlToSiteMap = `${host}/generated/sitemap.xml`;
const get = /^https:/.test(host) ? httpsGet : httpGet;
@ -43,9 +44,7 @@ export async function loadRemoteSitemapUrls(host: string) {
.on('error', reject));
});
// Currently, all sitemaps use `angular.io` as host in URLs (which is fine since we only use the
// sitemap `angular.io`). See also `aio/src/extra-files/*/robots.txt`.
return extractSitemapUrls(xml, 'https://angular.io/');
return extractSitemapUrls(xml);
}
export function loadSWRoutes() {
@ -69,8 +68,22 @@ export function loadSWRoutes() {
}
// Private functions
function extractSitemapUrls(xml: string, host = '%%DEPLOYMENT_HOST%%') {
function extractSitemapUrls(xml: string) {
// Currently, all sitemaps use `angular.io` as host in URLs (which is fine since we only use the
// sitemap in `angular.io`). See also `aio/src/extra-files/*/robots.txt`.
const host = 'https://angular.io';
const urls: string[] = [];
xml.replace(/<loc>([^<]+)<\/loc>/g, (_, loc) => urls.push(loc.replace(host, '')) as any);
// Ensure none of the URLs contains the scheme/host.
// (That would mean that the URL contains a different than expected host, which can in turn lead
// to tests passing while they shouldn't).
urls.forEach(url => {
if (url.includes('://')) {
throw new Error(`Sitemap URL (${url}) contains unexpected host. Expected: ${host}`);
}
});
return urls;
}

View File

@ -11,8 +11,8 @@
"postinstall": "yarn webdriver:update"
},
"engines": {
"node": ">=8.9.1 <9.0.0",
"yarn": ">=1.3.2 <2.0.0"
"node": ">=10.9.0 <11.0.0",
"yarn": ">=1.9.2 <2.0.0"
},
"keywords": [],
"author": "",

View File

@ -8485,8 +8485,8 @@ untildify@^3.0.2:
resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.2.tgz#7f1f302055b3fea0f3e81dc78eb36766cb65e3f1"
upath@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/upath/-/upath-1.0.4.tgz#ee2321ba0a786c50973db043a50b7bcba822361d"
version "1.1.0"
resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd"
upper-case@^1.1.1:
version "1.1.3"

View File

@ -10510,8 +10510,8 @@ unzip-response@^2.0.1:
resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97"
upath@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/upath/-/upath-1.0.4.tgz#ee2321ba0a786c50973db043a50b7bcba822361d"
version "1.1.0"
resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd"
update-notifier@^0.5.0:
version "0.5.0"

View File

@ -4,7 +4,7 @@
# found in the LICENSE file at https://angular.io/license
""" Public API surface is re-exported here.
This API is exported for user's building angular from source in downstream
This API is exported for users building angular from source in downstream
projects. The rules from packages/bazel are re-exported here as well
as the ng_setup_workspace repository rule needed when building angular
from source downstream. Alternately, this API is available from the
@ -12,11 +12,13 @@ from source downstream. Alternately, this API is available from the
used in a downstream project.
"""
load("//packages/bazel:index.bzl",
load(
"//packages/bazel:index.bzl",
_ng_module = "ng_module",
_ng_package = "ng_package",
_protractor_web_test = "protractor_web_test",
_protractor_web_test_suite = "protractor_web_test_suite")
_protractor_web_test_suite = "protractor_web_test_suite",
)
load("//tools:ng_setup_workspace.bzl", _ng_setup_workspace = "ng_setup_workspace")
ng_module = _ng_module

View File

@ -4,42 +4,33 @@ exports_files([
"protractor.conf.js",
])
# ts_library and ng_module use the `//:tsconfig.json` target
# by default. This alias allows omitting explicit tsconfig
# attribute.
alias(
name = "tsconfig.json",
actual = "//src:tsconfig.json",
)
filegroup(
name = "node_modules",
srcs = glob(
["node_modules/**/*"],
[
# Include only .js, .json & .d.ts files to reduce the number of
# files in the //:node_modules filegroup
"node_modules/**/*.js",
"node_modules/**/*.json",
"node_modules/**/*.d.ts",
# All the files in the http-server package are necesssary
"node_modules/http-server/**",
# All the files in the protractor package are necesssary
"node_modules/protractor/**",
# Also include all files in node_modules/.bin
"node_modules/.bin/*",
],
exclude = [
# Exclude rxjs because we build it from sources using the label @rxjs//:rxjs
"node_modules/rxjs/**",
# Exclude directories that commonly contain filenames which are
# illegal bazel labels
# e.g. node_modules/adm-zip/test/assets/attributes_test/New folder/hidden.txt
"node_modules/**/test/**",
# e.g. node_modules/xpath/docs/function resolvers.md
"node_modules/**/docs/**",
# Exclude files with spaces which are illegal bazel labels
"node_modules/**/* *",
],
),
)
ANGULAR_TESTING = [
"node_modules/@angular/*/bundles/*-testing.umd.js",
# We use AOT, so the compiler and the dynamic platform-browser should be
# visible only in tests
"node_modules/@angular/compiler/bundles/*.umd.js",
"node_modules/@angular/platform-browser-dynamic/bundles/*.umd.js",
]
filegroup(
name = "angular_bundles",
srcs = glob(
["node_modules/@angular/*/bundles/*.umd.js"],
exclude = ANGULAR_TESTING,
),
)
filegroup(
name = "angular_test_bundles",
testonly = 1,
srcs = glob(ANGULAR_TESTING),
)

View File

@ -3,46 +3,18 @@ workspace(name = "bazel_integration_test")
#
# Download Bazel toolchain dependencies as needed by build actions
#
http_archive(
name = "build_bazel_rules_nodejs",
urls = ["https://github.com/bazelbuild/rules_nodejs/archive/0.11.4.zip"],
strip_prefix = "rules_nodejs-0.11.4",
sha256 = "c31c4ead696944a50fad2b3ee9dfbbeffe31a8dcca0b21b9bf5b3e6c6b069801",
)
http_archive(
name = "bazel_skylib",
urls = ["https://github.com/bazelbuild/bazel-skylib/archive/0.3.1.zip"],
strip_prefix = "bazel-skylib-0.3.1",
sha256 = "95518adafc9a2b656667bbf517a952e54ce7f350779d0dd95133db4eb5c27fb1",
)
http_archive(
name = "io_bazel_rules_webtesting",
url = "https://github.com/bazelbuild/rules_webtesting/archive/0.2.1.zip",
strip_prefix = "rules_webtesting-0.2.1",
sha256 = "7d490aadff9b5262e5251fa69427ab2ffd1548422467cb9f9e1d110e2c36f0fa",
)
http_archive(
local_repository(
name = "build_bazel_rules_typescript",
url = "https://github.com/bazelbuild/rules_typescript/archive/0.16.0.zip",
strip_prefix = "rules_typescript-0.16.0",
sha256 = "e65c5639a42e2f6d3f9d2bda62487d6b42734830dda45be1620c3e2b1115070c",
)
http_archive(
name = "io_bazel_rules_go",
url = "https://github.com/bazelbuild/rules_go/releases/download/0.11.0/rules_go-0.11.0.tar.gz",
sha256 = "f70c35a8c779bb92f7521ecb5a1c6604e9c3edd431e50b6376d7497abc8ad3c1",
path = "node_modules/@bazel/typescript",
)
load("@build_bazel_rules_typescript//:package.bzl", "rules_typescript_dependencies")
rules_typescript_dependencies()
http_archive(
name = "io_bazel_rules_sass",
url = "https://github.com/bazelbuild/rules_sass/archive/0.1.0.zip",
strip_prefix = "rules_sass-0.1.0",
sha256 = "b243c4d64f054c174051785862ab079050d90b37a1cef7da93821c6981cb9ad4",
url = "https://github.com/bazelbuild/rules_sass/archive/1.11.0.zip",
strip_prefix = "rules_sass-1.11.0",
sha256 = "dbe9fb97d5a7833b2a733eebc78c9c1e3880f676ac8af16e58ccf2139cbcad03",
)
#
@ -51,7 +23,7 @@ http_archive(
local_repository(
name = "angular",
path = "node_modules/@angular/bazel",
path = "../..",
)
local_repository(
@ -65,8 +37,12 @@ local_repository(
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories")
check_bazel_version("0.16.0")
node_repositories(package_json = ["//:package.json"])
check_bazel_version("0.17.0")
node_repositories(
package_json = ["//:package.json"],
node_version = "10.9.0",
yarn_version = "1.9.2",
)
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")

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