Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
769c132d22 | |||
ee65d0d4ab | |||
cd48a5383d | |||
0323affe18 | |||
b793aa3f85 | |||
4382ceae02 | |||
9a81e52eac | |||
903aef07f4 | |||
7f15c014d3 | |||
546299b6f2 | |||
a593b3f877 | |||
2bb4263a26 | |||
0c4430b384 | |||
c68826059c | |||
1cdffbdc2e | |||
00f44ee6d4 | |||
dcfde77de5 | |||
4994527e71 | |||
69eb46ad97 | |||
6e190b1a3f | |||
2b1820dfea | |||
9af4dcc442 | |||
16ba4ae2a7 | |||
cd95db482d | |||
e5cbc9b22b | |||
cd2a2f7989 | |||
8031e58410 | |||
508db0f467 | |||
57047acf17 |
@ -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
|
||||
|
10
CHANGELOG.md
10
CHANGELOG.md
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
|
@ -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",
|
||||
{
|
||||
|
@ -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.
|
||||
|
@ -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`.
|
||||
|
||||
|
@ -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">
|
||||
|
@ -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)">
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
BIN
aio/content/images/bios/ahsanayaz.jpg
Normal file
BIN
aio/content/images/bios/ahsanayaz.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 78 KiB |
BIN
aio/content/images/marketing/home/ng-conf.png
Normal file
BIN
aio/content/images/marketing/home/ng-conf.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.5 KiB |
@ -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"]
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
],
|
||||
|
@ -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",
|
||||
|
@ -3,7 +3,7 @@
|
||||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime": 3713,
|
||||
"main": 509261,
|
||||
"main": 484710,
|
||||
"polyfills": 53926
|
||||
}
|
||||
}
|
||||
|
@ -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!');
|
||||
}
|
49
aio/scripts/switch-to-ivy.js
Normal file
49
aio/scripts/switch-to-ivy.js
Normal 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\`.)`);
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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$),
|
||||
);
|
||||
};
|
||||
|
@ -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 [];
|
||||
}
|
104
aio/src/app/search/search-worker.ts
Normal file
104
aio/src/app/search/search-worker.ts
Normal 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;
|
@ -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);
|
||||
});
|
||||
|
@ -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),
|
||||
|
5
aio/src/app/shared/web-worker-message.ts
Normal file
5
aio/src/app/shared/web-worker-message.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface WebWorkerMessage {
|
||||
type: string;
|
||||
payload: any;
|
||||
id?: number;
|
||||
}
|
@ -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) {
|
||||
|
@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
11
aio/src/tsconfig.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "es2015",
|
||||
"lib": [
|
||||
"es2018",
|
||||
"dom",
|
||||
"webworker"
|
||||
],
|
||||
}
|
||||
}
|
14
aio/src/tsconfig.worker.json
Normal file
14
aio/src/tsconfig.worker.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/worker",
|
||||
"lib": [
|
||||
"es2018",
|
||||
"webworker"
|
||||
],
|
||||
"types": [
|
||||
"lunr"
|
||||
],
|
||||
},
|
||||
"include": ["**/*-worker.ts"]
|
||||
}
|
1869
aio/yarn.lock
1869
aio/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||
|
@ -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 }))),
|
||||
* ])
|
||||
* ])
|
||||
* ]
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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', () => {
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
Reference in New Issue
Block a user