Compare commits
78 Commits
Author | SHA1 | Date | |
---|---|---|---|
74c202a5cd | |||
483e8d28ec | |||
a6fd118f79 | |||
fe6e76c1e7 | |||
de560019f2 | |||
52ed53d071 | |||
5b72d4d676 | |||
c1aa1bf872 | |||
dad1bc7ca3 | |||
2109c30afe | |||
874919a25b | |||
c64e666755 | |||
1da403d8f3 | |||
60c5ebd46a | |||
b71d1987cd | |||
e6325eb9ef | |||
114519ab6a | |||
a9a095d44a | |||
5f9d574d4d | |||
baeb446392 | |||
0d1bfdc505 | |||
fa130e9445 | |||
194710fc1d | |||
6ae5e2b32a | |||
d85d396c26 | |||
1a25144297 | |||
449da8c18e | |||
0f37ed1060 | |||
5c85b4f1e9 | |||
22bc6ef22a | |||
bf928d1c9e | |||
d11c2f915b | |||
98c99e5073 | |||
2c63108faa | |||
0ff48a1266 | |||
ea2a3f8335 | |||
cbed4851a3 | |||
7c157780a9 | |||
cc1b2a5373 | |||
5076185fc8 | |||
65375f4c21 | |||
068a6070dc | |||
47e9761a01 | |||
bef52d20b5 | |||
fe50710021 | |||
71b66fb862 | |||
8db05b408e | |||
351610ca8d | |||
ef78e33560 | |||
12b8a6e351 | |||
260ac20e92 | |||
aed48e00d2 | |||
a9d46e4952 | |||
2f19ad9b46 | |||
81678e62db | |||
cf82fbceba | |||
1d67cb0ce1 | |||
318bd83a6e | |||
f767c22b31 | |||
8346a6dca2 | |||
6397885e74 | |||
8cee56e8c5 | |||
a1b9995731 | |||
35f7ff047a | |||
4ad691a33d | |||
c5af3f8617 | |||
bc1032866c | |||
63e6d1a896 | |||
cb9fd9b4d7 | |||
d5dca0764c | |||
bcd1a09dec | |||
898c0134e7 | |||
763d2150cc | |||
beacbfcb8e | |||
f72319cf6e | |||
5877b3f702 | |||
cf310ba1fa | |||
c2d2953ee4 |
10
.bazelrc
@ -42,6 +42,16 @@ build --watchfs
|
||||
run --nolegacy_external_runfiles
|
||||
test --nolegacy_external_runfiles
|
||||
|
||||
# Turn on --incompatible_strict_action_env which was on by default
|
||||
# in Bazel 0.21.0 but turned off again in 0.22.0. Follow
|
||||
# https://github.com/bazelbuild/bazel/issues/7026 for more details.
|
||||
# This flag is needed to so that the bazel cache is not invalidated
|
||||
# when running bazel via `yarn bazel`.
|
||||
# See https://github.com/angular/angular/issues/27514.
|
||||
build --incompatible_strict_action_env
|
||||
run --incompatible_strict_action_env
|
||||
test --incompatible_strict_action_env
|
||||
|
||||
###############################
|
||||
# Release support #
|
||||
# Turn on these settings with #
|
||||
|
@ -2,6 +2,10 @@
|
||||
# We do this by copying this file to /etc/bazel.bazelrc at the start of the build.
|
||||
# See documentation in /docs/BAZEL.md
|
||||
|
||||
# Save downloaded repositories in a location that can be cached by CircleCI. This helps us
|
||||
# speeding up the analysis time significantly with Bazel managed node dependencies on the CI.
|
||||
build --repository_cache=/home/circleci/bazel_repository_cache
|
||||
|
||||
# Don't be spammy in the logs
|
||||
# TODO(gmagolan): Hide progress again once build performance improves
|
||||
# Presently, CircleCI can timeout during bazel test ... with the following
|
||||
|
@ -11,10 +11,19 @@
|
||||
# needed for jobs that run tests without Bazel. Bazel runs tests with browsers that will be
|
||||
# fetched by the Webtesting rules. Therefore for jobs that run tests with Bazel, we don't need a
|
||||
# docker image with browsers pre-installed.
|
||||
# **NOTE**: If you change the version of the docker images, also change the `cache_key` suffix.
|
||||
# **NOTE 1**: If you change the version of the `*-browsers` docker image, make sure the
|
||||
# `CI_CHROMEDRIVER_VERSION_ARG` env var (in `.circleci/env.sh`) points to a ChromeDriver
|
||||
# version that is compatible with the Chrome version in the image.
|
||||
# **NOTE 2**: If you change the version of the docker images, also change the `cache_key` suffix.
|
||||
var_1: &default_docker_image circleci/node:10.12
|
||||
var_2: &browsers_docker_image circleci/node:10.12-browsers
|
||||
var_3: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-node-10.12
|
||||
# We don't want to include the current branch name in the cache key because that would prevent
|
||||
# PRs from being able to restore the cache since the branch names are always different for PRs.
|
||||
# The cache key should only consist of dynamic values that change whenever something in the
|
||||
# cache changes. For example:
|
||||
# 1) yarn lock file changes --> cached "node_modules" are different.
|
||||
# 2) bazel repository definitions change --> cached bazel repositories are different.
|
||||
var_3: &cache_key v2-angular-{{ checksum "yarn.lock" }}-{{ checksum "WORKSPACE" }}-{{ checksum "packages/bazel/package.bzl" }}-node-10.12
|
||||
|
||||
# Define common ENV vars
|
||||
var_4: &define_env_vars
|
||||
@ -45,7 +54,13 @@ var_7: &post_checkout
|
||||
var_8: &yarn_install
|
||||
run:
|
||||
name: Running Yarn install
|
||||
command: yarn install --frozen-lockfile --non-interactive
|
||||
command: |
|
||||
# Yarn's requests sometimes take more than 10mins to complete.
|
||||
# Print something to stdout, to prevent CircleCI from failing due to not output.
|
||||
while true; do sleep 60; echo "[`date`] Keeping alive..."; done &
|
||||
KEEP_ALIVE_PID=$!
|
||||
yarn install --frozen-lockfile --non-interactive
|
||||
kill $KEEP_ALIVE_PID
|
||||
|
||||
var_9: &setup_circleci_bazel_config
|
||||
run:
|
||||
@ -157,9 +172,9 @@ jobs:
|
||||
# (Run before unit and e2e tests, which destroy the `dist/` directory.)
|
||||
- run: yarn --cwd aio payload-size
|
||||
# Run unit tests
|
||||
- run: yarn --cwd aio test --watch=false
|
||||
- run: yarn --cwd aio test --progress=false --watch=false
|
||||
# Run e2e tests
|
||||
- run: yarn --cwd aio e2e
|
||||
- run: yarn --cwd aio e2e --configuration=ci
|
||||
# Run unit tests for Firebase redirects
|
||||
- run: yarn --cwd aio redirects-test
|
||||
|
||||
@ -197,9 +212,22 @@ jobs:
|
||||
# (Run before unit and e2e tests, which destroy the `dist/` directory.)
|
||||
- run: yarn --cwd aio test-pwa-score-localhost $CI_AIO_MIN_PWA_SCORE
|
||||
# Run unit tests
|
||||
- run: yarn --cwd aio test --watch=false
|
||||
- run: yarn --cwd aio test --progress=false --watch=false
|
||||
# Run e2e tests
|
||||
- run: yarn --cwd aio e2e
|
||||
- run: yarn --cwd aio e2e --configuration=ci
|
||||
|
||||
test_aio_local_ivy:
|
||||
<<: *job_defaults
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- restore_cache:
|
||||
key: *cache_key
|
||||
- attach_workspace:
|
||||
at: dist
|
||||
- *define_env_vars
|
||||
# Build aio with Ivy (using local Angular packages)
|
||||
- run: yarn --cwd aio build-with-ivy --progress=false
|
||||
|
||||
test_aio_tools:
|
||||
<<: *job_defaults
|
||||
@ -278,14 +306,16 @@ jobs:
|
||||
name: Wait for preview and run tests
|
||||
command: node aio/scripts/test-preview.js $CI_PULL_REQUEST $CI_COMMIT $CI_AIO_MIN_PWA_SCORE
|
||||
|
||||
# This job exists only for backwards-compatibility with old scripts and tests
|
||||
# that rely on the pre-Bazel dist/packages-dist layout.
|
||||
# It duplicates some work with the job above: we build the bazel packages
|
||||
# twice. Even though we have a remote cache, these jobs will typically run in
|
||||
# parallel so up-to-date outputs will not be available at the time the build
|
||||
|
||||
# The `build-npm-packages` tasks exist for backwards-compatibility with old scripts and
|
||||
# tests that rely on the pre-Bazel `dist/packages-dist` output structure (build.sh).
|
||||
# Having multiple jobs that independently build in this manner duplicates some work; we build
|
||||
# the bazel packages more than once. Even though we have a remote cache, these jobs will
|
||||
# typically run in parallel so up-to-date outputs will not be available at the time the build
|
||||
# starts.
|
||||
# No new jobs should depend on this one.
|
||||
build-packages-dist:
|
||||
|
||||
# Build the view engine npm packages. No new jobs should depend on this.
|
||||
build-npm-packages:
|
||||
<<: *job_defaults
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
@ -306,6 +336,29 @@ jobs:
|
||||
root: dist
|
||||
paths:
|
||||
- packages-dist
|
||||
|
||||
|
||||
# Build the ivy npm packages.
|
||||
build-ivy-npm-packages:
|
||||
<<: *job_defaults
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- restore_cache:
|
||||
key: *cache_key
|
||||
- *define_env_vars
|
||||
- *setup_circleci_bazel_config
|
||||
- *yarn_install
|
||||
- *setup_bazel_remote_execution
|
||||
|
||||
- run: scripts/build-ivy-npm-packages.sh
|
||||
|
||||
# Save the npm packages from //packages/... for other workflow jobs to read
|
||||
# https://circleci.com/docs/2.0/workflows/#using-workspaces-to-share-data-among-jobs
|
||||
- persist_to_workspace:
|
||||
root: dist
|
||||
paths:
|
||||
- packages-dist-ivy-aot
|
||||
|
||||
# We run the integration tests outside of Bazel for now.
|
||||
@ -481,7 +534,7 @@ workflows:
|
||||
- lint
|
||||
- test
|
||||
- test_ivy_aot
|
||||
- build-packages-dist
|
||||
- build-npm-packages
|
||||
- test_aio
|
||||
- legacy-unit-tests-local
|
||||
- legacy-unit-tests-saucelabs
|
||||
@ -490,19 +543,19 @@ workflows:
|
||||
- test_aio
|
||||
- legacy-e2e-tests:
|
||||
requires:
|
||||
- build-packages-dist
|
||||
- build-npm-packages
|
||||
- legacy-misc-tests:
|
||||
requires:
|
||||
- build-packages-dist
|
||||
- build-npm-packages
|
||||
- test_aio_local:
|
||||
requires:
|
||||
- build-packages-dist
|
||||
- build-npm-packages
|
||||
- test_aio_tools:
|
||||
requires:
|
||||
- build-packages-dist
|
||||
- build-npm-packages
|
||||
- test_docs_examples:
|
||||
requires:
|
||||
- build-packages-dist
|
||||
- build-npm-packages
|
||||
- aio_preview:
|
||||
# Only run on PR builds. (There can be no previews for non-PR builds.)
|
||||
filters:
|
||||
@ -513,7 +566,7 @@ workflows:
|
||||
- aio_preview
|
||||
- integration_test:
|
||||
requires:
|
||||
- build-packages-dist
|
||||
- build-npm-packages
|
||||
- publish_snapshot:
|
||||
# Note: no filters on this job because we want it to run for all upstream branches
|
||||
# We'd really like to filter out pull requests here, but not yet available:
|
||||
@ -526,10 +579,11 @@ workflows:
|
||||
- integration_test
|
||||
# Only publish if `aio`/`docs` tests using the locally built Angular packages pass
|
||||
- test_aio_local
|
||||
# - test_aio_local_ivy
|
||||
- test_docs_examples
|
||||
# Get the artifacts to publish from the build-packages-dist job
|
||||
# Get the artifacts to publish from the build-npm-packages job
|
||||
# since the publishing script expects the legacy outputs layout.
|
||||
- build-packages-dist
|
||||
- build-npm-packages
|
||||
- legacy-e2e-tests
|
||||
- legacy-misc-tests
|
||||
- legacy-unit-tests-local
|
||||
|
@ -15,7 +15,7 @@
|
||||
#
|
||||
# Usage: `setPublicVar <name> <value>`
|
||||
function setPublicVar() {
|
||||
setSecretVar $1 $2;
|
||||
setSecretVar $1 "$2";
|
||||
echo "$1=$2";
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,11 @@ setPublicVar PROJECT_ROOT "$(pwd)";
|
||||
setPublicVar CI_AIO_MIN_PWA_SCORE "95";
|
||||
# This is the branch being built; e.g. `pull/12345` for PR builds.
|
||||
setPublicVar CI_BRANCH "$CIRCLE_BRANCH";
|
||||
# ChromeDriver version compatible with the Chrome version included in the docker image used in
|
||||
# `.circleci/config.yml`. See http://chromedriver.chromium.org/downloads for a list of versions.
|
||||
# This variable is intended to be passed as an arg to the `webdriver-manager update` command (e.g.
|
||||
# `"postinstall": "webdriver-manager update $CI_CHROMEDRIVER_VERSION_ARG"`).
|
||||
setPublicVar CI_CHROMEDRIVER_VERSION_ARG "--versions.chrome 2.45";
|
||||
setPublicVar CI_COMMIT "$CIRCLE_SHA1";
|
||||
# `CI_COMMIT_RANGE` will only be available when `CIRCLE_COMPARE_URL` is also available (or can be
|
||||
# retrieved via `get-compare-url.js`), i.e. on push builds (a.k.a. non-PR, non-scheduled builds and
|
||||
|
3
.github/CODEOWNERS
vendored
@ -54,6 +54,7 @@
|
||||
# kara - Kara Erickson
|
||||
# kyliau - Keen Yee Liau
|
||||
# matsko - Matias Niemelä
|
||||
# mgechev - Minko Gechev
|
||||
# mhevery - Misko Hevery
|
||||
# ocombe - Olivier Combe
|
||||
# petebacondarwin - Pete Bacon Darwin
|
||||
@ -113,6 +114,7 @@
|
||||
# - alexeagle
|
||||
# - kyliau
|
||||
# - IgorMinar
|
||||
# - mgechev
|
||||
|
||||
|
||||
# ===========================================================
|
||||
@ -122,6 +124,7 @@
|
||||
# - alexeagle
|
||||
# - filipesilva
|
||||
# - hansl
|
||||
# - mgechev
|
||||
|
||||
|
||||
# ===========================================================
|
||||
|
34
CHANGELOG.md
@ -1,3 +1,37 @@
|
||||
<a name="7.2.4"></a>
|
||||
## [7.2.4](https://github.com/angular/angular/compare/7.2.3...7.2.4) (2019-02-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** Bazel builder resolves with require.resolve() ([#28478](https://github.com/angular/angular/issues/28478)) ([d85d396](https://github.com/angular/angular/commit/d85d396))
|
||||
* **bazel:** fix integration test for bazel-schematics ([#28460](https://github.com/angular/angular/issues/28460)) ([449da8c](https://github.com/angular/angular/commit/449da8c))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* pngcrush all pngs ([#28479](https://github.com/angular/angular/issues/28479)) ([1a25144](https://github.com/angular/angular/commit/1a25144)), closes [#18243](https://github.com/angular/angular/issues/18243)
|
||||
|
||||
|
||||
|
||||
<a name="7.2.3"></a>
|
||||
## [7.2.3](https://github.com/angular/angular/compare/7.2.2...7.2.3) (2019-01-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** add [@npm](https://github.com/npm)//tslib dep to e2e ts_library target in bazel-workspace schematic ([#28358](https://github.com/angular/angular/issues/28358)) ([8cee56e](https://github.com/angular/angular/commit/8cee56e))
|
||||
* **bazel:** Bazel-workspace schematics should run in ScopedTree ([#28349](https://github.com/angular/angular/issues/28349)) ([260ac20](https://github.com/angular/angular/commit/260ac20))
|
||||
* **bazel:** Builder should invoke local bazel/iblaze ([#28303](https://github.com/angular/angular/issues/28303)) ([12b8a6e](https://github.com/angular/angular/commit/12b8a6e))
|
||||
* **bazel:** ng-new should run yarn install ([#28381](https://github.com/angular/angular/issues/28381)) ([a9d46e4](https://github.com/angular/angular/commit/a9d46e4))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* yarn version upgrade ([#28360](https://github.com/angular/angular/issues/28360)) ([cc1b2a5](https://github.com/angular/angular/commit/cc1b2a5))
|
||||
|
||||
|
||||
|
||||
<a name="7.2.2"></a>
|
||||
## [7.2.2](https://github.com/angular/angular/compare/7.2.1...7.2.2) (2019-01-22)
|
||||
|
||||
|
2
LICENSE
@ -1,6 +1,6 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2014-2018 Google, Inc. http://angular.io
|
||||
Copyright (c) 2010-2019 Google LLC. http://angular.io/license
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -62,8 +62,8 @@ local_repository(
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install")
|
||||
|
||||
# Bazel version must be at least v0.21.0 because:
|
||||
# - 0.21.0 --experimental_strict_action_env flag turned on by default which fixes cache when
|
||||
# running `yarn bazel` (see https://github.com/angular/angular/issues/27514#issuecomment-451438271)
|
||||
# - 0.21.0 Using --incompatible_strict_action_env flag fixes cache when running `yarn bazel`
|
||||
# (see https://github.com/angular/angular/issues/27514#issuecomment-451438271)
|
||||
check_bazel_version("0.21.0", """
|
||||
You no longer need to install Bazel on your machine.
|
||||
Angular has a dependency on the @bazel/bazel package which supplies it.
|
||||
|
@ -104,6 +104,9 @@
|
||||
},
|
||||
"archive": {
|
||||
"browserTarget": "site:build:archive"
|
||||
},
|
||||
"ci": {
|
||||
"progress": false
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -166,6 +169,11 @@
|
||||
"options": {
|
||||
"protractorConfig": "tests/e2e/protractor.conf.js",
|
||||
"devServerTarget": "site:serve"
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"devServerTarget": "site:serve:ci"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 14 KiB |
@ -97,7 +97,7 @@ export class HeroService {
|
||||
/** POST: add a new hero to the server */
|
||||
addHero (hero: Hero): Observable<Hero> {
|
||||
return this.http.post<Hero>(this.heroesUrl, hero, httpOptions).pipe(
|
||||
tap((hero: Hero) => this.log(`added hero w/ id=${hero.id}`)),
|
||||
tap((newHero: Hero) => this.log(`added hero w/ id=${newHero.id}`)),
|
||||
catchError(this.handleError<Hero>('addHero'))
|
||||
);
|
||||
}
|
||||
|
@ -1508,7 +1508,7 @@ done manually.
|
||||
|
||||
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.
|
||||
Setting this option to `true` is not recommended because unsupported versions of TypeScript might have undefined behavior.
|
||||
|
||||
This option is `false` by default.
|
||||
|
||||
|
@ -273,8 +273,8 @@ The CLI uses [Autoprefixer](https://github.com/postcss/autoprefixer) to ensure c
|
||||
You may find it necessary to target specific browsers or exclude certain browser versions from your build.
|
||||
|
||||
Internally, Autoprefixer relies on a library called [Browserslist](https://github.com/browserslist/browserslist) to figure out which browsers to support with prefixing.
|
||||
Browserlist looks for configuration options in a `browserlist` property of the package configuration file, or in a configuration file named `.browserslistrc`.
|
||||
Autoprefixer looks for the Browserlist configuration when it prefixes your CSS.
|
||||
Browserlist looks for configuration options in a `browserslist` property of the package configuration file, or in a configuration file named `.browserslistrc`.
|
||||
Autoprefixer looks for the `browserslist` configuration when it prefixes your CSS.
|
||||
|
||||
* You can tell Autoprefixer what browsers to target by adding a browserslist property to the package configuration file, `package.json`:
|
||||
```
|
||||
|
@ -53,7 +53,7 @@ Workspace-wide `node_modules` dependencies are visible to this project.
|
||||
| `app/` | Contains the component files in which your app logic and data are defined. See details in [App source folder](#app-src) below. |
|
||||
| `assets/` | Contains image files and other asset files to be copied as-is when you build your application. |
|
||||
| `environments/` | Contains build configuration options for particular target environments. By default there is an unnamed standard development environment and a production ("prod") environment. You can define additional target environment configurations. |
|
||||
| `browserlist` | Configures sharing of target browsers and Node.js versions among various front-end tools. See [Browserlist on GitHub](https://github.com/browserslist/browserslist) for more information. |
|
||||
| `browserslist` | Configures sharing of target browsers and Node.js versions among various front-end tools. See [Browserslist on GitHub](https://github.com/browserslist/browserslist) for more information. |
|
||||
| `favicon.ico` | An icon to use for this app in the bookmark bar. |
|
||||
| `index.html` | The main HTML page that is served when someone visits your site. The CLI automatically adds all JavaScript and CSS files when building your app, so you typically don't need to add any `<script>` or` <link>` tags here manually. |
|
||||
| `main.ts` | The main entry point for your app. Compiles the application with the [JIT compiler](https://angular.io/guide/glossary#jit) and bootstraps the application's root module (AppModule) to run in the browser. You can also use the [AOT compiler](https://angular.io/guide/aot-compiler) without changing any code by appending the `--aot` flag to the CLI `build` and `serve` commands. |
|
||||
|
@ -124,7 +124,7 @@ calls the lifecycle hook methods in the following sequence at specific moments:
|
||||
|
||||
Respond after Angular checks the component's views and child views / the view that a directive is in.
|
||||
|
||||
Called after the `ngAfterViewInit` and every subsequent `ngAfterContentChecked()`.
|
||||
Called after the `ngAfterViewInit()` and every subsequent `ngAfterContentChecked()`.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
Before Width: | Height: | Size: 350 KiB After Width: | Height: | Size: 343 KiB |
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 154 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 20 KiB |
BIN
aio/content/images/guide/forms-overview/dataflow-reactive-forms-mtv.png
Executable file → Normal file
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 14 KiB |
BIN
aio/content/images/guide/forms-overview/dataflow-reactive-forms-vtm.png
Executable file → Normal file
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 14 KiB |
BIN
aio/content/images/guide/forms-overview/dataflow-td-forms-mtv.png
Executable file → Normal file
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 26 KiB |
BIN
aio/content/images/guide/forms-overview/dataflow-td-forms-vtm.png
Executable file → Normal file
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 24 KiB |
BIN
aio/content/images/guide/forms-overview/key-diff-reactive-forms.png
Executable file → Normal file
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 8.9 KiB |
BIN
aio/content/images/guide/forms-overview/key-diff-td-forms.png
Executable file → Normal file
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 21 KiB |
0
aio/content/images/guide/structural-directives/template-rendering.png
Executable file → Normal file
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 6.3 KiB |
@ -2,7 +2,7 @@
|
||||
@description
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2014-2018 Google, Inc.
|
||||
Copyright (c) 2010-2019 Google LLC. http://angular.io/license
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -698,5 +698,13 @@
|
||||
"website": "https://brianflove.com",
|
||||
"bio": "Brian is a software engineer and GDE in Angular with a passion for learning, writing, speaking, teaching and mentoring. Brian has been building web applications for over 20 years and has long been a fanboy of JavaScript. When not in front of his Macbook Pro Brian is in the Rocky Mountains skiing or hiking.",
|
||||
"group": "GDE"
|
||||
},
|
||||
"jeffbcross": {
|
||||
"name": "Jeff Cross",
|
||||
"picture": "jeff-cross.jpg",
|
||||
"twitter": "jeffbcross",
|
||||
"website": "https://nrwl.io/",
|
||||
"bio": "Jeff is an Angular Consultant at nrwl.io where he helps enterprise teams succeed with Angular. Prior to founding Nrwl, Jeff was one of the earliest members of the Angular Core Team at Google, and contributed to many of the early state management and performance efforts of AngularJS and Angular.",
|
||||
"group": "GDE"
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,12 @@
|
||||
<td>Atlanta, Georgia</td>
|
||||
<td>January 9 - 12, 2019</td>
|
||||
</tr>
|
||||
<!-- ng-India 2019-->
|
||||
<tr>
|
||||
<th><a href="https://www.ng-ind.com/" title="ng-India">ng-India</a></th>
|
||||
<td>Gurgaon, India</td>
|
||||
<td>February 23, 2019</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -753,6 +753,10 @@
|
||||
{
|
||||
"title": "日本語版",
|
||||
"url": "https://angular.jp/"
|
||||
},
|
||||
{
|
||||
"title": "한국어",
|
||||
"url": "https://angular.kr/"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ Inject `HttpClient` into the constructor in a private property called `http`.
|
||||
</code-example>
|
||||
|
||||
Keep injecting the `MessageService`. You'll call it so frequently that
|
||||
you'll wrap it in private `log` method.
|
||||
you'll wrap it in a private `log()` method.
|
||||
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
|
@ -17,7 +17,9 @@
|
||||
"build": "yarn ~~build",
|
||||
"prebuild-local": "yarn setup-local",
|
||||
"build-local": "yarn ~~build",
|
||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js 02d2ec250",
|
||||
"prebuild-with-ivy": "yarn setup-local && yarn ivy-ngcc",
|
||||
"build-with-ivy": "node scripts/build-with-ivy",
|
||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js b5e796a03",
|
||||
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",
|
||||
"test": "yarn check-env && ng test",
|
||||
"pree2e": "yarn check-env && yarn update-webdriver",
|
||||
@ -60,7 +62,7 @@
|
||||
"generate-stackblitz": "node ./tools/stackblitz-builder/generateStackblitz",
|
||||
"generate-zips": "node ./tools/example-zipper/generateZips",
|
||||
"build-404-page": "node scripts/build-404-page",
|
||||
"update-webdriver": "webdriver-manager update --standalone false --gecko false $CHROMEDRIVER_VERSION_ARG",
|
||||
"update-webdriver": "webdriver-manager update --standalone false --gecko false $CI_CHROMEDRIVER_VERSION_ARG",
|
||||
"~~check-env": "node scripts/check-environment",
|
||||
"~~clean-generated": "node --eval \"require('shelljs').rm('-rf', 'src/generated')\"",
|
||||
"~~build": "ng build --configuration=stable",
|
||||
@ -71,7 +73,7 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.9.0 <11.0.0",
|
||||
"yarn": ">=1.10.1 <1.13.0"
|
||||
"yarn": ">=1.10.1 <1.14.0"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
@ -91,12 +93,11 @@
|
||||
"core-js": "^2.4.1",
|
||||
"rxjs": "^6.3.0",
|
||||
"tslib": "^1.9.0",
|
||||
"web-animations-js": "^2.2.5",
|
||||
"zone.js": "^0.8.26"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~0.10.0",
|
||||
"@angular/cli": "7.2.0-beta.2",
|
||||
"@angular-devkit/build-angular": "^0.12.1",
|
||||
"@angular/cli": "7.2.1",
|
||||
"@angular/compiler": "^7.0.0",
|
||||
"@angular/compiler-cli": "^7.0.0",
|
||||
"@angular/language-service": "^7.0.0",
|
||||
@ -107,7 +108,7 @@
|
||||
"canonical-path": "1.0.0",
|
||||
"chalk": "^2.1.0",
|
||||
"cjson": "^0.5.0",
|
||||
"codelyzer": "~4.2.1",
|
||||
"codelyzer": "~4.5.0",
|
||||
"cross-spawn": "^5.1.0",
|
||||
"css-selector-parser": "^1.3.0",
|
||||
"dgeni": "^0.4.11",
|
||||
|
@ -9,7 +9,7 @@ const SRC_DIR = resolve(__dirname, '../src');
|
||||
const DIST_DIR = resolve(__dirname, '../dist');
|
||||
|
||||
// Run
|
||||
_main(process.argv.slice(2));
|
||||
_main();
|
||||
|
||||
// Functions - Definitions
|
||||
function _main() {
|
||||
|
46
aio/scripts/build-with-ivy.js
Normal file
@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Imports
|
||||
const {extend, parse} = require('cjson');
|
||||
const {readFileSync, writeFileSync} = require('fs');
|
||||
const {join, resolve} = require('path');
|
||||
const {exec, set} = require('shelljs');
|
||||
|
||||
set('-e');
|
||||
|
||||
// Constants
|
||||
const ROOT_DIR = resolve(__dirname, '..');
|
||||
const TS_CONFIG_PATH = join(ROOT_DIR, 'tsconfig.json');
|
||||
const NG_COMPILER_OPTS = {
|
||||
angularCompilerOptions: {
|
||||
// Related Jira issue: FW-737
|
||||
allowEmptyCodegenFiles: true,
|
||||
enableIvy: 'ngtsc',
|
||||
},
|
||||
};
|
||||
|
||||
// Run
|
||||
_main(process.argv.slice(2));
|
||||
|
||||
// Functions - Definitions
|
||||
function _main(buildArgs) {
|
||||
console.log('\nModifying `tsconfig.json`...');
|
||||
const oldTsConfigStr = readFileSync(TS_CONFIG_PATH, 'utf8');
|
||||
const oldTsConfigObj = parse(oldTsConfigStr);
|
||||
const newTsConfigObj = extend(true, oldTsConfigObj, NG_COMPILER_OPTS);
|
||||
const newTsConfigStr = JSON.stringify(newTsConfigObj, null, 2);
|
||||
writeFileSync(TS_CONFIG_PATH, newTsConfigStr);
|
||||
console.log(newTsConfigStr);
|
||||
|
||||
try {
|
||||
const buildArgsStr = buildArgs.join(' ');
|
||||
|
||||
console.log(`\nBuilding${buildArgsStr && ` with args \`${buildArgsStr}\``}...`);
|
||||
exec(`yarn ~~build ${buildArgsStr}`, {cwd: ROOT_DIR});
|
||||
} finally {
|
||||
console.log('\nRestoring `tsconfig.json`...');
|
||||
writeFileSync(TS_CONFIG_PATH, oldTsConfigStr);
|
||||
}
|
||||
|
||||
console.log('\nDone!');
|
||||
}
|
@ -61,7 +61,7 @@
|
||||
(docInserted)="onDocInserted()"
|
||||
(docRendered)="onDocRendered()">
|
||||
</aio-doc-viewer>
|
||||
<aio-dt [on]="dtOn" [(doc)]="currentDocument"></aio-dt>
|
||||
<aio-dt *ngIf="dtOn" [(doc)]="currentDocument"></aio-dt>
|
||||
</main>
|
||||
|
||||
</mat-sidenav-container>
|
||||
|
@ -169,6 +169,13 @@ describe('AppComponent', () => {
|
||||
|
||||
expect(component.tocMaxHeight).toMatch(/^\d+\.\d{2}$/);
|
||||
});
|
||||
|
||||
it('should update `scrollService.updateScrollPositonInHistory()`', () => {
|
||||
const scrollService = fixture.debugElement.injector.get<ScrollService>(ScrollService);
|
||||
spyOn(scrollService, 'updateScrollPositionInHistory');
|
||||
component.onScroll();
|
||||
expect(scrollService.updateScrollPositionInHistory).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('SideNav', () => {
|
||||
@ -461,11 +468,15 @@ describe('AppComponent', () => {
|
||||
let scrollService: ScrollService;
|
||||
let scrollSpy: jasmine.Spy;
|
||||
let scrollToTopSpy: jasmine.Spy;
|
||||
let scrollAfterRenderSpy: jasmine.Spy;
|
||||
let removeStoredScrollPositionSpy: jasmine.Spy;
|
||||
|
||||
beforeEach(() => {
|
||||
scrollService = fixture.debugElement.injector.get<ScrollService>(ScrollService);
|
||||
scrollSpy = spyOn(scrollService, 'scroll');
|
||||
scrollToTopSpy = spyOn(scrollService, 'scrollToTop');
|
||||
scrollAfterRenderSpy = spyOn(scrollService, 'scrollAfterRender');
|
||||
removeStoredScrollPositionSpy = spyOn(scrollService, 'removeStoredScrollPosition');
|
||||
});
|
||||
|
||||
it('should not scroll immediately when the docId (path) changes', () => {
|
||||
@ -510,33 +521,24 @@ describe('AppComponent', () => {
|
||||
expect(scrollSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should scroll to top when call `onDocRemoved` directly', () => {
|
||||
scrollToTopSpy.calls.reset();
|
||||
|
||||
it('should call `removeStoredScrollPosition` when call `onDocRemoved` directly', () => {
|
||||
component.onDocRemoved();
|
||||
expect(scrollToTopSpy).toHaveBeenCalled();
|
||||
expect(removeStoredScrollPositionSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should scroll after a delay when call `onDocInserted` directly', fakeAsync(() => {
|
||||
it('should call `scrollAfterRender` when call `onDocInserted` directly', (() => {
|
||||
component.onDocInserted();
|
||||
expect(scrollSpy).not.toHaveBeenCalled();
|
||||
|
||||
tick(scrollDelay);
|
||||
expect(scrollSpy).toHaveBeenCalled();
|
||||
expect(scrollAfterRenderSpy).toHaveBeenCalledWith(scrollDelay);
|
||||
}));
|
||||
|
||||
it('should scroll (via `onDocInserted`) when finish navigating to a new doc', fakeAsync(() => {
|
||||
expect(scrollToTopSpy).not.toHaveBeenCalled();
|
||||
|
||||
it('should call `scrollAfterRender` (via `onDocInserted`) when navigate to a new Doc', fakeAsync(() => {
|
||||
locationService.go('guide/pipes');
|
||||
tick(1); // triggers the HTTP response for the document
|
||||
tick(1); // triggers the HTTP response for the document
|
||||
fixture.detectChanges(); // triggers the event that calls `onDocInserted`
|
||||
|
||||
expect(scrollToTopSpy).toHaveBeenCalled();
|
||||
expect(scrollSpy).not.toHaveBeenCalled();
|
||||
expect(scrollAfterRenderSpy).toHaveBeenCalledWith(scrollDelay);
|
||||
|
||||
tick(scrollDelay);
|
||||
expect(scrollSpy).toHaveBeenCalled();
|
||||
tick(500); // there are other outstanding timers in the AppComponent that are not relevant
|
||||
}));
|
||||
});
|
||||
|
||||
|
@ -127,7 +127,7 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
if (path === this.currentPath) {
|
||||
// scroll only if on same page (most likely a change to the hash)
|
||||
this.autoScroll();
|
||||
this.scrollService.scroll();
|
||||
} else {
|
||||
// don't scroll; leave that to `onDocRendered`
|
||||
this.currentPath = path;
|
||||
@ -187,11 +187,6 @@ export class AppComponent implements OnInit {
|
||||
.subscribe(() => this.updateShell());
|
||||
}
|
||||
|
||||
// Scroll to the anchor in the hash fragment or top of doc.
|
||||
autoScroll() {
|
||||
this.scrollService.scroll();
|
||||
}
|
||||
|
||||
onDocReady() {
|
||||
// About to transition to new view.
|
||||
this.isTransitioning = true;
|
||||
@ -204,9 +199,7 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
onDocRemoved() {
|
||||
// The previous document has been removed.
|
||||
// Scroll to top to restore a clean visual state for the new document.
|
||||
this.scrollService.scrollToTop();
|
||||
this.scrollService.removeStoredScrollPosition();
|
||||
}
|
||||
|
||||
onDocInserted() {
|
||||
@ -216,9 +209,8 @@ export class AppComponent implements OnInit {
|
||||
// (e.g. sidenav, host classes) needs to happen asynchronously.
|
||||
setTimeout(() => this.updateShell());
|
||||
|
||||
// Scroll 500ms after the new document has been inserted into the doc-viewer.
|
||||
// The delay is to allow time for async layout to complete.
|
||||
setTimeout(() => this.autoScroll(), 500);
|
||||
// Scroll the good position depending on the context
|
||||
this.scrollService.scrollAfterRender(500);
|
||||
}
|
||||
|
||||
onDocRendered() {
|
||||
@ -242,7 +234,7 @@ export class AppComponent implements OnInit {
|
||||
|
||||
@HostListener('window:resize', ['$event.target.innerWidth'])
|
||||
onResize(width: number) {
|
||||
this.isSideBySide = width > this.sideBySideWidth;
|
||||
this.isSideBySide = width >= this.sideBySideWidth;
|
||||
this.showFloatingToc.next(width > this.showFloatingTocWidth);
|
||||
|
||||
if (this.isSideBySide && !this.isSideNavDoc) {
|
||||
@ -256,7 +248,6 @@ export class AppComponent implements OnInit {
|
||||
|
||||
@HostListener('click', ['$event.target', '$event.button', '$event.ctrlKey', '$event.metaKey', '$event.altKey'])
|
||||
onClick(eventTarget: HTMLElement, button: number, ctrlKey: boolean, metaKey: boolean, altKey: boolean): boolean {
|
||||
|
||||
// Hide the search results if we clicked outside both the "search box" and the "search results"
|
||||
if (!this.searchElements.some(element => element.nativeElement.contains(eventTarget))) {
|
||||
this.hideSearchResults();
|
||||
@ -348,6 +339,9 @@ export class AppComponent implements OnInit {
|
||||
// Dynamically change height of table of contents container
|
||||
@HostListener('window:scroll')
|
||||
onScroll() {
|
||||
|
||||
this.scrollService.updateScrollPositionInHistory();
|
||||
|
||||
if (!this.tocMaxHeightOffset) {
|
||||
// Must wait until `mat-toolbar` is measurable.
|
||||
const el = this.hostElement.nativeElement as Element;
|
||||
|
@ -46,7 +46,7 @@ export interface TabInfo {
|
||||
export class CodeTabsComponent implements OnInit, AfterViewInit {
|
||||
tabs: TabInfo[];
|
||||
|
||||
@Input('linenums') linenums: string;
|
||||
@Input() linenums: string;
|
||||
|
||||
@ViewChild('content') content;
|
||||
|
||||
|
@ -4,18 +4,17 @@ import { DocumentContents } from 'app/documents/document.service';
|
||||
@Component({
|
||||
selector: 'aio-dt',
|
||||
template: `
|
||||
<div *ngIf="on">
|
||||
<hr>
|
||||
<textarea #dt [value]="text" rows="10" cols="80"></textarea>
|
||||
<br/>
|
||||
<button (click)="dtextSet()">Show change</button>
|
||||
</div>
|
||||
<div>
|
||||
<hr>
|
||||
<textarea #dt [value]="text" rows="10" cols="80"></textarea>
|
||||
<br/>
|
||||
<button (click)="dtextSet()">Show change</button>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class DtComponent {
|
||||
|
||||
@Input() on = false;
|
||||
@Input('doc') doc: DocumentContents;
|
||||
@Input() doc: DocumentContents;
|
||||
@Output() docChange = new EventEmitter<DocumentContents>();
|
||||
|
||||
@ViewChild('dt', { read: ElementRef })
|
||||
|
@ -1,23 +1,20 @@
|
||||
|
||||
<div class="grid-fluid">
|
||||
<div class="footer-block" *ngFor="let node of nodes">
|
||||
<h3>{{node.title}}</h3>
|
||||
<ul>
|
||||
<li *ngFor="let item of node.children">
|
||||
<a class="link" [href]="item.url"
|
||||
[title]="item.tooltip || item.title">{{ item.title }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="grid-fluid">
|
||||
<div class="footer-block" *ngFor="let node of nodes">
|
||||
<h3>{{node.title}}</h3>
|
||||
<ul>
|
||||
<li *ngFor="let item of node.children">
|
||||
<a class="link" [href]="item.url" [title]="item.tooltip || item.title">{{ item.title }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Powered by Google ©2010-2018.
|
||||
Code licensed under an <a href="license" title="License text" >MIT-style License</a>.
|
||||
Documentation licensed under
|
||||
<a href="http://creativecommons.org/licenses/by/4.0/">CC BY 4.0</a>.
|
||||
</p>
|
||||
<p>
|
||||
Version {{versionInfo?.full}}.
|
||||
</p>
|
||||
<!-- TODO: twitter widget (but only on pages that use twitter) -->
|
||||
<p>
|
||||
Super-powered by Google ©2010-2019.
|
||||
Code licensed under an <a href="license" title="License text">MIT-style License</a>.
|
||||
Documentation licensed under
|
||||
<a href="http://creativecommons.org/licenses/by/4.0/">CC BY 4.0</a>.
|
||||
</p>
|
||||
<p>
|
||||
Version {{versionInfo?.full}}.
|
||||
</p>
|
@ -1,9 +1,3 @@
|
||||
/*
|
||||
Copyright 2016 Google Inc. All Rights Reserved.
|
||||
Use of this source code is governed by an MIT-style license that
|
||||
can be found in the LICENSE file at http://angular.io/license
|
||||
*/
|
||||
|
||||
import { NgZone, Injectable } from '@angular/core';
|
||||
import { ConnectableObservable, Observable, race, ReplaySubject, timer } from 'rxjs';
|
||||
import { concatMap, first, publishReplay } from 'rxjs/operators';
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { ReflectiveInjector } from '@angular/core';
|
||||
import { PlatformLocation } from '@angular/common';
|
||||
import { Location, LocationStrategy, PlatformLocation, ViewportScroller } from '@angular/common';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { MockLocationStrategy, SpyLocation } from '@angular/common/testing';
|
||||
import { fakeAsync, tick } from '@angular/core/testing';
|
||||
|
||||
import { ScrollService, topMargin } from './scroll.service';
|
||||
|
||||
@ -8,8 +10,9 @@ describe('ScrollService', () => {
|
||||
const topOfPageElem = {} as Element;
|
||||
let injector: ReflectiveInjector;
|
||||
let document: MockDocument;
|
||||
let location: MockPlatformLocation;
|
||||
let platformLocation: MockPlatformLocation;
|
||||
let scrollService: ScrollService;
|
||||
let location: SpyLocation;
|
||||
|
||||
class MockPlatformLocation {
|
||||
hash: string;
|
||||
@ -27,6 +30,11 @@ describe('ScrollService', () => {
|
||||
scrollIntoView = jasmine.createSpy('Element scrollIntoView');
|
||||
}
|
||||
|
||||
const viewportScrollerStub = jasmine.createSpyObj(
|
||||
'viewportScroller',
|
||||
['getScrollPosition', 'scrollToPosition']);
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(window, 'scrollBy');
|
||||
});
|
||||
@ -34,12 +42,24 @@ describe('ScrollService', () => {
|
||||
beforeEach(() => {
|
||||
injector = ReflectiveInjector.resolveAndCreate([
|
||||
ScrollService,
|
||||
{ provide: Location, useClass: SpyLocation },
|
||||
{ provide: DOCUMENT, useClass: MockDocument },
|
||||
{ provide: PlatformLocation, useClass: MockPlatformLocation }
|
||||
{ provide: PlatformLocation, useClass: MockPlatformLocation },
|
||||
{ provide: ViewportScroller, useValue: viewportScrollerStub },
|
||||
{ provide: LocationStrategy, useClass: MockLocationStrategy }
|
||||
]);
|
||||
location = injector.get(PlatformLocation);
|
||||
platformLocation = injector.get(PlatformLocation);
|
||||
document = injector.get(DOCUMENT);
|
||||
scrollService = injector.get(ScrollService);
|
||||
location = injector.get(Location);
|
||||
});
|
||||
|
||||
it('should set `scrollRestoration` to `manual` if supported', () => {
|
||||
if (scrollService.supportManualScrollRestoration) {
|
||||
expect(window.history.scrollRestoration).toBe('manual');
|
||||
} else {
|
||||
expect(window.history.scrollRestoration).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
describe('#topOffset', () => {
|
||||
@ -107,7 +127,7 @@ describe('ScrollService', () => {
|
||||
|
||||
describe('#scroll', () => {
|
||||
it('should scroll to the top if there is no hash', () => {
|
||||
location.hash = '';
|
||||
platformLocation.hash = '';
|
||||
|
||||
const topOfPage = new MockElement();
|
||||
document.getElementById.and
|
||||
@ -118,7 +138,7 @@ describe('ScrollService', () => {
|
||||
});
|
||||
|
||||
it('should not scroll if the hash does not match an element id', () => {
|
||||
location.hash = 'not-found';
|
||||
platformLocation.hash = 'not-found';
|
||||
document.getElementById.and.returnValue(null);
|
||||
|
||||
scrollService.scroll();
|
||||
@ -128,7 +148,7 @@ describe('ScrollService', () => {
|
||||
|
||||
it('should scroll to the element whose id matches the hash', () => {
|
||||
const element = new MockElement();
|
||||
location.hash = 'some-id';
|
||||
platformLocation.hash = 'some-id';
|
||||
document.getElementById.and.returnValue(element);
|
||||
|
||||
scrollService.scroll();
|
||||
@ -139,7 +159,7 @@ describe('ScrollService', () => {
|
||||
|
||||
it('should scroll to the element whose id matches the hash with encoded characters', () => {
|
||||
const element = new MockElement();
|
||||
location.hash = '%F0%9F%91%8D'; // 👍
|
||||
platformLocation.hash = '%F0%9F%91%8D'; // 👍
|
||||
document.getElementById.and.returnValue(element);
|
||||
|
||||
scrollService.scroll();
|
||||
@ -210,4 +230,136 @@ describe('ScrollService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#isLocationWithHash', () => {
|
||||
it('should return true when the location has a hash', () => {
|
||||
platformLocation.hash = 'anchor';
|
||||
expect(scrollService.isLocationWithHash()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when the location has no hash', () => {
|
||||
platformLocation.hash = '';
|
||||
expect(scrollService.isLocationWithHash()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#needToFixScrollPosition', async() => {
|
||||
it('should return true when popState event was fired after a back navigation if the browser supports ' +
|
||||
'scrollRestoration`. Otherwise, needToFixScrollPosition() returns false', () => {
|
||||
|
||||
if (scrollService.supportManualScrollRestoration) {
|
||||
location.go('/initial-url1');
|
||||
// We simulate a scroll down
|
||||
location.replaceState('/initial-url1', 'hack', {scrollPosition: [2000, 0]});
|
||||
location.go('/initial-url2');
|
||||
location.back();
|
||||
|
||||
expect(scrollService.popStateFired).toBe(true);
|
||||
expect(scrollService.scrollPosition).toEqual([2000, 0]);
|
||||
expect(scrollService.needToFixScrollPosition()).toBe(true);
|
||||
} else {
|
||||
location.go('/initial-url1');
|
||||
location.go('/initial-url2');
|
||||
location.back();
|
||||
|
||||
expect(scrollService.popStateFired).toBe(false); // popStateFired is always false
|
||||
expect(scrollService.scrollPosition).toEqual([0, 0]); // scrollPosition always equals [0, 0]
|
||||
expect(scrollService.needToFixScrollPosition()).toBe(false);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
it('should return true when popState event was fired after a forward navigation if the browser supports ' +
|
||||
'scrollRestoration`. Otherwise, needToFixScrollPosition() returns false', () => {
|
||||
|
||||
if (scrollService.supportManualScrollRestoration) {
|
||||
location.go('/initial-url1');
|
||||
location.go('/initial-url2');
|
||||
// We simulate a scroll down
|
||||
location.replaceState('/initial-url1', 'hack', {scrollPosition: [2000, 0]});
|
||||
|
||||
location.back();
|
||||
scrollService.popStateFired = false;
|
||||
scrollService.scrollPosition = [0, 0];
|
||||
location.forward();
|
||||
|
||||
expect(scrollService.popStateFired).toBe(true);
|
||||
expect(scrollService.scrollPosition).toEqual([2000, 0]);
|
||||
expect(scrollService.needToFixScrollPosition()).toBe(true);
|
||||
} else {
|
||||
location.go('/initial-url1');
|
||||
location.go('/initial-url2');
|
||||
location.back();
|
||||
location.forward();
|
||||
|
||||
expect(scrollService.popStateFired).toBe(false); // popStateFired is always false
|
||||
expect(scrollService.scrollPosition).toEqual([0, 0]); // scrollPosition always equals [0, 0]
|
||||
expect(scrollService.needToFixScrollPosition()).toBe(false);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('#scrollAfterRender', async() => {
|
||||
|
||||
let scrollSpy: jasmine.Spy;
|
||||
let scrollToTopSpy: jasmine.Spy;
|
||||
let needToFixScrollPositionSpy: jasmine.Spy;
|
||||
let scrollToPosition: jasmine.Spy;
|
||||
let isLocationWithHashSpy: jasmine.Spy;
|
||||
let getStoredScrollPositionSpy: jasmine.Spy;
|
||||
const scrollDelay = 500;
|
||||
|
||||
beforeEach(() => {
|
||||
scrollSpy = spyOn(scrollService, 'scroll');
|
||||
scrollToTopSpy = spyOn(scrollService, 'scrollToTop');
|
||||
scrollToPosition = spyOn(scrollService, 'scrollToPosition');
|
||||
needToFixScrollPositionSpy = spyOn(scrollService, 'needToFixScrollPosition');
|
||||
getStoredScrollPositionSpy = spyOn(scrollService, 'getStoredScrollPosition');
|
||||
isLocationWithHashSpy = spyOn(scrollService, 'isLocationWithHash');
|
||||
});
|
||||
|
||||
|
||||
it('should call `scroll` when we navigate to a location with anchor', fakeAsync(() => {
|
||||
needToFixScrollPositionSpy.and.returnValue(false);
|
||||
getStoredScrollPositionSpy.and.returnValue(null);
|
||||
isLocationWithHashSpy.and.returnValue(true);
|
||||
|
||||
scrollService.scrollAfterRender(scrollDelay);
|
||||
|
||||
expect(scrollSpy).not.toHaveBeenCalled();
|
||||
tick(scrollDelay);
|
||||
expect(scrollSpy).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should call `scrollToTop` when we navigate to a location without anchor', fakeAsync(() => {
|
||||
needToFixScrollPositionSpy.and.returnValue(false);
|
||||
getStoredScrollPositionSpy.and.returnValue(null);
|
||||
isLocationWithHashSpy.and.returnValue(false);
|
||||
|
||||
scrollService.scrollAfterRender(scrollDelay);
|
||||
|
||||
expect(scrollToTopSpy).toHaveBeenCalled();
|
||||
tick(scrollDelay);
|
||||
expect(scrollSpy).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should call `viewportScroller.scrollToPosition` when we reload a page', fakeAsync(() => {
|
||||
getStoredScrollPositionSpy.and.returnValue([0, 1000]);
|
||||
|
||||
scrollService.scrollAfterRender(scrollDelay);
|
||||
|
||||
expect(viewportScrollerStub.scrollToPosition).toHaveBeenCalled();
|
||||
expect(getStoredScrollPositionSpy).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should call `scrollToPosition` after a popState', fakeAsync(() => {
|
||||
needToFixScrollPositionSpy.and.returnValue(true);
|
||||
getStoredScrollPositionSpy.and.returnValue(null);
|
||||
scrollService.scrollAfterRender(scrollDelay);
|
||||
expect(scrollToPosition).toHaveBeenCalled();
|
||||
tick(scrollDelay);
|
||||
expect(scrollSpy).not.toHaveBeenCalled();
|
||||
expect(scrollToTopSpy).not.toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Injectable, Inject } from '@angular/core';
|
||||
import { PlatformLocation } from '@angular/common';
|
||||
import { Location, PlatformLocation, ViewportScroller } from '@angular/common';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { fromEvent } from 'rxjs';
|
||||
|
||||
@ -13,6 +13,13 @@ export class ScrollService {
|
||||
private _topOffset: number | null;
|
||||
private _topOfPageElement: Element;
|
||||
|
||||
// true when popState event has been fired.
|
||||
popStateFired = false;
|
||||
// scroll position which has to be restored after the popState event
|
||||
scrollPosition: [number, number] = [0, 0];
|
||||
// true when the browser supports `scrollTo`, `scrollX`, `scrollY` and `scrollRestoration`
|
||||
supportManualScrollRestoration: boolean;
|
||||
|
||||
// Offset from the top of the document to bottom of any static elements
|
||||
// at the top (e.g. toolbar) + some margin
|
||||
get topOffset() {
|
||||
@ -32,9 +39,37 @@ export class ScrollService {
|
||||
|
||||
constructor(
|
||||
@Inject(DOCUMENT) private document: any,
|
||||
private location: PlatformLocation) {
|
||||
private platformLocation: PlatformLocation,
|
||||
private viewportScroller: ViewportScroller,
|
||||
private location: Location) {
|
||||
// On resize, the toolbar might change height, so "invalidate" the top offset.
|
||||
fromEvent(window, 'resize').subscribe(() => this._topOffset = null);
|
||||
|
||||
try {
|
||||
this.supportManualScrollRestoration = !!window && !!window.scrollTo && 'scrollX' in window
|
||||
&& 'scrollY' in window && !!history && !!history.scrollRestoration;
|
||||
} catch {
|
||||
this.supportManualScrollRestoration = false;
|
||||
}
|
||||
|
||||
// Change scroll restoration strategy to `manual` if it's supported
|
||||
if (this.supportManualScrollRestoration) {
|
||||
history.scrollRestoration = 'manual';
|
||||
// we have to detect forward and back navigation thanks to popState event
|
||||
this.location.subscribe(event => {
|
||||
// the type is `hashchange` when the fragment identifier of the URL has changed. It allows us to go to position
|
||||
// just before a click on an anchor
|
||||
if (event.type === 'hashchange') {
|
||||
this.scrollToPosition();
|
||||
} else {
|
||||
// The popstate event is always triggered by doing a browser action such as a click on the back or forward button.
|
||||
// It can be follow by a event of type `hashchange`.
|
||||
this.popStateFired = true;
|
||||
// we always should have a scrollPosition in our state history
|
||||
this.scrollPosition = event.state ? event.state['scrollPosition'] : null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,6 +85,44 @@ export class ScrollService {
|
||||
this.scrollToElement(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* test if the current location has a hash
|
||||
*/
|
||||
isLocationWithHash(): boolean {
|
||||
return !!this.getCurrentHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* When we load a document, we have to scroll to the correct position depending on whether this is a new location,
|
||||
* a back/forward in the history, or a refresh
|
||||
* @param delay before we scroll to the good position
|
||||
*/
|
||||
scrollAfterRender(delay: number) {
|
||||
// If we do rendering following a refresh, we use the scroll position from the storage.
|
||||
const storedScrollPosition = this.getStoredScrollPosition();
|
||||
if (storedScrollPosition) {
|
||||
this.viewportScroller.scrollToPosition(storedScrollPosition);
|
||||
} else {
|
||||
if (this.needToFixScrollPosition()) {
|
||||
// The document was reloaded following a popState `event` (called by the forward/back button), so we manage
|
||||
// the scroll position
|
||||
this.scrollToPosition();
|
||||
} else {
|
||||
// The document was loaded either of the following cases: a direct navigation via typing the URL in the
|
||||
// address bar or a click on a link. If the location contains a hash, we have to wait for async
|
||||
// layout.
|
||||
if (this.isLocationWithHash()) {
|
||||
// Scroll 500ms after the new document has been inserted into the doc-viewer.
|
||||
// The delay is to allow time for async layout to complete.
|
||||
setTimeout(() => this.scroll(), delay);
|
||||
} else {
|
||||
// If the location doesn't contain a hash, we scroll to the top of the page.
|
||||
this.scrollToTop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to the element.
|
||||
* Don't scroll if no element.
|
||||
@ -79,10 +152,42 @@ export class ScrollService {
|
||||
this.scrollToElement(this.topOfPageElement);
|
||||
}
|
||||
|
||||
scrollToPosition() {
|
||||
this.viewportScroller.scrollToPosition(this.scrollPosition);
|
||||
this.popStateFired = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state with scroll position into history.
|
||||
*/
|
||||
updateScrollPositionInHistory() {
|
||||
if (this.supportManualScrollRestoration) {
|
||||
const currentScrollPosition = this.viewportScroller.getScrollPosition();
|
||||
this.location.replaceState(this.location.path(true), undefined, {scrollPosition: currentScrollPosition});
|
||||
window.sessionStorage.setItem('scrollPosition', currentScrollPosition.toString());
|
||||
}
|
||||
}
|
||||
|
||||
getStoredScrollPosition(): [number, number] | null {
|
||||
const position = window.sessionStorage.getItem('scrollPosition');
|
||||
return position ? JSON.parse('[' + position + ']') : null;
|
||||
}
|
||||
|
||||
removeStoredScrollPosition() {
|
||||
window.sessionStorage.removeItem('scrollPosition');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the scroll position need to be manually fixed after popState event
|
||||
*/
|
||||
needToFixScrollPosition(): boolean {
|
||||
return this.popStateFired && this.scrollPosition && this.supportManualScrollRestoration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hash fragment from the `PlatformLocation`, minus the leading `#`.
|
||||
*/
|
||||
private getCurrentHash() {
|
||||
return decodeURIComponent(this.location.hash.replace(/^#/, ''));
|
||||
return decodeURIComponent(this.platformLocation.hash.replace(/^#/, ''));
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,3 @@
|
||||
/*
|
||||
Copyright 2016 Google Inc. All Rights Reserved.
|
||||
Use of this source code is governed by an MIT-style license that
|
||||
can be found in the LICENSE file at http://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgZone} from '@angular/core';
|
||||
import {Observable} from 'rxjs';
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@ -17,5 +17,5 @@ import 'core-js/es6/set';
|
||||
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
|
||||
import 'classlist.js';
|
||||
|
||||
/** IE10 and IE11 requires the following to support `@angular/animation`. */
|
||||
import 'web-animations-js';
|
||||
/** Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. */
|
||||
// import 'web-animations-js';
|
||||
|
@ -47,6 +47,7 @@ aio-shell.page-resources mat-toolbar.mat-toolbar {
|
||||
|
||||
// DOCS PAGES OVERRIDE: HAMBURGER
|
||||
aio-shell.folder-api mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-cli mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-docs mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-guide mat-toolbar.mat-toolbar,
|
||||
aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
|
||||
|