Compare commits

..

30 Commits
4.4.3 ... 4.4.4

Author SHA1 Message Date
d489ad595d docs: add changelog for 4.4.4 2017-09-28 12:18:14 -07:00
0a3753bcce release: cut the 4.4.4 release 2017-09-28 12:13:13 -07:00
7fc2dceaf5 fix(compiler): do not consider a reference with members as a reference (#19466)
fixes: #18170
ref: #19454
2017-09-28 12:04:20 -07:00
c3b39bac52 fix(compiler): correctly map error message locations (#19424) 2017-09-28 10:55:17 -07:00
2cd88bfb0f docs(animations): document usage of negative limit values for query (#19451) 2017-09-28 09:35:10 -07:00
0cefb0b79b docs(aio): add copy about NgForOf (#18686) 2017-09-27 15:29:23 -07:00
55a7443974 docs(aio): edit summary and next step headers (#16962) 2017-09-27 13:47:03 -07:00
f9ebaf1b90 docs(aio): remove links to top of documents (#16971) 2017-09-27 13:46:50 -07:00
bc81fbdd27 fix(animations): properly support the query limit option value (#19419)
Closes #19232
2017-09-26 13:18:47 -07:00
c7aa8a132d fix(compiler): skip   when trimming / removing whitespaces (#19310)
Fixes #19304
2017-09-25 13:35:32 -07:00
5c99b01512 build(aio): run the upload server as a non-previleged user
closes #19352

Previously, the upload server (for PR previews) was run as root and
"downleveled" to a non-privileged user from inside the node script.

Now, with the latest version of `pm2` (which is used to run the upload server
scripts), we can get rid of that workaround and set the desired UID directly
through `pm2`.
2017-09-25 12:05:00 -07:00
992ba33a28 build(aio): upgrade all preview server dependencies 2017-09-25 12:04:51 -07:00
17e7c58981 build(aio): add metadata aliases for directives, components and pipes (#19317)
This change will enable people to link to the API docs via their selectors
or names, as used in a template.

Since the selectors can be quite complex we are not able to get 100%
accuracy.

Closes #16787
2017-09-25 12:04:41 -07:00
b8f15d2b77 build(aio): render class/interface "descendants" in API docs (#19343)
For classes, the tree of subclasses is rendered, recursively.

For interfaces, the descendants are separated into child interfaces, which
extend the interface, and classes, which implement the interface.

Closes #19306
2017-09-25 12:04:28 -07:00
5f9a10aab9 build(aio): ensure decorators with shared interface types are found (#19361)
Closes #19358
2017-09-25 12:03:31 -07:00
04bc5257a6 docs(aio): tidy up tooling documentation (#18151)
PR Close #18151
2017-09-21 11:27:01 -07:00
5bca58e748 docs(aio): applying some feedback (#18151)
PR Close #18151
2017-09-21 11:27:01 -07:00
526a67f8f4 docs(aio): add zipper documentation (#18151)
PR Close #18151
2017-09-21 11:27:00 -07:00
53a27e07b1 docs(aio): complete the plunker-builder docs (#18151)
PR Close #18151
2017-09-21 11:27:00 -07:00
ba0fb1e055 docs(aio): add boilerplate readme (#18151)
PR Close #18151
2017-09-21 11:27:00 -07:00
ed2c8aa6f8 docs(aio): high-level documentation of the transforms folder (#18151)
PR Close #18151
2017-09-21 11:27:00 -07:00
9226760120 docs(aio): high-level documentation of AIO tooling (#18151)
PR Close #18151
2017-09-21 11:27:00 -07:00
b9ee8b46a0 refactor(core): viewDef related refactoring (#19272)
- optimize the way node flags are propagated in `viewDef()`,
- fix `elementDef()` signature to make `namespaceAndName` nullable,
- move render parent computation with the parent computation

PR Close #19272
2017-09-21 11:26:43 -07:00
e7eb0b8b7c docs: update README and author guide about doc build errors (#19276)
- tells reader about `yarn serve-and-sync`.
- directs reader to look to docs-style-guide if get doc build error.
- update docs-style-guide to warn about ignored code files.

PR Close #19276
2017-09-21 09:56:33 -07:00
ae52851458 fix(tsc-wrapped): add metadata for type declarations (#19040)
PR Close #19040
2017-09-20 17:06:25 -07:00
e63cf3b89e ci(aio): correctly serialize commit messages with "double-quotes" (#19184)
PR Close #19184
2017-09-20 17:05:31 -07:00
9624fda082 build(aio): improve error message for ignored example files (#19265)
Addresses https://github.com/angular/angular/pull/18707#issuecomment-330396771

PR Close #19265
2017-09-20 16:50:36 -07:00
e19b6a8f38 build(aio): remove commented out code (#19265)
PR Close #19265
2017-09-20 16:50:36 -07:00
2aa6b54201 build: pin docker image to a tag for CircleCI (#19295)
Fixes the broken build
PR Close #19295
2017-09-20 16:50:20 -07:00
fe8550d278 docs: animations - replace iterator with simple code style (#18965)
Replaces iterator facade over the HeroService because webpack threw up.
Also this was an obscure distraction for readers with no obvious benefits.

PR Close #18965
2017-09-20 16:50:05 -07:00
87 changed files with 1674 additions and 1518 deletions

View File

@ -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.

View File

@ -1,3 +1,16 @@
<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 &nbsp; 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)

View File

@ -52,7 +52,7 @@ 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 +107,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.
* 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).

View File

@ -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

View File

@ -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) {

View File

@ -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"
}
}

View File

@ -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');

View File

@ -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(() => {

View File

@ -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');

View File

@ -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]();
});

View File

@ -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();

View File

@ -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/**/*",

View File

@ -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

View File

@ -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}

View File

@ -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 \

View File

@ -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

View File

@ -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[];
}

View File

@ -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[];
}

View File

@ -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[];
}

View File

@ -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[];
}

View File

@ -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[];
}

View File

@ -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[];
}

View File

@ -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[];
}

View File

@ -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);

View File

@ -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[];
}

View File

@ -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[];
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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)

View File

@ -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>

View File

@ -348,7 +348,7 @@ Here's the brief markup that produced that lengthy snippet:
```
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.
@ -360,6 +360,23 @@ located in the `content/examples/docs-style-guide` directory.
</div>
<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:

View File

@ -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)

View File

@ -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.

View File

@ -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&mdash;interpolation&mdash;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/>
@ -147,7 +146,6 @@ the global namespace. 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/>

View File

@ -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'>

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -443,7 +443,7 @@ Here are the code files discussed in this page.
## The road youve 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.

View File

@ -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.

View File

@ -1356,7 +1356,7 @@ Verify that you have the following structure:
## The road youve 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.

View File

@ -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.

24
aio/e2e/api.e2e-spec.ts Normal file
View File

@ -0,0 +1,24 @@
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);
});
});

30
aio/e2e/api.po.ts Normal file
View File

@ -0,0 +1,30 @@
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());
}
}

View File

@ -82,7 +82,7 @@
"concurrently": "^3.4.0",
"cross-spawn": "^5.1.0",
"dgeni": "^0.4.7",
"dgeni-packages": "^0.21.2",
"dgeni-packages": "^0.21.3",
"entities": "^1.1.1",
"eslint": "^3.19.0",
"eslint-plugin-jasmine": "^2.2.0",

View File

@ -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}}"

70
aio/tools/README.md Normal file
View File

@ -0,0 +1,70 @@
# AIO project tooling
This document gives an overview of the tools that we use to generate the content for the angular.io website.
The application that actually renders this content can be found in the `/aio/src` folder.
The handwritten content can be found in the `/aio/content` folder.
Each subfolder in this `/aio/tools/` folder contains a self-contained tool and its configuration. There is
a `README.md` file in each folder that describes the tool in more detail.
## cli-patches
The AIO application is built using the Angular CLI tool. We are often trialling new features for the CLI, which
we apply to the library after it is installed. This folder contains git patch files that contain these new features
and a utility to apply those patches to the CLI library.
See the [README.md](cli-patches/README.md) for more details.
## examples
Many of the documentation pages contain snippets of code examples. We extract these snippets from real
working example applications, which are stored in subfolders of the `/aio/content/examples` folder. Each
example can be built and run independently. Each example also provides e2e specs, which are run as part
of our Travis build tasks, to verify that the examples continue to work as expected, as changes are made
to the core Angular libraries.
In order to build, run and test these examples independently we need to install dependencies into their
sub-folder. Also there are a number of common boilerplate files that are needed to configure each
example's project. We maintain these common boilerplate files centrally to reduce the amount of effort
if one of them needs to change.
This `examples` tool folder contains two utilities:
* example-boilerplate.js - install/remove the npm dependencies and boilerplate files into/from each of the
examples' subfolders.
* run-example-e2e.js - run the e2e tests for one or more examples
See the [README.md](examples/README.md) for more details.
## example-zipper
In the AIO application, we offer the reader the option to download each example as a full self-contained
runnable project packaged as a zip file. These zip files are generated by the utility in this folder.
See the [README.md](example-zipper/README.md) for more details.
## plunker-builder
In the AIO application, we can embed a running version of the example as a [Plunker](http://plnkr.co/).
We can also provide a link to create a runnable version of the example in the [Plunker](http://plnkr.co/edit)
editor.
This folder contains three utilities: `regularPlunker.js`, `embeddedPlunker.js`, `generatePlunkers.js`.
See the [README.md](plunker-builder/README.md) for more details.
## transforms
All the content that is rendered by the AIO application, and some of its configuration files, are
generated from source files by [Dgeni](https://github.com/angular/dgeni). Dgeni is a general purpose
documentation generation tool.
Markdown files in `/aio/content`, code comments in the core Angular source files and example files are
processed and transformed into files that are consumed by the AIO application.
Dgeni is configured by "packages", which contain services and processors. Some of these packages are
installed as `node_modules` from the [dgeni-packages](https://github.com/angular/dgeni-packages) and
some are specific to the AIO project.
The project specific packages are stored in the `aio/tools/transforms` folder. See the
[README.md](transforms/README.md) for more details.

View File

@ -0,0 +1,7 @@
# Overview
The AIO application is built using the Angular CLI tool. We are often trialling new features for the CLI, which
we apply to the library after it is installed. This folder contains git patch files that contain these new features
and a utility to apply those patches to the CLI library.
**Currently, we have no patches to run.**

View File

@ -0,0 +1,84 @@
# Overview
In the AIO application, we offer the reader the option to download each example as a full self-contained
runnable project packaged as a zip file. These zip files are generated by the utility in this folder.
## Example zipper
The `exampleZipper.js` tool is very similar to the Plunker's `builder.js`. The latter creates an HTML file
with all the examples' files and the former creates a zip file instead. They both use the `plnkr.json` file
to flag an example as something to plunker or zip. For example:
```json
{
"description": "Tour of Heroes: Part 6",
"basePath": "src/",
"files":[
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1,2].*"
],
"tags": ["tutorial", "tour", "heroes", "http"]
}
```
The zipper will use this information for creating new zips.
## Three kinds of examples
The majority of examples in AIO use `System.js` but there are also `CLI` and `Webpack` projects. This
tool is able to differentiate between them.
The boilerplate uses a `package.json` that contains packages and scripts to run any kind of example.
Using that `package.json` in the zips would confuse the users.
Thanks to the `package.json` customizer, we can create a new `package.json` on the fly that would
only contain the packages and scripts needed to run that example.
The `exampleZipper.js` won't include any `System.js` related files for `CLI` or `Webpack` projects.
### The package.json customizer
Given a `type`, this tool will populate a `package.json` file customized for that type.
Here you find a:
* **base.json** - All the common scripts and packages
* **cli.json** - Extra scripts and packages for the CLI
* **webpack.json** - Extra scripts and packages for Webpack
* **systemjs.json** - All the System.js related packages but it also contains the remainder scripts
that are not in the other files.
The tool will also give some standard names to the scripts.
## The zipper.json
As mentioned, the tool uses the `plnkr.json` as a flag and also a configuration file for the zipper.
The problem is that not all examples have a plunker but they could offer a zip instead.
In those cases, you can create a `zipper.json` file with the same syntax. It will be ignored by the
plunker tool.
## Choosing the zip "type"
In both `plnkr.json` and `zipper.json` you can use two extra properties for the zipper configuration:
```
{
...
"removeSystemJsConfig": true,
"type": "webpack"
}
```
This would generate a zip for a webpack application and it will also remove everything related with
SystemJS.
## Executing the zip generation
`generateZips.js` will create a zip for each `plnkr.json` or `zipper.json` it finds.
Where? At `src/generated/zips/`.
Then the `<live-example>` embedded component will look at this folder to get the zip it needs for
the example.

View File

@ -0,0 +1,108 @@
# Overview
Many of the documentation pages contain snippets of code examples. We extract these snippets from
real working example applications, which are stored in subfolders of the `/aio/content/examples`
folder. Each example can be built and run independently. Each example also provides e2e specs, which
are run as part of our Travis build tasks, to verify that the examples continue to work as expected,
as changes are made to the core Angular libraries.
In order to build, run and test these examples independently we need to install dependencies into
their sub-folder. Also there are a number of common boilerplate files that are needed to configure
each example's project. We maintain these common boilerplate files centrally to reduce the amount
of effort if one of them needs to change.
## Boilerplate overview
As mentioned, many of the documentation pages contain snippets extracted from real example applications.
To achieve that, all those applications needs to contain a basic boilerplate. E.g. a `node_modules`
folder, `package.json` with scripts, `system.js` configuration, etc.
No one wants to maintain the boilerplate on each example, so the goal of this tool is to provide a
generic boilerplate that works in all the examples.
### Boilerplate files
Inside `/aio/tools/examples/shared/boilerplate` you will see all the common boilerplate you can find
in any Angular application using System.js. This is the boilerplate that will be carried to each example.
Among these files, there are a few special ones:
* **src/systemjs.config.js** - This is the configuration of System.js used to run the example locally.
* **src/systemjs.config.web.js** - This configuration replaces the previous one on Plunkers.
* **src/systemjs.config.web.build.js** - Same as the previous one but for using angular's `-builds`
versions.
* **src/systemjs-angular-loader.js** - It is a System.js plugin that removes the need of `moduleId`.
* **package.json** - This package.json only contains scripts, no dependencies. It contains the
different tasks needed to run any example. Doesn't matter if CLI, System.js or Webpack.
* **plnkr.json** - This file is used by the Plunker tool to generate a plunker for an example. This
concrete file is just a placeholder. Authors needs to tweak it for each guide. More at the
[plunker docs](../plunker-builder/README.md).
* **example-config.json** - This file serves as a flag to indicate that the current folder is an
example. This concrete file is just a placeholder. More on this later in this readme.
### The example-config.json
So what is this **example-config.json** again? If an author wants to create a new example, say
`/aio/content/examples/awesome-example`. The author needs to create an empty `example-config.json`
in that folder. That serves as a flag so this tool will say "Hey, that is an example, let's copy
all the boilerplate there".
So when the tool runs, it finds **all** the folders with an `example-config.json` file, and puts
a copy of the boilerplate in those folders.
Normally the file is empty, but we can add information in it, for example:
```json
{
"build": "build:cli",
"run": "serve:cli"
}
```
In this case, this would indicate that this is a CLI example. Won't make any difference on the
boilerplate, but will be useful for e2e tests (more on this later). Also works as a hint for
the example to know how is executed.
### A node_modules to share
With all the boilerplate files in place, the only missing piece are the installed packages. For
that we have a `/aio/tools/examples/shared/package.json` which contains **all** the packages
needed to run all the examples.
After installing these dependencies, a `node_modules` will be created at
`/aio/tools/examples/shared/node_modules`. This folder will be **symlinked** into each example.
So it is not a copy like the other boilerplate files. This solution works in all OSes. Windows
may require admin rights.
### End to end tests
Each example contains an `e2e-spec.ts` file. We can find all the related configuration files for
e2e in the `/aio/tools/examples/shared` folder.
This tool expects all the examples to be build with `npm run build`. If an example is not built
with that script, the author would need to specify the new build command in the `example-config.json`
as shown earlier.
### add-example-boilerplate.js
This script installs all the dependencies that are shared among all the examples, creates the
`node_modules` symlinks and copy all the boilerplate files where needed. It won't do anything
about plunkers nor e2e tests.
It also contains a function to remove all the boilerplate. It uses a `git clean -xdf` to do
the job. It will remove all files that don't exist in the git repository, **including any
new file that you are working on that hasn't been stage yet.** So be sure to save your work
before removing the boilerplate.
### run-example-e2e.js
This script will find all the `e2e-spec.ts` files and run them.
To not run all tests, you can use the `--filter=name` flag to run the example's e2e that contains
that name.
It also has an optional `--setup` flag to run the `add-example-boilerplate.js` script and install
the latest `webdriver`.
It will create a `/aio/protractor-results-txt` file when it finishes running tests.

View File

@ -0,0 +1,78 @@
# Overview
[Plunker](http://plnkr.co) is an online tool for creating, collaborating and sharing ideas. In AIO
we use it to share one or more runnable versions of our examples.
Plunker comes in two flavours. The [classic UI](http://plnkr.co/edit) and an [embedded UI](http://embed.plnkr.co).
The latter can be used both in a new tab or embedded within a guide. The AIO project uses the
embedded version in both ways.
* `regularPlunker.js` - generates an HTML file for each example that will post to Plunker to create
a new editable project, when rendered.
* `embeddedPlunker.js` - generates an HTML file for each example that can be used in an iframe to
render an embedded Plunker project.
* `generatePlunkers.js` - executes each of the `regularPlunker.js` and `embeddedPlunker.js` utilities
to generate all the example plunker files.
## Plunker generation
Both flavours are created within `builder.js`. How is a plunker created? What is the process from a
directory with files to a link with a plunker.
An "executable" plunker is an HTML file with a `<form>` that makes a post to plunker on submit. It
contains an `<input>` element for each file we need in the plunker.
The form will be submitted on load, so you can either double click the HTML file or open it with an
anchor tag to open the plunker.
So the `builder.js` job is to get all the needed files from an example and build this HTML file for you.
For plunkers, we use a special `systemjs.config` that exists in
`/aio/tools/examples/shared/boilerplate/src/systemjs.config.web.js` and we also add the Google's copyright
to each file.
## Customizing the generation per example basis
How does this tool know what is an example and what is not? It will look for all folders containing a
`plnkr.json` file. If found, all files within the folder and subfolders will be used in the plunker, with
a few generic exceptions that you can find at `builder.js`.
You can use the `plnkr.json` to customize the plunker generation. For example:
```json
{
"description": "Tour of Heroes: Part 6",
"basePath": "src/",
"files":[
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1,2].*"
],
"tags": ["tutorial", "tour", "heroes", "http"]
}
```
Here you can specify a description for the plunker, some tags, a basePath and also a files array where you
can specify extra files to add or to ignore.
## Classic plunkers and embedded ones
Luckily, both kind of plunkers are very similar, they are created in the same way with minor exceptions.
To handle those exceptions, we have the `embeddedPlunker.js` and the `regularPlunker.js`. Thanks to them,
the `builder.js` is as generic as possible.
## Executing the plunker generation
`generatePlunkers.js` will create a classic plunker and an embedded plunker for each `plnkr.json` it finds.
Where? At `src/generated/live-examples/`.
Then the `<live-example>` embedded component will look at this folder to get the plunker it needs for the
example.
## Appendix: Why not generating plunkers at runtime?
At AngularJS, all the plunkers were generated a runtime. The downside is that all the example codes would
need to be deployed as well and they won't be no longer useful after the plunker is generated. This tool
takes a few seconds to run, and the end result is only 3mb~.

View File

@ -1,27 +1,50 @@
# Documentation Generation
# Overview
The dgeni tool is used to generate the documentation from the source files held in this repository.
The documentation generation is configured by a dgeni package defined in `tools/transforms/angular.io-package/index.js`.
This package, in turn requires a number of other packages, some are defined locally in the `tools/transforms` folder,
such as `tools/transforms/cheatsheet-package` and `tools/transforms/content-package`, etc. And some are brought in from the
`dgeni-packages` node modules, such as `jsdoc` and `nunjucks`.
All the content that is rendered by the AIO application, and some of its configuration files, are
generated from source files by [Dgeni](https://github.com/angular/dgeni). Dgeni is a general purpose
documentation generation tool.
## Generating the docs
Markdown files in `/aio/content`, code comments in the core Angular source files and example files
are processed and transformed into files that are consumed by the AIO application.
To generate the documentation simply run `yarn docs` from the command line.
Dgeni is configured by "packages", which contain services and processors. Some of these packages are
installed as `node_modules` from the [dgeni-packages](https://github.com/angular/dgeni-packages) and
some are specific to the AIO project.
## Testing the dgeni packages
The project specific packages are stored in this folder (`aio/tools/transforms`).
The local packages have unit tests that you can execute by running `yarn docs-test` from the command line.
If you are an author and want to know how to generate the documentation, the steps are outlined in
the top level [README.md](../../README.md#guide-to-authoring).
## What does it generate?
## Root packages
The output from dgeni is written to files in the `src/generated` folder.
To run Dgeni, you must specify a root package, which acts as the entry point to the documentation
generation.
This root package, in turn requires a number of other packages, some are defined locally in the
`tools/transforms` folder, such as `tools/transforms/cheatsheet-package` and
`tools/transforms/content-package`, etc. And some are brought in from the `dgeni-packages` node
modules, such as `jsdoc` and `nunjucks`.
Notably this includes a JSON file containing the partial HTML for each "page" of the documentation, such as API pages and guides.
It also includes JSON files that contain metadata about the documentation such as navigation data and
keywords for building a search index.
* The primary root package is defined in `tools/transforms/angular.io-package/index.js`. This package
is used to run a full generation of all the documentation.
* There are also root packages defined in `tools/transforms/authors-package/*-package.js`. These
packages are used by the documentation authors when writing docs, since it allows them to run partial
doc generation, which is not complete but is faster for quickly seeing changes to the document that
you are working on.
## Viewing the docs
## Other packages
You can view the pages by running `yarn start` and navigating to https://localhost:4200.
* angular-base-package
* angular-api-package
* angular-content-package
* content-package
* examples-package
* links-package
* post-process-package
* remark-package
* target-package
## Templates
All the templates for the angular.io dgeni transformations are stoted in the `tools/transforms/templates`
folder. See the [README](./templates/README.md).

View File

@ -20,6 +20,7 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage])
.processor(require('./processors/mergeDecoratorDocs'))
.processor(require('./processors/extractDecoratedClasses'))
.processor(require('./processors/matchUpDirectiveDecorators'))
.processor(require('./processors/addMetadataAliases'))
.processor(require('./processors/filterContainedDocs'))
.processor(require('./processors/markBarredODocsAsPrivate'))
.processor(require('./processors/filterPrivateDocs'))

View File

@ -0,0 +1,40 @@
/**
* @dgProcessor addMetadataAliases
*
* Directives and components can also be referenced by their selectors,
* and Pipes can be referenced by their name.
* So let's add each selector as an alias to this doc.
*/
module.exports = function addMetadataAliasesProcessor() {
return {
$runAfter: ['extractDecoratedClassesProcessor'],
$runBefore: ['computing-ids'],
$process: function(docs) {
docs.forEach(doc => {
switch(doc.docType) {
case 'directive':
case 'component':
doc.aliases = doc.aliases.concat(extractSelectors(doc[doc.docType + 'Options'].selector));
break;
case 'pipe':
if (doc.pipeOptions.name) {
doc.aliases = doc.aliases.concat(stripQuotes(doc.pipeOptions.name));
}
break;
}
});
}
};
};
function extractSelectors(selectors) {
if (selectors) {
return stripQuotes(selectors).split(',').map(selector => selector.replace(/^\W*([\w-]+)\W*$/, '$1'));
} else {
return [];
}
}
function stripQuotes(value) {
return (typeof(value) === 'string') ? value.trim().replace(/^(['"])(.*)\1$/, '$2') : value;
}

View File

@ -0,0 +1,57 @@
const testPackage = require('../../helpers/test-package');
const processorFactory = require('./addMetadataAliases');
const Dgeni = require('dgeni');
describe('addSelectorsAsAliases processor', () => {
it('should be available on the injector', () => {
const dgeni = new Dgeni([testPackage('angular-api-package')]);
const injector = dgeni.configureInjector();
const processor = injector.get('addMetadataAliasesProcessor');
expect(processor.$process).toBeDefined();
});
it('should run after the correct processor', () => {
const processor = processorFactory();
expect(processor.$runAfter).toEqual(['extractDecoratedClassesProcessor']);
});
it('should run before the correct processor', () => {
const processor = processorFactory();
expect(processor.$runBefore).toEqual(['computing-ids']);
});
it('should add new aliases for directives, components and pipes', () => {
const processor = processorFactory();
const docs = [
{ docType: 'class', name: 'MyClass', aliases: ['MyClass'] },
{ docType: 'interface', name: 'MyInterface', aliases: ['MyInterface'] },
{ docType: 'enum', name: 'MyEnum', aliases: ['MyEnum'] },
{ docType: 'function', name: 'myFunction', aliases: ['myFunction'] },
{ docType: 'pipe', name: 'MyPipe', aliases: ['MyPipe'], pipeOptions: { name: '\'myPipe\'' } },
{ docType: 'directive', name: 'MyDirective', aliases: ['MyDirective'], directiveOptions: { selector: '\'my-directive,[myDirective],[my-directive]\'' } },
{ docType: 'directive', name: 'NgModel', aliases: ['NgModel'], directiveOptions: { selector: '\'[ngModel]:not([formControlName]):not([formControl])\'' } },
{ docType: 'component', name: 'MyComponent', aliases: ['MyComponent'], componentOptions: { selector: '\'my-component\'' } },
{ docType: 'decorator', name: 'MyDecorator', aliases: ['MyDecorator'] },
{ docType: 'module', name: 'myModule', aliases: ['myModule'], id: 'some/myModule' },
{ docType: 'var', name: 'myVar', aliases: ['myVar'] },
{ docType: 'let', name: 'myLet', aliases: ['myLet'] },
{ docType: 'const', name: 'myConst', aliases: ['myConst'] },
{ docType: 'type-alias', name: 'myType', aliases: ['myType'] },
];
processor.$process(docs);
expect(docs[0].aliases).toEqual([docs[0].name]);
expect(docs[1].aliases).toEqual([docs[1].name]);
expect(docs[2].aliases).toEqual([docs[2].name]);
expect(docs[3].aliases).toEqual([docs[3].name]);
expect(docs[4].aliases).toEqual([docs[4].name, 'myPipe']);
expect(docs[5].aliases).toEqual([docs[5].name, 'my-directive', 'myDirective', 'my-directive']);
expect(docs[6].aliases).toEqual([docs[6].name, '[ngModel]:not([formControlName]):not([formControl])']);
expect(docs[7].aliases).toEqual([docs[7].name, 'my-component']);
expect(docs[8].aliases).toEqual([docs[8].name]);
expect(docs[9].aliases).toEqual([docs[9].name]);
expect(docs[10].aliases).toEqual([docs[10].name]);
expect(docs[11].aliases).toEqual([docs[11].name]);
expect(docs[12].aliases).toEqual([docs[12].name]);
});
});

View File

@ -48,9 +48,9 @@ module.exports = function mergeDecoratorDocs(log) {
$runAfter: ['processing-docs'],
$runBefore: ['docs-processed'],
makeDecoratorCalls: [
{type: '', description: 'toplevel'},
{type: 'Prop', description: 'property'},
{type: 'Param', description: 'parameter'},
{type: '', description: 'toplevel', functionName: 'makeDecorator'},
{type: 'Prop', description: 'property', functionName: 'makePropDecorator'},
{type: 'Param', description: 'parameter', functionName: 'makeParamDecorator'},
],
$process: function(docs) {
@ -58,24 +58,25 @@ module.exports = function mergeDecoratorDocs(log) {
var docsToMerge = Object.create(null);
docs.forEach(function(doc) {
const initializer = getInitializer(doc);
if (initializer) {
makeDecoratorCalls.forEach(function(call) {
// find all the decorators, signified by a call to `make...Decorator<Decorator>(metadata)`
if (initializer.expression && initializer.expression.text === call.functionName) {
log.debug('mergeDecoratorDocs: found decorator', doc.docType, doc.name);
doc.docType = 'decorator';
doc.decoratorLocation = call.description;
// Get the type of the decorator metadata from the first "type" argument of the call.
// For example the `X` of `createDecorator<X>(...)`.
doc.decoratorType = initializer.arguments[0].text;
// clear the symbol type named since it is not needed
doc.symbolTypeName = undefined;
makeDecoratorCalls.forEach(function(call) {
// find all the decorators, signified by a call to `make...Decorator<Decorator>(metadata)`
var makeDecorator = getMakeDecoratorCall(doc, call.type);
if (makeDecorator) {
log.debug('mergeDecoratorDocs: found decorator', doc.docType, doc.name);
doc.docType = 'decorator';
doc.decoratorLocation = call.description;
// Get the type of the decorator metadata from the first "type" argument of the call.
// For example the `X` of `createDecorator<X>(...)`.
doc.decoratorType = makeDecorator.arguments[0].text;
// clear the symbol type named since it is not needed
doc.symbolTypeName = undefined;
// keep track of the names of the metadata interface that will need to be merged into this decorator doc
docsToMerge[doc.name + 'Decorator'] = doc;
}
});
// keep track of the names of the metadata interface that will need to be merged into this decorator doc
docsToMerge[doc.name + 'Decorator'] = doc;
}
});
}
});
// merge the metadata docs into the decorator docs
@ -106,27 +107,19 @@ module.exports = function mergeDecoratorDocs(log) {
};
};
function getMakeDecoratorCall(doc, type) {
var makeDecoratorFnName = 'make' + (type || '') + 'Decorator';
var initializer = doc.declaration &&
doc.declaration.initializer;
if (initializer) {
// There appear to be two forms of initializer:
// export var Injectable: InjectableFactory =
// <InjectableFactory>makeDecorator(InjectableMetadata);
// and
// export var RouteConfig: (configs: RouteDefinition[]) => ClassDecorator =
// makeDecorator(RouteConfigAnnotation);
// In the first case, the type assertion `<InjectableFactory>` causes the AST to contain an
// extra level of expression
// to hold the new type of the expression.
if (initializer.expression && initializer.expression.expression) {
initializer = initializer.expression;
}
if (initializer.expression && initializer.expression.text === makeDecoratorFnName) {
return initializer;
}
function getInitializer(doc) {
var initializer = doc.symbol && doc.symbol.valueDeclaration && doc.symbol.valueDeclaration.initializer;
// There appear to be two forms of initializer:
// export var Injectable: InjectableFactory =
// <InjectableFactory>makeDecorator(InjectableMetadata);
// and
// export var RouteConfig: (configs: RouteDefinition[]) => ClassDecorator =
// makeDecorator(RouteConfigAnnotation);
// In the first case, the type assertion `<InjectableFactory>` causes the AST to contain an
// extra level of expression
// to hold the new type of the expression.
if (initializer && initializer.expression && initializer.expression.expression) {
initializer = initializer.expression;
}
return initializer;
}

View File

@ -2,11 +2,11 @@ var testPackage = require('../../helpers/test-package');
var Dgeni = require('dgeni');
describe('mergeDecoratorDocs processor', () => {
var dgeni, injector, processor, moduleDoc, decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc;
let processor, moduleDoc, decoratorDoc, metadataDoc, otherDoc;
beforeEach(() => {
dgeni = new Dgeni([testPackage('angular-api-package')]);
injector = dgeni.configureInjector();
const dgeni = new Dgeni([testPackage('angular-api-package')]);
const injector = dgeni.configureInjector();
processor = injector.get('mergeDecoratorDocs');
moduleDoc = {};
@ -15,7 +15,9 @@ describe('mergeDecoratorDocs processor', () => {
name: 'Component',
docType: 'const',
description: 'A description of the metadata for the Component decorator',
declaration: {initializer: {expression: {text: 'makeDecorator'}, arguments: [{text: 'X'}]}},
symbol: {
valueDeclaration: { initializer: { expression: { text: 'makeDecorator' }, arguments: [{ text: 'X' }] } }
},
members: [
{ name: 'templateUrl', description: 'A description of the templateUrl property' }
],
@ -40,45 +42,45 @@ describe('mergeDecoratorDocs processor', () => {
moduleDoc
};
decoratorDocWithTypeAssertion = {
name: 'Y',
docType: 'const',
declaration: { initializer: { expression: {type: {}, expression: {text: 'makeDecorator'}, arguments: [{text: 'Y'}]} } },
moduleDoc
};
otherDoc = {
name: 'Y',
docType: 'const',
declaration: {initializer: {expression: {text: 'otherCall'}, arguments: [{text: 'param1'}]}},
symbol: {
valueDeclaration: { initializer: { expression: { text: 'otherCall' }, arguments: [{ text: 'param1' }] } }
},
moduleDoc
};
moduleDoc.exports = [decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc];
moduleDoc.exports = [decoratorDoc, metadataDoc, otherDoc];
});
it('should change the docType of only the docs that are initialied by a call to makeDecorator', () => {
processor.$process([decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc]);
it('should change the docType of only the docs that are initialized by a call to makeDecorator', () => {
processor.$process([decoratorDoc, metadataDoc, otherDoc]);
expect(decoratorDoc.docType).toEqual('decorator');
expect(decoratorDocWithTypeAssertion.docType).toEqual('decorator');
expect(otherDoc.docType).toEqual('const');
});
it('should extract the "type" of the decorator meta data', () => {
processor.$process([decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc]);
processor.$process([decoratorDoc, metadataDoc, otherDoc]);
expect(decoratorDoc.decoratorType).toEqual('X');
expect(decoratorDocWithTypeAssertion.decoratorType).toEqual('Y');
});
it('should copy across properties from the call signature doc', () => {
processor.$process([decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc]);
processor.$process([decoratorDoc, metadataDoc, otherDoc]);
expect(decoratorDoc.description).toEqual('The actual description of the call signature');
expect(decoratorDoc.whatItDoes).toEqual('Does something cool...');
expect(decoratorDoc.howToUse).toEqual('Use it like this...');
});
it('should remove the metadataDoc from the module exports', () => {
processor.$process([decoratorDoc, metadataDoc, decoratorDocWithTypeAssertion, otherDoc]);
processor.$process([decoratorDoc, metadataDoc, otherDoc]);
expect(moduleDoc.exports).not.toContain(metadataDoc);
});
it('should cope with decorators that have type params', () => {
decoratorDoc.symbol.valueDeclaration.initializer.expression.type = {};
processor.$process([decoratorDoc, metadataDoc, otherDoc]);
expect(decoratorDoc.docType).toEqual('decorator');
});
});

View File

@ -0,0 +1,9 @@
module.exports = function filterBy() {
return {
name: 'filterByPropertyValue',
process: function(list, property, value) {
if (!list) return list;
return list.filter(item => item[property] === value);
}
};
};

View File

@ -0,0 +1,14 @@
const factory = require('./filterByPropertyValue');
describe('filterByPropertyValue filter', () => {
let filter;
beforeEach(function() { filter = factory(); });
it('should be called "filterByPropertyValue"', function() { expect(filter.name).toEqual('filterByPropertyValue'); });
it('should filter out items that do not match the given property value', function() {
expect(filter.process([{ a: 1 }, { a: 2 }, { b: 1 }, { a: 1, b: 2 }, { a: null }, { a: undefined }], 'a', 1))
.toEqual([{ a: 1 }, { a: 1, b: 2 }]);
});
});

View File

@ -20,14 +20,25 @@ module.exports = new Package('angular-content', [basePackage, contentPackage])
// Where do we get the source files?
.config(function(readFilesProcessor, collectExamples) {
const gitignoreFile = fs.readFileSync(path.resolve(GUIDE_EXAMPLES_PATH, '.gitignore'), 'utf8');
const gitignoreFilePath = path.resolve(GUIDE_EXAMPLES_PATH, '.gitignore');
const gitignoreFile = fs.readFileSync(gitignoreFilePath, 'utf8');
const gitignore = ignore().add(gitignoreFile);
const examplePaths = glob.sync('**/*', { cwd: GUIDE_EXAMPLES_PATH, dot: true, ignore: '**/node_modules/**', mark: true })
.filter(filePath => filePath !== '.gitignore') // we are not interested in the .gitignore file itself
.filter(filePath => !/\/$/.test(filePath)); // this filter removes the folders, leaving only files
const filteredExamplePaths = gitignore.filter(examplePaths) // filter out files that match the .gitignore rules
.map(filePath => path.resolve(GUIDE_EXAMPLES_PATH, filePath)); // we need the full paths for the filereader
const ignoredExamplePaths = [];
const resolvedExamplePaths = [];
examplePaths.forEach(filePath => {
// filter out files that match the .gitignore rules
if (gitignore.ignores(filePath)) {
ignoredExamplePaths.push(filePath);
} else {
// we need the full paths for the filereader
resolvedExamplePaths.push(path.resolve(GUIDE_EXAMPLES_PATH, filePath));
}
});
readFilesProcessor.sourceFiles = readFilesProcessor.sourceFiles.concat([
{
@ -48,7 +59,7 @@ module.exports = new Package('angular-content', [basePackage, contentPackage])
},
{
basePath: CONTENTS_PATH,
include: filteredExamplePaths,
include: resolvedExamplePaths,
fileReader: 'exampleFileReader'
},
{
@ -69,6 +80,7 @@ module.exports = new Package('angular-content', [basePackage, contentPackage])
]);
collectExamples.exampleFolders.push('examples');
collectExamples.registerIgnoredExamples(ignoredExamplePaths, gitignoreFilePath);
})

View File

@ -5,7 +5,6 @@ module.exports =
new Package('examples', [jsdocPackage])
.factory(require('./inline-tag-defs/example'))
// .factory(require('./inline-tag-defs/exampleTabs'))
.factory(require('./services/parseArgString'))
.factory(require('./services/example-map'))
.factory(require('./file-readers/example-reader'))
@ -21,7 +20,6 @@ module.exports =
.config(function(inlineTagProcessor, exampleInlineTagDef) {
inlineTagProcessor.inlineTagDefinitions.push(exampleInlineTagDef);
// inlineTagProcessor.inlineTagDefinitions.push(exampleTabsInlineTagDef);
})
.config(function(computePathsProcessor) {

View File

@ -6,7 +6,26 @@ module.exports = function collectExamples(exampleMap, regionParser, log, createD
$runAfter: ['files-read'],
$runBefore: ['parsing-tags'],
$validate: {exampleFolders: {presence: true}},
$process: function(docs) {
exampleFolders: [],
ignoredExamples: {},
/**
* Call this method to indicate to the processor that some files, that actually exist,
* have been filtered out from being processed.
* @param paths an array of relative paths to the examples that have been ignored.
* @param gitIgnorePath the path to the gitignore file that caused this example to be ignored.
*/
registerIgnoredExamples(paths, gitIgnorePath) {
paths.forEach(path => { this.ignoredExamples[path] = gitIgnorePath; });
},
/**
* Call this method to find out if an example was ignored.
* @param path a relative path to the example file to test for being ignored.
* @returns the path to the .gitignore file.
*/
isExampleIgnored(path) {
return this.ignoredExamples[path];
},
$process(docs) {
const exampleFolders = this.exampleFolders;
const regionDocs = [];
docs = docs.filter((doc) => {

View File

@ -23,161 +23,175 @@ describe('collectExampleRegions processor', () => {
processor.exampleFolders = ['examples-1', 'examples-2'];
});
it('should identify example files that are in the exampleFolders', () => {
const docs = [
createDoc('A', 'examples-1/x/app.js'), createDoc('B', 'examples-1/y/index.html'),
createDoc('C', 'examples-2/s/app.js'), createDoc('D', 'examples-2/t/style.css'),
createDoc('E', 'other/b/c.js')
];
describe('$process', () => {
processor.$process(docs);
it('should identify example files that are in the exampleFolders', () => {
const docs = [
createDoc('A', 'examples-1/x/app.js'), createDoc('B', 'examples-1/y/index.html'),
createDoc('C', 'examples-2/s/app.js'), createDoc('D', 'examples-2/t/style.css'),
createDoc('E', 'other/b/c.js')
];
expect(exampleMap['examples-1']['x/app.js']).toBeDefined();
expect(exampleMap['examples-1']['y/index.html']).toBeDefined();
expect(exampleMap['examples-2']['s/app.js']).toBeDefined();
expect(exampleMap['examples-2']['t/style.css']).toBeDefined();
processor.$process(docs);
expect(exampleMap['other']).toBeUndefined();
});
expect(exampleMap['examples-1']['x/app.js']).toBeDefined();
expect(exampleMap['examples-1']['y/index.html']).toBeDefined();
expect(exampleMap['examples-2']['s/app.js']).toBeDefined();
expect(exampleMap['examples-2']['t/style.css']).toBeDefined();
it('should remove example files from the docs collection', () => {
const docs = [
createDoc('Example A', 'examples-1/x/app.js'),
createDoc('Example B', 'examples-1/y/index.html'),
createDoc('Other doc 1', 'examples-2/t/style.css', 'content'),
createDoc('Example C', 'examples-2/s/app.js'),
createDoc('Other doc 2', 'other/b/c.js', 'content')
];
expect(exampleMap['other']).toBeUndefined();
});
const processedDocs = processor.$process(docs);
it('should remove example files from the docs collection', () => {
const docs = [
createDoc('Example A', 'examples-1/x/app.js'),
createDoc('Example B', 'examples-1/y/index.html'),
createDoc('Other doc 1', 'examples-2/t/style.css', 'content'),
createDoc('Example C', 'examples-2/s/app.js'),
createDoc('Other doc 2', 'other/b/c.js', 'content')
];
expect(processedDocs.filter(doc => doc.docType === 'example-file')).toEqual([]);
});
const processedDocs = processor.$process(docs);
it('should not remove docs from the docs collection that are not example files', () => {
const docs = [
createDoc('Example A', 'examples-1/x/app.js'),
createDoc('Example B', 'examples-1/y/index.html'),
createDoc('Other doc 1', 'examples-2/t/style.css', 'content'),
createDoc('Example C', 'examples-2/s/app.js'),
createDoc('Other doc 2', 'other/b/c.js', 'content')
];
expect(processedDocs.filter(doc => doc.docType === 'example-file')).toEqual([]);
});
const processedDocs = processor.$process(docs);
it('should not remove docs from the docs collection that are not example files', () => {
const docs = [
createDoc('Example A', 'examples-1/x/app.js'),
createDoc('Example B', 'examples-1/y/index.html'),
createDoc('Other doc 1', 'examples-2/t/style.css', 'content'),
createDoc('Example C', 'examples-2/s/app.js'),
createDoc('Other doc 2', 'other/b/c.js', 'content')
];
expect(processedDocs.filter(doc => doc.docType !== 'example-file'))
.toEqual(jasmine.objectContaining([
const processedDocs = processor.$process(docs);
expect(processedDocs.filter(doc => doc.docType !== 'example-file'))
.toEqual(jasmine.objectContaining([
createDoc('Other doc 1', 'examples-2/t/style.css', 'content'),
createDoc('Other doc 2', 'other/b/c.js', 'content')
]));
});
it('should call `regionParser` from with the content and file extension of each example doc',
() => {
const docs = [
createDoc('Example A', 'examples-1/x/app.js'),
createDoc('Example B', 'examples-1/y/index.html'),
createDoc('Other doc 1', 'examples-2/t/style.css', 'content'),
createDoc('Example C', 'examples-2/s/app.js'),
createDoc('Other doc 2', 'other/b/c.js', 'content')
]));
});
];
it('should call `regionParser` from with the content and file extension of each example doc',
() => {
const docs = [
createDoc('Example A', 'examples-1/x/app.js'),
createDoc('Example B', 'examples-1/y/index.html'),
createDoc('Other doc 1', 'examples-2/t/style.css', 'content'),
createDoc('Example C', 'examples-2/s/app.js'),
createDoc('Other doc 2', 'other/b/c.js', 'content')
];
processor.$process(docs);
processor.$process(docs);
expect(regionParser).toHaveBeenCalledTimes(3);
expect(regionParser).toHaveBeenCalledWith('Example A', 'js');
expect(regionParser).toHaveBeenCalledWith('Example B', 'html');
expect(regionParser).toHaveBeenCalledWith('Example C', 'js');
});
expect(regionParser).toHaveBeenCalledTimes(3);
expect(regionParser).toHaveBeenCalledWith('Example A', 'js');
expect(regionParser).toHaveBeenCalledWith('Example B', 'html');
expect(regionParser).toHaveBeenCalledWith('Example C', 'js');
});
it('should attach parsed content as renderedContent to the example file docs', () => {
const docs = [
createDoc('A', 'examples-1/x/app.js'),
createDoc('B', 'examples-1/y/index.html'),
createDoc('C', 'examples-2/s/app.js'),
createDoc('D', 'examples-2/t/style.css'),
];
it('should attach parsed content as renderedContent to the example file docs', () => {
const docs = [
createDoc('A', 'examples-1/x/app.js'),
createDoc('B', 'examples-1/y/index.html'),
createDoc('C', 'examples-2/s/app.js'),
createDoc('D', 'examples-2/t/style.css'),
];
processor.$process(docs);
processor.$process(docs);
expect(exampleMap['examples-1']['x/app.js'].renderedContent).toEqual('PARSED:A');
expect(exampleMap['examples-1']['y/index.html'].renderedContent).toEqual('PARSED:B');
expect(exampleMap['examples-2']['s/app.js'].renderedContent).toEqual('PARSED:C');
expect(exampleMap['examples-2']['t/style.css'].renderedContent).toEqual('PARSED:D');
expect(exampleMap['examples-1']['x/app.js'].renderedContent).toEqual('PARSED:A');
expect(exampleMap['examples-1']['y/index.html'].renderedContent).toEqual('PARSED:B');
expect(exampleMap['examples-2']['s/app.js'].renderedContent).toEqual('PARSED:C');
expect(exampleMap['examples-2']['t/style.css'].renderedContent).toEqual('PARSED:D');
});
it('should create region docs for each region in the example file docs', () => {
const docs = [
createDoc('/* #docregion X */\nA', 'examples-1/x/app.js'),
createDoc('<!-- #docregion Y -->\nB', 'examples-1/y/index.html'),
createDoc('/* #docregion Z */\nC', 'examples-2/t/style.css'),
];
const newDocs = processor.$process(docs);
expect(newDocs.length).toEqual(3);
expect(newDocs).toEqual([
jasmine.objectContaining({
docType: 'example-region',
name: 'dummy',
id: 'examples-1/x/app.js#dummy',
contents: 'js'
}),
jasmine.objectContaining({
docType: 'example-region',
name: 'dummy',
id: 'examples-1/y/index.html#dummy',
contents: 'html'
}),
jasmine.objectContaining({
docType: 'example-region',
name: 'dummy',
id: 'examples-2/t/style.css#dummy',
contents: 'css'
})
]);
});
it('should attach region docs to the example file docs', () => {
const docs = [
createDoc('/* #docregion X */\nA', 'examples-1/x/app.js'),
createDoc('<!-- #docregion Y -->\nB', 'examples-1/y/index.html'),
createDoc('/* #docregion Z */\nC', 'examples-2/t/style.css'),
];
processor.$process(docs);
expect(exampleMap['examples-1']['x/app.js'].regions).toEqual({
dummy: {
docType: 'example-region',
path: 'examples-1/x/app.js',
name: 'dummy',
id: 'examples-1/x/app.js#dummy',
aliases: ['examples-1/x/app.js#dummy'],
contents: 'js'
}
});
expect(exampleMap['examples-1']['y/index.html'].regions).toEqual({
dummy: {
docType: 'example-region',
path: 'examples-1/y/index.html',
name: 'dummy',
id: 'examples-1/y/index.html#dummy',
aliases: ['examples-1/y/index.html#dummy'],
contents: 'html'
}
it('should create region docs for each region in the example file docs', () => {
const docs = [
createDoc('/* #docregion X */\nA', 'examples-1/x/app.js'),
createDoc('<!-- #docregion Y -->\nB', 'examples-1/y/index.html'),
createDoc('/* #docregion Z */\nC', 'examples-2/t/style.css'),
];
const newDocs = processor.$process(docs);
expect(newDocs.length).toEqual(3);
expect(newDocs).toEqual([
jasmine.objectContaining({
docType: 'example-region',
name: 'dummy',
id: 'examples-1/x/app.js#dummy',
contents: 'js'
}),
jasmine.objectContaining({
docType: 'example-region',
name: 'dummy',
id: 'examples-1/y/index.html#dummy',
contents: 'html'
}),
jasmine.objectContaining({
docType: 'example-region',
name: 'dummy',
id: 'examples-2/t/style.css#dummy',
contents: 'css'
})
]);
});
expect(exampleMap['examples-2']['t/style.css'].regions).toEqual({
dummy: {
docType: 'example-region',
path: 'examples-2/t/style.css',
name: 'dummy',
id: 'examples-2/t/style.css#dummy',
aliases: ['examples-2/t/style.css#dummy'],
contents: 'css'
}
it('should attach region docs to the example file docs', () => {
const docs = [
createDoc('/* #docregion X */\nA', 'examples-1/x/app.js'),
createDoc('<!-- #docregion Y -->\nB', 'examples-1/y/index.html'),
createDoc('/* #docregion Z */\nC', 'examples-2/t/style.css'),
];
processor.$process(docs);
expect(exampleMap['examples-1']['x/app.js'].regions).toEqual({
dummy: {
docType: 'example-region',
path: 'examples-1/x/app.js',
name: 'dummy',
id: 'examples-1/x/app.js#dummy',
aliases: ['examples-1/x/app.js#dummy'],
contents: 'js'
}
});
expect(exampleMap['examples-1']['y/index.html'].regions).toEqual({
dummy: {
docType: 'example-region',
path: 'examples-1/y/index.html',
name: 'dummy',
id: 'examples-1/y/index.html#dummy',
aliases: ['examples-1/y/index.html#dummy'],
contents: 'html'
}
});
expect(exampleMap['examples-2']['t/style.css'].regions).toEqual({
dummy: {
docType: 'example-region',
path: 'examples-2/t/style.css',
name: 'dummy',
id: 'examples-2/t/style.css#dummy',
aliases: ['examples-2/t/style.css#dummy'],
contents: 'css'
}
});
});
});
describe('filtered examples', () => {
it('should indicate if an example was filtered', () => {
processor.registerIgnoredExamples(['c/d/e', 'e/f/g'], 'path/to/gitignore');
processor.registerIgnoredExamples(['x/y/z'], 'path/to/other/gitignore');
expect(processor.isExampleIgnored('a/b/c')).toBeFalsy();
expect(processor.isExampleIgnored('c/d/e')).toEqual('path/to/gitignore');
expect(processor.isExampleIgnored('e/f/g')).toEqual('path/to/gitignore');
expect(processor.isExampleIgnored('x/y/z')).toEqual('path/to/other/gitignore');
});
});
});

View File

@ -14,15 +14,22 @@ module.exports = function getExampleRegion(exampleMap, createDocMessage, collect
// If still no file then we error
if (!exampleFile) {
const message = createDocMessage('Missing example file... relativePath: "' + relativePath + '".', doc) + '\n' +
'Example files can be found in: ' + EXAMPLES_FOLDERS.join(', ');
throw new Error(message);
const gitIgnoreFile = collectExamples.isExampleIgnored(relativePath);
if( gitIgnoreFile) {
const message = createDocMessage('Ignored example file... relativePath: "' + relativePath + '"', doc) + '\n' +
'This example file exists but has been ignored by a rule, in "' + gitIgnoreFile + '".';
throw new Error(message);
} else {
const message = createDocMessage('Missing example file... relativePath: "' + relativePath + '".', doc) + '\n' +
'Example files can be found in the following relative paths: ' + EXAMPLES_FOLDERS.map(function(folder) { return '"' + folder + '"'; }).join(', ');
throw new Error(message);
}
}
var sourceCodeDoc = exampleFile.regions[regionName || ''];
if (!sourceCodeDoc) {
const message = createDocMessage('Missing example region... relativePath: "' + relativePath + '", region: "' + regionName + '".', doc) + '\n' +
'Regions available are:' + Object.keys[exampleFile.regions];
'Regions available are: ' + Object.keys(exampleFile.regions).map(function(region) { return '"' + region + '"'; }).join(', ');
throw new Error(message);
}

View File

@ -11,6 +11,7 @@ describe('getExampleRegion', () => {
collectExamples = injector.get('collectExamples');
exampleMap = injector.get('exampleMap');
collectExamples.exampleFolders = ['examples'];
collectExamples.registerIgnoredExamples(['filtered/path'], 'some/gitignore');
exampleMap['examples'] = {
'test/url': { regions: {
'': { renderedContent: 'whole file' },
@ -27,12 +28,19 @@ describe('getExampleRegion', () => {
expect(getExampleRegion({}, 'test/url', 'region-1')).toEqual('region 1 contents');
});
it('should throw an error if an example doesn\'t exist', function() {
expect(function() {
it('should throw an error if an example doesn\'t exist', () => {
expect(() => {
getExampleRegion({}, 'missing/file', 'region-1');
}).toThrowError();
expect(function() {
}).toThrowError('Missing example file... relativePath: "missing/file". - doc\nExample files can be found in the following relative paths: "examples"');
expect(() => {
getExampleRegion({}, 'test/url', 'missing-region');
}).toThrowError();
}).toThrowError('Missing example region... relativePath: "test/url", region: "missing-region". - doc\nRegions available are: "", "region-1"');
});
it('should throw an error if an example has been filtered out', () => {
expect(() => {
getExampleRegion({}, 'filtered/path', 'any-region');
}).toThrowError('Ignored example file... relativePath: "filtered/path" - doc\n' +
'This example file exists but has been ignored by a rule, in "some/gitignore".');
});
});

View File

@ -10,7 +10,8 @@ child. The template extension hierarchy looks like this (with declared blocks in
- layout/base.template.html (base)
- module.template.html
- layout/api-base.template.html (jumpNav, jumpNavLinks, whatItDoes, infoBar, securityConsiderations, deprecationNotes, howToUse, details)
- layout/api-base.template.html (jumpNav, jumpNavLinks, whatItDoes, infoBar, securityConsiderations,
deprecationNotes, howToUse, details)
- class.template.html
- directive.template.html
- enum.template.html
@ -26,119 +27,6 @@ child. The template extension hierarchy looks like this (with declared blocks in
# Doc Properties
It is useful to know what properties are available on each doc type when working with the templates.
Here is an overview:
## class
- docType
- name
- id
- moduleDoc
- path
- description
- notYetDocumented
- members
## directive
- docType
- name
- id
- moduleDoc
- path
- description
- notYetDocumented
- members
## enum
- docType
- name
- id
- moduleDoc
- path
- description
- notYetDocumented
## var
- docType
- name
- id
- moduleDoc
- path
- description
- notYetDocumented
## const
- docType
- name
- id
- moduleDoc
- path
- description
- notYetDocumented
## let
- docType
- name
- id
- moduleDoc
- path
- description
- notYetDocumented
## decorator
- docType
- name
- id
- moduleDoc
- path
- description
- notYetDocumented
- members
## function
- docType
- name
- id
- moduleDoc
- path
- description
- notYetDocumented
## interface
- docType
- name
- id
- moduleDoc
- path
- description
- notYetDocumented
- members
## type-alias
- docType
- name
- id
- moduleDoc
- path
- description
- notYetDocumented
## pipe
- docType
- name
- id
- moduleDoc
- path
- description
- notYetDocumented
The `typescript` Dgeni package is now written in TypeScript and there is a class for each of the types of
API document. See https://github.com/angular/dgeni-packages/tree/master/typescript/src/api-doc-types.
This is a good place to go to see what properties you can use in the templates.

View File

@ -1,4 +1,5 @@
{% import "lib/memberHelpers.html" as memberHelpers -%}
{% import "lib/descendants.html" as descendants -%}
{% import "lib/paramList.html" as params -%}
{% extends 'export-base.template.html' -%}
@ -6,6 +7,7 @@
{% block details %}
{% block additional %}{% endblock %}
{% include "includes/description.html" %}
{$ descendants.renderDescendants(doc, 'class', 'Subclasses') $}
{$ memberHelpers.renderMemberDetails(doc.statics, 'static-members', 'static-member', 'Static Members') $}
{% if doc.constructorDoc %}{$ memberHelpers.renderMemberDetails([doc.constructorDoc], 'constructors', 'constructor', 'Constructor') $}{% endif %}
{$ memberHelpers.renderMemberDetails(doc.members, 'instance-members', 'instance-member', 'Members') $}

View File

@ -1,9 +1,11 @@
{% import "lib/memberHelpers.html" as memberHelper -%}
{% if doc.members.length %}
<section class="decorator-overview">
<h2>Metadata Overview</h2>
<h2>Metadata Overview</h2>
<code-example language="ts" hideCopy="true">
@{$ doc.name $}{$ doc.typeParams | escape $}({ {$ memberHelper.renderMembers(doc) $}
})
</code-example>
</section>
</section>
{% endif %}

View File

@ -1,9 +1,12 @@
{% import "lib/paramList.html" as params -%}
{% import "lib/memberHelpers.html" as memberHelper -%}
{% import "lib/descendants.html" as descendants -%}
{% extends 'export-base.template.html' -%}
{% block overview %}{% include "includes/interface-overview.html" %}{% endblock %}
{% block details %}
{% include "includes/description.html" %}
{$ descendants.renderDescendants(doc, 'interface', 'Child Interfaces') $}
{$ descendants.renderDescendants(doc, 'class', 'Class Implementations') $}
{$ memberHelper.renderMemberDetails(doc.members, 'instance-members', 'instance-member', 'Members') $}
{% endblock %}

View File

@ -0,0 +1,14 @@
{% macro renderDescendants(doc, docType, title='', recursed=false) %}
{% set descendants = doc.descendants | filterByPropertyValue('docType', docType) %}
{% if descendants.length %}
{% if title %}<h2>{$ title $}</h2>{% endif %}
<ul {% if not recursed %}class="descendants {$ docType $}"{% endif %}>
{% for descendant in descendants %}
<li>
<pre class="prettyprint lang-ts"><code>{$ descendant.name $}</code></pre>
{$ renderDescendants(descendant, docType, '', true) $}
</li>
{% endfor %}
</ul>
{% endif %}
{% endmacro %}

View File

@ -2002,9 +2002,9 @@ devtools-timeline-model@1.1.6:
chrome-devtools-frontend "1.0.401423"
resolve "1.1.7"
dgeni-packages@^0.21.2:
version "0.21.2"
resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.21.2.tgz#b031194176507b7c7d1c9735ea14664970763866"
dgeni-packages@^0.21.3:
version "0.21.3"
resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.21.3.tgz#49d5264400cdd8c8a2f66040267e38c099d540f4"
dependencies:
canonical-path "0.0.2"
catharsis "^0.8.1"

View File

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

View File

@ -556,7 +556,11 @@ export class AnimationTimelineContext {
}
if (selector.length > 0) { // if :self is only used then the selector is empty
const multi = limit != 1;
results.push(...this._driver.query(this.element, selector, multi));
let elements = this._driver.query(this.element, selector, multi);
if (limit !== 0) {
elements = elements.slice(0, limit);
}
results.push(...elements);
}
if (!optional && results.length == 0) {

View File

@ -220,6 +220,13 @@ export interface AnimationGroupMetadata extends AnimationMetadata {
*/
export declare interface AnimationQueryOptions extends AnimationOptions {
optional?: boolean;
/**
* Used to limit the total amount of results from the start of the query list.
*
* If a negative value is provided then the queried results will be limited from the
* end of the query list towards the beginning (e.g. if `limit: -3` is used then the
* final 3 (or less) queried results will be used for the animation).
*/
limit?: number;
}

View File

@ -425,7 +425,9 @@ export class StaticReflector implements CompileReflector {
for (const item of (<any>expression)) {
// Check for a spread expression
if (item && item.__symbolic === 'spread') {
const spreadArray = simplify(item.expression);
// We call with references as 0 because we require the actual value and cannot
// tolerate a reference here.
const spreadArray = simplifyInContext(context, item.expression, depth, 0);
if (Array.isArray(spreadArray)) {
for (const spreadItem of spreadArray) {
result.push(spreadItem);
@ -444,7 +446,7 @@ export class StaticReflector implements CompileReflector {
if (expression instanceof StaticSymbol) {
// Stop simplification at builtin symbols or if we are in a reference context
if (expression === self.injectionToken || expression === self.opaqueToken ||
self.conversionMap.has(expression) || references > 0) {
self.conversionMap.has(expression) || (references > 0 && !expression.members.length)) {
return expression;
} else {
const staticSymbol = expression;

View File

@ -14,6 +14,12 @@ export const PRESERVE_WS_ATTR_NAME = 'ngPreserveWhitespaces';
const SKIP_WS_TRIM_TAGS = new Set(['pre', 'template', 'textarea', 'script', 'style']);
// Equivalent to \s with \u00a0 (non-breaking space) excluded.
// Based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
const WS_CHARS = ' \f\n\r\t\v\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff';
const NO_WS_REGEXP = new RegExp(`[^${WS_CHARS}]`);
const WS_REPLACE_REGEXP = new RegExp(`[${WS_CHARS}]{2,}`, 'g');
function hasPreserveWhitespacesAttr(attrs: html.Attribute[]): boolean {
return attrs.some((attr: html.Attribute) => attr.name === PRESERVE_WS_ATTR_NAME);
}
@ -63,10 +69,11 @@ class WhitespaceVisitor implements html.Visitor {
}
visitText(text: html.Text, context: any): any {
const isBlank = text.value.trim().length === 0;
const isNotBlank = text.value.match(NO_WS_REGEXP);
if (!isBlank) {
return new html.Text(replaceNgsp(text.value).replace(/\s\s+/g, ' '), text.sourceSpan);
if (isNotBlank) {
return new html.Text(
replaceNgsp(text.value).replace(WS_REPLACE_REGEXP, ' '), text.sourceSpan);
}
return null;

View File

@ -161,7 +161,7 @@ export class EmitterVisitorContext {
spanOf(line: number, column: number): ParseSourceSpan|null {
const emittedLine = this._lines[line - this._preambleLineCount];
if (emittedLine) {
let columnsLeft = column - emittedLine.indent;
let columnsLeft = column - _createIndent(emittedLine.indent).length;
for (let partIndex = 0; partIndex < emittedLine.parts.length; partIndex++) {
const part = emittedLine.parts[partIndex];
if (part.length > columnsLeft) {

View File

@ -872,6 +872,42 @@ describe('StaticReflector', () => {
});
});
// Regression #18170
it('should continue to aggresively evaluate enum member accessors', () => {
const data = Object.create(DEFAULT_TEST_DATA);
const file = '/tmp/src/my_component.ts';
data[file] = `
import {Component} from '@angular/core';
import {intermediate} from './index';
@Component({
template: '<div></div>',
providers: [{provide: 'foo', useValue: [...intermediate]}]
})
export class MyComponent { }
`;
data['/tmp/src/intermediate.ts'] = `
import {MyEnum} from './indirect';
export const intermediate = [{
data: {
c: [MyEnum.Value]
}
}];`;
data['/tmp/src/index.ts'] = `export * from './intermediate';`;
data['/tmp/src/indirect.ts'] = `export * from './consts';`;
data['/tmp/src/consts.ts'] = `
export enum MyEnum {
Value = 3
}
`;
init(data);
expect(reflector.annotations(reflector.getStaticSymbol(file, 'MyComponent'))[0]
.providers[0]
.useValue)
.toEqual([{data: {c: [3]}}]);
});
});
const DEFAULT_TEST_DATA: {[key: string]: any} = {

View File

@ -64,6 +64,16 @@ export function main() {
expect(parseAndRemoveWS(' \n foo \t ')).toEqual([[html.Text, ' foo ', 0]]);
});
it('should not replace &nbsp;', () => {
expect(parseAndRemoveWS('&nbsp;')).toEqual([[html.Text, '\u00a0', 0]]);
});
it('should not replace sequences of &nbsp;', () => {
expect(parseAndRemoveWS('&nbsp;&nbsp;foo&nbsp;&nbsp;')).toEqual([
[html.Text, '\u00a0\u00a0foo\u00a0\u00a0', 0]
]);
});
it('should not replace single tab and newline with spaces', () => {
expect(parseAndRemoveWS('\nfoo')).toEqual([[html.Text, '\nfoo', 0]]);
expect(parseAndRemoveWS('\tfoo')).toEqual([[html.Text, '\tfoo', 0]]);

View File

@ -9,7 +9,7 @@
import {StaticSymbol} from '@angular/compiler/src/aot/static_symbol';
import * as o from '@angular/compiler/src/output/output_ast';
import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter';
import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '@angular/compiler/src/parse_util';
import {stripSourceMapAndNewLine} from './abstract_emitter_spec';
const someGenFilePath = 'somePackage/someGenFile';
@ -420,5 +420,46 @@ export function main() {
'/* SomePreamble */', 'a;'
].join('\n'));
});
describe('emitter context', () => {
it('should be able to back to the generating span', () => {
const file = new ParseSourceFile('some content', 'a.ts');
const returnSpan = new ParseSourceSpan(
new ParseLocation(file, 100, 10, 10), new ParseLocation(file, 200, 20, 10));
const referenceSpan = new ParseSourceSpan(
new ParseLocation(file, 150, 15, 10), new ParseLocation(file, 175, 17, 10));
const statements = [new o.ClassStmt(
'SomeClass', null, [], [], new o.ClassMethod(null, [], []),
[new o.ClassMethod('someMethod', [new o.FnParam('a', o.INT_TYPE)], [
o.variable('someVar', o.INT_TYPE).set(o.literal(0)).toDeclStmt(),
new o.ReturnStatement(o.variable('someVar', null, referenceSpan), returnSpan)
])])];
const {sourceText, context} =
emitter.emitStatementsAndContext('a.ts', 'a.ts', statements, '/* some preamble /*\n\n');
const spanOf = (text: string, after: number = 0) => {
const location = sourceText.indexOf(text, after);
const {line, col} = calculateLineCol(sourceText, location);
return context.spanOf(line, col);
};
const returnLoc = sourceText.indexOf('return');
expect(spanOf('return someVar')).toEqual(returnSpan, 'return span calculated incorrectly');
expect(spanOf(';', returnLoc)).toEqual(returnSpan, 'reference span calculated incorrectly');
expect(spanOf('someVar', returnLoc))
.toEqual(referenceSpan, 'return span calculated incorrectly');
});
});
});
}
function calculateLineCol(text: string, offset: number): {line: number, col: number} {
const lines = text.split('\n');
let line = 0;
for (let cur = 0; cur < text.length; line++) {
const next = cur + lines[line].length + 1;
if (next > offset) {
return {line, col: offset - cur};
}
cur = next;
}
return {line, col: 0};
}

View File

@ -55,7 +55,7 @@ export function anchorDef(
export function elementDef(
flags: NodeFlags, matchedQueriesDsl: [string | number, QueryValueType][],
ngContentIndex: number, childCount: number, namespaceAndName: string,
ngContentIndex: number, childCount: number, namespaceAndName: string | null,
fixedAttrs: [string, string][] = [],
bindings?: [BindingFlags, string, string | SecurityContext][], outputs?: ([string, string])[],
handleEvent?: ElementHandleEventFn, componentView?: ViewDefinitionFactory,

View File

@ -233,6 +233,7 @@ export const enum QueryValueType {
}
export interface ElementDef {
// set to null for `<ng-container>`
name: string|null;
ns: string|null;
/** ns, name, value */

View File

@ -30,34 +30,21 @@ export function viewDef(
let viewRootNodeFlags = 0;
let viewMatchedQueries = 0;
let currentParent: NodeDef|null = null;
let currentRenderParent: NodeDef|null = null;
let currentElementHasPublicProviders = false;
let currentElementHasPrivateProviders = false;
let lastRenderRootNode: NodeDef|null = null;
for (let i = 0; i < nodes.length; i++) {
while (currentParent && i > currentParent.index + currentParent.childCount) {
const newParent: NodeDef|null = currentParent.parent;
if (newParent) {
newParent.childFlags |= currentParent.childFlags !;
newParent.childMatchedQueries |= currentParent.childMatchedQueries;
}
currentParent = newParent;
}
const node = nodes[i];
node.index = i;
node.parent = currentParent;
node.bindingIndex = viewBindingCount;
node.outputIndex = viewDisposableCount;
// renderParent needs to account for ng-container!
let currentRenderParent: NodeDef|null;
if (currentParent && currentParent.flags & NodeFlags.TypeElement &&
!currentParent.element !.name) {
currentRenderParent = currentParent.renderParent;
} else {
currentRenderParent = currentParent;
}
node.renderParent = currentRenderParent;
viewNodeFlags |= node.flags;
viewMatchedQueries |= node.matchedQueryIds;
if (node.element) {
const elDef = node.element;
elDef.publicProviders =
@ -66,24 +53,13 @@ export function viewDef(
// Note: We assume that all providers of an element are before any child element!
currentElementHasPublicProviders = false;
currentElementHasPrivateProviders = false;
if (node.element.template) {
viewMatchedQueries |= node.element.template.nodeMatchedQueries;
}
}
validateNode(currentParent, node, nodes.length);
viewNodeFlags |= node.flags;
viewMatchedQueries |= node.matchedQueryIds;
if (node.element && node.element.template) {
viewMatchedQueries |= node.element.template.nodeMatchedQueries;
}
if (currentParent) {
currentParent.childFlags |= node.flags;
currentParent.directChildFlags |= node.flags;
currentParent.childMatchedQueries |= node.matchedQueryIds;
if (node.element && node.element.template) {
currentParent.childMatchedQueries |= node.element.template.nodeMatchedQueries;
}
} else {
viewRootNodeFlags |= node.flags;
}
viewBindingCount += node.bindings.length;
viewDisposableCount += node.outputs.length;
@ -91,6 +67,7 @@ export function viewDef(
if (!currentRenderParent && (node.flags & NodeFlags.CatRenderNode)) {
lastRenderRootNode = node;
}
if (node.flags & NodeFlags.CatProvider) {
if (!currentElementHasPublicProviders) {
currentElementHasPublicProviders = true;
@ -106,7 +83,7 @@ export function viewDef(
} else {
if (!currentElementHasPrivateProviders) {
currentElementHasPrivateProviders = true;
// Use protoyypical inheritance to not get O(n^2) complexity...
// Use prototypical inheritance to not get O(n^2) complexity...
currentParent !.element !.allProviders =
Object.create(currentParent !.element !.publicProviders);
}
@ -116,20 +93,50 @@ export function viewDef(
currentParent !.element !.componentProvider = node;
}
}
if (node.childCount) {
if (currentParent) {
currentParent.childFlags |= node.flags;
currentParent.directChildFlags |= node.flags;
currentParent.childMatchedQueries |= node.matchedQueryIds;
if (node.element && node.element.template) {
currentParent.childMatchedQueries |= node.element.template.nodeMatchedQueries;
}
} else {
viewRootNodeFlags |= node.flags;
}
if (node.childCount > 0) {
currentParent = node;
if (!isNgContainer(node)) {
currentRenderParent = node;
}
} else {
// When the current node has no children, check if it is the last children of its parent.
// When it is, propagate the flags up.
// The loop is required because an element could be the last transitive children of several
// elements. We loop to either the root or the highest opened element (= with remaining
// children)
while (currentParent && i === currentParent.index + currentParent.childCount) {
const newParent: NodeDef|null = currentParent.parent;
if (newParent) {
newParent.childFlags |= currentParent.childFlags;
newParent.childMatchedQueries |= currentParent.childMatchedQueries;
}
currentParent = newParent;
// We also need to update the render parent & account for ng-container
if (currentParent && isNgContainer(currentParent)) {
currentRenderParent = currentParent.renderParent;
} else {
currentRenderParent = currentParent;
}
}
}
}
while (currentParent) {
const newParent = currentParent.parent;
if (newParent) {
newParent.childFlags |= currentParent.childFlags;
newParent.childMatchedQueries |= currentParent.childMatchedQueries;
}
currentParent = newParent;
}
const handleEvent: ViewHandleEventFn = (view, nodeIndex, eventName, event) =>
nodes[nodeIndex].element !.handleEvent !(view, eventName, event);
return {
// Will be filled later...
factory: null,
@ -138,13 +145,16 @@ export function viewDef(
nodeMatchedQueries: viewMatchedQueries, flags,
nodes: nodes,
updateDirectives: updateDirectives || NOOP,
updateRenderer: updateRenderer || NOOP,
handleEvent: handleEvent || NOOP,
updateRenderer: updateRenderer || NOOP, handleEvent,
bindingCount: viewBindingCount,
outputCount: viewDisposableCount, lastRenderRootNode
};
}
function isNgContainer(node: NodeDef): boolean {
return (node.flags & NodeFlags.TypeElement) !== 0 && node.element !.name === null;
}
function validateNode(parent: NodeDef | null, node: NodeDef, nodeCount: number) {
const template = node.element && node.element.template;
if (template) {

View File

@ -461,8 +461,8 @@ export function main() {
selector: 'ani-cmp',
template: `
<div [@myAnimation]="exp">
<header></header>
<footer></footer>
<header></header>
<footer></footer>
</div>
`,
animations: [
@ -587,8 +587,8 @@ export function main() {
selector: 'ani-cmp',
template: `
<div [@myAnimation]="exp">
<header></header>
<footer></footer>
<header></header>
<footer></footer>
</div>
`,
animations: [
@ -640,9 +640,9 @@ export function main() {
template: `
<div [@myAnimation]="exp">
<div *ngFor="let item of items" class="item">
{{ item }}
</div>
</div>
{{ item }}
</div>
</div>
`,
animations: [
trigger(
@ -706,9 +706,9 @@ export function main() {
template: `
<div [@myAnimation]="exp">
<div *ngFor="let item of items" class="item">
{{ item }}
</div>
</div>
{{ item }}
</div>
</div>
`,
animations: [trigger(
'myAnimation',
@ -809,8 +809,8 @@ export function main() {
template: `
<div @myAnimation>
<div *ngFor="let item of items" class="child">
{{ item }}
</div>
{{ item }}
</div>
</div>
`,
animations: [trigger(
@ -923,8 +923,8 @@ export function main() {
template: `
<div [@myAnimation]="exp" class="parent">
<div *ngFor="let item of items" class="child">
{{ item }}
</div>
{{ item }}
</div>
</div>
`,
animations: [trigger(
@ -1111,8 +1111,8 @@ export function main() {
template: `
<div [@myAnimation]="exp" class="parent">
<div *ngFor="let item of items" class="child">
{{ item }}
</div>
{{ item }}
</div>
</div>
`,
animations: [trigger(
@ -1333,8 +1333,8 @@ export function main() {
template: `
<div [@myAnimation]="exp" class="parent">
<div *ngFor="let item of items" class="child">
{{ item }}
</div>
{{ item }}
</div>
</div>
`,
animations: [trigger(
@ -1385,8 +1385,8 @@ export function main() {
template: `
<div [@myAnimation]="exp" class="parent">
<div *ngFor="let item of items" class="child">
{{ item }}
</div>
{{ item }}
</div>
</div>
`,
animations: [trigger(
@ -1436,8 +1436,8 @@ export function main() {
template: `
<div [@one]="exp1" [@two]="exp2" class="parent">
<div *ngFor="let item of items" class="child">
{{ item }}
</div>
{{ item }}
</div>
</div>
`,
animations: [
@ -1519,8 +1519,8 @@ export function main() {
template: `
<div [@myAnimation]="exp" class="parent">
<div *ngFor="let item of items" class="child">
{{ item }}
</div>
{{ item }}
</div>
</div>
`,
animations: [trigger(
@ -1569,8 +1569,8 @@ export function main() {
template: `
<div [@myAnimation]="exp" class="parent">
<div *ngFor="let item of items" class="child">
{{ item }}
</div>
{{ item }}
</div>
</div>
`,
animations: [
@ -1713,6 +1713,56 @@ export function main() {
expect(players[1].element.innerText.trim()).toEqual('5');
expect(players[2].element.innerText.trim()).toEqual('6');
});
describe('options.limit', () => {
it('should limit results when a limit value is passed into the query options', () => {
@Component({
selector: 'cmp',
template: `
<div [@myAnimation]="exp">
<div *ngFor="let item of items" class="item">
{{ item }}
</div>
</div>
`,
animations: [
trigger(
'myAnimation',
[
transition(
'* => go',
[
query(
'.item',
[
style({opacity: 0}),
animate('1s', style({opacity: 1})),
],
{limit: 2}),
]),
]),
]
})
class Cmp {
public exp: any;
public items: any[] = [];
}
TestBed.configureTestingModule({declarations: [Cmp]});
const fixture = TestBed.createComponent(Cmp);
const cmp = fixture.componentInstance;
cmp.items = ['a', 'b', 'c', 'd', 'e'];
fixture.detectChanges();
cmp.exp = 'go';
fixture.detectChanges();
const players = getLog() as any[];
expect(players.length).toEqual(2);
expect(players[0].element.innerText.trim()).toEqual('a');
expect(players[1].element.innerText.trim()).toEqual('b');
});
});
});
describe('sub triggers', () => {
@ -2314,7 +2364,7 @@ export function main() {
</div>
</div>
</section>
</div>
</div>
`
})
class Cmp {

View File

@ -265,12 +265,12 @@ export class MetadataCollector {
const isExportedIdentifier = (identifier: ts.Identifier) => exportMap.has(identifier.text);
const isExported =
(node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.InterfaceDeclaration |
(node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.InterfaceDeclaration | ts.TypeAliasDeclaration |
ts.EnumDeclaration) => isExport(node) || isExportedIdentifier(node.name);
const exportedIdentifierName = (identifier: ts.Identifier) =>
exportMap.get(identifier.text) || identifier.text;
const exportedName =
(node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.InterfaceDeclaration |
(node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.InterfaceDeclaration | ts.TypeAliasDeclaration |
ts.EnumDeclaration) => exportedIdentifierName(node.name);
@ -364,7 +364,16 @@ export class MetadataCollector {
}
// Otherwise don't record metadata for the class.
break;
case ts.SyntaxKind.TypeAliasDeclaration:
const typeDeclaration = <ts.TypeAliasDeclaration>node;
if (typeDeclaration.name && isExported(typeDeclaration)) {
const name = exportedName(typeDeclaration);
if (name) {
if (!metadata) metadata = {};
metadata[name] = {__symbolic: 'interface'};
}
}
break;
case ts.SyntaxKind.InterfaceDeclaration:
const interfaceDeclaration = <ts.InterfaceDeclaration>node;
if (interfaceDeclaration.name && isExported(interfaceDeclaration)) {
@ -372,7 +381,6 @@ export class MetadataCollector {
metadata[exportedName(interfaceDeclaration)] = {__symbolic: 'interface'};
}
break;
case ts.SyntaxKind.FunctionDeclaration:
// Record functions that return a single value. Record the parameter
// names substitution will be performed by the StaticReflector.

View File

@ -35,6 +35,7 @@ describe('Collector', () => {
'exported-functions.ts',
'exported-enum.ts',
'exported-consts.ts',
'exported-type.ts',
'local-symbol-ref.ts',
'local-function-ref.ts',
'local-symbol-ref-func.ts',
@ -66,6 +67,13 @@ describe('Collector', () => {
expect(metadata).toBeUndefined();
});
it('should return an interface reference for types', () => {
const sourceFile = program.getSourceFile('/exported-type.ts');
const metadata = collector.getMetadata(sourceFile);
expect(metadata).toEqual(
{__symbolic: 'module', version: 3, metadata: {SomeType: {__symbolic: 'interface'}}});
});
it('should return an interface reference for interfaces', () => {
const sourceFile = program.getSourceFile('app/hero.ts');
const metadata = collector.getMetadata(sourceFile);
@ -1234,6 +1242,9 @@ const FILES: Directory = {
}
export declare function declaredFn();
`,
'exported-type.ts': `
export type SomeType = 'a' | 'b';
`,
'exported-enum.ts': `
import {constValue} from './exported-consts';