Compare commits

...

29 Commits

Author SHA1 Message Date
769c132d22 release: cut the v7.2.14 release 2019-04-23 13:10:31 -07:00
ee65d0d4ab fix(common): prevent repeated application of HttpParams mutations (#29045)
Previously, an instance of HttpParams would retain its list of mutations
after they have been materialized as a result of a read operation. Not
only does this unnecessarily hold onto memory, more importantly does it
introduce a bug where branching of off a materialized instance would
reconsider the set of mutations that had already been applied, resulting
in repeated application of mutations.

This commit fixes the bug by clearing the list of pending mutations
after they have been materialized, such that they will not be considered
once again for branched off instances.

Fixes #20430

PR Close #29045
2019-04-23 08:43:54 -07:00
cd48a5383d build(docs-infra): pin versions of packages installed in preview server docker image (#29976)
This minimises the risk of unexpected failures due to breaking changes,
when building a new image (e.g. as a result of an unrelated config
change in Dockerfile).

PR Close #29976
2019-04-23 08:33:28 -07:00
0323affe18 build(docs-infra): upgrade preview server docker image to Debian 9 (#29976)
Previously, the preview server docker image was based on Debian 8
(jessie). Recently, `jessie-updates` and `jessie-backborts` were removed
from the Debian mirrors ([more info][1]), thus breaking new builds of
the image.

Instead of updating `/etc/apt/sources.list` to remove the obsolete
sources, this commit upgrades to Debian 9 (stretch).

(The GCE VM running the preview server docker container was also
upgraded from Debian 8 to 9 this morning.)

---
Other changes:
- Removed dependency on `chkconfig`, which is not supported on Debian 9.
- Installing `nginx` from the regular repositories (instead of
  `*-backports).
- Upgraded to `pm2` v3, which can handle hooking itself up to system
  startup better (without `chkconfig` - see above).
- Updated tests to reflect the fact that `nginx` has dropped the reason
  phrase in response status lines for HTTP/2 (in compliance with
  [the spec][2]). (HTTP/1.1: `HTTP/1.1 200 OK` | HTTP/2: `HTTP/2 200`)

[1]: https://www.lucas-nussbaum.net/blog/?p=947
[2]: https://http2.github.io/http2-spec/#rfc.section.8.1.2.4

PR Close #29976
2019-04-23 08:33:28 -07:00
b793aa3f85 build(docs-infra): increase build artifact size limit for preview server (#29976)
In #29926, the size of the build artifacts has increased due to turning
on differential loading (which generates an es2015/es5 pair for each JS
resource).

To avoid the preview server's rejecting the build artifacts (as in
[288181][1]), this commit increases the max allowed artifact size from
20MB to 25MB (current artifact size after #29926 is ~22MB).

[1]: https://circleci.com/gh/angular/angular/288181

PR Close #29976
2019-04-23 08:33:28 -07:00
4382ceae02 docs: fix grammatical errors in the guides and API documentation (#29928)
Fix grammatical errors in the DI and HttpClient guides as well as the Resolve API documentaiton.

There is no associated issue.

PR Close #29928
2019-04-22 17:32:31 -07:00
9a81e52eac docs: fixed typo on HttpParamsOptions (#29930)
PR Close #29930
2019-04-22 16:36:53 -07:00
903aef07f4 docs: fix typo in routing section of Tour of Heroes (#29961)
PR Close #29961
2019-04-22 11:18:45 -07:00
7f15c014d3 docs: change doc for address form group in reactive forms guide (#30007)
Closes #29925

PR Close #30007
2019-04-22 11:17:38 -07:00
546299b6f2 docs: add ng-japan 2019 event (#30021)
PR Close #30021
2019-04-22 08:45:45 -07:00
a593b3f877 ci(docs-infra): do not build with Ivy on 7.2.x (#29993)
The 7.2.x does not include the code necessary to build with Ivy. The
`test_aio_local_ivy` job needs to be skipped on 7.2.x.

The job was accidentally enabled while rebasing 1cdffbdc2.

PR Close #29993
2019-04-19 14:15:03 -07:00
2bb4263a26 ci(docs-infra): increase wait for SW on all origins to avoid CI flakes (#29988)
In #29953, the wait period for SW on localhost was increased to avoid CI
flakes for the PWA score tests.

This commit expands the fix to non-localhost origins to avoid flakes in
the `aio_monitoring` job, when CircleCI VMs/network are slow.
(For reference, example failures: [289127], [289238])

[289127]: https://circleci.com/gh/angular/angular/289127
[289238]: https://circleci.com/gh/angular/angular/289238

PR Close #29988
2019-04-19 09:59:37 -07:00
0c4430b384 build(docs-infra): make tsconfig path detection in switch-to-ivy more robust (#29989)
In light of #29926, that will change the path of `tsconfig.app.json`,
this commit switches from a hard-coded `tsconfig.app.json` path to
looking it up in `angular.json` (to be more future-proof).

PR Close #29989
2019-04-19 09:51:09 -07:00
c68826059c build(docs-infra): change build-with-ivy script to switch-to-ivy (#29989)
Previously, the `build-with-ivy` script could be used to build the `aio`
project with Ivy (once it had been prepared with `ivy-ngcc`, etc.) and
then restored the configuration (e.g. `tsconfig.json`) to non-ivy mode.

As a result, it was not useful for running other commands (e.g. unit/e2e
tests) in Ivy mode.

This commit renames the script to `switch-to-ivy` and employs a
different model (similar to `ng-packages-installer`), where the project
is setup to run in Ivy mode and then all subsequent commands are
executed in that mode (until restored).

Since this is currently only used on CI, there is no automatic way to
switch back to non-ivy mode (but it could be implemented in the future
if needed).

Finally, the script now modifies `src/tsconfig.app/json` instead of
`tsconfig.json` to ensure that the `angularCompilerOptions` are not
ignored/overwritten. This is also closer to what the cli generates
with the `--enable-ivy` option.

PR Close #29989
2019-04-19 09:50:43 -07:00
1cdffbdc2e ci(docs-infra): use pre-ivy packages in test_aio_local_ivy to test ngcc (#29989)
To better test ngcc (in addition to Ivy) on angular.io, change the
`test_aio_local_ivy` CircleCI job to use the pre-ivy Angular packages
(and have ngcc transform them to Ivy ones).

PR Close #29989
2019-04-19 09:49:10 -07:00
00f44ee6d4 refactor(docs-infra): remove unnecessary cast to any (#29989)
PR Close #29989
2019-04-19 09:46:48 -07:00
dcfde77de5 docs(animations): fixed some closing brackets on query animation page (#29854) (#29855)
PR Close #29855
2019-04-18 18:19:49 -07:00
4994527e71 docs: add Muhammad Ahsan Ayaz to GDE contributors group (#29838)
PR Close #29838
2019-04-18 18:18:17 -07:00
69eb46ad97 docs: update extra options available for RouterModule.forRoot() method (#29846)
PR Close #29846
2019-04-17 17:25:32 -07:00
6e190b1a3f ci(docs-infra): increase wait for SW on localhost to avoid CI flakes (#29953)
The server used for testing on localhost has less optimizations (e.g.
serves uncompressed files), so we need to wait longer the ServiceWorker
to be loaded and registered to allow Lighthouse to reliably detect it,
especially on slower environments (e.g. CI).

Related: https://github.com/GoogleChrome/lighthouse/issues/5527#issuecomment-483710849

Fixes #29910

PR Close #29953
2019-04-17 12:14:40 -07:00
2b1820dfea refactor(docs-infra): switch test-pwa-score.js to async/await (#29953)
PR Close #29953
2019-04-17 12:14:40 -07:00
9af4dcc442 docs: use correct values from code-example in i18n (#29920)
PR Close #29920
2019-04-16 10:31:17 -07:00
16ba4ae2a7 docs(service-worker): fix small typo in devops guide (#29866)
PR Close #29866
2019-04-16 10:30:53 -07:00
cd95db482d ci(docs-infra): temporarily lower the min required PWA score for localhost tests (#29911)
The PWA score tests have been occasionally failing on CI recently
(possibly due to CI VM/network issues).

This commit temporarily disables them, until we investigate the
root-cause and/or put a work-around in place.

The PWA score tests are still run against the deployed versions (which
don't suffer as much) or PRs with public previews (as part of the
`test_aio_preview` job) and on upstream builds (as part of the
`deploy_aio` job).

Related to #29910.

[1]: https://github.com/angular/angular/blob/3a836c362/.circleci/config.yml#L390

PR Close #29911
2019-04-15 12:34:43 -07:00
e5cbc9b22b build(docs-infra): convert search-worker.js to TypeScript (#29908)
PR Close #29908
2019-04-15 11:18:00 -07:00
cd2a2f7989 build(docs-infra): update to CLI 8 beta.13 (#29908)
PR Close #29908
2019-04-15 11:18:00 -07:00
8031e58410 fix(docs-infra): add missing ng-conf image (#29902)
As of b9fead7f8, the image is referenced in `announcements.json`, but it
was already removed (as it wasn't used at the time) in 7f905da33.

PR Close #29902
2019-04-15 09:12:59 -07:00
508db0f467 build(docs-infra): add missing dependencies (chrome-launcher, lighthouse-logger) (#29904)
We are using `chrome-launcher` and `lighthouse-logger` in
[test-pwa-score][1], but we do not explicitly list them as
`devDependencies`, so we are relying on the fact that they happen to be
hoisted in `node_modules/` by yarn. This may unexpectedly break in the
future.

This commit fixes this, by explicitly listing them as `devDependencies`.

[1]: https://github.com/angular/angular/blob/ea70d41ac/aio/scripts/test-pwa-score.js#L14-L18

PR Close #29904
2019-04-15 09:12:38 -07:00
57047acf17 build(docs-infra): upgrade lighthouse to 4.3.0 (#29904)
Also, log the Lighthouse version in `test-pwa-score` to aid in
debugging.

PR Close #29904
2019-04-15 09:12:38 -07:00
41 changed files with 1122 additions and 1421 deletions

View File

@ -266,12 +266,6 @@ jobs:
steps:
- *attach_workspace
- *init_environment
# Rename the Ivy packages dist folder to "dist/packages-dist" as the AIO
# package installer picks up the locally built packages from that location.
# *Note*: We could also adjust the packages installer, but given we won't have
# two different folders of Angular distributions in the future, it's likely not
# worth the efforts to change the AIO packages installer.
- run: mv dist/packages-dist-ivy-aot dist/packages-dist
# Build aio with Ivy (using local Angular packages)
- run: yarn --cwd aio build-with-ivy --progress=false
@ -552,7 +546,7 @@ workflows:
- build-npm-packages
# - test_aio_local_ivy:
# requires:
# - build-ivy-npm-packages
# - build-npm-packages
- test_aio_tools:
requires:
- build-npm-packages

View File

@ -1,3 +1,13 @@
<a name="7.2.14"></a>
## [7.2.14](https://github.com/angular/angular/compare/7.2.13...7.2.14) (2019-04-23)
### Bug Fixes
* **common:** prevent repeated application of HttpParams mutations ([#29045](https://github.com/angular/angular/issues/29045)) ([ee65d0d](https://github.com/angular/angular/commit/ee65d0d)), closes [#20430](https://github.com/angular/angular/issues/20430)
<a name="7.2.13"></a>
## [7.2.13](https://github.com/angular/angular/compare/7.2.12...7.2.13) (2019-04-12)

View File

@ -1,5 +1,5 @@
# Image metadata and config
FROM debian:jessie
FROM debian:stretch
LABEL name="angular.io PR preview" \
description="This image implements the PR preview functionality for angular.io." \
@ -40,7 +40,7 @@ ARG AIO_TRUSTED_PR_LABEL="aio: preview"
ARG TEST_AIO_TRUSTED_PR_LABEL="aio: preview"
ARG AIO_PREVIEW_SERVER_HOSTNAME=preview.localhost
ARG TEST_AIO_PREVIEW_SERVER_HOSTNAME=preview.localhost
ARG AIO_ARTIFACT_MAX_SIZE=20971520
ARG AIO_ARTIFACT_MAX_SIZE=26214400
ARG TEST_AIO_ARTIFACT_MAX_SIZE=200
ARG AIO_PREVIEW_SERVER_PORT=3000
ARG TEST_AIO_PREVIEW_SERVER_PORT=3001
@ -76,21 +76,20 @@ RUN apt-get update -y && apt-get install -y curl
RUN curl --silent --show-error --location https://deb.nodesource.com/setup_10.x | bash -
RUN curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN echo "deb http://ftp.debian.org/debian jessie-backports main" | tee /etc/apt/sources.list.d/backports.list
RUN echo "deb http://ftp.debian.org/debian stretch-backports main" | tee /etc/apt/sources.list.d/backports.list
# Install packages
RUN apt-get update -y && apt-get install -y \
chkconfig \
cron \
dnsmasq \
nano \
nodejs \
openssl \
rsyslog \
yarn
RUN apt-get install -t jessie-backports -y nginx
RUN yarn global add pm2@2
cron=3.0pl1-128+deb9u1 \
dnsmasq=2.76-5+deb9u2 \
nano=2.7.4-1 \
nginx=1.10.3-1+deb9u2 \
nodejs=10.15.3-1nodesource1 \
openssl=1.1.0j-1~deb9u1 \
rsyslog=8.24.0-1 \
yarn=1.15.2-1
RUN yarn global add pm2@3.5.0
# Set up log rotation
@ -151,8 +150,7 @@ RUN sed -i "s|{{\$AIO_PREVIEW_SERVER_PORT}}|$TEST_AIO_PREVIEW_SERVER_PORT|g" /et
# Set up pm2
RUN pm2 startup systemv -u root > /dev/null
RUN chkconfig pm2-root on
RUN pm2 startup --user root > /dev/null
# Set up the shell scripts

View File

@ -1,7 +1,6 @@
// Imports
import * as cp from 'child_process';
import * as fs from 'fs';
import * as http from 'http';
import * as path from 'path';
import * as shell from 'shelljs';
import {AIO_DOWNLOADS_DIR, HIDDEN_DIR_PREFIX} from '../common/constants';
@ -105,18 +104,7 @@ class Helper {
Object.keys(this.portPerScheme).forEach(scheme => suiteFactory(scheme, this.portPerScheme[scheme]));
}
public verifyResponse(status: number | [number, string], regex: string | RegExp = /^/): VerifyCmdResultFn {
let statusCode: number;
let statusText: string;
if (Array.isArray(status)) {
statusCode = status[0];
statusText = status[1];
} else {
statusCode = status;
statusText = http.STATUS_CODES[statusCode] || 'UNKNOWN_STATUS_CODE';
}
public verifyResponse(status: number, regex: string | RegExp = /^/): VerifyCmdResultFn {
return (result: CmdResult) => {
const [headers, body] = result.stdout.
split(/(?:\r?\n){2,}/).
@ -131,7 +119,7 @@ class Helper {
}
expect(result.success).toBe(true);
expect(headers).toContain(`${statusCode} ${statusText}`);
expect(headers).toMatch(new RegExp(`HTTP/(?:1\\.1|2) ${status} `));
expect(body).toMatch(regex);
};
}

View File

@ -259,10 +259,10 @@ describe(`nginx`, () => {
it('should disallow non-GET requests', async () => {
await Promise.all([
h.runCmd(`curl -iLX POST ${baseUrl}/42`).then(h.verifyResponse([405, 'Not Allowed'])),
h.runCmd(`curl -iLX PUT ${baseUrl}/42`).then(h.verifyResponse([405, 'Not Allowed'])),
h.runCmd(`curl -iLX PATCH ${baseUrl}/42`).then(h.verifyResponse([405, 'Not Allowed'])),
h.runCmd(`curl -iLX DELETE ${baseUrl}/42`).then(h.verifyResponse([405, 'Not Allowed'])),
h.runCmd(`curl -iLX POST ${baseUrl}/42`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX PUT ${baseUrl}/42`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX PATCH ${baseUrl}/42`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX DELETE ${baseUrl}/42`).then(h.verifyResponse(405)),
]);
});
@ -295,10 +295,10 @@ describe(`nginx`, () => {
const url = `${scheme}://${host}/circle-build`;
Promise.all([
h.runCmd(`curl -iLX GET ${url}`).then(h.verifyResponse([405, 'Not Allowed'])),
h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse([405, 'Not Allowed'])),
h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse([405, 'Not Allowed'])),
h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse([405, 'Not Allowed'])),
h.runCmd(`curl -iLX GET ${url}`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse(405)),
]).then(done);
});
@ -334,10 +334,10 @@ describe(`nginx`, () => {
it('should disallow non-POST requests', done => {
Promise.all([
h.runCmd(`curl -iLX GET ${url}`).then(h.verifyResponse([405, 'Not Allowed'])),
h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse([405, 'Not Allowed'])),
h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse([405, 'Not Allowed'])),
h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse([405, 'Not Allowed'])),
h.runCmd(`curl -iLX GET ${url}`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX PUT ${url}`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX PATCH ${url}`).then(h.verifyResponse(405)),
h.runCmd(`curl -iLX DELETE ${url}`).then(h.verifyResponse(405)),
]).then(done);
});

