Compare commits
119 Commits
Author | SHA1 | Date | |
---|---|---|---|
018750154d | |||
b19216d58b | |||
84fc1a3663 | |||
1c40be26c6 | |||
2c5cf19c6d | |||
0dacf6d5f1 | |||
e9f1d44015 | |||
1d9024ee9a | |||
6a6164ab4f | |||
7231f5e26a | |||
86415223cb | |||
269f5acc54 | |||
6e6c866de9 | |||
732ed92cb7 | |||
53a807ae09 | |||
3342a8253b | |||
630c19f52d | |||
af8c2fa4be | |||
0789601dd6 | |||
ce0ac46e42 | |||
b531d87580 | |||
23a2154817 | |||
76d2496f24 | |||
b85cb410f1 | |||
1be22df0df | |||
a805839d38 | |||
3ac61a7550 | |||
57ea33bc5c | |||
4891649d68 | |||
93aba1bb1c | |||
f983a6c615 | |||
18f1b016e5 | |||
591dcc26af | |||
4acd322128 | |||
32a814bdfa | |||
912068e71c | |||
df8e57dc5d | |||
f27f6e498f | |||
01bfbcb84a | |||
a15abbb324 | |||
28c29d560e | |||
65ca7fd4aa | |||
3e3f918bb3 | |||
fc1dcffbdd | |||
ee3c681f98 | |||
6225fedcb8 | |||
2905069559 | |||
47202dd747 | |||
fb130c4eae | |||
734378c90b | |||
3959b7ef28 | |||
e292548523 | |||
c2506a78a1 | |||
34f70c6de2 | |||
3d9c2a6352 | |||
3232125650 | |||
c9f8718d2a | |||
f89ac92e5e | |||
f82efcf942 | |||
dc22f4dc69 | |||
173ccf03ab | |||
1d5c3c1c9b | |||
e4dac421db | |||
c639cdd3b3 | |||
5bcfa7cdfe | |||
d8fd892e71 | |||
074a997302 | |||
62616f541a | |||
156442f80d | |||
37112f549a | |||
cf4b4d53ba | |||
d45e3aa433 | |||
ccc25ee901 | |||
3052063c05 | |||
e107322f5c | |||
f0774254de | |||
6c66031c4a | |||
8f668807cf | |||
f98eb35179 | |||
cc8ae32503 | |||
95f3b1dbe6 | |||
6b96b069bf | |||
ecfe85b06c | |||
5439d4cd49 | |||
df91fd032d | |||
2d5ef15e08 | |||
544a7ad0a5 | |||
ec2c1bec4a | |||
e064d2607d | |||
d489ad595d | |||
0a3753bcce | |||
7fc2dceaf5 | |||
c3b39bac52 | |||
2cd88bfb0f | |||
0cefb0b79b | |||
55a7443974 | |||
f9ebaf1b90 | |||
bc81fbdd27 | |||
c7aa8a132d | |||
5c99b01512 | |||
992ba33a28 | |||
17e7c58981 | |||
b8f15d2b77 | |||
5f9a10aab9 | |||
04bc5257a6 | |||
5bca58e748 | |||
526a67f8f4 | |||
53a27e07b1 | |||
ba0fb1e055 | |||
ed2c8aa6f8 | |||
9226760120 | |||
b9ee8b46a0 | |||
e7eb0b8b7c | |||
ae52851458 | |||
e63cf3b89e | |||
9624fda082 | |||
e19b6a8f38 | |||
2aa6b54201 | |||
fe8550d278 |
@ -11,7 +11,7 @@
|
||||
anchor_1: &job_defaults
|
||||
working_directory: ~/ng
|
||||
docker:
|
||||
- image: angular/ngcontainer
|
||||
- image: angular/ngcontainer:0.0.2
|
||||
|
||||
# After checkout, rebase on top of master.
|
||||
# Similar to travis behavior, but not quite the same.
|
||||
@ -27,10 +27,9 @@ jobs:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- restore_cache:
|
||||
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
|
||||
- run: npm install
|
||||
- run: npm run postinstall
|
||||
- run: yarn install --freeze-lockfile --non-interactive
|
||||
- run: ./node_modules/.bin/gulp lint
|
||||
|
||||
build:
|
||||
@ -39,12 +38,12 @@ jobs:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- restore_cache:
|
||||
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
|
||||
- run: bazel run @build_bazel_rules_typescript_node//:bin/npm install
|
||||
- run: bazel run @yarn//:yarn
|
||||
- run: bazel build ...
|
||||
- save_cache:
|
||||
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||
key: angular-{{ .Branch }}-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- "node_modules"
|
||||
|
||||
|
@ -104,7 +104,7 @@ groups:
|
||||
animations:
|
||||
conditions:
|
||||
files:
|
||||
- "packages/animation/*"
|
||||
- "packages/animations/*"
|
||||
- "packages/platform-browser/animations/*"
|
||||
users:
|
||||
- matsko #primary
|
||||
|
@ -1,12 +1,10 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
# force trusty as Google Chrome addon is not supported on Precise
|
||||
dist: trusty
|
||||
node_js:
|
||||
- '6.9.5'
|
||||
|
||||
addons:
|
||||
chrome: stable
|
||||
# firefox: "38.0"
|
||||
apt:
|
||||
sources:
|
||||
@ -50,7 +48,8 @@ env:
|
||||
- CI_MODE=e2e_2
|
||||
- CI_MODE=js
|
||||
- CI_MODE=saucelabs_required
|
||||
- CI_MODE=browserstack_required
|
||||
# deactivated, see #19768
|
||||
# - CI_MODE=browserstack_required
|
||||
- CI_MODE=saucelabs_optional
|
||||
- CI_MODE=browserstack_optional
|
||||
- CI_MODE=aio_tools_test
|
||||
|
55
CHANGELOG.md
55
CHANGELOG.md
@ -1,3 +1,58 @@
|
||||
<a name="4.4.7"></a>
|
||||
## [4.4.7](https://github.com/angular/angular/compare/4.4.6...4.4.7) (2018-04-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** use appropriate inert document strategy for Firefox & Safari ([#22077](https://github.com/angular/angular/issues/22077)) ([2c5cf19](https://github.com/angular/angular/commit/2c5cf19))
|
||||
|
||||
|
||||
|
||||
<a name="4.4.6"></a>
|
||||
## [4.4.6](https://github.com/angular/angular/compare/4.4.5...4.4.6) (2017-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** properly support boolean-based transitions and state changes ([#19672](https://github.com/angular/angular/issues/19672)) ([f983a6c](https://github.com/angular/angular/commit/f983a6c)), closes [#9396](https://github.com/angular/angular/issues/9396) [#12337](https://github.com/angular/angular/issues/12337)
|
||||
* **common:** attempt to JSON.parse errors for JSON responses ([#19773](https://github.com/angular/angular/issues/19773)) ([269f5ac](https://github.com/angular/angular/commit/269f5ac))
|
||||
* **router:** RouterLinkActive should update its state right after checking the children ([53a807a](https://github.com/angular/angular/commit/53a807a)), closes [#18983](https://github.com/angular/angular/issues/18983)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **animations:** reduce size of bundle by removing AST classes ([#19673](https://github.com/angular/angular/issues/19673)) ([76d2496](https://github.com/angular/angular/commit/76d2496))
|
||||
|
||||
|
||||
|
||||
<a name="4.4.5"></a>
|
||||
## [4.4.5](https://github.com/angular/angular/compare/4.4.4...4.4.5) (2017-10-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** `TestBed.overrideProvider` should keep imported `NgModule`s eager ([#19624](https://github.com/angular/angular/issues/19624)) ([734378c](https://github.com/angular/angular/commit/734378c))
|
||||
* **compiler:** correctly instantiate eager providers that are used via `Injector.get` ([#19558](https://github.com/angular/angular/issues/19558)) ([e292548](https://github.com/angular/angular/commit/e292548)), closes [#15501](https://github.com/angular/angular/issues/15501)
|
||||
* **compiler:** disallow references for select and index evaluation ([95f3b1d](https://github.com/angular/angular/commit/95f3b1d))
|
||||
* **core:** make dynamic & inline code checking behave the same ([#19189](https://github.com/angular/angular/issues/19189)) ([6c66031](https://github.com/angular/angular/commit/6c66031))
|
||||
* **platform-browser:** support customEqualityTesters when overriding Jasmine toEqual ([cc8ae32](https://github.com/angular/angular/commit/cc8ae32))
|
||||
* **tsc-wrapped:** don't rewrite imports when annotating for closure ([#19579](https://github.com/angular/angular/issues/19579)) ([c9f8718](https://github.com/angular/angular/commit/c9f8718))
|
||||
|
||||
|
||||
|
||||
<a name="4.4.4"></a>
|
||||
## [4.4.4](https://github.com/angular/angular/compare/4.4.3...4.4.4) (2017-09-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** support negative query limit value ([#19419](https://github.com/angular/angular/issues/19419)) ([bc81fbd](https://github.com/angular/angular/commit/bc81fbd)), closes [#19232](https://github.com/angular/angular/issues/19232)
|
||||
* **compiler:** correctly map error message locations ([#19424](https://github.com/angular/angular/issues/19424)) ([c3b39ba](https://github.com/angular/angular/commit/c3b39ba))
|
||||
* **compiler:** do not consider a reference with members as a reference ([#19466](https://github.com/angular/angular/issues/19466)) ([7fc2dce](https://github.com/angular/angular/commit/7fc2dce))
|
||||
* **compiler:** skip when trimming / removing whitespaces ([#19310](https://github.com/angular/angular/issues/19310)) ([c7aa8a1](https://github.com/angular/angular/commit/c7aa8a1)), closes [#19304](https://github.com/angular/angular/issues/19304)
|
||||
* **tsc-wrapped:** add metadata for `type` declarations ([#19040](https://github.com/angular/angular/issues/19040)) ([ae52851](https://github.com/angular/angular/commit/ae52851))
|
||||
|
||||
|
||||
<a name="4.4.3"></a>
|
||||
## [4.4.3](https://github.com/angular/angular/compare/4.4.2...4.4.3) (2017-09-19)
|
||||
|
||||
|
@ -13,7 +13,11 @@ You should run all these tasks from the `angular/aio` folder.
|
||||
Here are the most important tasks you might need to use:
|
||||
|
||||
* `yarn` - install all the dependencies.
|
||||
* `yarn setup` - Install all the dependencies, boilerplate, plunkers, zips and runs dgeni on the docs.
|
||||
* `yarn setup` - install all the dependencies, boilerplate, plunkers, zips and run dgeni on the docs.
|
||||
* `yarn setup-local` - same as `setup`, but use the locally built Angular packages for aio and docs examples boilerplate.
|
||||
|
||||
* `yarn build` - create a production build of the application (after installing dependencies, boilerplate, etc).
|
||||
* `yarn build-local` - same as `build`, but use `setup-local` instead of `setup`.
|
||||
|
||||
* `yarn start` - run a development web server that watches the files; then builds the doc-viewer and reloads the page, as necessary.
|
||||
* `yarn serve-and-sync` - run both the `docs-watch` and `start` in the same console.
|
||||
@ -26,33 +30,33 @@ Here are the most important tasks you might need to use:
|
||||
* `yarn docs-lint` - check that the doc gen code follows our style rules.
|
||||
* `yarn docs-test` - run the unit tests for the doc generation code.
|
||||
|
||||
* `yarn boilerplate:add` - generate all the boilerplate code for the examples, so that they can be run locally. Add the option `-- --local` to use your local version of Angular contained in the "dist" folder.
|
||||
* `yarn boilerplate:add` - generate all the boilerplate code for the examples, so that they can be run locally. Add the option `--local` to use your local version of Angular contained in the "dist" folder.
|
||||
* `yarn boilerplate:remove` - remove all the boilerplate code that was added via `yarn boilerplate:add`.
|
||||
* `yarn generate-plunkers` - generate the plunker files that are used by the `live-example` tags in the docs.
|
||||
* `yarn generate-zips` - generate the zip files from the examples. Zip available via the `live-example` tags in the docs.
|
||||
|
||||
* `yarn example-e2e` - run all e2e tests for examples
|
||||
- `yarn example-e2e -- --setup` - force webdriver update & other setup, then run tests
|
||||
- `yarn example-e2e -- --filter=foo` - limit e2e tests to those containing the word "foo"
|
||||
- `yarn example-e2e -- --setup --local` - run e2e tests with the local version of Angular contained in the "dist" folder
|
||||
- `yarn example-e2e --setup` - force webdriver update & other setup, then run tests
|
||||
- `yarn example-e2e --filter=foo` - limit e2e tests to those containing the word "foo"
|
||||
- `yarn example-e2e --setup --local` - run e2e tests with the local version of Angular contained in the "dist" folder
|
||||
|
||||
* `yarn build-ie-polyfills` - generates a js file of polyfills that can be loaded in Internet Explorer.
|
||||
|
||||
## Using ServiceWorker locally
|
||||
|
||||
Since abb36e3cb, running `yarn start -- --prod` will no longer set up the ServiceWorker, which
|
||||
Since abb36e3cb, running `yarn start --prod` will no longer set up the ServiceWorker, which
|
||||
would require manually running `yarn sw-manifest` and `yarn sw-copy` (something that is not possible
|
||||
with webpack serving the files from memory).
|
||||
|
||||
If you want to test ServiceWorker locally, you can use `yarn build` and serve the files in `dist/`
|
||||
with `yarn http-server -- dist -p 4200`.
|
||||
with `yarn http-server dist -p 4200`.
|
||||
|
||||
For more details see #16745.
|
||||
|
||||
|
||||
## Guide to authoring
|
||||
|
||||
There are two types of content in the documentatation:
|
||||
There are two types of content in the documentation:
|
||||
|
||||
* **API docs**: descriptions of the modules, classes, interfaces, decorators, etc that make up the Angular platform.
|
||||
API docs are generated directly from the source code.
|
||||
@ -107,8 +111,16 @@ yarn start
|
||||
yarn docs-watch
|
||||
```
|
||||
|
||||
>Alternatively, try the consolidated `serve-and-sync` command that builds, watches and serves in the same terminal window
|
||||
```bash
|
||||
yarn serve-and-sync
|
||||
```
|
||||
|
||||
* Open a browser at https://localhost:4200/ and navigate to the document on which you want to work.
|
||||
You can automatically open the browser by using `yarn start -- -o` in the first terminal.
|
||||
You can automatically open the browser by using `yarn start -o` in the first terminal.
|
||||
|
||||
* Make changes to the page's associated doc or example files. Every time a file is saved, the doc will
|
||||
be regenerated, the app will rebuild and the page will reload.
|
||||
|
||||
* If you get a build error complaining about examples or any other odd behavior, be sure to consult
|
||||
the [Authors Style Guide](https://angular.io/guide/docs-style-guide).
|
||||
|
@ -156,7 +156,7 @@ RUN find $AIO_SCRIPTS_SH_DIR -maxdepth 1 -type f -printf "%P\n" \
|
||||
# Set up the Node.js scripts
|
||||
COPY scripts-js/ $AIO_SCRIPTS_JS_DIR/
|
||||
WORKDIR $AIO_SCRIPTS_JS_DIR/
|
||||
RUN yarn install --production
|
||||
RUN yarn install --production --freeze-lockfile
|
||||
|
||||
|
||||
# Set up health check
|
||||
|
@ -6,7 +6,7 @@ server=8.8.4.4
|
||||
# Listen for DHCP and DNS requests only on this address.
|
||||
listen-address=127.0.0.1
|
||||
|
||||
# Force an IP addres for these domains.
|
||||
# Force an IP address for these domains.
|
||||
address=/{{$AIO_NGINX_HOSTNAME}}/127.0.0.1
|
||||
address=/{{$AIO_UPLOAD_HOSTNAME}}/127.0.0.1
|
||||
address=/{{$TEST_AIO_NGINX_HOSTNAME}}/127.0.0.1
|
||||
|
@ -13,10 +13,8 @@ const AIO_REPO_SLUG = getEnvVar('AIO_REPO_SLUG');
|
||||
const AIO_TRUSTED_PR_LABEL = getEnvVar('AIO_TRUSTED_PR_LABEL');
|
||||
const AIO_UPLOAD_HOSTNAME = getEnvVar('AIO_UPLOAD_HOSTNAME');
|
||||
const AIO_UPLOAD_PORT = +getEnvVar('AIO_UPLOAD_PORT');
|
||||
const AIO_WWW_USER = getEnvVar('AIO_WWW_USER');
|
||||
|
||||
// Run
|
||||
process.setuid(AIO_WWW_USER); // TODO(gkalpak): Find more suitable way to run as `www-data`.
|
||||
_main();
|
||||
|
||||
// Functions
|
||||
|
@ -18,7 +18,7 @@ const TEST_AIO_UPLOAD_PORT = +getEnvVar('TEST_AIO_UPLOAD_PORT');
|
||||
const WWW_USER = getEnvVar('AIO_WWW_USER');
|
||||
|
||||
// Interfaces - Types
|
||||
export interface CmdResult { success: boolean; err: Error; stdout: string; stderr: string; }
|
||||
export interface CmdResult { success: boolean; err: Error | null; stdout: string; stderr: string; }
|
||||
export interface FileSpecs { content?: string; size?: number; }
|
||||
|
||||
export type CleanUpFn = () => void;
|
||||
@ -143,7 +143,7 @@ class Helper {
|
||||
statusText = status[1];
|
||||
} else {
|
||||
statusCode = status;
|
||||
statusText = http.STATUS_CODES[statusCode];
|
||||
statusText = http.STATUS_CODES[statusCode] || 'UNKNOWN_STATUS_CODE';
|
||||
}
|
||||
|
||||
return (result: CmdResult) => {
|
||||
@ -196,7 +196,7 @@ class Helper {
|
||||
}
|
||||
|
||||
// Methods - Protected
|
||||
protected createCleanUpFn(fn: Function): CleanUpFn {
|
||||
protected createCleanUpFn(fn: () => void): CleanUpFn {
|
||||
const cleanUpFn = () => {
|
||||
const idx = this.cleanUpFns.indexOf(cleanUpFn);
|
||||
if (idx !== -1) {
|
||||
|
@ -8,7 +8,7 @@
|
||||
"scripts": {
|
||||
"prebuild": "yarn clean-dist",
|
||||
"build": "tsc",
|
||||
"build-watch": "yarn tsc -- --watch",
|
||||
"build-watch": "yarn tsc --watch",
|
||||
"clean-dist": "node --eval \"require('shelljs').rm('-rf', 'dist')\"",
|
||||
"dev": "concurrently --kill-others --raw --success first \"yarn build-watch\" \"yarn test-watch\"",
|
||||
"lint": "tslint --project tsconfig.json",
|
||||
@ -20,26 +20,26 @@
|
||||
"test-watch": "nodemon --exec \"yarn ~~test-only\" --watch dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"body-parser": "^1.17.2",
|
||||
"express": "^4.14.1",
|
||||
"jasmine": "^2.5.3",
|
||||
"jsonwebtoken": "^7.3.0",
|
||||
"shelljs": "^0.7.6"
|
||||
"body-parser": "^1.18.2",
|
||||
"express": "^4.15.4",
|
||||
"jasmine": "^2.8.0",
|
||||
"jsonwebtoken": "^8.0.1",
|
||||
"shelljs": "^0.7.8",
|
||||
"tslib": "^1.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/body-parser": "^1.16.4",
|
||||
"@types/express": "^4.0.35",
|
||||
"@types/jasmine": "^2.5.43",
|
||||
"@types/jsonwebtoken": "^7.2.0",
|
||||
"@types/node": "^7.0.5",
|
||||
"@types/shelljs": "^0.7.0",
|
||||
"@types/supertest": "^2.0.0",
|
||||
"concurrently": "^3.3.0",
|
||||
"eslint": "^3.15.0",
|
||||
"eslint-plugin-jasmine": "^2.2.0",
|
||||
"nodemon": "^1.11.0",
|
||||
"@types/body-parser": "^1.16.5",
|
||||
"@types/express": "^4.0.37",
|
||||
"@types/jasmine": "^2.6.0",
|
||||
"@types/jsonwebtoken": "^7.2.3",
|
||||
"@types/node": "^8.0.30",
|
||||
"@types/shelljs": "^0.7.4",
|
||||
"@types/supertest": "^2.0.3",
|
||||
"concurrently": "^3.5.0",
|
||||
"nodemon": "^1.12.1",
|
||||
"supertest": "^3.0.0",
|
||||
"tslint": "^4.4.2",
|
||||
"typescript": "^2.1.6"
|
||||
"tslint": "^5.7.0",
|
||||
"tslint-jasmine-noSkipOrFocus": "^1.0.8",
|
||||
"typescript": "^2.5.2"
|
||||
}
|
||||
}
|
||||
|
@ -39,8 +39,8 @@ describe('BuildCleaner', () => {
|
||||
let cleanerGetExistingBuildNumbersSpy: jasmine.Spy;
|
||||
let cleanerGetOpenPrNumbersSpy: jasmine.Spy;
|
||||
let cleanerRemoveUnnecessaryBuildsSpy: jasmine.Spy;
|
||||
let existingBuildsDeferred: {resolve: Function, reject: Function};
|
||||
let openPrsDeferred: {resolve: Function, reject: Function};
|
||||
let existingBuildsDeferred: {resolve: (v?: any) => void, reject: (e?: any) => void};
|
||||
let openPrsDeferred: {resolve: (v?: any) => void, reject: (e?: any) => void};
|
||||
let promise: Promise<void>;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -195,7 +195,7 @@ describe('BuildCleaner', () => {
|
||||
|
||||
|
||||
describe('getOpenPrNumbers()', () => {
|
||||
let prDeferred: {resolve: Function, reject: Function};
|
||||
let prDeferred: {resolve: (v: any) => void, reject: (v: any) => void};
|
||||
let promise: Promise<number[]>;
|
||||
|
||||
beforeEach(() => {
|
||||
@ -277,7 +277,10 @@ describe('BuildCleaner', () => {
|
||||
|
||||
it('should catch errors and log them', () => {
|
||||
const consoleErrorSpy = spyOn(console, 'error');
|
||||
shellRmSpy.and.callFake(() => { throw 'Test'; });
|
||||
shellRmSpy.and.callFake(() => {
|
||||
// tslint:disable-next-line: no-string-throw
|
||||
throw 'Test';
|
||||
});
|
||||
|
||||
(cleaner as any).removeDir('/foo/bar');
|
||||
|
||||
|
@ -56,7 +56,7 @@ describe('GithubApi', () => {
|
||||
|
||||
|
||||
it('should not pass data to \'request()\'', () => {
|
||||
(api.get as Function)('foo', {}, {});
|
||||
(api.get as any)('foo', {}, {});
|
||||
|
||||
expect(apiRequestSpy).toHaveBeenCalled();
|
||||
expect(apiRequestSpy.calls.argsFor(0)[2]).toBeUndefined();
|
||||
@ -144,7 +144,7 @@ describe('GithubApi', () => {
|
||||
|
||||
|
||||
describe('getPaginated()', () => {
|
||||
let deferreds: {resolve: Function, reject: Function}[];
|
||||
let deferreds: {resolve: (v: any) => void, reject: (v: any) => void}[];
|
||||
|
||||
beforeEach(() => {
|
||||
deferreds = [];
|
||||
@ -292,7 +292,7 @@ describe('GithubApi', () => {
|
||||
|
||||
|
||||
describe('onResponse', () => {
|
||||
let promise: Promise<Object>;
|
||||
let promise: Promise<object>;
|
||||
let respond: (statusCode: number) => IncomingMessage;
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -22,7 +22,7 @@ describe('GithubPullRequests', () => {
|
||||
|
||||
describe('addComment()', () => {
|
||||
let prs: GithubPullRequests;
|
||||
let deferred: {resolve: Function, reject: Function};
|
||||
let deferred: {resolve: (v: any) => void, reject: (v: any) => void};
|
||||
|
||||
beforeEach(() => {
|
||||
prs = new GithubPullRequests('12345', 'foo/bar');
|
||||
|
@ -223,6 +223,7 @@ describe('BuildCreator', () => {
|
||||
|
||||
|
||||
it('should reject with an UploadError', done => {
|
||||
// tslint:disable-next-line: no-string-throw
|
||||
shellMkdirSpy.and.callFake(() => { throw 'Test'; });
|
||||
bc.create(pr, sha, archive, isPublic).catch(err => {
|
||||
expectToBeUploadError(err, 500, `Error while uploading to directory: ${shaDir}\nTest`);
|
||||
@ -407,6 +408,7 @@ describe('BuildCreator', () => {
|
||||
|
||||
|
||||
it('should reject with an UploadError', done => {
|
||||
// tslint:disable-next-line: no-string-throw
|
||||
shellMvSpy.and.callFake(() => { throw 'Test'; });
|
||||
bc.updatePrVisibility(pr, makePublic).catch(err => {
|
||||
expectToBeUploadError(err, 500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\nTest`);
|
||||
@ -434,11 +436,11 @@ describe('BuildCreator', () => {
|
||||
|
||||
describe('exists()', () => {
|
||||
let fsAccessSpy: jasmine.Spy;
|
||||
let fsAccessCbs: Function[];
|
||||
let fsAccessCbs: ((v?: any) => void)[];
|
||||
|
||||
beforeEach(() => {
|
||||
fsAccessCbs = [];
|
||||
fsAccessSpy = spyOn(fs, 'access').and.callFake((_: string, cb: Function) => fsAccessCbs.push(cb));
|
||||
fsAccessSpy = spyOn(fs, 'access').and.callFake((_: string, cb: (v?: any) => void) => fsAccessCbs.push(cb));
|
||||
});
|
||||
|
||||
|
||||
@ -482,7 +484,7 @@ describe('BuildCreator', () => {
|
||||
let shellChmodSpy: jasmine.Spy;
|
||||
let shellRmSpy: jasmine.Spy;
|
||||
let cpExecSpy: jasmine.Spy;
|
||||
let cpExecCbs: Function[];
|
||||
let cpExecCbs: ((...args: any[]) => void)[];
|
||||
|
||||
beforeEach(() => {
|
||||
cpExecCbs = [];
|
||||
@ -490,7 +492,7 @@ describe('BuildCreator', () => {
|
||||
consoleWarnSpy = spyOn(console, 'warn');
|
||||
shellChmodSpy = spyOn(shell, 'chmod');
|
||||
shellRmSpy = spyOn(shell, 'rm');
|
||||
cpExecSpy = spyOn(cp, 'exec').and.callFake((_: string, cb: Function) => cpExecCbs.push(cb));
|
||||
cpExecSpy = spyOn(cp, 'exec').and.callFake((_: string, cb: (...args: any[]) => void) => cpExecCbs.push(cb));
|
||||
});
|
||||
|
||||
|
||||
@ -556,7 +558,11 @@ describe('BuildCreator', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
shellChmodSpy.and.callFake(() => { throw 'Test'; });
|
||||
shellChmodSpy.and.callFake(() => {
|
||||
// tslint:disable-next-line: no-string-throw
|
||||
throw 'Test';
|
||||
});
|
||||
|
||||
cpExecCbs[0]();
|
||||
});
|
||||
|
||||
@ -569,7 +575,11 @@ describe('BuildCreator', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
shellRmSpy.and.callFake(() => { throw 'Test'; });
|
||||
shellRmSpy.and.callFake(() => {
|
||||
// tslint:disable-next-line: no-string-throw
|
||||
throw 'Test';
|
||||
});
|
||||
|
||||
cpExecCbs[0]();
|
||||
});
|
||||
|
||||
|
@ -116,7 +116,7 @@ describe('uploadServerFactory', () => {
|
||||
|
||||
it('should log the server address info on \'listening\'', () => {
|
||||
const consoleInfoSpy = spyOn(console, 'info');
|
||||
const server = createUploadServer('builds/dir');
|
||||
const server = createUploadServer();
|
||||
server.address = () => ({address: 'foo', family: '', port: 1337});
|
||||
|
||||
expect(consoleInfoSpy).not.toHaveBeenCalled();
|
||||
|
@ -1,25 +1,66 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"alwaysStrict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"inlineSourceMap": true,
|
||||
/* Basic Options */
|
||||
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
"lib": [
|
||||
"es2016"
|
||||
],
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"outDir": "dist",
|
||||
"pretty": true,
|
||||
"rootDir": ".",
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"target": "es5",
|
||||
"es2015",
|
||||
"es2016.array.include"
|
||||
], /* Specify library files to be included in the compilation: */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "dist", /* Redirect output structure to the directory. */
|
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
"importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
"noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
"noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
]
|
||||
], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
"inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Other */
|
||||
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
|
||||
"newLine": "LF", /* Use the specified end of line sequence to be used when emitting files: "crlf" (windows) or "lf" (unix). */
|
||||
"pretty": true, /* Stylize errors and messages using color and context. */
|
||||
"skipLibCheck": true /* Skip type checking of all declaration files (*.d.ts). */
|
||||
},
|
||||
"include": [
|
||||
"lib/**/*",
|
||||
|
@ -1,5 +1,9 @@
|
||||
{
|
||||
"extends": "tslint:recommended",
|
||||
"defaultSeverity": "error",
|
||||
"extends": [
|
||||
"tslint:recommended"
|
||||
],
|
||||
"jsRules": {},
|
||||
"rules": {
|
||||
"array-type": [true, "array"],
|
||||
"arrow-parens": [true, "ban-single-arg-parens"],
|
||||
@ -7,9 +11,14 @@
|
||||
"max-classes-per-file": [true, 4],
|
||||
"no-consecutive-blank-lines": [true, 2],
|
||||
"no-console": [false],
|
||||
"no-focused-test": true,
|
||||
"no-namespace": [true, "allow-declarations"],
|
||||
"no-skipped-test": true,
|
||||
"no-string-literal": false,
|
||||
"quotemark": [true, "single"],
|
||||
"variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore"]
|
||||
}
|
||||
},
|
||||
"rulesDirectory": [
|
||||
"node_modules/tslint-jasmine-noSkipOrFocus"
|
||||
]
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -6,10 +6,9 @@ export AIO_GITHUB_TOKEN=$(head -c -1 /aio-secrets/GITHUB_TOKEN 2>/dev/null || ec
|
||||
export AIO_PREVIEW_DEPLOYMENT_TOKEN=$(head -c -1 /aio-secrets/PREVIEW_DEPLOYMENT_TOKEN 2>/dev/null || echo "MISSING_PREVIEW_DEPLOYMENT_TOKEN")
|
||||
|
||||
# Start the upload-server instance
|
||||
# TODO(gkalpak): Ideally, the upload server should be run as a non-privileged user.
|
||||
# (Currently, there doesn't seem to be a straight forward way.)
|
||||
action=$([ "$1" == "stop" ] && echo "stop" || echo "start")
|
||||
pm2 $action $AIO_SCRIPTS_JS_DIR/dist/lib/upload-server \
|
||||
--uid $AIO_WWW_USER \
|
||||
--log /var/log/aio/upload-server-prod.log \
|
||||
--name aio-upload-server-prod \
|
||||
${@:2}
|
||||
|
@ -15,13 +15,12 @@ export AIO_GITHUB_TOKEN=$(head -c -1 /aio-secrets/TEST_GITHUB_TOKEN 2>/dev/null
|
||||
export AIO_PREVIEW_DEPLOYMENT_TOKEN=$(head -c -1 /aio-secrets/TEST_PREVIEW_DEPLOYMENT_TOKEN 2>/dev/null || echo "TEST_PREVIEW_DEPLOYMENT_TOKEN")
|
||||
|
||||
# Start the upload-server instance
|
||||
# TODO(gkalpak): Ideally, the upload server should be run as a non-privileged user.
|
||||
# (Currently, there doesn't seem to be a straight forward way.)
|
||||
appName=aio-upload-server-test
|
||||
if [[ "$1" == "stop" ]]; then
|
||||
pm2 delete $appName
|
||||
else
|
||||
pm2 start $AIO_SCRIPTS_JS_DIR/dist/lib/verify-setup/start-test-upload-server.js \
|
||||
--uid $AIO_WWW_USER \
|
||||
--log /var/log/aio/upload-server-test.log \
|
||||
--name $appName \
|
||||
--no-autorestart \
|
||||
|
@ -9,7 +9,7 @@ VM host to update the preview server based on changes in the source code.
|
||||
The script will pull the latest changes from the origin's master branch and examine if there have
|
||||
been any changes in files inside the preview server source code directory (see below). If there are,
|
||||
it will create a new image and verify that is works as expected. Finally, it will stop and remove
|
||||
the old docker container and image, create and new container based on the new image and start it.
|
||||
the old docker container and image, create a new container based on the new image and start it.
|
||||
|
||||
The script assumes that the preview server source code is in the repository's
|
||||
`aio/aio-builds-setup/` directory and expects the following inputs:
|
||||
|
@ -9,7 +9,7 @@ readonly defaultImageNameAndTag="aio-builds:latest"
|
||||
# (Necessary, because only `scripts-js/dist/` is copied to the docker image.)
|
||||
(
|
||||
cd "$SCRIPTS_JS_DIR"
|
||||
yarn install
|
||||
yarn install --freeze-lockfile --non-interactive
|
||||
yarn build
|
||||
)
|
||||
|
||||
|
@ -7,6 +7,6 @@ source "`dirname $0`/_env.sh"
|
||||
# Test `scripts-js/`
|
||||
(
|
||||
cd "$SCRIPTS_JS_DIR"
|
||||
yarn install
|
||||
yarn install --freeze-lockfile --non-interactive
|
||||
yarn test
|
||||
)
|
||||
|
@ -335,17 +335,17 @@ describe('Animation Tests', () => {
|
||||
});
|
||||
}
|
||||
|
||||
function getBoundingClientWidth(el: ElementFinder): promise.Promise<number> {
|
||||
function getBoundingClientWidth(el: ElementFinder) {
|
||||
return browser.executeScript(
|
||||
'return arguments[0].getBoundingClientRect().width',
|
||||
el.getWebElement()
|
||||
);
|
||||
) as PromiseLike<number>;
|
||||
}
|
||||
|
||||
function getOffsetWidth(el: ElementFinder): promise.Promise<number> {
|
||||
function getOffsetWidth(el: ElementFinder) {
|
||||
return browser.executeScript(
|
||||
'return arguments[0].offsetWidth',
|
||||
el.getWebElement()
|
||||
);
|
||||
) as PromiseLike<number>;
|
||||
}
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
// #docregion animations-module
|
||||
// #docplaster
|
||||
import { NgModule } from '@angular/core';
|
||||
// #docregion animations-module
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
// #enddocregion animations-module
|
||||
@ -15,11 +16,12 @@ import { HeroListAutoComponent } from './hero-list-auto.component';
|
||||
import { HeroListGroupsComponent } from './hero-list-groups.component';
|
||||
import { HeroListMultistepComponent } from './hero-list-multistep.component';
|
||||
import { HeroListTimingsComponent } from './hero-list-timings.component';
|
||||
// #docregion animations-module
|
||||
|
||||
// #docregion animation-module
|
||||
@NgModule({
|
||||
imports: [ BrowserModule, BrowserAnimationsModule ],
|
||||
// #enddocregion animation-module
|
||||
// ... more stuff ...
|
||||
// #enddocregion animations-module
|
||||
declarations: [
|
||||
HeroTeamBuilderComponent,
|
||||
HeroListBasicComponent,
|
||||
@ -34,5 +36,8 @@ import { HeroListTimingsComponent } from './hero-list-timings.component';
|
||||
HeroListGroupsComponent
|
||||
],
|
||||
bootstrap: [ HeroTeamBuilderComponent ]
|
||||
// #docregion animations-module
|
||||
})
|
||||
export class AppModule { }
|
||||
// #enddocregion animations-module
|
||||
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-auto',
|
||||
@ -43,5 +43,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListAutoComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
} from '@angular/animations';
|
||||
// #enddocregion imports
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-basic',
|
||||
@ -66,5 +66,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListBasicComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
} from '@angular/animations';
|
||||
// #enddocregion imports
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-combined-transitions',
|
||||
@ -55,5 +55,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListCombinedTransitionsComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-enter-leave-states',
|
||||
@ -59,5 +59,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListEnterLeaveStatesComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-enter-leave',
|
||||
@ -47,5 +47,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListEnterLeaveComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
group
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-groups',
|
||||
@ -76,5 +76,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListGroupsComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
} from '@angular/animations';
|
||||
// #enddocregion imports
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-inline-styles',
|
||||
@ -56,5 +56,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListInlineStylesComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
AnimationEvent
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-multistep',
|
||||
@ -59,7 +59,7 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListMultistepComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
|
||||
animationStarted(event: AnimationEvent) {
|
||||
console.warn('Animation started: ', event);
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
transition
|
||||
} from '@angular/animations';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-timings',
|
||||
@ -54,5 +54,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListTimingsComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
} from '@angular/animations';
|
||||
// #enddocregion imports
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-list-twoway',
|
||||
@ -54,5 +54,5 @@ import { Heroes } from './hero.service';
|
||||
// #enddocregion animationdef
|
||||
})
|
||||
export class HeroListTwowayComponent {
|
||||
@Input() heroes: Heroes;
|
||||
@Input() heroes: Hero[];
|
||||
}
|
||||
|
@ -1,40 +1,41 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { Heroes } from './hero.service';
|
||||
import { Hero, HeroService } from './hero.service';
|
||||
|
||||
@Component({
|
||||
selector: 'hero-team-builder',
|
||||
template: `
|
||||
<div class="buttons">
|
||||
<button [disabled]="!heroes.canAdd()" (click)="heroes.addInactive()">Add inactive hero</button>
|
||||
<button [disabled]="!heroes.canAdd()" (click)="heroes.addActive()">Add active hero</button>
|
||||
<button [disabled]="!heroes.canRemove()" (click)="heroes.remove()">Remove hero</button>
|
||||
<button [disabled]="!heroService.canAdd()" (click)="heroService.addInactive()">Add inactive hero</button>
|
||||
<button [disabled]="!heroService.canAdd()" (click)="heroService.addActive()">Add active hero</button>
|
||||
<button [disabled]="!heroService.canRemove()" (click)="heroService.remove()">Remove hero</button>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<h4>Basic State</h4>
|
||||
<p>Switch between active/inactive on click.</p>
|
||||
<hero-list-basic [heroes]=heroes></hero-list-basic>
|
||||
<hero-list-basic [heroes]="heroes"></hero-list-basic>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Styles inline in transitions</h4>
|
||||
<p>Animated effect on click, no persistend end styles.</p>
|
||||
<hero-list-inline-styles [heroes]=heroes></hero-list-inline-styles>
|
||||
<p>Animated effect on click, no persistent end styles.</p>
|
||||
<hero-list-inline-styles [heroes]="heroes"></hero-list-inline-styles>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Combined transition syntax</h4>
|
||||
<p>Switch between active/inactive on click. Define just one transition used in both directions.</p>
|
||||
<hero-list-combined-transitions [heroes]=heroes></hero-list-combined-transitions>
|
||||
<hero-list-combined-transitions [heroes]="heroes"></hero-list-combined-transitions>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Two-way transition syntax</h4>
|
||||
<p>Switch between active/inactive on click. Define just one transition used in both directions using the <=> syntax.</p>
|
||||
<hero-list-twoway [heroes]=heroes></hero-list-twoway>
|
||||
<hero-list-twoway [heroes]="heroes"></hero-list-twoway>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Enter & Leave</h4>
|
||||
<p>Enter and leave animations using the void state.</p>
|
||||
<hero-list-enter-leave [heroes]=heroes></hero-list-enter-leave>
|
||||
<hero-list-enter-leave [heroes]="heroes"></hero-list-enter-leave>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns">
|
||||
@ -44,27 +45,27 @@ import { Heroes } from './hero.service';
|
||||
Enter and leave animations combined with active/inactive state animations.
|
||||
Different enter and leave transitions depending on state.
|
||||
</p>
|
||||
<hero-list-enter-leave-states [heroes]=heroes></hero-list-enter-leave-states>
|
||||
<hero-list-enter-leave-states [heroes]="heroes"></hero-list-enter-leave-states>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Auto Style Calc</h4>
|
||||
<p>Leave animation from the current computed height using the auto-style value *.</p>
|
||||
<hero-list-auto [heroes]=heroes></hero-list-auto>
|
||||
<hero-list-auto [heroes]="heroes"></hero-list-auto>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Different Timings</h4>
|
||||
<p>Enter and leave animations with different easings, ease-in for enter, ease-out for leave.</p>
|
||||
<hero-list-timings [heroes]=heroes></hero-list-timings>
|
||||
<hero-list-timings [heroes]="heroes"></hero-list-timings>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Multiple Keyframes</h4>
|
||||
<p>Enter and leave animations with three keyframes in each, to give the transition some bounce.</p>
|
||||
<hero-list-multistep [heroes]=heroes></hero-list-multistep>
|
||||
<hero-list-multistep [heroes]="heroes"></hero-list-multistep>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h4>Parallel Groups</h4>
|
||||
<p>Enter and leave animations with multiple properties animated in parallel with different timings.</p>
|
||||
<hero-list-groups [heroes]=heroes></hero-list-groups>
|
||||
<hero-list-groups [heroes]="heroes"></hero-list-groups>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
@ -87,8 +88,12 @@ import { Heroes } from './hero.service';
|
||||
min-height: 6em;
|
||||
}
|
||||
`],
|
||||
providers: [Heroes]
|
||||
providers: [HeroService]
|
||||
})
|
||||
export class HeroTeamBuilderComponent {
|
||||
constructor(private heroes: Heroes) { }
|
||||
heroes: Hero[];
|
||||
|
||||
constructor(private heroService: HeroService) {
|
||||
this.heroes = heroService.heroes;
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
class Hero {
|
||||
constructor(public name: string,
|
||||
public state = 'inactive') {
|
||||
}
|
||||
// #docregion hero
|
||||
export class Hero {
|
||||
constructor(public name: string, public state = 'inactive') { }
|
||||
|
||||
toggleState() {
|
||||
this.state = (this.state === 'active' ? 'inactive' : 'active');
|
||||
this.state = this.state === 'active' ? 'inactive' : 'active';
|
||||
}
|
||||
}
|
||||
// #enddocregion hero
|
||||
|
||||
let ALL_HEROES = [
|
||||
const ALL_HEROES = [
|
||||
'Windstorm',
|
||||
'RubberMan',
|
||||
'Bombasto',
|
||||
@ -25,36 +25,30 @@ let ALL_HEROES = [
|
||||
].map(name => new Hero(name));
|
||||
|
||||
@Injectable()
|
||||
export class Heroes implements Iterable<Hero> {
|
||||
export class HeroService {
|
||||
|
||||
currentHeroes: Hero[] = [];
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this.currentHeroes.values();
|
||||
}
|
||||
heroes: Hero[] = [];
|
||||
|
||||
canAdd() {
|
||||
return this.currentHeroes.length < ALL_HEROES.length;
|
||||
return this.heroes.length < ALL_HEROES.length;
|
||||
}
|
||||
|
||||
canRemove() {
|
||||
return this.currentHeroes.length > 0;
|
||||
return this.heroes.length > 0;
|
||||
}
|
||||
|
||||
addActive() {
|
||||
let hero = ALL_HEROES[this.currentHeroes.length];
|
||||
hero.state = 'active';
|
||||
this.currentHeroes.push(hero);
|
||||
addActive(active = true) {
|
||||
let hero = ALL_HEROES[this.heroes.length];
|
||||
hero.state = active ? 'active' : 'inactive';
|
||||
this.heroes.push(hero);
|
||||
}
|
||||
|
||||
addInactive() {
|
||||
let hero = ALL_HEROES[this.currentHeroes.length];
|
||||
hero.state = 'inactive';
|
||||
this.currentHeroes.push(hero);
|
||||
this.addActive(false);
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.currentHeroes.splice(this.currentHeroes.length - 1, 1);
|
||||
this.heroes.length -= 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,8 +12,10 @@ describe('Component Style Tests', function () {
|
||||
let componentH1 = element(by.css('hero-app > h1'));
|
||||
let externalH1 = element(by.css('body > h1'));
|
||||
|
||||
expect(componentH1.getCssValue('fontWeight')).toEqual('normal');
|
||||
expect(externalH1.getCssValue('fontWeight')).not.toEqual('normal');
|
||||
// Note: sometimes webdriver returns the fontWeight as "normal",
|
||||
// othertimes as "400", both of which are equal in CSS terms.
|
||||
expect(componentH1.getCssValue('fontWeight')).toMatch(/normal|400/);
|
||||
expect(externalH1.getCssValue('fontWeight')).not.toMatch(/normal|400/);
|
||||
});
|
||||
|
||||
|
||||
|
@ -7,3 +7,4 @@
|
||||
<p>We're sorry. The page you are looking for cannot be found.</p>
|
||||
</div>
|
||||
</div>
|
||||
<aio-file-not-found-search></aio-file-not-found-search>
|
||||
|
@ -150,11 +150,6 @@ The following table lists some of the key AngularJS template features with their
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
[Back to top](guide/ajs-quick-reference#top)
|
||||
|
||||
|
||||
|
||||
## Template directives
|
||||
AngularJS provides more than seventy built-in directives for templates.
|
||||
Many of them aren't needed in Angular because of its more capable and expressive binding system.
|
||||
@ -721,10 +716,6 @@ The following are some of the key AngularJS built-in directives and their equiva
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
[Back to top](guide/ajs-quick-reference#top)
|
||||
|
||||
|
||||
{@a filters-pipes}
|
||||
|
||||
|
||||
@ -992,9 +983,6 @@ For more information on pipes, see [Pipes](guide/pipes).
|
||||
|
||||
|
||||
|
||||
[Back to top](guide/ajs-quick-reference#top)
|
||||
|
||||
|
||||
{@a controllers-components}
|
||||
|
||||
|
||||
@ -1215,11 +1203,6 @@ The Angular code is shown using TypeScript.
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
[Back to top](guide/ajs-quick-reference#top)
|
||||
|
||||
|
||||
{@a style-sheets}
|
||||
|
||||
|
||||
@ -1297,6 +1280,3 @@ also encapsulate a style sheet within a specific component.
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
[Back to top](guide/ajs-quick-reference#top)
|
||||
|
@ -28,19 +28,37 @@ The examples in this page are available as a <live-example></live-example>.
|
||||
|
||||
</div>
|
||||
|
||||
## Setup
|
||||
|
||||
Before you can add animations to your application, you need
|
||||
to import a few animation-specific modules and functions to the root application module.
|
||||
|
||||
<code-example path="animations/src/app/app.module.ts" region="animations-module" title="app.module.ts (animation module import excerpt)" linenums="false"></code-example>
|
||||
|
||||
#### Example basics
|
||||
|
||||
The animations examples in this guide animate a list of heroes.
|
||||
|
||||
A `Hero` class has a `name` property, a `state` property that indicates if the hero is active or not,
|
||||
and a `toggleState()` method to switch between the states.
|
||||
|
||||
<code-example path="animations/src/app/hero.service.ts" region="hero" title="hero.service.ts (Hero class)" linenums="false"></code-example>
|
||||
|
||||
Across the top of the screen (`app.hero-team-builder.component.ts`)
|
||||
are a series of buttons that add and remove heroes from the list (via the `HeroService`).
|
||||
The buttons trigger changes to the list that all of the example components see at the same time.
|
||||
|
||||
{@a example-transitioning-between-states}
|
||||
|
||||
## Quickstart example: Transitioning between two states
|
||||
## Transitioning between two states
|
||||
|
||||
<img src="generated/images/guide/animations/animation_basic_click.gif" alt="A simple transition animation" class="right">
|
||||
|
||||
You can build a simple animation that transitions an element between two states
|
||||
driven by a model attribute.
|
||||
|
||||
Animations are defined inside `@Component` metadata. Before you can add animations, you need
|
||||
to import a few animation-specific imports and functions:
|
||||
|
||||
<code-example path="animations/src/app/app.module.ts" region="animations-module" title="app.module.ts (@NgModule imports excerpt)" linenums="false"></code-example>
|
||||
Animations can be defined inside `@Component` metadata.
|
||||
|
||||
<code-example path="animations/src/app/hero-list-basic.component.ts" region="imports" title="hero-list-basic.component.ts" linenums="false"></code-example>
|
||||
|
||||
|
@ -2,13 +2,13 @@
|
||||
|
||||
This page presents design and layout guidelines for Angular documentation pages. These guidelines should be followed by all guide page authors. Deviations must be approved by the documentation editor.
|
||||
|
||||
Most guide pages should have [accompanying sample code](#from-code-samples) with
|
||||
[special markup](#source-code-markup) for the code snippets on the page.
|
||||
Code samples should adhere to the
|
||||
[style guide for Angular applications](guide/styleguide "Application Code Style Guide")
|
||||
Most guide pages should have [accompanying sample code](#from-code-samples) with
|
||||
[special markup](#source-code-markup) for the code snippets on the page.
|
||||
Code samples should adhere to the
|
||||
[style guide for Angular applications](guide/styleguide "Application Code Style Guide")
|
||||
because readers expect consistency.
|
||||
|
||||
For clarity and precision, every guideline on _this_ page is illustrated with a working example,
|
||||
For clarity and precision, every guideline on _this_ page is illustrated with a working example,
|
||||
followed by the page markup for that example ... as shown here.
|
||||
|
||||
```html
|
||||
@ -19,13 +19,13 @@ followed by the page markup for that example ... as shown here.
|
||||
|
||||
To make changes to the documentation pages and sample code, clone the [Angular github repository](https://github.com/angular/angular "Angular repo") and go to the `aio/` folder.
|
||||
|
||||
The [aio/README.md](https://github.com/angular/angular/blob/master/aio/README.md "AIO ReadMe") explains how to install and use the tools to edit and test your changes.
|
||||
The [aio/README.md](https://github.com/angular/angular/blob/master/aio/README.md "AIO ReadMe") explains how to install and use the tools to edit and test your changes.
|
||||
|
||||
Here are a few essential commands for guide page authors.
|
||||
|
||||
1. `yarn setup` — installs packages; builds docs, plunkers, and zips.
|
||||
|
||||
1. `yarn docs-watch -- --watch-only` — watches for saved content changes and refreshes the browser. The (optional) `--watch-only` flag skips the initial docs rebuild.
|
||||
1. `yarn docs-watch --watch-only` — watches for saved content changes and refreshes the browser. The (optional) `--watch-only` flag skips the initial docs rebuild.
|
||||
|
||||
1. `yarn start` — starts the doc viewer application so you can see your local changes in the browser.
|
||||
|
||||
@ -33,9 +33,9 @@ Here are a few essential commands for guide page authors.
|
||||
|
||||
## Guide pages
|
||||
|
||||
All but a few guide pages are [markdown](https://daringfireball.net/projects/markdown/syntax "markdown") files with an `.md` extension.
|
||||
All but a few guide pages are [markdown](https://daringfireball.net/projects/markdown/syntax "markdown") files with an `.md` extension.
|
||||
|
||||
Every guide page file is stored in the `content/guide` directory. Although the [side navigation](#navigation) panel displays as a hierarchy, the directory is flat with no sub-folders.
|
||||
Every guide page file is stored in the `content/guide` directory. Although the [side navigation](#navigation) panel displays as a hierarchy, the directory is flat with no sub-folders.
|
||||
The flat folder approach allows us to shuffle the apparent navigation structure without moving page files or redirecting old page URLs.
|
||||
|
||||
The doc generation process consumes the markdown files in the `content/guide` directory and produces JSON files in the `src/generated/docs/guide` directory, which is also flat. Those JSON files contain a combination of document metadata and HTML content.
|
||||
@ -52,7 +52,7 @@ _Tutorial_ pages are exactly like guide pages. The only difference is that they
|
||||
_API_ pages are generated from Angular source code into the `src/generated/docs/api` directory.
|
||||
The doc viewer translates URLs that begin `api/` into requests for document JSON files in that directory. This style guide does not discuss creation or maintenance of API pages.
|
||||
|
||||
_Marketing_ pages are similar to guide pages. They're located in the `content/marketing` directory. While they can be markdown files, they may be static HTML pages or dynamic HTML pages that render with JSON data.
|
||||
_Marketing_ pages are similar to guide pages. They're located in the `content/marketing` directory. While they can be markdown files, they may be static HTML pages or dynamic HTML pages that render with JSON data.
|
||||
|
||||
Only a few people are authorized to write marketing pages. This style guide does not discuss creation or maintenance of marketing pages.
|
||||
|
||||
@ -70,14 +70,14 @@ Standard markdown processors don't allow you to put markdown _within_ HTML tags.
|
||||
|
||||
<div class="alert is-critical">
|
||||
|
||||
**Always** follow every opening and closing HTML tag with _a blank line_.
|
||||
**Always** follow every opening and closing HTML tag with _a blank line_.
|
||||
|
||||
</div>
|
||||
|
||||
```html
|
||||
<div class="alert is-critical">
|
||||
|
||||
**Always** follow every opening and closing HTML tag with _a blank line_.
|
||||
**Always** follow every opening and closing HTML tag with _a blank line_.
|
||||
|
||||
</div>
|
||||
```
|
||||
@ -111,7 +111,7 @@ Title text should be in "Title Case", which means that you use capital letters t
|
||||
|
||||
## Sections
|
||||
|
||||
A typical document is divided into sections.
|
||||
A typical document is divided into sections.
|
||||
|
||||
All section heading text should be in "Sentence case", which means the first word is capitalized and all other words are lower case.
|
||||
|
||||
@ -122,7 +122,7 @@ Main section heading
|
||||
</h2>
|
||||
There are usually one or more main sections that may be further divided into secondary sections.
|
||||
|
||||
Begin a main section heading with the markdown `##` characters. Alternatively, you can write the equivalent `<h2>` HTML tag.
|
||||
Begin a main section heading with the markdown `##` characters. Alternatively, you can write the equivalent `<h2>` HTML tag.
|
||||
|
||||
The main section heading should be followed by a blank line and then the content for that heading.
|
||||
|
||||
@ -164,7 +164,7 @@ Try to minimize the heading depth, preferably only two. But more headings, such
|
||||
|
||||
Subsections typically present extra detail and references to other pages.
|
||||
|
||||
Use subsections for commentary that _enriches_ the reader's understanding of the text that precedes it.
|
||||
Use subsections for commentary that _enriches_ the reader's understanding of the text that precedes it.
|
||||
|
||||
A subsection _must not_ contain anything _essential_ to that understanding. Don't put a critical instruction or a tutorial step in a subsection.
|
||||
|
||||
@ -192,7 +192,7 @@ Note that at least one blank line must follow the opening `<div>`. A blank line
|
||||
|
||||
Most pages display a table of contents (TOC). The TOC appears in the right panel when the viewport is wide. When narrow, the TOC appears in an expandable/collapsible region near the top of the page.
|
||||
|
||||
You should not create your own TOC by hand. The TOC is generated automatically from the page's main and secondary section headers.
|
||||
You should not create your own TOC by hand. The TOC is generated automatically from the page's main and secondary section headers.
|
||||
|
||||
To exclude a heading from the TOC, create the heading as an `<h2>` or `<h3>` element with a class called 'no-toc'. You can't do this with markdown.
|
||||
|
||||
@ -214,7 +214,7 @@ A guide without a TOC
|
||||
|
||||
The navigation links at the top, left, and bottom of the screen are generated from the JSON configuration file, `content/navigation.json`.
|
||||
|
||||
The authority to change the `navigation.json` file is limited to a few core team members.
|
||||
The authority to change the `navigation.json` file is limited to a few core team members.
|
||||
But for a new guide page, you should suggest a navigation title and position in the left-side navigation panel called the "side nav".
|
||||
|
||||
Look for the `SideNav` node in `navigation.json`. The `SideNav` node is an array of navigation nodes. Each node is either an _item_ node for a single document or a _header_ node with child nodes.
|
||||
@ -302,7 +302,7 @@ For terminal input and output, put the content between `<code-example>` tags, se
|
||||
|
||||
Inline, hand-coded snippets like this one are _not_ testable and, therefore, are intrinsically unreliable.
|
||||
This example belongs to the small set of pre-approved, inline snippets that includes
|
||||
user input in a command shell or the _output_ of some process.
|
||||
user input in a command shell or the _output_ of some process.
|
||||
|
||||
**Do not write inline code snippets** unless you have a good reason and the editor's permission to do so.
|
||||
In all other cases, code snippets should be generated automatically from tested code samples.
|
||||
@ -341,26 +341,43 @@ The following _code-example_ displays the sample's `app.module.ts`.
|
||||
Here's the brief markup that produced that lengthy snippet:
|
||||
|
||||
```html
|
||||
<code-example
|
||||
path="docs-style-guide/src/app/app.module.ts"
|
||||
<code-example
|
||||
path="docs-style-guide/src/app/app.module.ts"
|
||||
title="src/app/app.module.ts">
|
||||
</code-example>
|
||||
```
|
||||
|
||||
You identified the snippet's source file by setting the `path` attribute to sample folder's location _within_ `content/examples`.
|
||||
In this example, that path is `docs-style-guide/src/app/app.module.ts`.
|
||||
In this example, that path is `docs-style-guide/src/app/app.module.ts`.
|
||||
|
||||
You added a header to tell the reader where to find the file by setting the `title` attribute.
|
||||
Following convention, you set the `title` attribute to the file's location within the sample's root folder.
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
Unless otherwise noted, all code snippets in this page are derived from sample source code
|
||||
Unless otherwise noted, all code snippets in this page are derived from sample source code
|
||||
located in the `content/examples/docs-style-guide` directory.
|
||||
|
||||
</div>
|
||||
|
||||
#### Code-example attributes
|
||||
<div class="alert is-important">
|
||||
|
||||
The doc tooling reports an error if the file identified in the path does not exist **or is _git_-ignored**.
|
||||
|
||||
Most `.js` files are _git_-ignored.
|
||||
If you want to include an ignored code file in your project and display it in a guide you must _un-ignore_ it.
|
||||
|
||||
The preferred way to un-ignore a file is to update the `content/examples/.gitignore` like this:
|
||||
|
||||
<code-example title="content/examples/.gitignore">
|
||||
# my-guide
|
||||
!my-guide/src/something.js
|
||||
!my-guide/more-javascript*.js
|
||||
</code-example>
|
||||
|
||||
</div>
|
||||
|
||||
#### Code-example attributes
|
||||
|
||||
You control the _code-example_ output by setting one or more of its attributes:
|
||||
|
||||
@ -384,8 +401,8 @@ You control the _code-example_ output by setting one or more of its attributes:
|
||||
|
||||
Often you want to focus on a fragment of code within a sample code file. In this example, you focus on the `AppModule` class and its `NgModule` metadata.
|
||||
|
||||
<code-example
|
||||
path="docs-style-guide/src/app/app.module.ts"
|
||||
<code-example
|
||||
path="docs-style-guide/src/app/app.module.ts"
|
||||
region="class">
|
||||
</code-example>
|
||||
|
||||
@ -394,8 +411,8 @@ Then you reference that _docregion_ in the `region` attribute of the `<code-exam
|
||||
|
||||
|
||||
```html
|
||||
<code-example
|
||||
path="docs-style-guide/src/app/app.module.ts"
|
||||
<code-example
|
||||
path="docs-style-guide/src/app/app.module.ts"
|
||||
region="class">
|
||||
</code-example>
|
||||
```
|
||||
@ -413,20 +430,20 @@ There's no need to repeat the header.
|
||||
|
||||
Sometimes you want to display an example of bad code or bad design.
|
||||
|
||||
You should be careful. Readers don't always read carefully and are likely to copy and paste your example of bad code in their own applications. So don't display bad code often.
|
||||
You should be careful. Readers don't always read carefully and are likely to copy and paste your example of bad code in their own applications. So don't display bad code often.
|
||||
|
||||
When you do, set the `class` to `avoid`. The code snippet will be framed in bright red to grab the reader's attention.
|
||||
|
||||
Here's the markup for an "avoid" example in the
|
||||
Here's the markup for an "avoid" example in the
|
||||
[_Angular Style Guide_](guide/styleguide#style-05-03 "Style 05-03: components as elements").
|
||||
|
||||
```html
|
||||
<code-example
|
||||
path="styleguide/src/05-03/app/heroes/shared/hero-button/hero-button.component.avoid.ts"
|
||||
region="example"
|
||||
<code-example
|
||||
path="styleguide/src/05-03/app/heroes/shared/hero-button/hero-button.component.avoid.ts"
|
||||
region="example"
|
||||
title="app/heroes/hero-button/hero-button.component.ts">
|
||||
</code-example>
|
||||
```
|
||||
```
|
||||
|
||||
<code-example path="styleguide/src/05-03/app/heroes/shared/hero-button/hero-button.component.avoid.ts" region="example" title="app/heroes/hero-button/hero-button.component.ts">
|
||||
</code-example>
|
||||
@ -450,22 +467,22 @@ The next example displays multiple code tabs, each with its own title.
|
||||
It demonstrates control over display of line numbers at both the `<code-tabs>` and `<code-pane>` levels.
|
||||
|
||||
<code-tabs linenums="false">
|
||||
<code-pane
|
||||
title="app.component.html"
|
||||
<code-pane
|
||||
title="app.component.html"
|
||||
path="docs-style-guide/src/app/app.component.html">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
title="app.component.ts"
|
||||
<code-pane
|
||||
title="app.component.ts"
|
||||
path="docs-style-guide/src/app/app.component.ts"
|
||||
linenums="true">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
title="app.component.css (heroes)"
|
||||
path="docs-style-guide/src/app/app.component.css"
|
||||
<code-pane
|
||||
title="app.component.css (heroes)"
|
||||
path="docs-style-guide/src/app/app.component.css"
|
||||
region="heroes">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
title="package.json (scripts)"
|
||||
<code-pane
|
||||
title="package.json (scripts)"
|
||||
path="docs-style-guide/package.1.json">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
@ -477,22 +494,22 @@ The `linenums` attribute in the second pane restores line numbering for _itself
|
||||
|
||||
```html
|
||||
<code-tabs linenums="false">
|
||||
<code-pane
|
||||
title="app.component.html"
|
||||
<code-pane
|
||||
title="app.component.html"
|
||||
path="docs-style-guide/src/app/app.component.html">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
title="app.component.ts"
|
||||
<code-pane
|
||||
title="app.component.ts"
|
||||
path="docs-style-guide/src/app/app.component.ts"
|
||||
linenums="true">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
title="app.component.css (heroes)"
|
||||
path="docs-style-guide/src/app/app.component.css"
|
||||
<code-pane
|
||||
title="app.component.css (heroes)"
|
||||
path="docs-style-guide/src/app/app.component.css"
|
||||
region="heroes">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
title="package.json (scripts)"
|
||||
<code-pane
|
||||
title="package.json (scripts)"
|
||||
path="docs-style-guide/package.1.json">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
@ -555,8 +572,8 @@ Every line of code _after_ that comment belongs in the region _until_ the code f
|
||||
|
||||
The `src/main.ts` is a simple example of a file with a single _#docregion_ at the top of the file.
|
||||
|
||||
<code-example
|
||||
path="docs-style-guide/src/main.ts"
|
||||
<code-example
|
||||
path="docs-style-guide/src/main.ts"
|
||||
title="src/main.ts"></code-example>
|
||||
|
||||
</div>
|
||||
@ -575,8 +592,8 @@ You distinguish among them by giving each fragment its own _#docregion name_ as
|
||||
Remember to refer to this region by name in the `region` attribute of the `<code-example>` or `<code-pane>` as you did in an example above like this:
|
||||
|
||||
```html
|
||||
<code-example
|
||||
path="docs-style-guide/src/app/app.module.ts"
|
||||
<code-example
|
||||
path="docs-style-guide/src/app/app.module.ts"
|
||||
region="class"></code-example>
|
||||
```
|
||||
|
||||
@ -586,7 +603,7 @@ The _#docregion_ with no name is the _default region_. Do _not_ set the `region`
|
||||
|
||||
You can nest _#docregions_ within _#docregions_
|
||||
```
|
||||
// #docregion
|
||||
// #docregion
|
||||
... some code ...
|
||||
// #docregion inner-region
|
||||
... more code ...
|
||||
@ -630,12 +647,12 @@ export class AppComponent {
|
||||
Here's are the two corresponding code snippets displayed side-by-side.
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
title="app.component.ts (class)"
|
||||
<code-pane
|
||||
title="app.component.ts (class)"
|
||||
path="docs-style-guide/src/app/app.component.ts"
|
||||
region="class">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
<code-pane
|
||||
title="app.component.ts (class-skeleton)"
|
||||
path="docs-style-guide/src/app/app.component.ts"
|
||||
region="class-skeleton">
|
||||
@ -644,7 +661,7 @@ Here's are the two corresponding code snippets displayed side-by-side.
|
||||
|
||||
Some observations:
|
||||
|
||||
* The `#docplaster` at the top is another bit of code snippet markup. It tells the processor how to join the fragments into a single snippet.
|
||||
* The `#docplaster` at the top is another bit of code snippet markup. It tells the processor how to join the fragments into a single snippet.
|
||||
|
||||
In this example, we tell the processor to put the fragments together without anything in between - without any "plaster". Most sample files define this _empty plaster_.
|
||||
|
||||
@ -656,7 +673,7 @@ Some observations:
|
||||
|
||||
Code snippet markup is not supported for JSON files because comments are forbidden in JSON files.
|
||||
|
||||
You can display an entire JSON file by referring to it in the `src` attribute.
|
||||
You can display an entire JSON file by referring to it in the `src` attribute.
|
||||
But you can't display JSON fragments because you can't add `#docregion` tags to the file.
|
||||
|
||||
If the JSON file is too big, you could copy the nodes-of-interest into markdown backticks.
|
||||
@ -667,12 +684,12 @@ You can't test this partial file and you'll never use it in the application. But
|
||||
|
||||
Here's an example that excerpts certain scripts from `package.json` into a partial file named `package.1.json`.
|
||||
|
||||
<code-example
|
||||
<code-example
|
||||
path="docs-style-guide/package.1.json"
|
||||
title="package.json (selected scripts)"></code-example>
|
||||
|
||||
```html
|
||||
<code-example
|
||||
<code-example
|
||||
path="docs-style-guide/package.1.json"
|
||||
title="package.json (selected scripts)"></code-example>
|
||||
```
|
||||
@ -696,7 +713,7 @@ You'll find many such files among the samples in the Angular documentation.
|
||||
|
||||
Remember to exclude these files from plunkers by listing them in the `plnkr.json` as illustrated here.
|
||||
|
||||
<code-example
|
||||
<code-example
|
||||
path="docs-style-guide/plnkr.json"
|
||||
title="plnkr.json"></code-example>
|
||||
|
||||
@ -705,7 +722,7 @@ Remember to exclude these files from plunkers by listing them in the `plnkr.json
|
||||
|
||||
By adding `<live-example>` to the page you generate links that run sample code in the Plunker live coding environment and download that code to the reader's file system.
|
||||
|
||||
Live examples (AKA "plunkers") are defined by one or more `plnkr.json` files in the root of a code sample folder. Each sample folder usually has a single unnamed definition file, the default `plnkr.json`.
|
||||
Live examples (AKA "plunkers") are defined by one or more `plnkr.json` files in the root of a code sample folder. Each sample folder usually has a single unnamed definition file, the default `plnkr.json`.
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
@ -728,7 +745,7 @@ Clicking the first link opens the code sample in a new browser tab in the "embed
|
||||
You can change the appearance and behavior of the live example with attributes and classes.
|
||||
|
||||
|
||||
<h3 class="no-toc">Custom label and tooltip</h3>
|
||||
<h3 class="no-toc">Custom label and tooltip</h3>
|
||||
|
||||
Give the live example anchor a custom label and tooltip by setting the `title` attribute.
|
||||
|
||||
@ -746,7 +763,7 @@ You can achieve the same effect by putting the label between the `<live-example>
|
||||
<live-example>Live example with content label</live-example>
|
||||
```
|
||||
|
||||
<h3 class="no-toc">Live example from another guide</h3>
|
||||
<h3 class="no-toc">Live example from another guide</h3>
|
||||
|
||||
To link to a plunker in a folder whose name is not the same as the current guide page, set the `name` attribute to the name of that folder.
|
||||
|
||||
@ -756,7 +773,7 @@ To link to a plunker in a folder whose name is not the same as the current guide
|
||||
<live-example name="router">Live Example from the Router guide</live-example>
|
||||
```
|
||||
|
||||
<h3 class="no-toc">Live Example for named plunker</h3>
|
||||
<h3 class="no-toc">Live Example for named plunker</h3>
|
||||
|
||||
To link to a plunker defined by a named `plnkr.json` file, set the `plnkr` attribute. The following example links to the plunker defined by `second.plnkr.json` in the current guide's directory.
|
||||
|
||||
@ -766,7 +783,7 @@ To link to a plunker defined by a named `plnkr.json` file, set the `plnkr` attri
|
||||
<live-example plnkr="second"></live-example>
|
||||
```
|
||||
|
||||
<h3 class="no-toc">Live Example without download</h3>
|
||||
<h3 class="no-toc">Live Example without download</h3>
|
||||
|
||||
To skip the download link, add the `noDownload` attribute.
|
||||
|
||||
@ -776,7 +793,7 @@ To skip the download link, add the `noDownload` attribute.
|
||||
<live-example noDownload>Just the plunker</live-example>
|
||||
```
|
||||
|
||||
<h3 class="no-toc">Live Example with download-only</h3>
|
||||
<h3 class="no-toc">Live Example with download-only</h3>
|
||||
|
||||
To skip the live plunker link and only link to the download, add the `downloadOnly` attribute.
|
||||
|
||||
@ -786,9 +803,9 @@ To skip the live plunker link and only link to the download, add the `downloadOn
|
||||
<live-example downloadOnly>Download only</live-example>
|
||||
```
|
||||
|
||||
<h3 class="no-toc">Embedded live example</h3>
|
||||
<h3 class="no-toc">Embedded live example</h3>
|
||||
|
||||
By default, a live example link opens a plunker in a separate browser tab.
|
||||
By default, a live example link opens a plunker in a separate browser tab.
|
||||
You can embed the plunker within the guide page itself by adding the `embedded` attribute.
|
||||
|
||||
For performance reasons, the plunker does not start right away. The reader sees an image instead. Clicking the image starts the sometimes-slow process of launching the embedded plunker within an iframe on the page.
|
||||
@ -836,10 +853,10 @@ When navigating within the page, you can omit the page URL when specifying the l
|
||||
|
||||
It is often a good idea to *lock-in* a good anchor name.
|
||||
|
||||
Sometimes the section header text makes for an unattractive anchor. [This one](#ugly-long-section-header-anchors) is pretty bad.
|
||||
Sometimes the section header text makes for an unattractive anchor. [This one](#ugly-long-section-header-anchors) is pretty bad.
|
||||
|
||||
```html
|
||||
[This one](#ugly-long-section-header-anchors) is pretty bad.
|
||||
[This one](#ugly-long-section-header-anchors) is pretty bad.
|
||||
```
|
||||
|
||||
The greater danger is that **a future rewording of the header text would break** a link to this section.
|
||||
@ -905,7 +922,7 @@ A helpful, informational alert.
|
||||
```
|
||||
|
||||
Alerts are meant to grab the user's attention and should be used sparingly.
|
||||
They are not for casual asides or commentary. Use [subsections](#subsections "subsections") for commentary.
|
||||
They are not for casual asides or commentary. Use [subsections](#subsections "subsections") for commentary.
|
||||
|
||||
## Callouts
|
||||
|
||||
@ -1146,9 +1163,9 @@ src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
|
||||
**Do not use the markdown image syntax, \!\[\.\.\.\]\(\.\.\.\).**
|
||||
|
||||
Images should be specified in an `<img>` tag.
|
||||
Images should be specified in an `<img>` tag.
|
||||
|
||||
For accessibility, always set the `alt` attribute with a meaningful description of the image.
|
||||
For accessibility, always set the `alt` attribute with a meaningful description of the image.
|
||||
|
||||
You should nest the `<img>` tag within a `<figure>` tag, which styles the image within a drop-shadow frame. You'll need the editor's permission to skip the `<figure>` tag.
|
||||
|
||||
@ -1160,7 +1177,7 @@ Here's a conforming example
|
||||
|
||||
```html
|
||||
<figure>
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying hero">
|
||||
</figure>
|
||||
```
|
||||
@ -1180,13 +1197,13 @@ Here's the "flying hero" at a more reasonable scale.
|
||||
```html
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying Angular hero"
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying Angular hero"
|
||||
width="200">
|
||||
</figure>
|
||||
```
|
||||
|
||||
Wide images can be a problem. Most browsers try to rescale the image but wide images may overflow the document in certain viewports.
|
||||
Wide images can be a problem. Most browsers try to rescale the image but wide images may overflow the document in certain viewports.
|
||||
|
||||
**Do not set a width greater than 700px**. If you wish to display a larger image, provide a link to the actual image that the user can click on to see the full size image separately as in this example of `source-map-explorer` output from the "Ahead-of-time Compilation" guide:
|
||||
|
||||
@ -1205,11 +1222,11 @@ Consider using an image compression web site such as [tinypng](https://tinypng.c
|
||||
|
||||
You can float the image to the left or right of text by applying the class="left" or class="right" attributes respectively.
|
||||
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying Angular hero"
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying Angular hero"
|
||||
width="200"
|
||||
class="left">
|
||||
|
||||
|
||||
This text wraps around to the right of the floating "flying hero" image.
|
||||
|
||||
Headings and code-examples automatically clear a floating image. If you need to force a piece of text to clear a floating image, add `<br class="clear">` where the text should break.
|
||||
@ -1219,11 +1236,11 @@ Headings and code-examples automatically clear a floating image. If you need to
|
||||
The markup for the above example is:
|
||||
|
||||
```html
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying Angular hero"
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying Angular hero"
|
||||
width="200"
|
||||
class="left">
|
||||
|
||||
|
||||
This text wraps around to the right of the floating "flying hero" image.
|
||||
|
||||
Headings and code-examples automatically clear a floating image. If you need to force a piece of text to clear a floating image, add `<br class="clear">` where the text should break.
|
||||
@ -1239,8 +1256,8 @@ If you have a floating image inside an alert, callout, or a subsection, it is a
|
||||
|
||||
<div class="l-sub-section clear-fix">
|
||||
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying Angular hero"
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying Angular hero"
|
||||
width="100"
|
||||
class="right">
|
||||
|
||||
@ -1251,12 +1268,12 @@ If you have a floating image inside an alert, callout, or a subsection, it is a
|
||||
```html
|
||||
<div class="l-sub-section clear-fix">
|
||||
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying Angular hero"
|
||||
<img src="generated/images/guide/docs-style-guide/flying-hero.png"
|
||||
alt="flying Angular hero"
|
||||
width="100"
|
||||
class="right">
|
||||
|
||||
|
||||
A subsection with **markdown** formatted text.
|
||||
|
||||
</div>
|
||||
```
|
||||
```
|
||||
|
@ -139,6 +139,10 @@ null if the control value is valid _or_ a validation error object.
|
||||
The validation error object typically has a property whose name is the validation key, `'forbiddenName'`,
|
||||
and whose value is an arbitrary dictionary of values that you could insert into an error message, `{name}`.
|
||||
|
||||
Custom async validators are similar to sync validators, but they must instead return a Promise or Observable
|
||||
that later emits null or a validation error object. In the case of an Observable, the Observable must complete,
|
||||
at which point the form uses the last value emitted for validation.
|
||||
|
||||
### Adding to reactive forms
|
||||
|
||||
In reactive forms, custom validators are fairly simple to add. All you have to do is pass the function directly
|
||||
|
@ -117,7 +117,7 @@ Now the extractor tool and compiler will generate a translation unit with _your
|
||||
<code-example path="i18n/src/locale/messages.es.xlf.html" region="custom-id" linenums="false">
|
||||
</code-example>
|
||||
|
||||
Here is the `i18n` attribute with a _definition_, followed by the custom `id`:
|
||||
Here is the `i18n` attribute with a _description_, followed by the custom `id`:
|
||||
|
||||
<code-example path='i18n/src/app/app.component.1.html' region='i18n-attribute-id' title='app/app.component.html' linenums="false">
|
||||
</code-example>
|
||||
|
@ -83,4 +83,3 @@ you'll have to provide a different `Title` service that understands
|
||||
the concept of a "document title" for that specific platform.
|
||||
Ideally, the application itself neither knows nor cares about the runtime environment.
|
||||
|
||||
[Back to top](guide/set-document-title#top)
|
||||
|
@ -316,10 +316,13 @@ which `NgFor` has initialized with the hero for the current iteration.
|
||||
* The [API guide](api/common/NgFor "API: NgFor")
|
||||
describes additional `NgFor` directive properties and context properties.
|
||||
|
||||
* `NgFor` is implemented by the `NgForOf` directive. Read more about additional `NgForOf` directive properties and context properties [NgForOf API reference](api/common/NgForOf).
|
||||
|
||||
|
||||
These microsyntax mechanisms are available to you when you write your own structural directives.
|
||||
Studying the
|
||||
[source code for `NgIf`](https://github.com/angular/angular/blob/master/packages/common/src/directives/ng_if.ts "Source: NgIf")
|
||||
and [`NgFor`](https://github.com/angular/angular/blob/master/packages/common/src/directives/ng_for_of.ts "Source: NgFor")
|
||||
and [`NgForOf`](https://github.com/angular/angular/blob/master/packages/common/src/directives/ng_for_of.ts "Source: NgForOf")
|
||||
is a great way to learn more.
|
||||
|
||||
|
||||
|
@ -4502,8 +4502,8 @@ helps instantly identify which members of the component serve which purpose.
|
||||
|
||||
**Why?** The property associated with `@HostBinding` or the method associated with `@HostListener`
|
||||
can be modified only in a single place—in the directive's class.
|
||||
If you use the `host` metadata property, you must modify both the property declaration inside the controller,
|
||||
and the metadata associated with the directive.
|
||||
If you use the `host` metadata property, you must modify both the property/method declaration in the
|
||||
directive's class and the metadata in the decorator associated with the directive.
|
||||
|
||||
|
||||
</div>
|
||||
|
@ -37,7 +37,6 @@ You can extend the HTML vocabulary of your templates with components and directi
|
||||
In the following sections, you'll learn how to get and set DOM (Document Object Model) values dynamically through data binding.
|
||||
|
||||
Begin with the first form of data binding—interpolation—to see how much richer template HTML can be.
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -81,7 +80,7 @@ Though this is not exactly true. Interpolation is a special syntax that Angular
|
||||
|
||||
But first, let's take a closer look at template expressions and statements.
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -143,11 +142,10 @@ The `hero` in `{{hero.name}}`
|
||||
refers to the template input variable, not the component's property.
|
||||
|
||||
Template expressions cannot refer to anything in
|
||||
the global namespace. They can't refer to `window` or `document`. They
|
||||
the global namespace (except `undefined`). They can't refer to `window` or `document`. They
|
||||
can't call `console.log` or `Math.max`. They are restricted to referencing
|
||||
members of the expression context.
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
{@a no-side-effects}
|
||||
|
||||
@ -204,7 +202,7 @@ Dependent values should not change during a single turn of the event loop.
|
||||
If an idempotent expression returns a string or a number, it returns the same string or number
|
||||
when called twice in a row. If the expression returns an object (including an `array`),
|
||||
it returns the same object *reference* when called twice in a row.
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -276,7 +274,6 @@ A method call or simple property assignment should be the norm.
|
||||
Now that you have a feel for template expressions and statements,
|
||||
you're ready to learn about the varieties of data binding syntax beyond interpolation.
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -585,7 +582,6 @@ The following table summarizes:
|
||||
</table>
|
||||
|
||||
With this broad view in mind, you're ready to look at binding types in detail.
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -773,7 +769,6 @@ content harmlessly.
|
||||
<img src='generated/images/guide/template-syntax/evil-title.png' alt="evil title made safe">
|
||||
</figure>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -847,7 +842,6 @@ is to set ARIA attributes, as in this example:
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="attrib-binding-aria" title="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -885,7 +879,6 @@ the [NgClass directive](guide/template-syntax#ngClass) is usually preferred when
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -921,8 +914,6 @@ Note that a _style property_ name can be written in either
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
{@a event-binding}
|
||||
@ -1053,7 +1044,6 @@ Deleting the hero updates the model, perhaps triggering other changes
|
||||
including queries and saves to a remote server.
|
||||
These changes percolate through the system and are ultimately displayed in this and other views.
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1118,7 +1108,6 @@ However, no native HTML element follows the `x` value and `xChange` event patter
|
||||
|
||||
Fortunately, the Angular [_NgModel_](guide/template-syntax#ngModel) directive is a bridge that enables two-way binding to form elements.
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1162,7 +1151,7 @@ This section is an introduction to the most commonly used attribute directives:
|
||||
* [`NgClass`](guide/template-syntax#ngClass) - add and remove a set of CSS classes
|
||||
* [`NgStyle`](guide/template-syntax#ngStyle) - add and remove a set of HTML styles
|
||||
* [`NgModel`](guide/template-syntax#ngModel) - two-way data binding to an HTML form element
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1203,8 +1192,6 @@ It's up to you to call `setCurrentClassess()`, both initially and when the depen
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
{@a ngStyle}
|
||||
@ -1241,7 +1228,6 @@ It's up to you to call `setCurrentStyles()`, both initially and when the depende
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1335,8 +1321,6 @@ Here are all variations in action, including the uppercase version:
|
||||
<img src='generated/images/guide/template-syntax/ng-model-anim.gif' alt="NgModel variations">
|
||||
</figure>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
{@a structural-directives}
|
||||
@ -1431,7 +1415,6 @@ described below.
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1511,8 +1494,8 @@ The next example captures the `index` in a variable named `i` and displays it wi
|
||||
|
||||
<div class="l-sub-section">
|
||||
|
||||
Learn about the other `NgFor` context values such as `last`, `even`,
|
||||
and `odd` in the [NgFor API reference](api/common/NgFor).
|
||||
`NgFor` is implemented by the `NgForOf` directive. Read more about the other `NgForOf` context values such as `last`, `even`,
|
||||
and `odd` in the [NgForOf API reference](api/common/NgForOf).
|
||||
|
||||
</div>
|
||||
|
||||
@ -1553,7 +1536,6 @@ Here is an illustration of the _trackBy_ effect.
|
||||
<img src="generated/images/guide/template-syntax/ng-for-track-by-anim.gif" alt="trackBy">
|
||||
</figure>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1601,8 +1583,6 @@ For example, you could replace the `<confused-hero>` switch case with the follow
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="NgSwitch-div" title="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
{@a template-reference-variable}
|
||||
@ -1673,7 +1653,6 @@ This example declares the `fax` variable as `ref-fax` instead of `#fax`.
|
||||
<code-example path="template-syntax/src/app/app.component.html" region="ref-fax" title="src/app/app.component.html" linenums="false">
|
||||
</code-example>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1810,7 +1789,6 @@ the directive property name on the *left* and the public alias on the *right*:
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1861,7 +1839,6 @@ The generated output would look something like this
|
||||
"rate": 325 }
|
||||
</code-example>
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1935,7 +1912,6 @@ The display is blank, but the app keeps rolling without errors.
|
||||
|
||||
It works perfectly with long property paths such as `a?.b?.c?.d`.
|
||||
|
||||
<a href="#top-of-page">back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
|
@ -18,7 +18,7 @@ The sample application and all tests in this guide are available as live example
|
||||
* <live-example name="setup" plnkr="quickstart-specs" embedded-style>The QuickStart seed's AppComponent spec</live-example>.
|
||||
* <live-example embedded-style>The sample application to be tested</live-example>.
|
||||
* <live-example plnkr="app-specs" embedded-style>All specs that test the sample application</live-example>.
|
||||
* <live-example plnkr="bag-specs" embedded-style>A grab bag of additional specs</live-example>.<a href="#top" class='to-top'>Back to top</a>
|
||||
* <live-example plnkr="bag-specs" embedded-style>A grab bag of additional specs</live-example>.
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -199,7 +199,6 @@ A comprehensive review of the Angular testing utilities appears [later in this g
|
||||
|
||||
But first you should write a dummy test to verify that your test environment is set up properly
|
||||
and to lock in a few basic testing skills.
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -365,7 +364,6 @@ Debug specs in the browser in the same way that you debug an application.
|
||||
|
||||
You can also try this test as a <live-example plnkr="1st-specs" title="First spec" embedded-style></live-example> in plunker.
|
||||
All of the tests in this guide are available as [live examples](guide/testing#live-examples "Live examples of these tests").
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -631,7 +629,7 @@ There is no harm in calling `detectChanges()` more often than is strictly necess
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -800,7 +798,7 @@ The tests in this guide only call `compileComponents` when necessary.
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -966,7 +964,6 @@ In a spec with multiple expectations, it can help clarify what went wrong and wh
|
||||
The remaining tests confirm the logic of the component when the service returns different values.
|
||||
The second test validates the effect of changing the user name.
|
||||
The third test checks that the component displays the proper message when there is no logged-in user.
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1202,7 +1199,7 @@ and `fakeAsync`, is a viable and occasionally necessary technique.
|
||||
For example, you can't call `async` or `fakeAsync` when testing
|
||||
code that involves the `intervalTimer`, as is common when
|
||||
testing async `Observable` methods.
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1482,7 +1479,7 @@ The tests themselves are almost identical to the stand-alone version:
|
||||
|
||||
Only the selected event test differs. It confirms that the selected `DashboardHeroComponent` hero
|
||||
really does find its way up through the event binding to the host component.
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1782,7 +1779,6 @@ Inspect and download _all_ of the guide's application test code with this <live-
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1848,7 +1844,7 @@ Here are a few more `HeroDetailComponent` tests to drive the point home.
|
||||
|
||||
</code-example>
|
||||
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -1945,8 +1941,6 @@ especially when the feature module is small and mostly self-contained, as featur
|
||||
|
||||
|
||||
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
|
||||
@ -2104,7 +2098,6 @@ The `TestBed` offers similar `overrideDirective`, `overrideModule`, and `overrid
|
||||
for digging into and replacing parts of these other classes.
|
||||
|
||||
Explore the options and combinations on your own.
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -2272,8 +2265,6 @@ tests with the `RouterTestingModule`.
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
|
||||
@ -2327,7 +2318,6 @@ such as misspelled or misused components and directives.
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -2427,7 +2417,7 @@ The test for the default color uses the injector of the second `<h2>` to get its
|
||||
and its `defaultColor`.
|
||||
|
||||
* `DebugElement.properties` affords access to the artificial custom property that is set by the directive.
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -2655,7 +2645,6 @@ may require meticulous preparation with the Angular testing utilities.
|
||||
On the other hand, isolated unit tests can't confirm that the `ButtonComp` is
|
||||
properly bound to its template or even data bound at all.
|
||||
Use Angular tests for that.
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<hr/>
|
||||
|
||||
@ -3642,8 +3631,6 @@ The Angular `By` class has three static methods for common predicates:
|
||||
|
||||
</code-example>
|
||||
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<div class='l' class='hr'>
|
||||
|
||||
</div>
|
||||
@ -3792,7 +3779,6 @@ The sample tests are written to run in Jasmine and karma.
|
||||
The two "fast path" setups added the appropriate Jasmine and karma npm packages to the
|
||||
`devDependencies` section of the `package.json`.
|
||||
They're installed when you run `npm install`.
|
||||
<a href="#top" class='to-top'>Back to top</a>
|
||||
|
||||
<div class='l' class='hr'>
|
||||
|
||||
|
BIN
aio/content/images/marketing/home/angular-connect.png
Normal file
BIN
aio/content/images/marketing/home/angular-connect.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.8 KiB |
@ -13,18 +13,6 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- ngJapan -->
|
||||
<tr>
|
||||
<th><a href="http://ngjapan.org/" title="ng-Japan">ng-Japan</a></th>
|
||||
<td>Tokyo, Japan</td>
|
||||
<td>June 17, 2017</td>
|
||||
</tr>
|
||||
<!-- AngularMix -->
|
||||
<tr>
|
||||
<th><a href="https://angularmix.com/" title="AngularMix">AngularMix</a></th>
|
||||
<td>Universal Studios, Orlando, Florida</td>
|
||||
<td>October 8, 2017</td>
|
||||
</tr>
|
||||
<!-- ReactiveConf -->
|
||||
<tr>
|
||||
<th><a href="https://reactiveconf.com/" title="ReactiveConf">ReactiveConf</a></th>
|
||||
@ -35,7 +23,7 @@
|
||||
<tr>
|
||||
<th><a href="http://angularconnect.com" title="AngularConnect">AngularConnect</a></th>
|
||||
<td>London, United Kingdom</td>
|
||||
<td>November 07, 2017</td>
|
||||
<td>November 7-8, 2017</td>
|
||||
</tr>
|
||||
<!-- ngAtlanta-->
|
||||
<tr>
|
||||
@ -43,6 +31,30 @@
|
||||
<td>Atlanta, Georgia</td>
|
||||
<td>January 30, 2018</td>
|
||||
</tr>
|
||||
<!-- ngVikings-->
|
||||
<tr>
|
||||
<th><a href="https://ngvikings.org/" title="ngVikings">ngVikings</a></th>
|
||||
<td>Helsinki, Finland</td>
|
||||
<td>March 1-2, 2018</td>
|
||||
</tr>
|
||||
<!-- ngconf 2018-->
|
||||
<tr>
|
||||
<th><a href="https://www.ng-conf.org/" title="ng-conf">ng-conf</a></th>
|
||||
<td>Salt Lake City, UT</td>
|
||||
<td>April 18-20, 2018</td>
|
||||
</tr>
|
||||
<!-- WeRDevs-->
|
||||
<tr>
|
||||
<th><a href="https://www.wearedevelopers.com/" title="WeAreDevs">WeAreDevelopers</a></th>
|
||||
<td>Vienna</td>
|
||||
<td>May 16-18, 2018</td>
|
||||
</tr>
|
||||
<!-- AngularConnect-->
|
||||
<tr>
|
||||
<th><a href="http://angularconnect.com" title="AngularConnect">AngularConnect</a></th>
|
||||
<td>London, United Kingdom</td>
|
||||
<td>November 5-7, 2018</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
|
@ -30,10 +30,10 @@
|
||||
<!--Announcement Bar-->
|
||||
<div class="homepage-container">
|
||||
<div class="announcement-bar">
|
||||
<img src="generated/images/marketing/home/angular-mix.png" height="40" width="151">
|
||||
<p>Join us at our newest event, October 2017</p>
|
||||
<a class="button" href="https://angularmix.com/">Learn More</a>
|
||||
</div>
|
||||
<img src="generated/images/marketing/home/angular-connect.png">
|
||||
<p>Join us in London for AngularConnect<br>November 7-8, 2017</p>
|
||||
<a class="button" href="https://angularconnect.com/">Learn More</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Group 1-->
|
||||
|
@ -317,7 +317,7 @@
|
||||
"desc": "Material Design components for Angular",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Angular Material 2",
|
||||
"title": "Angular Material",
|
||||
"url": "https://github.com/angular/material2"
|
||||
},
|
||||
"aggrid": {
|
||||
|
@ -87,7 +87,7 @@ Here's the app in action:
|
||||
|
||||
|
||||
|
||||
## Up next
|
||||
## Next step
|
||||
|
||||
You'll build the Tour of Heroes app, step by step.
|
||||
Each step is motivated with a requirement that you've likely
|
||||
|
@ -263,7 +263,7 @@ You can edit the hero's name and see the changes reflected immediately in the `<
|
||||
|
||||
|
||||
|
||||
## The road you've travelled
|
||||
## Summary
|
||||
|
||||
Take stock of what you've built.
|
||||
|
||||
@ -286,7 +286,7 @@ Here's the complete `app.component.ts` as it stands now:
|
||||
|
||||
|
||||
|
||||
## The road ahead
|
||||
## Next step
|
||||
In the [next tutorial page](tutorial/toh-pt2 "Master/Detail"), you'll build on the Tour of Heroes app to display a list of heroes.
|
||||
You'll also allow the user to select heroes and display their details.
|
||||
You'll learn more about how to retrieve lists and bind them to the template.
|
||||
|
@ -450,7 +450,7 @@ Here's the complete `app.component.ts` as of now:
|
||||
|
||||
|
||||
|
||||
## The road you've travelled
|
||||
## Summary
|
||||
|
||||
Here's what you achieved in this page:
|
||||
|
||||
@ -460,7 +460,7 @@ Here's what you achieved in this page:
|
||||
|
||||
Your app should look like this <live-example></live-example>.
|
||||
|
||||
## The road ahead
|
||||
## Next step
|
||||
You've expanded the Tour of Heroes app, but it's far from complete.
|
||||
An app shouldn't be one monolithic component.
|
||||
In the [next page](tutorial/toh-pt3 "Multiple Components"), you'll split the app into subcomponents and make them work together.
|
||||
|
@ -443,7 +443,7 @@ Here are the code files discussed in this page.
|
||||
|
||||
|
||||
|
||||
## The road you’ve travelled
|
||||
## Summary
|
||||
|
||||
Here's what you achieved in this page:
|
||||
|
||||
@ -457,7 +457,7 @@ Your app should look like this <live-example></live-example>.
|
||||
|
||||
|
||||
|
||||
## The road ahead
|
||||
## Next step
|
||||
The Tour of Heroes app is more reusable with shared components,
|
||||
but its (mock) data is still hard coded within the `AppComponent`.
|
||||
That's not sustainable.
|
||||
|
@ -593,7 +593,7 @@ Here are the code files discussed in this page.
|
||||
|
||||
|
||||
|
||||
## The road you've travelled
|
||||
## Summary
|
||||
Here's what you achieved in this page:
|
||||
|
||||
* You created a service class that can be shared by many components.
|
||||
@ -604,7 +604,7 @@ Here's what you achieved in this page:
|
||||
|
||||
Your app should look like this <live-example></live-example>.
|
||||
|
||||
## The road ahead
|
||||
## Next step
|
||||
The Tour of Heroes has become more reusable using shared components and services.
|
||||
The next goal is to create a dashboard, add menu links that route between the views, and format data in a template.
|
||||
As the app evolves, you'll discover how to design it to make it easier to grow and maintain.
|
||||
|
@ -1207,7 +1207,7 @@ Here's an excerpt:
|
||||
|
||||
|
||||
Create the file <code>styles.css</code>.
|
||||
Ensure that the file contains the [master styles provided here](https://raw.githubusercontent.com/angular/angular/master/aio/tools/examples/shared/boilerplate/src/styles.css).
|
||||
Ensure that the file contains the [master styles provided here](https://raw.githubusercontent.com/angular/angular/master/aio/tools/examples/shared/boilerplate/common/src/styles.css).
|
||||
Also edit <code>index.html</code> to refer to this stylesheet.
|
||||
|
||||
|
||||
@ -1356,7 +1356,7 @@ Verify that you have the following structure:
|
||||
|
||||
|
||||
|
||||
## The road you’ve travelled
|
||||
## Summary
|
||||
Here's what you achieved in this page:
|
||||
|
||||
* You added the Angular router to navigate among different components.
|
||||
@ -1368,7 +1368,7 @@ Here's what you achieved in this page:
|
||||
|
||||
Your app should look like this <live-example></live-example>.
|
||||
|
||||
### The road ahead
|
||||
### Next step
|
||||
|
||||
You have much of the foundation you need to build an app.
|
||||
You're still missing a key piece: remote data access.
|
||||
|
@ -518,7 +518,7 @@ Verify that you have the following structure:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Home Stretch
|
||||
## Summary
|
||||
|
||||
You're at the end of your journey, and you've accomplished a lot.
|
||||
* You added the necessary dependencies to use HTTP in the app.
|
||||
|
44
aio/e2e/api.e2e-spec.ts
Normal file
44
aio/e2e/api.e2e-spec.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { ApiPage } from './api.po';
|
||||
|
||||
describe('Api pages', function() {
|
||||
it('should show direct subclasses of a class', () => {
|
||||
const page = new ApiPage('api/forms/AbstractControlDirective');
|
||||
// We must use `as any` (here and below) because of broken typings for jasmine
|
||||
expect(page.getDescendants('class', true)).toEqual(['ControlContainer', 'NgControl'] as any);
|
||||
});
|
||||
|
||||
it('should show direct and indirect subclasses of a class', () => {
|
||||
const page = new ApiPage('api/forms/AbstractControlDirective');
|
||||
expect(page.getDescendants('class')).toEqual(['ControlContainer', 'AbstractFormGroupDirective', 'NgControl'] as any);
|
||||
});
|
||||
|
||||
it('should show child interfaces that extend an interface', () => {
|
||||
const page = new ApiPage('api/forms/Validator');
|
||||
expect(page.getDescendants('interface')).toEqual(['AsyncValidator'] as any);
|
||||
});
|
||||
|
||||
it('should show classes that implement an interface', () => {
|
||||
const page = new ApiPage('api/animations/AnimationPlayer');
|
||||
expect(page.getDescendants('class')).toEqual(['NoopAnimationPlayer', 'MockAnimationPlayer'] as any);
|
||||
});
|
||||
|
||||
it('should show type params of type-aliases', () => {
|
||||
const page = new ApiPage('api/common/http/HttpEvent');
|
||||
expect(page.getOverview('type-alias').getText()).toContain('type HttpEvent<T>');
|
||||
});
|
||||
|
||||
it('should show readonly properties as getters', () => {
|
||||
const page = new ApiPage('api/common/http/HttpRequest');
|
||||
expect(page.getOverview('class').getText()).toContain('get body: T|null');
|
||||
});
|
||||
|
||||
it('should not show parenthesis for getters', () => {
|
||||
const page = new ApiPage('api/core/NgModuleRef');
|
||||
expect(page.getOverview('class').getText()).toContain('get injector: Injector');
|
||||
});
|
||||
|
||||
it('should show both type and initializer if set', () => {
|
||||
const page = new ApiPage('api/common/HashLocationStrategy');
|
||||
expect(page.getOverview('class').getText()).toContain('path(includeHash: boolean = false): string');
|
||||
});
|
||||
});
|
34
aio/e2e/api.po.ts
Normal file
34
aio/e2e/api.po.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { element, by } from 'protractor';
|
||||
import { SitePage } from './app.po';
|
||||
|
||||
export class ApiPage extends SitePage {
|
||||
constructor(url: string) {
|
||||
super();
|
||||
this.navigateTo(url);
|
||||
}
|
||||
|
||||
getDescendants(docType: string, onlyDirect = false) {
|
||||
// This selector is horrible because we have potentially recursive HTML lists
|
||||
//
|
||||
// ul
|
||||
// li
|
||||
// code
|
||||
// ul
|
||||
// li
|
||||
// code
|
||||
// ul
|
||||
// li
|
||||
// code
|
||||
// li
|
||||
// code
|
||||
//
|
||||
// and we want to be able to pull out the code elements from only the first level
|
||||
// if `onlyDirect` is set to `true`.
|
||||
const selector = `.descendants.${docType} ${onlyDirect ? '>' : ''} li > :not(ul) code`;
|
||||
return element.all(by.css(selector)).map<string>(item => item.getText());
|
||||
}
|
||||
|
||||
getOverview(docType) {
|
||||
return element(by.css(`.${docType}-overview`));
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { browser, element, by, promise } from 'protractor';
|
||||
import { element, by } from 'protractor';
|
||||
import { SitePage } from './app.po';
|
||||
|
||||
describe('site App', function() {
|
||||
@ -41,20 +41,20 @@ describe('site App', function() {
|
||||
|
||||
describe('scrolling to the top', () => {
|
||||
it('should scroll to the top when navigating to another page', () => {
|
||||
page.navigateTo('guide/docs');
|
||||
page.navigateTo('guide/security');
|
||||
page.scrollToBottom();
|
||||
page.getScrollTop().then(scrollTop => expect(scrollTop).toBeGreaterThan(0));
|
||||
|
||||
page.navigateTo('guide/api');
|
||||
page.navigateTo('api');
|
||||
page.getScrollTop().then(scrollTop => expect(scrollTop).toBe(0));
|
||||
});
|
||||
|
||||
it('should scroll to the top when navigating to the same page', () => {
|
||||
page.navigateTo('guide/docs');
|
||||
page.navigateTo('guide/security');
|
||||
page.scrollToBottom();
|
||||
page.getScrollTop().then(scrollTop => expect(scrollTop).toBeGreaterThan(0));
|
||||
|
||||
page.navigateTo('guide/docs');
|
||||
page.navigateTo('guide/security');
|
||||
page.getScrollTop().then(scrollTop => expect(scrollTop).toBe(0));
|
||||
});
|
||||
});
|
||||
@ -66,7 +66,9 @@ describe('site App', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('google analytics', () => {
|
||||
// TODO(https://github.com/angular/angular/issues/19785): Activate this again
|
||||
// once it is no more flaky.
|
||||
xdescribe('google analytics', () => {
|
||||
beforeEach(done => page.gaReady.then(done));
|
||||
|
||||
it('should call ga', done => {
|
||||
@ -100,4 +102,12 @@ describe('site App', function() {
|
||||
expect(page.getSearchResults().map(link => link.getText())).toContain('ControlValueAccessor');
|
||||
});
|
||||
});
|
||||
|
||||
describe('404 page', () => {
|
||||
it('should search the index for words found in the url', () => {
|
||||
page.navigateTo('http/router');
|
||||
expect(page.getSearchResults().map(link => link.getText())).toContain('Http');
|
||||
expect(page.getSearchResults().map(link => link.getText())).toContain('Router');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -30,8 +30,14 @@ module.exports = function (config) {
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
browsers: ['CustomChrome'],
|
||||
browserNoActivityTimeout: 60000,
|
||||
singleRun: false
|
||||
singleRun: false,
|
||||
customLaunchers: {
|
||||
CustomChrome: {
|
||||
base: 'Chrome',
|
||||
flags: process.env.TRAVIS && ['--no-sandbox']
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -6,33 +6,45 @@
|
||||
"author": "Angular",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"aio-use-local": "node tools/ng-packages-installer overwrite . --debug --ignore-packages @angular/service-worker",
|
||||
"aio-use-npm": "node tools/ng-packages-installer restore .",
|
||||
"aio-check-local": "node tools/ng-packages-installer check .",
|
||||
"ng": "yarn check-env && ng",
|
||||
"start": "yarn check-env && ng serve",
|
||||
"prebuild": "yarn check-env && yarn setup",
|
||||
"build": "ng build --target=production --environment=stable -sm --build-optimizer",
|
||||
"postbuild": "yarn sw-manifest && yarn sw-copy",
|
||||
"prebuild": "yarn setup",
|
||||
"build": "yarn ~~build",
|
||||
"prebuild-local": "yarn setup-local",
|
||||
"build-local": "yarn ~~build",
|
||||
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint",
|
||||
"test": "yarn check-env && ng test",
|
||||
"pree2e": "yarn check-env && yarn ~~update-webdriver",
|
||||
"e2e": "ng e2e --no-webdriver-update",
|
||||
"setup": "yarn && yarn build-ie-polyfills && yarn boilerplate:remove && yarn boilerplate:add && yarn generate-plunkers && yarn generate-zips && yarn docs",
|
||||
"pretest-pwa-score-local": "yarn build",
|
||||
"test-pwa-score-local": "concurrently --kill-others --success first \"http-server dist -p 4200 --silent\" \"yarn test-pwa-score -- http://localhost:4200 90\"",
|
||||
"presetup": "yarn install --freeze-lockfile && yarn ~~check-env && yarn boilerplate:remove",
|
||||
"setup": "yarn aio-use-npm && yarn example-use-npm",
|
||||
"postsetup": "yarn boilerplate:add && yarn build-ie-polyfills && yarn generate-plunkers && yarn generate-zips && yarn docs",
|
||||
"presetup-local": "yarn presetup",
|
||||
"setup-local": "yarn aio-use-local && yarn example-use-local",
|
||||
"postsetup-local": "yarn postsetup",
|
||||
"pretest-pwa-score-localhost": "yarn build",
|
||||
"test-pwa-score-localhost": "concurrently --kill-others --success first \"http-server dist -p 4200 --silent\" \"yarn test-pwa-score http://localhost:4200 90\"",
|
||||
"test-pwa-score": "node scripts/test-pwa-score",
|
||||
"example-e2e": "node ./tools/examples/run-example-e2e",
|
||||
"example-e2e": "yarn example-check-local && node ./tools/examples/run-example-e2e",
|
||||
"example-lint": "tslint -c \"content/examples/tslint.json\" \"content/examples/**/*.ts\" -e \"content/examples/styleguide/**/*.avoid.ts\"",
|
||||
"example-use-local": "node tools/ng-packages-installer overwrite ./tools/examples/shared",
|
||||
"example-use-npm": "node tools/ng-packages-installer restore ./tools/examples/shared",
|
||||
"example-check-local": "node tools/ng-packages-installer check ./tools/examples/shared",
|
||||
"deploy-preview": "scripts/deploy-preview.sh",
|
||||
"deploy-production": "scripts/deploy-to-firebase.sh",
|
||||
"check-env": "node scripts/check-environment",
|
||||
"check-env": "yarn ~~check-env",
|
||||
"postcheck-env": "yarn aio-check-local",
|
||||
"payload-size": "scripts/payload.sh",
|
||||
"predocs": "rimraf src/generated/{docs,*.json}",
|
||||
"docs": "dgeni ./tools/transforms/angular.io-package",
|
||||
"docs-watch": "node tools/transforms/authors-package/watchr.js",
|
||||
"docs-lint": "eslint --ignore-path=\"tools/transforms/.eslintignore\" tools/transforms",
|
||||
"docs-test": "node tools/transforms/test.js",
|
||||
"tools-test": "./scripts/deploy-to-firebase.test.sh && yarn docs-test",
|
||||
"tools-test": "./scripts/deploy-to-firebase.test.sh && yarn docs-test && yarn boilerplate:test && jasmine tools/ng-packages-installer/index.spec.js",
|
||||
"serve-and-sync": "concurrently --kill-others \"yarn docs-watch\" \"yarn start\"",
|
||||
"~~update-webdriver": "webdriver-manager update --standalone false --gecko false",
|
||||
"boilerplate:add": "node ./tools/examples/example-boilerplate add",
|
||||
"boilerplate:remove": "node ./tools/examples/example-boilerplate remove",
|
||||
"boilerplate:test": "node tools/examples/test.js",
|
||||
@ -41,7 +53,11 @@
|
||||
"sw-manifest": "ngu-sw-manifest --dist dist --in ngsw-manifest.json --out dist/ngsw-manifest.json",
|
||||
"sw-copy": "cp node_modules/@angular/service-worker/bundles/worker-basic.min.js dist/",
|
||||
"postinstall": "uglifyjs node_modules/lunr/lunr.js -c -m -o src/assets/js/lunr.min.js --source-map",
|
||||
"build-ie-polyfills": "node node_modules/webpack/bin/webpack.js -p src/ie-polyfills.js src/generated/ie-polyfills.min.js"
|
||||
"build-ie-polyfills": "node node_modules/webpack/bin/webpack.js -p src/ie-polyfills.js src/generated/ie-polyfills.min.js",
|
||||
"~~check-env": "node scripts/check-environment",
|
||||
"~~build": "ng build --target=production --environment=stable -sm --build-optimizer",
|
||||
"post~~build": "yarn sw-manifest && yarn sw-copy",
|
||||
"~~update-webdriver": "webdriver-manager update --standalone false --gecko false $CHROMEDRIVER_VERSION_ARG"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.5 <7.0.0",
|
||||
@ -66,7 +82,7 @@
|
||||
"core-js": "^2.4.1",
|
||||
"jasmine": "^2.6.0",
|
||||
"ng-pwa-tools": "^0.0.10",
|
||||
"rxjs": "^5.2.0",
|
||||
"rxjs": "^5.4.3",
|
||||
"tslib": "^1.7.1",
|
||||
"web-animations-js": "^2.2.5",
|
||||
"zone.js": "^0.8.16"
|
||||
@ -78,11 +94,12 @@
|
||||
"@types/node": "~6.0.60",
|
||||
"archiver": "^1.3.0",
|
||||
"canonical-path": "^0.0.2",
|
||||
"chalk": "^2.1.0",
|
||||
"codelyzer": "~2.0.0",
|
||||
"concurrently": "^3.4.0",
|
||||
"cross-spawn": "^5.1.0",
|
||||
"dgeni": "^0.4.7",
|
||||
"dgeni-packages": "^0.21.2",
|
||||
"dgeni-packages": "0.22.0",
|
||||
"entities": "^1.1.1",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-plugin-jasmine": "^2.2.0",
|
||||
@ -117,7 +134,7 @@
|
||||
"semver": "^5.3.0",
|
||||
"shelljs": "^0.7.7",
|
||||
"tree-kill": "^1.1.0",
|
||||
"ts-node": "~2.0.0",
|
||||
"ts-node": "^3.3.0",
|
||||
"tslint": "~4.5.0",
|
||||
"typescript": "2.3.2",
|
||||
"uglify-js": "^3.0.15",
|
||||
|
@ -12,7 +12,8 @@ exports.config = {
|
||||
browserName: 'chrome',
|
||||
// For Travis
|
||||
chromeOptions: {
|
||||
binary: process.env.CHROME_BIN
|
||||
binary: process.env.CHROME_BIN,
|
||||
args: ['--no-sandbox']
|
||||
}
|
||||
},
|
||||
directConnect: true,
|
||||
|
@ -3,7 +3,7 @@
|
||||
set -u -e -o pipefail
|
||||
|
||||
declare -A limitUncompressed
|
||||
limitUncompressed=(["inline"]=1600 ["main"]=525000 ["polyfills"]=38000)
|
||||
limitUncompressed=(["inline"]=1600 ["main"]=525500 ["polyfills"]=38000)
|
||||
declare -A limitGzip7
|
||||
limitGzip7=(["inline"]=1000 ["main"]=127000 ["polyfills"]=12500)
|
||||
declare -A limitGzip9
|
||||
|
@ -51,6 +51,6 @@ readonly relevantChangedFilesCount=$(git diff --name-only $TRAVIS_COMMIT_RANGE |
|
||||
# Run PWA-score tests (unless the deployment is not public yet;
|
||||
# i.e. it could not be automatically verified).
|
||||
if [[ $httpCode -ne 202 ]] && [[ "$isHidden" != "true" ]]; then
|
||||
yarn test-pwa-score -- "$DEPLOYED_URL" "$MIN_PWA_SCORE"
|
||||
yarn test-pwa-score "$DEPLOYED_URL" "$MIN_PWA_SCORE"
|
||||
fi
|
||||
)
|
||||
|
@ -67,7 +67,7 @@ case $deployEnv in
|
||||
readonly firebaseToken=$FIREBASE_TOKEN
|
||||
;;
|
||||
archive)
|
||||
readonly projectId=angular-io-${majorVersion}
|
||||
readonly projectId=v${majorVersion}-angular-io
|
||||
readonly deployedUrl=https://v${majorVersion}.angular.io/
|
||||
readonly firebaseToken=$FIREBASE_TOKEN
|
||||
;;
|
||||
@ -87,7 +87,7 @@ fi
|
||||
cd "`dirname $0`/.."
|
||||
|
||||
# Build the app
|
||||
yarn build -- --env=$deployEnv
|
||||
yarn build --env=$deployEnv
|
||||
|
||||
# Include any mode-specific files
|
||||
cp -rf src/extra-files/$deployEnv/. dist/
|
||||
@ -100,5 +100,5 @@ fi
|
||||
firebase deploy --message "Commit: $TRAVIS_COMMIT" --non-interactive --token "$firebaseToken"
|
||||
|
||||
# Run PWA-score tests
|
||||
yarn test-pwa-score -- "$deployedUrl" "$MIN_PWA_SCORE"
|
||||
yarn test-pwa-score "$deployedUrl" "$MIN_PWA_SCORE"
|
||||
)
|
||||
|
@ -94,7 +94,7 @@ Deployment URL : https://angular.io/"
|
||||
)
|
||||
expected="Git branch : 2.4.x
|
||||
Build/deploy mode : archive
|
||||
Firebase project : angular-io-2
|
||||
Firebase project : v2-angular-io
|
||||
Deployment URL : https://v2.angular.io/"
|
||||
check "$actual" "$expected"
|
||||
)
|
||||
|
@ -61,7 +61,7 @@ else
|
||||
# Nothing changed in aio/
|
||||
exit 0
|
||||
fi
|
||||
message=$(echo $TRAVIS_COMMIT_MESSAGE | sed 's/"/\\"/g' | sed 's/\\/\\\\/g')
|
||||
message=$(echo $TRAVIS_COMMIT_MESSAGE | sed 's/\\/\\\\/g' | sed 's/"/\\"/g')
|
||||
payloadData="$payloadData\"change\": \"$change\", \"message\": \"$message\""
|
||||
|
||||
payloadData="{${payloadData}}"
|
||||
|
@ -17,8 +17,16 @@ const printer = require('lighthouse/lighthouse-cli/printer');
|
||||
const config = require('lighthouse/lighthouse-core/config/default.js');
|
||||
|
||||
// Constants
|
||||
const CHROME_LAUNCH_OPTS = {};
|
||||
const VIEWER_URL = 'https://googlechrome.github.io/lighthouse/viewer/';
|
||||
|
||||
|
||||
// Specify the path and flags for Chrome on Travis
|
||||
if (process.env.TRAVIS) {
|
||||
process.env.LIGHTHOUSE_CHROMIUM_PATH = process.env.CHROME_BIN;
|
||||
CHROME_LAUNCH_OPTS.chromeFlags = ['--no-sandbox'];
|
||||
}
|
||||
|
||||
// Run
|
||||
_main(process.argv.slice(2));
|
||||
|
||||
@ -66,7 +74,7 @@ function ignoreHttpsAudits(config) {
|
||||
}
|
||||
|
||||
function launchChromeAndRunLighthouse(url, flags, config) {
|
||||
return chromeLauncher.launch().then(chrome => {
|
||||
return chromeLauncher.launch(CHROME_LAUNCH_OPTS).then(chrome => {
|
||||
flags.port = chrome.port;
|
||||
return lighthouse(url, flags, config).
|
||||
then(results => chrome.kill().then(() => results)).
|
||||
|
@ -13,7 +13,7 @@
|
||||
<aio-top-menu *ngIf="isSideBySide" [nodes]="topMenuNodes"></aio-top-menu>
|
||||
<aio-search-box class="search-container" #searchBox (onSearch)="doSearch($event)" (onFocus)="doSearch($event)"></aio-search-box>
|
||||
</md-toolbar>
|
||||
<aio-search-results #searchResults *ngIf="showSearchResults" (resultSelected)="hideSearchResults()"></aio-search-results>
|
||||
<aio-search-results #searchResultsView *ngIf="showSearchResults" [searchResults]="searchResults | async" (resultSelected)="hideSearchResults()"></aio-search-results>
|
||||
|
||||
<md-sidenav-container class="sidenav-container" [class.starting]="isStarting" [class.has-floating-toc]="hasFloatingToc" role="main">
|
||||
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { NO_ERRORS_SCHEMA, DebugElement } from '@angular/core';
|
||||
import { async, inject, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
|
||||
import { inject, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { APP_BASE_HREF } from '@angular/common';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { MdProgressBar, MdSidenav } from '@angular/material';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { of } from 'rxjs/observable/of';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
@ -22,9 +21,9 @@ import { MockSearchService } from 'testing/search.service';
|
||||
import { NavigationNode } from 'app/navigation/navigation.service';
|
||||
import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
||||
import { SearchResultsComponent } from 'app/search/search-results/search-results.component';
|
||||
import { SearchResultsComponent } from 'app/shared/search-results/search-results.component';
|
||||
import { SearchService } from 'app/search/search.service';
|
||||
import { SelectComponent, Option } from 'app/shared/select/select.component';
|
||||
import { SelectComponent } from 'app/shared/select/select.component';
|
||||
import { TocComponent } from 'app/embedded/toc/toc.component';
|
||||
import { TocItem, TocService } from 'app/shared/toc.service';
|
||||
|
||||
@ -1054,11 +1053,6 @@ class TestGaService {
|
||||
locationChanged = jasmine.createSpy('locationChanged');
|
||||
}
|
||||
|
||||
class TestSearchService {
|
||||
initWorker = jasmine.createSpy('initWorker');
|
||||
loadIndex = jasmine.createSpy('loadIndex');
|
||||
}
|
||||
|
||||
class TestHttpClient {
|
||||
|
||||
static versionInfo = {
|
||||
|
@ -2,18 +2,18 @@ import { Component, ElementRef, HostBinding, HostListener, OnInit,
|
||||
QueryList, ViewChild, ViewChildren } from '@angular/core';
|
||||
import { MdSidenav } from '@angular/material';
|
||||
|
||||
import { CurrentNodes, NavigationService, NavigationViews, NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
|
||||
import { CurrentNodes, NavigationService, NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
|
||||
import { DocumentService, DocumentContents } from 'app/documents/document.service';
|
||||
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
|
||||
import { Deployment } from 'app/shared/deployment.service';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { NavMenuComponent } from 'app/layout/nav-menu/nav-menu.component';
|
||||
import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { SearchResultsComponent } from 'app/search/search-results/search-results.component';
|
||||
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
||||
import { SearchResults } from 'app/search/interfaces';
|
||||
import { SearchService } from 'app/search/search.service';
|
||||
import { TocService } from 'app/shared/toc.service';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { combineLatest } from 'rxjs/observable/combineLatest';
|
||||
|
||||
@ -89,10 +89,9 @@ export class AppComponent implements OnInit {
|
||||
|
||||
// Search related properties
|
||||
showSearchResults = false;
|
||||
@ViewChildren('searchBox, searchResults', { read: ElementRef })
|
||||
searchResults: Observable<SearchResults>;
|
||||
@ViewChildren('searchBox, searchResultsView', { read: ElementRef })
|
||||
searchElements: QueryList<ElementRef>;
|
||||
@ViewChild(SearchResultsComponent)
|
||||
searchResults: SearchResultsComponent;
|
||||
@ViewChild(SearchBoxComponent)
|
||||
searchBox: SearchBoxComponent;
|
||||
|
||||
@ -332,7 +331,7 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
|
||||
doSearch(query) {
|
||||
this.searchService.search(query);
|
||||
this.searchResults = this.searchService.search(query);
|
||||
this.showSearchResults = !!query;
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,6 @@ import { NavMenuComponent } from 'app/layout/nav-menu/nav-menu.component';
|
||||
import { NavItemComponent } from 'app/layout/nav-item/nav-item.component';
|
||||
import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { ScrollSpyService } from 'app/shared/scroll-spy.service';
|
||||
import { SearchResultsComponent } from './search/search-results/search-results.component';
|
||||
import { SearchBoxComponent } from './search/search-box/search-box.component';
|
||||
import { TocService } from 'app/shared/toc.service';
|
||||
|
||||
@ -95,7 +94,6 @@ export const svgIconProviders = [
|
||||
ModeBannerComponent,
|
||||
NavMenuComponent,
|
||||
NavItemComponent,
|
||||
SearchResultsComponent,
|
||||
SearchBoxComponent,
|
||||
TopMenuComponent,
|
||||
],
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||
import { Injector } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { MockLocationService } from 'testing/location.service';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
|
||||
import { ApiListComponent } from './api-list.component';
|
||||
|
@ -13,7 +13,7 @@ import { ReplaySubject } from 'rxjs/ReplaySubject';
|
||||
import { combineLatest } from 'rxjs/observable/combineLatest';
|
||||
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { ApiItem, ApiSection, ApiService } from './api.service';
|
||||
import { ApiSection, ApiService } from './api.service';
|
||||
|
||||
import { Option } from 'app/shared/select/select.component';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||
import { Injector } from '@angular/core';
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
|
||||
|
@ -1,11 +1,9 @@
|
||||
import { Component, ElementRef, ViewChild, OnChanges, OnDestroy, Input } from '@angular/core';
|
||||
import { Component, ElementRef, ViewChild, OnChanges, Input } from '@angular/core';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { PrettyPrinter } from './pretty-printer.service';
|
||||
import { CopierService } from 'app/shared/copier.service';
|
||||
import { MdSnackBar } from '@angular/material';
|
||||
|
||||
const originalLabel = 'Copy Code';
|
||||
const copiedLabel = 'Copied!';
|
||||
const defaultLineNumsCount = 10; // by default, show linenums over this number
|
||||
|
||||
/**
|
||||
|
@ -3,7 +3,7 @@ import { Injector } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ContributorService } from './contributor.service';
|
||||
import { Contributor, ContributorGroup } from './contributors.model';
|
||||
import { ContributorGroup } from './contributors.model';
|
||||
|
||||
describe('ContributorService', () => {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { MockLocationService } from 'testing/location.service';
|
||||
import { CurrentLocationComponent } from './current-location.component';
|
||||
|
@ -20,6 +20,7 @@ import { CodeTabsComponent } from './code/code-tabs.component';
|
||||
import { ContributorListComponent } from './contributor/contributor-list.component';
|
||||
import { ContributorComponent } from './contributor/contributor.component';
|
||||
import { CurrentLocationComponent } from './current-location.component';
|
||||
import { FileNotFoundSearchComponent } from './search/file-not-found-search.component';
|
||||
import { LiveExampleComponent, EmbeddedPlunkerComponent } from './live-example/live-example.component';
|
||||
import { ResourceListComponent } from './resource/resource-list.component';
|
||||
import { ResourceService } from './resource/resource.service';
|
||||
@ -30,7 +31,8 @@ import { TocComponent } from './toc/toc.component';
|
||||
*/
|
||||
export const embeddedComponents: any[] = [
|
||||
ApiListComponent, CodeExampleComponent, CodeTabsComponent, ContributorListComponent,
|
||||
CurrentLocationComponent, LiveExampleComponent, ResourceListComponent, TocComponent
|
||||
CurrentLocationComponent, FileNotFoundSearchComponent, LiveExampleComponent, ResourceListComponent,
|
||||
TocComponent
|
||||
];
|
||||
|
||||
/** Injectable class w/ property returning components that can be embedded in docs */
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Component, DebugElement, ElementRef } from '@angular/core';
|
||||
import { Component, DebugElement } from '@angular/core';
|
||||
import { Location } from '@angular/common';
|
||||
|
||||
import { LiveExampleComponent, EmbeddedPlunkerComponent } from './live-example.component';
|
||||
@ -71,7 +71,6 @@ describe('LiveExampleComponent', () => {
|
||||
|
||||
describe('when not embedded', () => {
|
||||
function getLiveExampleAnchor() { return getAnchors()[0]; }
|
||||
function getDownloadAnchor() { return getAnchors()[1]; }
|
||||
|
||||
it('should create LiveExampleComponent', () => {
|
||||
testComponent(() => {
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { ReflectiveInjector } from '@angular/core';
|
||||
import { PlatformLocation } from '@angular/common';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { of } from 'rxjs/observable/of';
|
||||
|
||||
import { ResourceListComponent } from './resource-list.component';
|
||||
|
@ -3,7 +3,7 @@ import { Injector } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ResourceService } from './resource.service';
|
||||
import { Category, SubCategory, Resource } from './resource.model';
|
||||
import { Category } from './resource.model';
|
||||
|
||||
describe('ResourceService', () => {
|
||||
|
||||
|
@ -0,0 +1,49 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { MockLocationService } from 'testing/location.service';
|
||||
import { SearchResults } from 'app/search/interfaces';
|
||||
import { SearchResultsComponent } from 'app/shared/search-results/search-results.component';
|
||||
import { SearchService } from 'app/search/search.service';
|
||||
import { FileNotFoundSearchComponent } from './file-not-found-search.component';
|
||||
|
||||
|
||||
describe('FileNotFoundSearchComponent', () => {
|
||||
let element: HTMLElement;
|
||||
let fixture: ComponentFixture<FileNotFoundSearchComponent>;
|
||||
let searchService: SearchService;
|
||||
let searchResultSubject: Subject<SearchResults>;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ FileNotFoundSearchComponent, SearchResultsComponent ],
|
||||
providers: [
|
||||
{ provide: LocationService, useValue: new MockLocationService('base/initial-url?some-query') },
|
||||
SearchService
|
||||
]
|
||||
});
|
||||
|
||||
fixture = TestBed.createComponent(FileNotFoundSearchComponent);
|
||||
searchService = TestBed.get(SearchService);
|
||||
searchResultSubject = new Subject<SearchResults>();
|
||||
spyOn(searchService, 'search').and.callFake(() => searchResultSubject.asObservable());
|
||||
fixture.detectChanges();
|
||||
element = fixture.nativeElement;
|
||||
});
|
||||
|
||||
it('should run a search with a query built from the current url', () => {
|
||||
expect(searchService.search).toHaveBeenCalledWith('base initial url');
|
||||
});
|
||||
|
||||
it('should pass through any results to the `aio-search-results` component', () => {
|
||||
const searchResultsComponent = fixture.debugElement.query(By.directive(SearchResultsComponent)).componentInstance;
|
||||
expect(searchResultsComponent.searchResults).toBe(null);
|
||||
|
||||
const results = { query: 'base initial url', results: []};
|
||||
searchResultSubject.next(results);
|
||||
fixture.detectChanges();
|
||||
expect(searchResultsComponent.searchResults).toEqual(results);
|
||||
});
|
||||
});
|
@ -0,0 +1,23 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { SearchResults } from 'app/search/interfaces';
|
||||
import { SearchService } from 'app/search/search.service';
|
||||
|
||||
@Component({
|
||||
selector: 'aio-file-not-found-search',
|
||||
template:
|
||||
`<p>Let's see if any of these search results help...</p>
|
||||
<aio-search-results class="embedded" [searchResults]="searchResults | async"></aio-search-results>`
|
||||
})
|
||||
export class FileNotFoundSearchComponent implements OnInit {
|
||||
searchResults: Observable<SearchResults>;
|
||||
constructor(private location: LocationService, private search: SearchService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.searchResults = this.location.currentPath.switchMap(path => {
|
||||
const query = path.split(/\W+/).join(' ');
|
||||
return this.search.search(query);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { Component, CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By, DOCUMENT } from '@angular/platform-browser';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { asap } from 'rxjs/scheduler/asap';
|
||||
|
||||
|
@ -1,11 +1,8 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
Component, ComponentFactoryResolver, DebugElement,
|
||||
ElementRef, Injector, NgModule, OnInit, ViewChild } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Component, DebugElement, ElementRef, NgModule, OnInit, ViewChild } from '@angular/core';
|
||||
import { DocViewerComponent } from './doc-viewer.component';
|
||||
import { DocumentContents } from 'app/documents/document.service';
|
||||
import { EmbeddedModule, embeddedComponents, EmbeddedComponents } from 'app/embedded/embedded.module';
|
||||
import { EmbeddedModule, EmbeddedComponents } from 'app/embedded/embedded.module';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { TocService } from 'app/shared/toc.service';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {
|
||||
Component, ComponentFactory, ComponentFactoryResolver, ComponentRef,
|
||||
DoCheck, ElementRef, EventEmitter, Injector, Input, OnDestroy,
|
||||
Output, ViewEncapsulation
|
||||
Output
|
||||
} from '@angular/core';
|
||||
|
||||
import { EmbeddedComponents } from 'app/embedded/embedded.module';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { SimpleChange, SimpleChanges, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
|
||||
import { NavItemComponent } from './nav-item.component';
|
||||
import { NavigationNode } from 'app/navigation/navigation.model';
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
|
||||
import { TopMenuComponent } from './top-menu.component';
|
||||
import { NavigationService, NavigationViews, NavigationNode } from 'app/navigation/navigation.service';
|
||||
import { NavigationService, NavigationViews } from 'app/navigation/navigation.service';
|
||||
|
||||
describe('TopMenuComponent', () => {
|
||||
let component: TopMenuComponent;
|
||||
|
@ -2,7 +2,6 @@ import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { AsyncSubject } from 'rxjs/AsyncSubject';
|
||||
import { combineLatest } from 'rxjs/observable/combineLatest';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/publishLast';
|
||||
|
19
aio/src/app/search/interfaces.ts
Normal file
19
aio/src/app/search/interfaces.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export interface SearchResults {
|
||||
query: string;
|
||||
results: SearchResult[];
|
||||
}
|
||||
|
||||
export interface SearchResult {
|
||||
path: string;
|
||||
title: string;
|
||||
type: string;
|
||||
titleWords: string;
|
||||
keywords: string;
|
||||
}
|
||||
|
||||
export interface SearchArea {
|
||||
name: string;
|
||||
pages: SearchResult[];
|
||||
priorityPages: SearchResult[];
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ import { Component } from '@angular/core';
|
||||
import { ComponentFixture, fakeAsync, inject, TestBed, tick } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { SearchBoxComponent } from './search-box.component';
|
||||
import { MockSearchService } from 'testing/search.service';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { MockLocationService } from 'testing/location.service';
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { ReflectiveInjector, NgZone } from '@angular/core';
|
||||
import { fakeAsync, tick } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/observable/of';
|
||||
import { SearchService } from './search.service';
|
||||
import { WebWorkerClient } from 'app/shared/web-worker';
|
||||
|
||||
@ -36,27 +37,29 @@ describe('SearchService', () => {
|
||||
|
||||
describe('search', () => {
|
||||
beforeEach(() => {
|
||||
// We must initialize the service before calling search
|
||||
service.initWorker('some/url', 100);
|
||||
// We must initialize the service before calling connectSearches
|
||||
service.initWorker('some/url', 1000);
|
||||
// Simulate the index being ready so that searches get sent to the worker
|
||||
(service as any).ready = Observable.of(true);
|
||||
});
|
||||
|
||||
it('should trigger a `loadIndex` synchronously', () => {
|
||||
service.search('some query');
|
||||
it('should trigger a `loadIndex` synchronously (not waiting for the delay)', () => {
|
||||
expect(mockWorker.sendMessage).not.toHaveBeenCalled();
|
||||
service.search('some query').subscribe();
|
||||
expect(mockWorker.sendMessage).toHaveBeenCalledWith('load-index');
|
||||
});
|
||||
|
||||
it('should send a "query-index" message to the worker', () => {
|
||||
service.search('some query');
|
||||
service.search('some query').subscribe();
|
||||
expect(mockWorker.sendMessage).toHaveBeenCalledWith('query-index', 'some query');
|
||||
});
|
||||
|
||||
it('should push the response to the `searchResults` observable', () => {
|
||||
it('should push the response to the returned observable', () => {
|
||||
const mockSearchResults = { results: ['a', 'b'] };
|
||||
let actualSearchResults;
|
||||
(mockWorker.sendMessage as jasmine.Spy).and.returnValue(Observable.of(mockSearchResults));
|
||||
let searchResults: any;
|
||||
service.searchResults.subscribe(results => searchResults = results);
|
||||
service.search('some query');
|
||||
expect(searchResults).toEqual(mockSearchResults);
|
||||
service.search('some query').subscribe(results => actualSearchResults = results);
|
||||
expect(actualSearchResults).toEqual(mockSearchResults);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -4,34 +4,21 @@ Use of this source code is governed by an MIT-style license that
|
||||
can be found in the LICENSE file at http://angular.io/license
|
||||
*/
|
||||
|
||||
import { NgZone, Injectable, Type } from '@angular/core';
|
||||
import { NgZone, Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { ReplaySubject } from 'rxjs/ReplaySubject';
|
||||
import 'rxjs/add/observable/race';
|
||||
import 'rxjs/add/observable/timer';
|
||||
import 'rxjs/add/operator/concatMap';
|
||||
import 'rxjs/add/operator/publish';
|
||||
import 'rxjs/add/operator/publishReplay';
|
||||
import { WebWorkerClient } from 'app/shared/web-worker';
|
||||
|
||||
export interface SearchResults {
|
||||
query: string;
|
||||
results: SearchResult[];
|
||||
}
|
||||
|
||||
export interface SearchResult {
|
||||
path: string;
|
||||
title: string;
|
||||
type: string;
|
||||
titleWords: string;
|
||||
keywords: string;
|
||||
}
|
||||
|
||||
import { SearchResults } from 'app/search/interfaces';
|
||||
|
||||
@Injectable()
|
||||
export class SearchService {
|
||||
private ready: Observable<boolean>;
|
||||
private searchesSubject = new ReplaySubject<string>(1);
|
||||
searchResults: Observable<SearchResults>;
|
||||
|
||||
private worker: WebWorkerClient;
|
||||
constructor(private zone: NgZone) {}
|
||||
|
||||
/**
|
||||
@ -43,36 +30,32 @@ export class SearchService {
|
||||
* @param initDelay the number of milliseconds to wait before we load the WebWorker and generate the search index
|
||||
*/
|
||||
initWorker(workerUrl: string, initDelay: number) {
|
||||
const searchResults = Observable
|
||||
const ready = this.ready = Observable
|
||||
// Wait for the initDelay or the first search
|
||||
.race(
|
||||
.race<any>(
|
||||
Observable.timer(initDelay),
|
||||
this.searchesSubject.first()
|
||||
(this.searchesSubject as Observable<string>).first()
|
||||
)
|
||||
.concatMap(() => {
|
||||
// Create the worker and load the index
|
||||
const worker = WebWorkerClient.create(workerUrl, this.zone);
|
||||
return worker.sendMessage('load-index').concatMap(() =>
|
||||
// Once the index has loaded, switch to listening to the searches coming in
|
||||
this.searchesSubject.switchMap((query) =>
|
||||
// Each search gets switched to a web worker message, whose results are returned via an observable
|
||||
worker.sendMessage<SearchResults>('query-index', query)
|
||||
)
|
||||
);
|
||||
}).publish();
|
||||
this.worker = WebWorkerClient.create(workerUrl, this.zone);
|
||||
return this.worker.sendMessage<boolean>('load-index');
|
||||
}).publishReplay(1);
|
||||
|
||||
// Connect to the observable to kick off the timer
|
||||
searchResults.connect();
|
||||
|
||||
// Expose the connected observable to the rest of the world
|
||||
this.searchResults = searchResults;
|
||||
// Connect to the observable to kick off the timer
|
||||
ready.connect();
|
||||
return ready;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a search query to the index.
|
||||
* The results will appear on the `searchResults` observable.
|
||||
* Search the index using the given query and emit results on the observable that is returned.
|
||||
* @param query The query to run against the index.
|
||||
* @returns an observable collection of search results
|
||||
*/
|
||||
search(query: string) {
|
||||
search(query: string): Observable<SearchResults> {
|
||||
// Trigger the searches subject to override the init delay timer
|
||||
this.searchesSubject.next(query);
|
||||
// Once the index has loaded, switch to listening to the searches coming in.
|
||||
return this.ready.concatMap(() => this.worker.sendMessage<SearchResults>('query-index', query));
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user