View File

@ -1,5 +1,5 @@
{
"$schema": "./node_modules/@angular-devkit/core/src/workspace/workspace-schema.json",
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"cli": {
"packageManager": "yarn",
@ -21,6 +21,7 @@
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "src/tsconfig.app.json",
"webWorkerTsConfig": "src/tsconfig.worker.json",
"aot": true,
"optimization": true,
"buildOptimizer": true,
@ -35,7 +36,6 @@
"assets": [
"src/assets",
"src/generated",
"src/app/search/search-worker.js",
"src/pwa-manifest.json",
"src/google385281288605d160.html",
{
@ -123,6 +123,7 @@
"karmaConfig": "src/karma.conf.js",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"webWorkerTsConfig": "src/tsconfig.worker.json",
"scripts": [],
"styles": [
"src/styles.scss"
@ -130,7 +131,6 @@
"assets": [
"src/assets",
"src/generated",
"src/app/search/search-worker.js",
"src/pwa-manifest.json",
"src/google385281288605d160.html",
{

View File

@ -209,7 +209,7 @@ Learn more in the [Testing](guide/testing) guide.
## Services that need other services
Service can have their own dependencies. `HeroService` is very simple and doesn't have any dependencies of its own. Suppose, however, that you want it to report its activities through a logging service. You can apply the same *constructor injection* pattern,
Services can have their own dependencies. `HeroService` is very simple and doesn't have any dependencies of its own. Suppose, however, that you want it to report its activities through a logging service. You can apply the same *constructor injection* pattern,
adding a constructor that takes a `Logger` parameter.
Here is the revised `HeroService` that injects `Logger`, side by side with the previous service for comparison.

View File

@ -750,7 +750,7 @@ with the injected `MessageService`.
header="app/http-interceptors/logging-interceptor.ts)">
</code-example>
The RxJS `tap` operator captures whether the request succeed or failed.
The RxJS `tap` operator captures whether the request succeeded or failed.
The RxJS `finalize` operator is called when the response observable either errors or completes (which it must),
and reports the outcome to the `MessageService`.

View File

@ -369,7 +369,7 @@ except that you choose among alternative translations based on a string value in
and you define those string values.
The following format message in the component template binds to the component's `gender` property,
which outputs one of the following string values: "m", "f" or "o".
which outputs one of the following string values: "male", "female" or "other".
The message maps those values to the appropriate translations:
<code-example path="i18n/src/app/app.component.html" region="i18n-select" header="src/app/app.component.html" linenums="false">

View File

@ -230,7 +230,7 @@ In this example, `address group` combines the current `firstName` and `lastName`
After you update the model in the component class, update the template to connect the form group instance and its input elements.
Add the `address` form group containing the `firstName` and `lastName` fields to the `ProfileEditor` template.
Add the `address` form group containing the `street`, `city`, `state`, and `zip` fields to the `ProfileEditor` template.
<code-example path="reactive-forms/src/app/profile-editor/profile-editor.component.1.html" region="formgroupname" linenums="false" header="src/app/profile-editor/profile-editor.component.html (template nested form group)">

View File

@ -1,6 +1,6 @@
# Service worker in production
This page is a reference for deploying and supporting production apps that use the Angular service worker. It explains how the Angular service worker fits into the larger production environment, the service worker's behavior under various conditions, and available recourses and fail-safes.
This page is a reference for deploying and supporting production apps that use the Angular service worker. It explains how the Angular service worker fits into the larger production environment, the service worker's behavior under various conditions, and available resources and fail-safes.
#### Prerequisites

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -778,5 +778,13 @@
"groups": ["Collaborators"],
"mentor": "gregmagolan",
"picture": "globegitter.jpg"
},
"ahsanayaz": {
"name": "Muhammad Ahsan Ayaz",
"picture": "ahsanayaz.jpg",
"twitter": "ahsan_ayz",
"website": "https://medium.com/@ahsan.ayaz",
"bio": "Muhammad Ahsan Ayaz is Software Architect at Modus Create. He is absolutely passionate about Angular and Web Technologies. Uses Angular daily and talks about it everywhere. Runs NodeSchool Karachi and is a co-organizer of NgPakistan",
"groups": ["GDE"]
}
}

View File

@ -25,6 +25,12 @@
<td>Copenhagen, Denmark</td>
<td>May 26 (workshops), 27-28 (conference), 2019</td>
</tr>
<!-- ngJapan-->
<tr>
<th><a href="https://ngjapan.org" title="ng-japan">ng-japan</a></th>
<td>Tokyo, Japan</td>
<td>July 13, 2019</td>
</tr>
<!-- AngularConnect 2019-->
<tr>
<th><a href="https://www.angularconnect.com/?utm_source=angular.io&utm_medium=referral" title="AngularConnect">AngularConnect</a></th>

View File

@ -101,7 +101,7 @@ configure it with the `routes` in one step by calling
## Add _RouterOutlet_
Open the `AppComponent` template replace the `<app-heroes>` element with a `<router-outlet>` element.
Open the `AppComponent` template and replace the `<app-heroes>` element with a `<router-outlet>` element.
<code-example path="toh-pt5/src/app/app.component.html"
region="outlet"

View File

@ -9,10 +9,8 @@
"files": [
"/index.html",
"/pwa-manifest.json",
"/app/search/search-worker.js",
"/assets/images/favicons/favicon.ico",
"/assets/js/*.js",
"/generated/lunr.min.js",
"/*.css",
"/*.js"
],

View File

@ -17,8 +17,8 @@
"build": "yarn ~~build",
"prebuild-local": "yarn setup-local",
"build-local": "yarn ~~build",
"prebuild-with-ivy": "yarn setup-local && yarn ivy-ngcc --formats fesm2015 fesm5",
"build-with-ivy": "node scripts/build-with-ivy",
"prebuild-with-ivy": "yarn setup-local && node scripts/switch-to-ivy",
"build-with-ivy": "yarn ~~build",
"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",
@ -26,7 +26,7 @@
"e2e": "ng e2e --no-webdriver-update",
"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 ~~build-ie-polyfills && yarn ~~minify-lunr && yarn boilerplate:add && yarn extract-cli-command-docs && yarn docs",
"postsetup": "yarn ~~build-ie-polyfills && yarn boilerplate:add && yarn extract-cli-command-docs && yarn docs",
"presetup-local": "yarn presetup",
"setup-local": "yarn aio-use-local && yarn example-use-local",
"postsetup-local": "yarn postsetup",
@ -69,8 +69,7 @@
"~~build": "ng build --configuration=stable",
"post~~build": "yarn build-404-page",
"~~build-ie-polyfills": "webpack-cli src/ie-polyfills.js -o src/generated/ie-polyfills.min.js --mode production",
"~~http-server": "http-server",
"~~minify-lunr": "uglifyjs node_modules/lunr/lunr.js -c -m -o src/generated/lunr.min.js --source-map"
"~~http-server": "http-server"
},
"engines": {
"node": ">=10.9.0 <11.0.0",
@ -89,6 +88,7 @@
"@angular/platform-browser-dynamic": "^7.0.0",
"@angular/router": "^7.0.0",
"@angular/service-worker": "^7.0.0",
"@types/lunr": "^2.3.2",
"@webcomponents/custom-elements": "^1.2.0",
"classlist.js": "^1.1.20150312",
"core-js": "^2.4.1",
@ -97,8 +97,8 @@
"zone.js": "^0.8.26"
},
"devDependencies": {
"@angular-devkit/build-angular": "^0.12.1",
"@angular/cli": "7.2.1",
"@angular-devkit/build-angular": "0.800.0-beta.13",
"@angular/cli": "8.0.0-beta.13",
"@angular/compiler": "^7.0.0",
"@angular/compiler-cli": "^7.0.0",
"@angular/language-service": "^7.0.0",
@ -109,6 +109,7 @@
"archiver": "^1.3.0",
"canonical-path": "1.0.0",
"chalk": "^2.1.0",
"chrome-launcher": "^0.10.5",
"cjson": "^0.5.0",
"codelyzer": "~4.5.0",
"cross-spawn": "^5.1.0",
@ -142,7 +143,8 @@
"karma-coverage-istanbul-reporter": "^1.3.0",
"karma-jasmine": "^1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"lighthouse": "^3.2.1",
"lighthouse": "^4.3.0",
"lighthouse-logger": "^1.2.0",
"lodash": "^4.17.4",
"lunr": "^2.1.0",
"npm-run-all": "^4.1.5",

View File

@ -3,7 +3,7 @@
"master": {
"uncompressed": {
"runtime": 3713,
"main": 509261,
"main": 484710,
"polyfills": 53926
}
}

View File

@ -1,46 +0,0 @@
#!/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!');
}

View File

@ -0,0 +1,49 @@
#!/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 NG_JSON = join(ROOT_DIR, 'angular.json');
const NG_COMPILER_OPTS = {
angularCompilerOptions: {
// Related Jira issue: FW-737
allowEmptyCodegenFiles: true,
enableIvy: true,
},
};
// Run
_main(process.argv.slice(2));
// Functions - Definitions
function _main() {
// Detect path to `tsconfig.app.json`.
const ngConfig = parse(readFileSync(NG_JSON, 'utf8'));
const tsConfigPath = join(ROOT_DIR, ngConfig.projects.site.architect.build.options.tsConfig);
// Enable Ivy in TS config.
console.log(`\nModifying \`${tsConfigPath}\`...`);
const oldTsConfigStr = readFileSync(tsConfigPath, 'utf8');
const oldTsConfigObj = parse(oldTsConfigStr);
const newTsConfigObj = extend(true, oldTsConfigObj, NG_COMPILER_OPTS);
const newTsConfigStr = JSON.stringify(newTsConfigObj, null, 2);
console.log(`\nNew config: ${newTsConfigStr}`);
writeFileSync(tsConfigPath, newTsConfigStr);
// Run ngcc.
const ngccArgs = '--loglevel debug --properties es2015 module';
console.log(`\nRunning ngcc (with args: ${ngccArgs})...`);
exec(`yarn ivy-ngcc ${ngccArgs}`);
// Done.
console.log('\nReady to build with Ivy!');
console.log('(To switch back to ViewEngine (with packages from npm), undo the changes in ' +
`\`${tsConfigPath}\` and run \`yarn aio-use-npm && yarn example-use-npm\`.)`);
}

View File

@ -2,7 +2,9 @@
/**
* Usage:
* node scripts/test-pwa-score <url> <min-score> [<log-file>]
* ```sh
* node scripts/test-pwa-score <url> <min-score> [<log-file>]
* ```
*
* Fails if the score is below `<min-score>`.
* If `<log-file>` is defined, the full results will be logged there.
@ -14,7 +16,6 @@
const chromeLauncher = require('chrome-launcher');
const lighthouse = require('lighthouse');
const printer = require('lighthouse/lighthouse-cli/printer');
const config = require('lighthouse/lighthouse-core/config/default-config.js');
const logger = require('lighthouse-logger');
// Constants
@ -22,6 +23,7 @@ const CHROME_LAUNCH_OPTS = {};
const LIGHTHOUSE_FLAGS = {logLevel: 'info'};
const SKIPPED_HTTPS_AUDITS = ['redirects-http'];
const VIEWER_URL = 'https://googlechrome.github.io/lighthouse/viewer/';
const WAIT_FOR_SW_DELAY = 5000;
// Be less verbose on CI.
if (process.env.CI) {
@ -32,22 +34,31 @@ if (process.env.CI) {
_main(process.argv.slice(2));
// Functions - Definitions
function _main(args) {
async function _main(args) {
const {url, minScore, logFile} = parseInput(args);
const isOnHttp = /^http:/.test(url);
const config = {
extends: 'lighthouse:default',
// Since the Angular ServiceWorker waits for the app to stabilize before registering,
// wait a few seconds after load to allow Lighthouse to reliably detect it.
passes: [{passName: 'defaultPass', pauseAfterLoadMs: WAIT_FOR_SW_DELAY}],
}
console.log(`Running PWA audit for '${url}'...`);
if (isOnHttp) {
skipHttpsAudits(config);
}
// If testing on HTTP, skip HTTPS-specific tests.
// (Note: Browsers special-case localhost and run ServiceWorker even on HTTP.)
if (isOnHttp) skipHttpsAudits(config);
logger.setLevel(LIGHTHOUSE_FLAGS.logLevel);
launchChromeAndRunLighthouse(url, LIGHTHOUSE_FLAGS, config).
then(results => processResults(results, logFile)).
then(score => evaluateScore(minScore, score)).
catch(onError);
try {
const results = await launchChromeAndRunLighthouse(url, LIGHTHOUSE_FLAGS, config);
const score = await processResults(results, logFile);
evaluateScore(minScore, score);
} catch (err) {
onError(err);
}
}
function evaluateScore(expectedScore, actualScore) {
@ -60,13 +71,15 @@ function evaluateScore(expectedScore, actualScore) {
}
}
function launchChromeAndRunLighthouse(url, flags, config) {
return chromeLauncher.launch(CHROME_LAUNCH_OPTS).then(chrome => {
flags.port = chrome.port;
return lighthouse(url, flags, config).
then(results => chrome.kill().then(() => results)).
catch(err => chrome.kill().then(() => { throw err; }, () => { throw err; }));
});
async function launchChromeAndRunLighthouse(url, flags, config) {
const chrome = await chromeLauncher.launch(CHROME_LAUNCH_OPTS);
flags.port = chrome.port;
try {
return await lighthouse(url, flags, config);
} finally {
await chrome.kill();
}
}
function onError(err) {
@ -88,34 +101,35 @@ function parseInput(args) {
return {url, minScore, logFile};
}
function processResults(results, logFile) {
async function processResults(results, logFile) {
const lhVersion = results.lhr.lighthouseVersion;
const categories = results.lhr.categories;
const report = results.report;
return Promise.resolve().
then(() => {
if (logFile) {
console.log(`Saving results in '${logFile}'...`);
console.log(`(LightHouse viewer: ${VIEWER_URL})`);
if (logFile) {
console.log(`\nSaving results in '${logFile}'...`);
console.log(`(LightHouse viewer: ${VIEWER_URL})`);
return printer.write(report, printer.OutputMode.json, logFile);
}
}).
then(() => {
const categoryData = Object.keys(categories).map(name => categories[name]);
const maxTitleLen = Math.max(...categoryData.map(({title}) => title.length));
await printer.write(report, printer.OutputMode.json, logFile);
}
console.log('\nAudit scores:');
categoryData.forEach(({title, score}) => {
const paddedTitle = `${title}:`.padEnd(maxTitleLen + 1);
const paddedScore = (score * 100).toFixed(0).padStart(3);
console.log(` - ${paddedTitle} ${paddedScore} / 100`);
});
}).
then(() => categories.pwa.score * 100);
const categoryData = Object.keys(categories).map(name => categories[name]);
const maxTitleLen = Math.max(...categoryData.map(({title}) => title.length));
console.log(`\nLighthouse version: ${lhVersion}`);
console.log('\nAudit scores:');
categoryData.forEach(({title, score}) => {
const paddedTitle = `${title}:`.padEnd(maxTitleLen + 1);
const paddedScore = (score * 100).toFixed(0).padStart(3);
console.log(` - ${paddedTitle} ${paddedScore} / 100`);
});
return categories.pwa.score * 100;
}
function skipHttpsAudits(config) {
console.info(`Skipping HTTPS-related audits (${SKIPPED_HTTPS_AUDITS.join(', ')})...`);
config.settings.skipAudits = SKIPPED_HTTPS_AUDITS;
const settings = config.settings || (config.settings = {});
settings.skipAudits = SKIPPED_HTTPS_AUDITS;
}

View File

@ -110,7 +110,7 @@ export class AppComponent implements OnInit {
// Do not initialize the search on browsers that lack web worker support
if ('Worker' in window) {
// Delay initialization by up to 2 seconds
this.searchService.initWorker('app/search/search-worker.js', 2000);
this.searchService.initWorker(2000);
}
this.onResize(window.innerWidth);

View File

@ -206,7 +206,7 @@ export class DocViewerComponent implements OnDestroy {
// setting each style.
switchMap(() => raf$), tap(() => elem.style[prop] = from),
switchMap(() => raf$), tap(() => elem.style.transition = `all ${duration}ms ease-in-out`),
switchMap(() => raf$), tap(() => (elem.style as any)[prop] = to),
switchMap(() => raf$), tap(() => elem.style[prop] = to),
switchMap(() => timer(getActualDuration(elem))), switchMap(() => this.void$),
);
};

View File

@ -1,106 +0,0 @@
'use strict';
/* eslint-env worker */
/* global importScripts, lunr */
var SEARCH_TERMS_URL = '/generated/docs/app/search-data.json';
// NOTE: This needs to be kept in sync with `ngsw-config.json`.
importScripts('/generated/lunr.min.js');
var index;
var pages /* : SearchInfo */ = {};
// interface SearchInfo {
// [key: string]: PageInfo;
// }
// interface PageInfo {
// path: string;
// type: string,
// titleWords: string;
// keyWords: string;
// }
self.onmessage = handleMessage;
// Create the lunr index - the docs should be an array of objects, each object containing
// the path and search terms for a page
function createIndex(addFn) {
lunr.QueryLexer.termSeparator = lunr.tokenizer.separator = /\s+/;
return lunr(/** @this */function() {
this.ref('path');
this.field('titleWords', {boost: 10});
this.field('headingWords', {boost: 5});
this.field('members', {boost: 4});
this.field('keywords', {boost: 2});
addFn(this);
});
}
// The worker receives a message to load the index and to query the index
function handleMessage(message) {
var type = message.data.type;
var id = message.data.id;
var payload = message.data.payload;
switch(type) {
case 'load-index':
makeRequest(SEARCH_TERMS_URL, function(searchInfo) {
index = createIndex(loadIndex(searchInfo));
self.postMessage({type: type, id: id, payload: true});
});
break;
case 'query-index':
self.postMessage({type: type, id: id, payload: {query: payload, results: queryIndex(payload)}});
break;
default:
self.postMessage({type: type, id: id, payload: {error: 'invalid message type'}})
}
}
// Use XHR to make a request to the server
function makeRequest(url, callback) {
// The JSON file that is loaded should be an array of PageInfo:
var searchDataRequest = new XMLHttpRequest();
searchDataRequest.onload = function() {
callback(JSON.parse(this.responseText));
};
searchDataRequest.open('GET', url);
searchDataRequest.send();
}
// Create the search index from the searchInfo which contains the information about each page to be indexed
function loadIndex(searchInfo /*: SearchInfo */) {
return function(index) {
// Store the pages data to be used in mapping query results back to pages
// Add search terms from each page to the search index
searchInfo.forEach(function(page /*: PageInfo */) {
index.add(page);
pages[page.path] = page;
});
};
}
// Query the index and return the processed results
function queryIndex(query) {
try {
if (query.length) {
var results = index.search(query);
if (results.length === 0) {
// Add a relaxed search in the title for the first word in the query
// E.g. if the search is "ngCont guide" then we search for "ngCont guide titleWords:ngCont*"
var titleQuery = 'titleWords:*' + query.split(' ', 1)[0] + '*';
results = index.search(query + ' ' + titleQuery);
}
// Map the hits into info about each page to be returned as results
return results.map(function(hit) { return pages[hit.ref]; });
}
} catch(e) {
// If the search query cannot be parsed the index throws an error
// Log it and recover
console.log(e);
}
return [];
}

View File

@ -0,0 +1,104 @@
import { WebWorkerMessage } from '../shared/web-worker-message';
import * as lunr from 'lunr';
const SEARCH_TERMS_URL = '/generated/docs/app/search-data.json';
let index: lunr.Index;
const pages: SearchInfo = {};
interface SearchInfo {
[key: string]: PageInfo;
}
interface PageInfo {
path: string;
type: string;
titleWords: string;
keyWords: string;
}
addEventListener('message', handleMessage);
// Create the lunr index - the docs should be an array of objects, each object containing
// the path and search terms for a page
function createIndex(loadIndex: IndexLoader): lunr.Index {
// The lunr typings are missing QueryLexer so we have to add them here manually.
const queryLexer = (lunr as any as { QueryLexer: { termSeparator: RegExp } }).QueryLexer;
queryLexer.termSeparator = lunr.tokenizer.separator = /\s+/;
return lunr(/** @this */function () {
this.ref('path');
this.field('titleWords', { boost: 10 });
this.field('headingWords', { boost: 5 });
this.field('members', { boost: 4 });
this.field('keywords', { boost: 2 });
loadIndex(this);
});
}
// The worker receives a message to load the index and to query the index
function handleMessage(message: { data: WebWorkerMessage }): void {
const type = message.data.type;
const id = message.data.id;
const payload = message.data.payload;
switch (type) {
case 'load-index':
makeRequest(SEARCH_TERMS_URL, function (searchInfo: PageInfo[]) {
index = createIndex(loadIndex(searchInfo));
postMessage({ type: type, id: id, payload: true });
});
break;
case 'query-index':
postMessage({ type: type, id: id, payload: { query: payload, results: queryIndex(payload) } });
break;
default:
postMessage({ type: type, id: id, payload: { error: 'invalid message type' } })
}
}
// Use XHR to make a request to the server
function makeRequest(url: string, callback: (response: any) => void): void {
// The JSON file that is loaded should be an array of PageInfo:
const searchDataRequest = new XMLHttpRequest();
searchDataRequest.onload = function () {
callback(JSON.parse(this.responseText));
};
searchDataRequest.open('GET', url);
searchDataRequest.send();
}
// Create the search index from the searchInfo which contains the information about each page to be indexed
function loadIndex(pagesData: PageInfo[]): IndexLoader {
return (indexBuilder: lunr.Builder) => {
// Store the pages data to be used in mapping query results back to pages
// Add search terms from each page to the search index
pagesData.forEach(page => {
indexBuilder.add(page);
pages[page.path] = page;
});
};
}
// Query the index and return the processed results
function queryIndex(query: string): PageInfo[] {
try {
if (query.length) {
let results = index.search(query);
if (results.length === 0) {
// Add a relaxed search in the title for the first word in the query
// E.g. if the search is "ngCont guide" then we search for "ngCont guide titleWords:ngCont*"
const titleQuery = 'titleWords:*' + query.split(' ', 1)[0] + '*';
results = index.search(query + ' ' + titleQuery);
}
// Map the hits into info about each page to be returned as results
return results.map(function (hit) { return pages[hit.ref]; });
}
} catch (e) {
// If the search query cannot be parsed the index throws an error
// Log it and recover
console.log(e);
}
return [];
}
type IndexLoader = (indexBuilder: lunr.Builder) => void;

View File

@ -25,11 +25,11 @@ describe('SearchService', () => {
describe('initWorker', () => {
it('should create the worker and load the index after the specified delay', fakeAsync(() => {
service.initWorker('some/url', 100);
service.initWorker(100);
expect(WebWorkerClient.create).not.toHaveBeenCalled();
expect(mockWorker.sendMessage).not.toHaveBeenCalled();
tick(100);
expect(WebWorkerClient.create).toHaveBeenCalledWith('some/url', jasmine.any(NgZone));
expect(WebWorkerClient.create).toHaveBeenCalledWith(jasmine.any(Worker), jasmine.any(NgZone));
expect(mockWorker.sendMessage).toHaveBeenCalledWith('load-index');
}));
});
@ -37,7 +37,7 @@ describe('SearchService', () => {
describe('search', () => {
beforeEach(() => {
// We must initialize the service before calling connectSearches
service.initWorker('some/url', 1000);
service.initWorker(1000);
// Simulate the index being ready so that searches get sent to the worker
(service as any).ready = of(true);
});

View File

@ -16,10 +16,9 @@ export class SearchService {
* initial rendering of the web page. Triggering a search will override this delay and cause the index to be
* loaded immediately.
*
* @param workerUrl the url of the WebWorker script that runs the searches
* @param initDelay the number of milliseconds to wait before we load the WebWorker and generate the search index
*/
initWorker(workerUrl: string, initDelay: number) {
initWorker(initDelay: number) {
// Wait for the initDelay or the first search
const ready = this.ready = race<any>(
timer(initDelay),
@ -28,7 +27,8 @@ export class SearchService {
.pipe(
concatMap(() => {
// Create the worker and load the index
this.worker = WebWorkerClient.create(workerUrl, this.zone);
const worker = new Worker('./search-worker', { type: 'module' });
this.worker = WebWorkerClient.create(worker, this.zone);
return this.worker.sendMessage<boolean>('load-index');
}),
publishReplay(1),

View File

@ -0,0 +1,5 @@
export interface WebWorkerMessage {
type: string;
payload: any;
id?: number;
}

View File

@ -1,17 +1,12 @@
import {NgZone} from '@angular/core';
import {Observable} from 'rxjs';
export interface WebWorkerMessage {
type: string;
payload: any;
id?: number;
}
import {WebWorkerMessage} from './web-worker-message';
export class WebWorkerClient {
private nextId = 0;
static create(workerUrl: string, zone: NgZone) {
return new WebWorkerClient(new Worker(workerUrl), zone);
static create(worker: Worker, zone: NgZone) {
return new WebWorkerClient(worker, zone);
}
private constructor(private worker: Worker, private zone: NgZone) {

View File

@ -55,21 +55,22 @@ table {
td {
letter-spacing: 0.30px;
tr td:first-child {
@media (max-width: 480px) {
background-color: $lightgray;
tr td:first-child {
@media (max-width: 480px) {
background-color: $lightgray;
}
}
}
}
th {
background: rgba($lightgray, 0.2);
border-right: 1px solid $lightgray;
font-weight: 600;
max-width: 100px;
}
}
tbody > tr {
&:last-child td {
border: none;
@ -111,4 +112,4 @@ table {
}
}
}
}
}

View File

@ -1,19 +1,23 @@
{
"extends": "../tsconfig.json",
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"module": "es2015",
"baseUrl": "",
"types": [],
"lib": [
"es2018",
"dom"
],
"importHelpers": true
},
"exclude": [
"testing/**/*",
"test.ts",
"test-specs.ts",
"**/*.spec.ts"
"**/*.spec.ts",
"**/*-worker.ts"
],
"angularCompilerOptions": {
"disableTypeScriptVersionCheck": true
}
}
}

11
aio/src/tsconfig.json Normal file
View File

@ -0,0 +1,11 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"module": "es2015",
"lib": [
"es2018",
"dom",
"webworker"
],
}
}

View File

@ -0,0 +1,14 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/worker",
"lib": [
"es2018",
"webworker"
],
"types": [
"lunr"
],
},
"include": ["**/*-worker.ts"]
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "7.2.13",
"version": "7.2.14",
"private": true,
"branchPattern": "2.0.*",
"description": "Angular - a web framework for modern web apps",

View File

@ -1200,8 +1200,8 @@ export function useAnimation(
* query('.content', style({ opacity: 0 })),
*
* // animate the inner elements in, one by one
* query('h1', animate(1000, style({ opacity: 1 })),
* query('.content', animate(1000, style({ opacity: 1 })),
* query('h1', animate(1000, style({ opacity: 1 }))),
* query('.content', animate(1000, style({ opacity: 1 }))),
* ])
* ])
* ]

View File

@ -83,7 +83,7 @@ export interface HttpParamsOptions {
*/
fromString?: string;
/** Object map of the HTTP params. Mutally exclusive with `fromString`. */
/** Object map of the HTTP params. Mutually exclusive with `fromString`. */
fromObject?: {[param: string]: string | string[]};
/** Encoding codec used to parse and serialize the params. */
@ -227,7 +227,7 @@ export class HttpParams {
}
}
});
this.cloneFrom = null;
this.cloneFrom = this.updates = null;
}
}
}

View File

@ -53,6 +53,15 @@ import {HttpParams} from '@angular/common/http/src/params';
const mutated = body.delete('a', '2').delete('a', '4');
expect(mutated.getAll('a')).toEqual(['1', '3', '5']);
});
it('should not repeat mutations that have already been materialized', () => {
const body = new HttpParams({fromString: 'a=b'});
const mutated = body.append('a', 'c');
expect(mutated.toString()).toEqual('a=b&a=c');
const mutated2 = mutated.append('c', 'd');
expect(mutated.toString()).toEqual('a=b&a=c');
expect(mutated2.toString()).toEqual('a=b&a=c&c=d');
});
});
describe('read operations', () => {

View File

@ -264,7 +264,7 @@ export type CanDeactivateFn<T> =
/**
* @description
*
* Interface that class can implement to be a data provider.
* Interface that classes can implement to be a data provider.
*
* ```
* class Backend {

View File

@ -143,18 +143,32 @@ export class RouterModule {
* Creates a module with all the router providers and directives. It also optionally sets up an
* application listener to perform an initial navigation.
*
* Options (see `ExtraOptions`):
* * `enableTracing` makes the router log all its internal events to the console.
* * `useHash` enables the location strategy that uses the URL fragment instead of the history
* Configuration Options:
*
* * `enableTracing` Toggles whether the router should log all navigation events to the console.
* * `useHash` Enables the location strategy that uses the URL fragment instead of the history
* API.
* * `initialNavigation` disables the initial navigation.
* * `errorHandler` provides a custom error handler.
* * `preloadingStrategy` configures a preloading strategy (see `PreloadAllModules`).
* * `onSameUrlNavigation` configures how the router handles navigation to the current URL. See
* `ExtraOptions` for more details.
* * `paramsInheritanceStrategy` defines how the router merges params, data and resolved data
* from parent to child routes.
*/
* * `initialNavigation` Disables the initial navigation.
* * `errorHandler` Defines a custom error handler for failed navigations.
* * `preloadingStrategy` Configures a preloading strategy. See `PreloadAllModules`.
* * `onSameUrlNavigation` Define what the router should do if it receives a navigation request to
* the current URL.
* * `scrollPositionRestoration` Configures if the scroll position needs to be restored when
* navigating back.
* * `anchorScrolling` Configures if the router should scroll to the element when the url has a
* fragment.
* * `scrollOffset` Configures the scroll offset the router will use when scrolling to an element.
* * `paramsInheritanceStrategy` Defines how the router merges params, data and resolved data from
* parent to child routes.
* * `malformedUriErrorHandler` Defines a custom malformed uri error handler function. This
* handler is invoked when encodedURI contains invalid character sequences.
* * `urlUpdateStrategy` Defines when the router updates the browser URL. The default behavior is
* to update after successful navigation.
* * `relativeLinkResolution` Enables the correct relative link resolution in components with
* empty paths.
*
* See `ExtraOptions` for more details about the above options.
*/
static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders<RouterModule> {
return {
ngModule: RouterModule,