diff --git a/aio/content/examples/.gitignore b/aio/content/examples/.gitignore new file mode 100644 index 0000000000..eedd04da43 --- /dev/null +++ b/aio/content/examples/.gitignore @@ -0,0 +1,17 @@ +# _boilerplate files +!_boilerplate/* +*/*/src/styles.css +*/*/src/systemjs.config.js +*/*/src/tsconfig.json +*/*/bs-config.e2e.json +*/*/bs-config.json +*/*/package.json +*/*/tslint.json + +# example files +_test-output +protractor-helpers.js +*/e2e-spec.js +**/ts/**/*.js +**/js-es6*/**/*.js +**/ts-snippets/**/*.js diff --git a/aio/content/examples/_boilerplate/bs-config.e2e.json b/aio/content/examples/_boilerplate/bs-config.e2e.json new file mode 100644 index 0000000000..24570dbcc9 --- /dev/null +++ b/aio/content/examples/_boilerplate/bs-config.e2e.json @@ -0,0 +1,14 @@ +{ + "open": false, + "logLevel": "silent", + "port": 8080, + "server": { + "baseDir": "src", + "routes": { + "/node_modules": "node_modules" + }, + "middleware": { + "0": null + } + } +} diff --git a/aio/content/examples/_boilerplate/bs-config.json b/aio/content/examples/_boilerplate/bs-config.json new file mode 100644 index 0000000000..4e58595267 --- /dev/null +++ b/aio/content/examples/_boilerplate/bs-config.json @@ -0,0 +1,8 @@ +{ + "server": { + "baseDir": "src", + "routes": { + "/node_modules": "node_modules" + } + } +} diff --git a/aio/content/examples/_boilerplate/example-config.json b/aio/content/examples/_boilerplate/example-config.json new file mode 100644 index 0000000000..ff5403e6ca --- /dev/null +++ b/aio/content/examples/_boilerplate/example-config.json @@ -0,0 +1,4 @@ +{ + "build": "build", + "run": "serve" +} diff --git a/aio/content/examples/_boilerplate/package.json b/aio/content/examples/_boilerplate/package.json new file mode 100644 index 0000000000..5ac9fa9de2 --- /dev/null +++ b/aio/content/examples/_boilerplate/package.json @@ -0,0 +1,44 @@ +{ + "name": "angular-examples", + "version": "1.0.0", + "private": true, + "description": "Example package.json, only contains needed scripts for examples. See _examples/package.json for master package.json.", + "scripts": { + "build": "tsc -p src/", + "build:watch": "tsc -p src/ -w", + "build:e2e": "tsc -p e2e/", + "serve": "lite-server -c=bs-config.json", + "serve:e2e": "lite-server -c=bs-config.e2e.json", + "prestart": "npm run build", + "start": "concurrently \"npm run build:watch\" \"npm run serve\"", + "pree2e": "webdriver-manager update && npm run build:e2e", + "e2e": "concurrently \"npm run serve:e2e\" \"npm run protractor\" --kill-others --success first", + "protractor": "protractor protractor.config.js", + "pretest": "npm run build", + "test": "concurrently \"npm run build:watch\" \"karma start karma.conf.js\"", + "pretest:once": "npm run build", + "test:once": "karma start karma.conf.js --single-run", + "lint": "tslint ./src/**/*.ts -t verbose", + + "build:upgrade": "tsc", + "serve:upgrade": "http-server", + "build:cli": "ng build --no-progress", + "serve:cli": "http-server dist/", + "build:aot": "ngc -p tsconfig-aot.json && rollup -c rollup-config.js", + "serve:aot": "lite-server -c bs-config.aot.json", + "start:webpack": "webpack-dev-server --inline --progress --port 8080", + "test:webpack": "karma start karma.webpack.conf.js", + "build:webpack": "rimraf dist && webpack --config config/webpack.prod.js --bail", + "build:babel": "babel src -d src --extensions \".es6\" --source-maps", + "copy-dist-files": "node ./copy-dist-files.js", + "i18n": "ng-xi18n" + }, + "keywords": [], + "author": "", + "license": "MIT", + "dependencies": {}, + "devDependencies": { + "angular-cli": "^1.0.0-beta.26" + }, + "repository": {} +} diff --git a/aio/content/examples/_boilerplate/plnkr.json b/aio/content/examples/_boilerplate/plnkr.json new file mode 100644 index 0000000000..5fb55b50ad --- /dev/null +++ b/aio/content/examples/_boilerplate/plnkr.json @@ -0,0 +1,10 @@ +{ + "description": "QuickStart", + "basePath": "src/", + "files": [ + "app/app.component.ts", + "index.html" + ], + "open": "app/app.component.ts", + "tags": ["quickstart"] +} diff --git a/aio/content/examples/_boilerplate/src/styles.css b/aio/content/examples/_boilerplate/src/styles.css new file mode 100644 index 0000000000..d81835d0cd --- /dev/null +++ b/aio/content/examples/_boilerplate/src/styles.css @@ -0,0 +1,116 @@ +/* #docregion , quickstart, toh */ +/* Master Styles */ +h1 { + color: #369; + font-family: Arial, Helvetica, sans-serif; + font-size: 250%; +} +h2, h3 { + color: #444; + font-family: Arial, Helvetica, sans-serif; + font-weight: lighter; +} +body { + margin: 2em; +} +/* #enddocregion quickstart */ +body, input[text], button { + color: #888; + font-family: Cambria, Georgia; +} +/* #enddocregion toh */ +a { + cursor: pointer; + cursor: hand; +} +button { + font-family: Arial; + background-color: #eee; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; + cursor: hand; +} +button:hover { + background-color: #cfd8dc; +} +button:disabled { + background-color: #eee; + color: #aaa; + cursor: auto; +} + +/* Navigation link styles */ +nav a { + padding: 5px 10px; + text-decoration: none; + margin-right: 10px; + margin-top: 10px; + display: inline-block; + background-color: #eee; + border-radius: 4px; +} +nav a:visited, a:link { + color: #607D8B; +} +nav a:hover { + color: #039be5; + background-color: #CFD8DC; +} +nav a.active { + color: #039be5; +} + +/* items class */ +.items { + margin: 0 0 2em 0; + list-style-type: none; + padding: 0; + width: 24em; +} +.items li { + cursor: pointer; + position: relative; + left: 0; + background-color: #EEE; + margin: .5em; + padding: .3em 0; + height: 1.6em; + border-radius: 4px; +} +.items li:hover { + color: #607D8B; + background-color: #DDD; + left: .1em; +} +.items li.selected { + background-color: #CFD8DC; + color: white; +} +.items li.selected:hover { + background-color: #BBD8DC; +} +.items .text { + position: relative; + top: -3px; +} +.items .badge { + display: inline-block; + font-size: small; + color: white; + padding: 0.8em 0.7em 0 0.7em; + background-color: #607D8B; + line-height: 1em; + position: relative; + left: -1px; + top: -4px; + height: 1.8em; + margin-right: .8em; + border-radius: 4px 0 0 4px; +} +/* #docregion toh */ +/* everywhere else */ +* { + font-family: Arial, Helvetica, sans-serif; +} diff --git a/aio/content/examples/_boilerplate/src/systemjs.config.js b/aio/content/examples/_boilerplate/src/systemjs.config.js new file mode 100644 index 0000000000..5fa7e9c24f --- /dev/null +++ b/aio/content/examples/_boilerplate/src/systemjs.config.js @@ -0,0 +1,42 @@ +/** + * System configuration for Angular samples + * Adjust as necessary for your application needs. + */ +(function (global) { + System.config({ + paths: { + // paths serve as alias + 'npm:': 'node_modules/' + }, + // map tells the System loader where to look for things + map: { + // our app is within the app folder + app: 'app', + + // angular bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.js', + '@angular/router/upgrade': 'npm:@angular/router/bundles/router-upgrade.umd.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', + + // other libraries + 'rxjs': 'npm:rxjs', + 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js' + }, + // packages tells the System loader how to load when no filename and/or no extension + packages: { + app: { + main: './main.js', + defaultExtension: 'js' + }, + rxjs: { + defaultExtension: 'js' + } + } + }); +})(this); diff --git a/aio/content/examples/_boilerplate/src/systemjs.config.web.build.js b/aio/content/examples/_boilerplate/src/systemjs.config.web.build.js new file mode 100644 index 0000000000..c27642ce02 --- /dev/null +++ b/aio/content/examples/_boilerplate/src/systemjs.config.web.build.js @@ -0,0 +1,88 @@ +/** + * WEB VERSION FOR CURRENT ANGULAR BUILD + * (based on systemjs.config.js in angular.io) + * System configuration for Angular samples + * Adjust as necessary for your application needs. + * + * UNTESTED ! + */ +(function (global) { + System.config({ + // DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER + transpiler: 'ts', + typescriptOptions: { + // Copy of compiler options in standard tsconfig.json + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": ["es2015", "dom"], + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true + }, + meta: { + 'typescript': { + "exports": "ts" + } + }, + paths: { + // paths serve as alias + 'npm:': 'https://unpkg.com/', + 'ng:': 'https://cdn.rawgit.com/angular/' + }, + // map tells the System loader where to look for things + map: { + // our app is within the app folder + app: 'app', + + // angular bundles + '@angular/core': 'ng:core-builds/master/bundles/core.umd.js', + '@angular/common': 'ng:common-builds/master/bundles/common.umd.js', + '@angular/compiler': 'ng:compiler-builds/master/bundles/compiler.umd.js', + '@angular/platform-browser': 'ng:platform-browser-builds/master/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'ng:platform-browser-dynamic-builds/master/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'ng:http-builds/master/bundles/http.umd.js', + '@angular/router': 'ng:router-builds/master/bundles/router.umd.js', + '@angular/router/upgrade': 'ng:router-builds/master/bundles/router-upgrade.umd.js', + '@angular/forms': 'ng:forms-builds/master/bundles/forms.umd.js', + '@angular/upgrade': 'ng:upgrade-builds/master/bundles/upgrade.umd.js', + '@angular/upgrade/static': 'ng:upgrade-builds/master/bundles/upgrade-static.umd.js', + + // angular testing umd bundles (overwrite the shim mappings) + '@angular/core/testing': 'ng:core-builds/master/bundles/core-testing.umd.js', + '@angular/common/testing': 'ng:common-builds/master/bundles/common-testing.umd.js', + '@angular/compiler/testing': 'ng:compiler-builds/master/bundles/compiler-testing.umd.js', + '@angular/platform-browser/testing': 'ng:platform-browser-builds/master/bundles/platform-browser-testing.umd.js', + '@angular/platform-browser-dynamic/testing': 'ng:platform-browser-dynamic-builds/master/bundles/platform-browser-dynamic-testing.umd.js', + '@angular/http/testing': 'ng:http-builds/master/bundles/http-testing.umd.js', + '@angular/router/testing': 'ng:router-builds/master/bundles/router-testing.umd.js', + '@angular/forms/testing': 'ng:forms-builds/master/bundles/forms-testing.umd.js', + + // other libraries + 'rxjs': 'npm:rxjs@5.0.1', + 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js', + 'ts': 'npm:plugin-typescript@5.2.7/lib/plugin.js', + 'typescript': 'npm:typescript@2.0.10/lib/typescript.js', + + }, + // packages tells the System loader how to load when no filename and/or no extension + packages: { + app: { + main: './main.ts', + defaultExtension: 'ts' + }, + rxjs: { + defaultExtension: 'js' + } + } + }); + +})(this); + +/* +Copyright 2016 Google Inc. All Rights Reserved. +Use of this source code is governed by an MIT-style license that +can be found in the LICENSE file at http://angular.io/license +*/ diff --git a/aio/content/examples/_boilerplate/src/systemjs.config.web.js b/aio/content/examples/_boilerplate/src/systemjs.config.web.js new file mode 100644 index 0000000000..dc0234a172 --- /dev/null +++ b/aio/content/examples/_boilerplate/src/systemjs.config.web.js @@ -0,0 +1,75 @@ +/** + * WEB ANGULAR VERSION + * (based on systemjs.config.js in angular.io) + * System configuration for Angular samples + * Adjust as necessary for your application needs. + */ +(function (global) { + System.config({ + // DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER + transpiler: 'ts', + typescriptOptions: { + // Copy of compiler options in standard tsconfig.json + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": ["es2015", "dom"], + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true + }, + meta: { + 'typescript': { + "exports": "ts" + } + }, + paths: { + // paths serve as alias + 'npm:': 'https://unpkg.com/' + }, + // map tells the System loader where to look for things + map: { + // our app is within the app folder + app: 'app', + + // angular bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.js', + '@angular/router/upgrade': 'npm:@angular/router/bundles/router-upgrade.umd.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', + '@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js', + '@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js', + + // other libraries + 'rxjs': 'npm:rxjs@5.0.1', + 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js', + 'ts': 'npm:plugin-typescript@5.2.7/lib/plugin.js', + 'typescript': 'npm:typescript@2.0.10/lib/typescript.js', + + }, + // packages tells the System loader how to load when no filename and/or no extension + packages: { + app: { + main: './main.ts', + defaultExtension: 'ts' + }, + rxjs: { + defaultExtension: 'js' + } + } + }); + +})(this); + +/* +Copyright 2016 Google Inc. All Rights Reserved. +Use of this source code is governed by an MIT-style license that +can be found in the LICENSE file at http://angular.io/license +*/ diff --git a/aio/content/examples/_boilerplate/src/tsconfig.json b/aio/content/examples/_boilerplate/src/tsconfig.json new file mode 100644 index 0000000000..05839ec2ff --- /dev/null +++ b/aio/content/examples/_boilerplate/src/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ "es2015", "dom" ], + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true, + "typeRoots": [ + "../../../node_modules/@types/" + ] + }, + "compileOnSave": true, + "exclude": [ + "node_modules/*", + "**/*-aot.ts" + ] +} diff --git a/aio/content/examples/_boilerplate/tslint.json b/aio/content/examples/_boilerplate/tslint.json new file mode 100644 index 0000000000..276453f4f5 --- /dev/null +++ b/aio/content/examples/_boilerplate/tslint.json @@ -0,0 +1,93 @@ +{ + "rules": { + "class-name": true, + "comment-format": [ + true, + "check-space" + ], + "curly": true, + "eofline": true, + "forin": true, + "indent": [ + true, + "spaces" + ], + "label-position": true, + "label-undefined": true, + "max-line-length": [ + true, + 140 + ], + "member-access": false, + "member-ordering": [ + true, + "static-before-instance", + "variables-before-functions" + ], + "no-arg": true, + "no-bitwise": true, + "no-console": [ + true, + "debug", + "info", + "time", + "timeEnd", + "trace" + ], + "no-construct": true, + "no-debugger": true, + "no-duplicate-key": true, + "no-duplicate-variable": true, + "no-empty": false, + "no-eval": true, + "no-inferrable-types": true, + "no-shadowed-variable": true, + "no-string-literal": false, + "no-switch-case-fall-through": true, + "no-trailing-whitespace": true, + "no-unused-expression": true, + "no-unused-variable": true, + "no-unreachable": true, + "no-use-before-declare": true, + "no-var-keyword": true, + "object-literal-sort-keys": false, + "one-line": [ + true, + "check-open-brace", + "check-catch", + "check-else", + "check-whitespace" + ], + "quotemark": [ + true, + "single" + ], + "radix": true, + "semicolon": [ + "always" + ], + "triple-equals": [ + true, + "allow-null-check" + ], + "typedef-whitespace": [ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + } + ], + "variable-name": false, + "whitespace": [ + true, + "check-branch", + "check-decl", + "check-operator", + "check-separator", + "check-type" + ] + } +} diff --git a/aio/content/examples/animations/e2e-spec.ts b/aio/content/examples/animations/e2e-spec.ts new file mode 100644 index 0000000000..4fba7ec475 --- /dev/null +++ b/aio/content/examples/animations/e2e-spec.ts @@ -0,0 +1,351 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by, ElementFinder } from 'protractor'; +import { logging, promise } from 'selenium-webdriver'; + +/** + * The tests here basically just checking that the end styles + * of each animation are in effect. + * + * Relies on the Angular testability only becoming stable once + * animation(s) have finished. + * + * Ideally we'd use https://developer.mozilla.org/en-US/docs/Web/API/Document/getAnimations + * but they're not supported in Chrome at the moment. The upcoming nganimate polyfill + * may also add some introspection support. + */ +describe('Animation Tests', () => { + + const INACTIVE_COLOR = 'rgba(238, 238, 238, 1)'; + const ACTIVE_COLOR = 'rgba(207, 216, 220, 1)'; + const NO_TRANSFORM_MATRIX_REGEX = /matrix\(1,\s*0,\s*0,\s*1,\s*0,\s*0\)/; + + beforeEach(() => { + browser.get(''); + }); + + describe('basic states', () => { + + let host: ElementFinder; + + beforeEach(() => { + host = element(by.css('hero-list-basic')); + }); + + it('animates between active and inactive', () => { + addInactiveHero(); + + let li = host.element(by.css('li')); + + expect(getScaleX(li)).toBe(1.0); + expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR); + + li.click(); + browser.driver.sleep(300); + expect(getScaleX(li)).toBe(1.1); + expect(li.getCssValue('backgroundColor')).toBe(ACTIVE_COLOR); + + li.click(); + browser.driver.sleep(300); + expect(getScaleX(li)).toBe(1.0); + expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR); + }); + + }); + + describe('styles inline in transitions', () => { + + let host: ElementFinder; + + beforeEach(function() { + host = element(by.css('hero-list-inline-styles')); + }); + + it('are not kept after animation', () => { + addInactiveHero(); + + let li = host.element(by.css('li')); + + li.click(); + browser.driver.sleep(300); + expect(getScaleX(li)).toBe(1.0); + expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR); + }); + + }); + + describe('combined transition syntax', () => { + + let host: ElementFinder; + + beforeEach(() => { + host = element(by.css('hero-list-combined-transitions')); + }); + + it('animates between active and inactive', () => { + addInactiveHero(); + + let li = host.element(by.css('li')); + + expect(getScaleX(li)).toBe(1.0); + expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR); + + li.click(); + browser.driver.sleep(300); + expect(getScaleX(li)).toBe(1.1); + expect(li.getCssValue('backgroundColor')).toBe(ACTIVE_COLOR); + + li.click(); + browser.driver.sleep(300); + expect(getScaleX(li)).toBe(1.0); + expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR); + }); + + }); + + describe('two-way transition syntax', () => { + + let host: ElementFinder; + + beforeEach(() => { + host = element(by.css('hero-list-twoway')); + }); + + it('animates between active and inactive', () => { + addInactiveHero(); + + let li = host.element(by.css('li')); + + expect(getScaleX(li)).toBe(1.0); + expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR); + + li.click(); + browser.driver.sleep(300); + expect(getScaleX(li)).toBe(1.1); + expect(li.getCssValue('backgroundColor')).toBe(ACTIVE_COLOR); + + li.click(); + browser.driver.sleep(300); + expect(getScaleX(li)).toBe(1.0); + expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR); + }); + + }); + + describe('enter & leave', () => { + + let host: ElementFinder; + + beforeEach(() => { + host = element(by.css('hero-list-enter-leave')); + }); + + it('adds and removes element', () => { + addInactiveHero(); + + let li = host.element(by.css('li')); + expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX); + + removeHero(); + expect(li.isPresent()).toBe(false); + }); + + }); + + describe('enter & leave & states', () => { + + let host: ElementFinder; + + beforeEach(function() { + host = element(by.css('hero-list-enter-leave-states')); + }); + + it('adds and removes and animates between active and inactive', () => { + addInactiveHero(); + + let li = host.element(by.css('li')); + + expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX); + + li.click(); + browser.driver.sleep(300); + expect(getScaleX(li)).toBe(1.1); + + li.click(); + browser.driver.sleep(300); + expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX); + + removeHero(); + expect(li.isPresent()).toBe(false); + }); + + }); + + describe('auto style calc', () => { + + let host: ElementFinder; + + beforeEach(function() { + host = element(by.css('hero-list-auto')); + }); + + it('adds and removes element', () => { + addInactiveHero(); + + let li = host.element(by.css('li')); + expect(li.getCssValue('height')).toBe('50px'); + + removeHero(); + expect(li.isPresent()).toBe(false); + }); + + }); + + describe('different timings', () => { + + let host: ElementFinder; + + beforeEach(() => { + host = element(by.css('hero-list-timings')); + }); + + it('adds and removes element', () => { + addInactiveHero(); + + let li = host.element(by.css('li')); + expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX); + expect(li.getCssValue('opacity')).toMatch('1'); + + removeHero(); + expect(li.isPresent()).toBe(false); + }); + + }); + + describe('multiple keyframes', () => { + + let host: ElementFinder; + + beforeEach(() => { + host = element(by.css('hero-list-multistep')); + }); + + it('adds and removes element', () => { + addInactiveHero(); + + let li = host.element(by.css('li')); + expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX); + expect(li.getCssValue('opacity')).toMatch('1'); + + removeHero(); + expect(li.isPresent()).toBe(false); + }); + + }); + + describe('parallel groups', () => { + + let host: ElementFinder; + + beforeEach(() => { + host = element(by.css('hero-list-groups')); + }); + + it('adds and removes element', () => { + addInactiveHero(); + + let li = host.element(by.css('li')); + expect(li.getCssValue('transform')).toMatch(NO_TRANSFORM_MATRIX_REGEX); + expect(li.getCssValue('opacity')).toMatch('1'); + + removeHero(700); + expect(li.isPresent()).toBe(false); + }); + + }); + + describe('adding active heroes', () => { + + let host: ElementFinder; + + beforeEach(() => { + host = element(by.css('hero-list-basic')); + }); + + it('animates between active and inactive', () => { + addActiveHero(); + + let li = host.element(by.css('li')); + + expect(getScaleX(li)).toBe(1.1); + expect(li.getCssValue('backgroundColor')).toBe(ACTIVE_COLOR); + + li.click(); + browser.driver.sleep(300); + expect(getScaleX(li)).toBe(1.0); + expect(li.getCssValue('backgroundColor')).toBe(INACTIVE_COLOR); + + li.click(); + browser.driver.sleep(300); + expect(getScaleX(li)).toBe(1.1); + expect(li.getCssValue('backgroundColor')).toBe(ACTIVE_COLOR); + }); + }); + + describe('callbacks', () => { + it('fires a callback on start and done', () => { + addActiveHero(); + browser.manage().logs().get(logging.Type.BROWSER) + .then((logs: logging.Entry[]) => { + const animationMessages = logs.filter((log) => { + return log.message.indexOf('Animation') !== -1 ? true : false; + }); + + expect(animationMessages.length).toBeGreaterThan(0); + }); + }); + }); + + function addActiveHero(sleep?: number) { + sleep = sleep || 500; + element(by.buttonText('Add active hero')).click(); + browser.driver.sleep(sleep); + } + + function addInactiveHero(sleep?: number) { + sleep = sleep || 500; + element(by.buttonText('Add inactive hero')).click(); + browser.driver.sleep(sleep); + } + + function removeHero(sleep?: number) { + sleep = sleep || 500; + element(by.buttonText('Remove hero')).click(); + browser.driver.sleep(sleep); + } + + function getScaleX(el: ElementFinder) { + return Promise.all([ + getBoundingClientWidth(el), + getOffsetWidth(el) + ]).then(function(promiseResolutions) { + let clientWidth = promiseResolutions[0]; + let offsetWidth = promiseResolutions[1]; + return clientWidth / offsetWidth; + }); + } + + function getBoundingClientWidth(el: ElementFinder): promise.Promise { + return browser.executeScript( + 'return arguments[0].getBoundingClientRect().width', + el.getWebElement() + ); + } + + function getOffsetWidth(el: ElementFinder): promise.Promise { + return browser.executeScript( + 'return arguments[0].offsetWidth', + el.getWebElement() + ); + } +}); diff --git a/aio/content/examples/animations/ts/.gitignore b/aio/content/examples/animations/ts/.gitignore new file mode 100644 index 0000000000..2cb7d2a2e9 --- /dev/null +++ b/aio/content/examples/animations/ts/.gitignore @@ -0,0 +1 @@ +**/*.js diff --git a/aio/content/examples/animations/ts/example-config.json b/aio/content/examples/animations/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/animations/ts/plnkr.json b/aio/content/examples/animations/ts/plnkr.json new file mode 100644 index 0000000000..f047395e7f --- /dev/null +++ b/aio/content/examples/animations/ts/plnkr.json @@ -0,0 +1,8 @@ +{ + "description": "Angular Animations", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js" + ] +} diff --git a/aio/content/examples/animations/ts/plnkr.no-link.html b/aio/content/examples/animations/ts/plnkr.no-link.html new file mode 100644 index 0000000000..eabffc1a6a --- /dev/null +++ b/aio/content/examples/animations/ts/plnkr.no-link.html @@ -0,0 +1,1011 @@ +
\ No newline at end of file diff --git a/aio/content/examples/animations/ts/src/app/app.module.ts b/aio/content/examples/animations/ts/src/app/app.module.ts new file mode 100644 index 0000000000..1550034236 --- /dev/null +++ b/aio/content/examples/animations/ts/src/app/app.module.ts @@ -0,0 +1,33 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { HeroTeamBuilderComponent } from './hero-team-builder.component'; +import { HeroListBasicComponent } from './hero-list-basic.component'; +import { HeroListInlineStylesComponent } from './hero-list-inline-styles.component'; +import { HeroListEnterLeaveComponent } from './hero-list-enter-leave.component'; +import { HeroListEnterLeaveStatesComponent } from './hero-list-enter-leave-states.component'; +import { HeroListCombinedTransitionsComponent } from './hero-list-combined-transitions.component'; +import { HeroListTwowayComponent } from './hero-list-twoway.component'; +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'; + +@NgModule({ + imports: [ BrowserModule ], + declarations: [ + HeroTeamBuilderComponent, + HeroListBasicComponent, + HeroListInlineStylesComponent, + HeroListCombinedTransitionsComponent, + HeroListTwowayComponent, + HeroListEnterLeaveComponent, + HeroListEnterLeaveStatesComponent, + HeroListAutoComponent, + HeroListTimingsComponent, + HeroListMultistepComponent, + HeroListGroupsComponent + ], + bootstrap: [ HeroTeamBuilderComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/animations/ts/src/app/hero-list-auto.component.ts b/aio/content/examples/animations/ts/src/app/hero-list-auto.component.ts new file mode 100644 index 0000000000..df7280d3ea --- /dev/null +++ b/aio/content/examples/animations/ts/src/app/hero-list-auto.component.ts @@ -0,0 +1,46 @@ +import { + Component, + Input, + trigger, + state, + style, + animate, + transition +} from '@angular/core'; + +import { Heroes } from './hero.service'; + +@Component({ + moduleId: module.id, + selector: 'hero-list-auto', + // #docregion template + template: ` + + `, + // #enddocregion template + styleUrls: ['./hero-list.component.css'], + + /* When the element leaves (transition "in => void" occurs), + * get the element's current computed height and animate + * it down to 0. + */ + // #docregion animationdef + animations: [ + trigger('shrinkOut', [ + state('in', style({height: '*'})), + transition('* => void', [ + style({height: '*'}), + animate(250, style({height: 0})) + ]) + ]) + ] + // #enddocregion animationdef +}) +export class HeroListAutoComponent { + @Input() heroes: Heroes; +} diff --git a/aio/content/examples/animations/ts/src/app/hero-list-basic.component.ts b/aio/content/examples/animations/ts/src/app/hero-list-basic.component.ts new file mode 100644 index 0000000000..a40f9ade94 --- /dev/null +++ b/aio/content/examples/animations/ts/src/app/hero-list-basic.component.ts @@ -0,0 +1,69 @@ +// #docplaster +// #docregion +// #docregion imports +import { + Component, + Input, + trigger, + state, + style, + transition, + animate +} from '@angular/core'; +// #enddocregion imports + +import { Heroes } from './hero.service'; + +@Component({ + moduleId: module.id, + selector: 'hero-list-basic', + // #enddocregion + /* The click event calls hero.toggleState(), which + * causes the state of that hero to switch from + * active to inactive or vice versa. + */ + // #docregion + // #docregion template + template: ` + + `, + // #enddocregion template + styleUrls: ['./hero-list.component.css'], + // #enddocregion + /** + * Define two states, "inactive" and "active", and the end + * styles that apply whenever the element is in those states. + * Then define animations for transitioning between the states, + * one in each direction + */ + // #docregion + // #docregion animationdef + animations: [ + trigger('heroState', [ + // #docregion states + state('inactive', style({ + backgroundColor: '#eee', + transform: 'scale(1)' + })), + state('active', style({ + backgroundColor: '#cfd8dc', + transform: 'scale(1.1)' + })), + // #enddocregion states + // #docregion transitions + transition('inactive => active', animate('100ms ease-in')), + transition('active => inactive', animate('100ms ease-out')) + // #enddocregion transitions + ]) + ] + // #enddocregion animationdef +}) +export class HeroListBasicComponent { + @Input() heroes: Heroes; +} diff --git a/aio/content/examples/animations/ts/src/app/hero-list-combined-transitions.component.ts b/aio/content/examples/animations/ts/src/app/hero-list-combined-transitions.component.ts new file mode 100644 index 0000000000..b40a0c3514 --- /dev/null +++ b/aio/content/examples/animations/ts/src/app/hero-list-combined-transitions.component.ts @@ -0,0 +1,58 @@ +// #docregion +// #docregion imports +import { + Component, + Input, + trigger, + state, + style, + transition, + animate +} from '@angular/core'; +// #enddocregion imports + +import { Heroes } from './hero.service'; + +@Component({ + moduleId: module.id, + selector: 'hero-list-combined-transitions', + // #docregion template + template: ` + + `, + // #enddocregion template + styleUrls: ['./hero-list.component.css'], + /* + * Define two states, "inactive" and "active", and the end + * styles that apply whenever the element is in those states. + * Then define an animated transition between these two + * states, in *both* directions. + */ + // #docregion animationdef + animations: [ + trigger('heroState', [ + state('inactive', style({ + backgroundColor: '#eee', + transform: 'scale(1)' + })), + state('active', style({ + backgroundColor: '#cfd8dc', + transform: 'scale(1.1)' + })), + // #docregion transitions + transition('inactive => active, active => inactive', + animate('100ms ease-out')) + // #enddocregion transitions + ]) + ] + // #enddocregion animationdef +}) +export class HeroListCombinedTransitionsComponent { + @Input() heroes: Heroes; +} diff --git a/aio/content/examples/animations/ts/src/app/hero-list-enter-leave-states.component.ts b/aio/content/examples/animations/ts/src/app/hero-list-enter-leave-states.component.ts new file mode 100644 index 0000000000..a88e08f251 --- /dev/null +++ b/aio/content/examples/animations/ts/src/app/hero-list-enter-leave-states.component.ts @@ -0,0 +1,62 @@ +import { + Component, + Input, + trigger, + state, + style, + animate, + transition +} from '@angular/core'; + +import { Heroes } from './hero.service'; + +@Component({ + moduleId: module.id, + selector: 'hero-list-enter-leave-states', + // #docregion template + template: ` + + `, + // #enddocregion template + styleUrls: ['./hero-list.component.css'], + /* The elements here have two possible states based + * on the hero state, "active", or "inactive". We animate + * six transitions: Between the two states in both directions, + * and between each state and void. With this we can animate + * the enter and leave of elements differently based on which + * state they are in when they are added and removed. + */ + // #docregion animationdef + animations: [ + trigger('heroState', [ + state('inactive', style({transform: 'translateX(0) scale(1)'})), + state('active', style({transform: 'translateX(0) scale(1.1)'})), + transition('inactive => active', animate('100ms ease-in')), + transition('active => inactive', animate('100ms ease-out')), + transition('void => inactive', [ + style({transform: 'translateX(-100%) scale(1)'}), + animate(100) + ]), + transition('inactive => void', [ + animate(100, style({transform: 'translateX(100%) scale(1)'})) + ]), + transition('void => active', [ + style({transform: 'translateX(0) scale(0)'}), + animate(200) + ]), + transition('active => void', [ + animate(200, style({transform: 'translateX(0) scale(0)'})) + ]) + ]) + ] + // #enddocregion animationdef +}) +export class HeroListEnterLeaveStatesComponent { + @Input() heroes: Heroes; +} diff --git a/aio/content/examples/animations/ts/src/app/hero-list-enter-leave.component.ts b/aio/content/examples/animations/ts/src/app/hero-list-enter-leave.component.ts new file mode 100644 index 0000000000..05944c84f1 --- /dev/null +++ b/aio/content/examples/animations/ts/src/app/hero-list-enter-leave.component.ts @@ -0,0 +1,50 @@ +import { + Component, + Input, + trigger, + state, + style, + animate, + transition +} from '@angular/core'; + +import { Heroes } from './hero.service'; + +@Component({ + moduleId: module.id, + selector: 'hero-list-enter-leave', + // #docregion template + template: ` + + `, + // #enddocregion template + styleUrls: ['./hero-list.component.css'], + /* The element here always has the state "in" when it + * is present. We animate two transitions: From void + * to in and from in to void, to achieve an animated + * enter and leave transition. The element enters from + * the left and leaves to the right using translateX. + */ + // #docregion animationdef + animations: [ + trigger('flyInOut', [ + state('in', style({transform: 'translateX(0)'})), + transition('void => *', [ + style({transform: 'translateX(-100%)'}), + animate(100) + ]), + transition('* => void', [ + animate(100, style({transform: 'translateX(100%)'})) + ]) + ]) + ] + // #enddocregion animationdef +}) +export class HeroListEnterLeaveComponent { + @Input() heroes: Heroes; +} diff --git a/aio/content/examples/animations/ts/src/app/hero-list-groups.component.ts b/aio/content/examples/animations/ts/src/app/hero-list-groups.component.ts new file mode 100644 index 0000000000..dda696eca2 --- /dev/null +++ b/aio/content/examples/animations/ts/src/app/hero-list-groups.component.ts @@ -0,0 +1,79 @@ +import { + Component, + Input, + trigger, + state, + style, + animate, + transition, + group +} from '@angular/core'; + +import { Heroes } from './hero.service'; + +@Component({ + moduleId: module.id, + selector: 'hero-list-groups', + template: ` + + `, + styleUrls: ['./hero-list.component.css'], + styles: [` + li { + padding: 0 !important; + text-align: center; + } + `], + /* The element here always has the state "in" when it + * is present. We animate two transitions: From void + * to in and from in to void, to achieve an animated + * enter and leave transition. + * + * The transitions have *parallel group* that allow + * animating several properties at the same time but + * with different timing configurations. On enter + * (void => *) we start the opacity animation 0.1s + * earlier than the translation/width animation. + * On leave (* => void) we do the opposite - + * the translation/width animation begins immediately + * and the opacity animation 0.1s later. + */ + // #docregion animationdef + animations: [ + trigger('flyInOut', [ + state('in', style({width: 120, transform: 'translateX(0)', opacity: 1})), + transition('void => *', [ + style({width: 10, transform: 'translateX(50px)', opacity: 0}), + group([ + animate('0.3s 0.1s ease', style({ + transform: 'translateX(0)', + width: 120 + })), + animate('0.3s ease', style({ + opacity: 1 + })) + ]) + ]), + transition('* => void', [ + group([ + animate('0.3s ease', style({ + transform: 'translateX(50px)', + width: 10 + })), + animate('0.3s 0.2s ease', style({ + opacity: 0 + })) + ]) + ]) + ]) + ] + // #enddocregion animationdef +}) +export class HeroListGroupsComponent { + @Input() heroes: Heroes; +} diff --git a/aio/content/examples/animations/ts/src/app/hero-list-inline-styles.component.ts b/aio/content/examples/animations/ts/src/app/hero-list-inline-styles.component.ts new file mode 100644 index 0000000000..19e6fb8cce --- /dev/null +++ b/aio/content/examples/animations/ts/src/app/hero-list-inline-styles.component.ts @@ -0,0 +1,59 @@ +// #docregion +// #docregion imports +import { + Component, + Input, + trigger, + style, + transition, + animate +} from '@angular/core'; +// #enddocregion imports + +import { Heroes } from './hero.service'; + +@Component({ + moduleId: module.id, + selector: 'hero-list-inline-styles', + // #docregion template + template: ` + + `, + // #enddocregion template + styleUrls: ['./hero-list.component.css'], + /** + * Define two states, "inactive" and "active", and the end + * styles that apply whenever the element is in those states. + * Then define an animation for the inactive => active transition. + * This animation has no end styles, but only styles that are + * defined inline inside the transition and thus are only kept + * as long as the animation is running. + */ + // #docregion animationdef + animations: [ + trigger('heroState', [ + // #docregion transitions + transition('inactive => active', [ + style({ + backgroundColor: '#cfd8dc', + transform: 'scale(1.3)' + }), + animate('80ms ease-in', style({ + backgroundColor: '#eee', + transform: 'scale(1)' + })) + ]), + // #enddocregion transitions + ]) + ] + // #enddocregion animationdef +}) +export class HeroListInlineStylesComponent { + @Input() heroes: Heroes; +} diff --git a/aio/content/examples/animations/ts/src/app/hero-list-multistep.component.ts b/aio/content/examples/animations/ts/src/app/hero-list-multistep.component.ts new file mode 100644 index 0000000000..25c10cc533 --- /dev/null +++ b/aio/content/examples/animations/ts/src/app/hero-list-multistep.component.ts @@ -0,0 +1,70 @@ +import { + Component, + Input, + trigger, + state, + style, + animate, + transition, + keyframes, + AnimationTransitionEvent +} from '@angular/core'; + +import { Heroes } from './hero.service'; + +@Component({ + moduleId: module.id, + selector: 'hero-list-multistep', + // #docregion template + template: ` + + `, + // #enddocregion template + styleUrls: ['./hero-list.component.css'], + /* The element here always has the state "in" when it + * is present. We animate two transitions: From void + * to in and from in to void, to achieve an animated + * enter and leave transition. Each transition is + * defined in terms of multiple keyframes, to give it + * a bounce effect. + */ + // #docregion animationdef + animations: [ + trigger('flyInOut', [ + state('in', style({transform: 'translateX(0)'})), + transition('void => *', [ + animate(300, keyframes([ + style({opacity: 0, transform: 'translateX(-100%)', offset: 0}), + style({opacity: 1, transform: 'translateX(15px)', offset: 0.3}), + style({opacity: 1, transform: 'translateX(0)', offset: 1.0}) + ])) + ]), + transition('* => void', [ + animate(300, keyframes([ + style({opacity: 1, transform: 'translateX(0)', offset: 0}), + style({opacity: 1, transform: 'translateX(-15px)', offset: 0.7}), + style({opacity: 0, transform: 'translateX(100%)', offset: 1.0}) + ])) + ]) + ]) + ] + // #enddocregion animationdef +}) +export class HeroListMultistepComponent { + @Input() heroes: Heroes; + + animationStarted(event: AnimationTransitionEvent) { + console.warn('Animation started: ', event); + } + + animationDone(event: AnimationTransitionEvent) { + console.warn('Animation done: ', event); + } +} diff --git a/aio/content/examples/animations/ts/src/app/hero-list-timings.component.ts b/aio/content/examples/animations/ts/src/app/hero-list-timings.component.ts new file mode 100644 index 0000000000..eed6f90004 --- /dev/null +++ b/aio/content/examples/animations/ts/src/app/hero-list-timings.component.ts @@ -0,0 +1,57 @@ +import { + Component, + Input, + trigger, + state, + style, + animate, + transition +} from '@angular/core'; + +import { Heroes } from './hero.service'; + +@Component({ + moduleId: module.id, + selector: 'hero-list-timings', + template: ` + + `, + styleUrls: ['./hero-list.component.css'], + /* The element here always has the state "in" when it + * is present. We animate two transitions: From void + * to in and from in to void, to achieve an animated + * enter and leave transition. The element enters from + * the left and leaves to the right using translateX, + * and fades in/out using opacity. We use different easings + * for enter and leave. + */ + // #docregion animationdef + animations: [ + trigger('flyInOut', [ + state('in', style({opacity: 1, transform: 'translateX(0)'})), + transition('void => *', [ + style({ + opacity: 0, + transform: 'translateX(-100%)' + }), + animate('0.2s ease-in') + ]), + transition('* => void', [ + animate('0.2s 10 ease-out', style({ + opacity: 0, + transform: 'translateX(100%)' + })) + ]) + ]) + ] + // #enddocregion animationdef +}) +export class HeroListTimingsComponent { + @Input() heroes: Heroes; +} diff --git a/aio/content/examples/animations/ts/src/app/hero-list-twoway.component.ts b/aio/content/examples/animations/ts/src/app/hero-list-twoway.component.ts new file mode 100644 index 0000000000..c9ec43f904 --- /dev/null +++ b/aio/content/examples/animations/ts/src/app/hero-list-twoway.component.ts @@ -0,0 +1,57 @@ +// #docregion +// #docregion imports +import { + Component, + Input, + trigger, + state, + style, + transition, + animate +} from '@angular/core'; +// #enddocregion imports + +import { Heroes } from './hero.service'; + +@Component({ + moduleId: module.id, + selector: 'hero-list-twoway', + // #docregion template + template: ` + + `, + // #enddocregion template + styleUrls: ['./hero-list.component.css'], + /* + * Define two states, "inactive" and "active", and the end + * styles that apply whenever the element is in those states. + * Then define an animated transition between these two + * states, in *both* directions. + */ + // #docregion animationdef + animations: [ + trigger('heroState', [ + state('inactive', style({ + backgroundColor: '#eee', + transform: 'scale(1)' + })), + state('active', style({ + backgroundColor: '#cfd8dc', + transform: 'scale(1.1)' + })), + // #docregion transitions + transition('inactive <=> active', animate('100ms ease-out')) + // #enddocregion transitions + ]) + ] + // #enddocregion animationdef +}) +export class HeroListTwowayComponent { + @Input() heroes: Heroes; +} diff --git a/aio/content/examples/animations/ts/src/app/hero-list.component.css b/aio/content/examples/animations/ts/src/app/hero-list.component.css new file mode 100644 index 0000000000..b256521e49 --- /dev/null +++ b/aio/content/examples/animations/ts/src/app/hero-list.component.css @@ -0,0 +1,27 @@ +ul { + list-style-type: none; + padding: 0; +} + +li { + display: block; + width: 120px; + line-height: 50px; + padding: 0 10px; + box-sizing: border-box; + background-color: #eee; + border-radius: 4px; + margin: 10px; + cursor: pointer; + overflow: hidden; + white-space: nowrap; +} + +.active { + background-color: #cfd8dc; + transform: scale(1.1); +} +.inactive { + background-color: #eee; + transform: scale(1); +} diff --git a/aio/content/examples/animations/ts/src/app/hero-team-builder.component.ts b/aio/content/examples/animations/ts/src/app/hero-team-builder.component.ts new file mode 100644 index 0000000000..e5413be50e --- /dev/null +++ b/aio/content/examples/animations/ts/src/app/hero-team-builder.component.ts @@ -0,0 +1,94 @@ +import { Component } from '@angular/core'; + +import { Heroes } from './hero.service'; + +@Component({ + selector: 'hero-team-builder', + template: ` +
+ + + +
+
+
+

Basic State

+

Switch between active/inactive on click.

+ +
+
+

Styles inline in transitions

+

Animated effect on click, no persistend end styles.

+ +
+
+

Combined transition syntax

+

Switch between active/inactive on click. Define just one transition used in both directions.

+ +
+
+

Two-way transition syntax

+

Switch between active/inactive on click. Define just one transition used in both directions using the <=> syntax.

+ +
+
+

Enter & Leave

+

Enter and leave animations using the void state.

+ +
+
+
+
+

Enter & Leave & States

+

+ Enter and leave animations combined with active/inactive state animations. + Different enter and leave transitions depending on state. +

+ +
+
+

Auto Style Calc

+

Leave animation from the current computed height using the auto-style value *.

+ +
+
+

Different Timings

+

Enter and leave animations with different easings, ease-in for enter, ease-out for leave.

+ +
+
+

Multiple Keyframes

+

Enter and leave animations with three keyframes in each, to give the transition some bounce.

+ +
+
+

Parallel Groups

+

Enter and leave animations with multiple properties animated in parallel with different timings.

+ +
+
+ `, + styles: [` + .buttons { + text-align: center; + } + button { + padding: 1.5em 3em; + } + .columns { + display: flex; + flex-direction: row; + } + .column { + flex: 1; + padding: 10px; + } + .column p { + min-height: 6em; + } + `], + providers: [Heroes] +}) +export class HeroTeamBuilderComponent { + constructor(private heroes: Heroes) { } +} diff --git a/aio/content/examples/animations/ts/src/app/hero.service.ts b/aio/content/examples/animations/ts/src/app/hero.service.ts new file mode 100644 index 0000000000..6bdeb5a512 --- /dev/null +++ b/aio/content/examples/animations/ts/src/app/hero.service.ts @@ -0,0 +1,60 @@ +import { Injectable } from '@angular/core'; + +class Hero { + constructor(public name: string, + public state = 'inactive') { + } + + toggleState() { + this.state = (this.state === 'active' ? 'inactive' : 'active'); + } +} + +let ALL_HEROES = [ + 'Windstorm', + 'RubberMan', + 'Bombasto', + 'Magneta', + 'Dynama', + 'Narco', + 'Celeritas', + 'Dr IQ', + 'Magma', + 'Tornado', + 'Mr. Nice' +].map(name => new Hero(name)); + +@Injectable() +export class Heroes implements Iterable { + + currentHeroes: Hero[] = []; + + [Symbol.iterator]() { + return this.currentHeroes.values(); + } + + canAdd() { + return this.currentHeroes.length < ALL_HEROES.length; + } + + canRemove() { + return this.currentHeroes.length > 0; + } + + addActive() { + let hero = ALL_HEROES[this.currentHeroes.length]; + hero.state = 'active'; + this.currentHeroes.push(hero); + } + + addInactive() { + let hero = ALL_HEROES[this.currentHeroes.length]; + hero.state = 'inactive'; + this.currentHeroes.push(hero); + } + + remove() { + this.currentHeroes.splice(this.currentHeroes.length - 1, 1); + } + +} diff --git a/aio/content/examples/animations/ts/src/index.html b/aio/content/examples/animations/ts/src/index.html new file mode 100644 index 0000000000..6c4a13adb2 --- /dev/null +++ b/aio/content/examples/animations/ts/src/index.html @@ -0,0 +1,34 @@ + + + + + Animations + + + + + + + + + + + + + + + + + + +

External H1 Title for E2E test

+ + +
    +
  • External list for E2E test
  • +
+ + + diff --git a/aio/content/examples/animations/ts/src/main.ts b/aio/content/examples/animations/ts/src/main.ts new file mode 100644 index 0000000000..f22933ba8e --- /dev/null +++ b/aio/content/examples/animations/ts/src/main.ts @@ -0,0 +1,4 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/architecture/e2e-spec.ts b/aio/content/examples/architecture/e2e-spec.ts new file mode 100644 index 0000000000..e967804483 --- /dev/null +++ b/aio/content/examples/architecture/e2e-spec.ts @@ -0,0 +1,100 @@ +'use strict'; // necessary for es6 output in node + +import { protractor, browser, element, by, ElementFinder } from 'protractor'; + +const nameSuffix = 'X'; + +class Hero { + id: number; + name: string; +} + +describe('Architecture', () => { + + const expectedTitle = 'Architecture of Angular'; + const expectedH2 = ['Hero List', 'Sales Tax Calculator']; + + beforeAll(() => browser.get('')); + + it(`has title '${expectedTitle}'`, () => { + expect(browser.getTitle()).toEqual(expectedTitle); + }); + + it(`has h2 '${expectedH2}'`, () => { + let h2 = element.all(by.css('h2')).map((elt: any) => elt.getText()); + expect(h2).toEqual(expectedH2); + }); + + describe('Hero', heroTests); + describe('Salex tax', salesTaxTests); +}); + +function heroTests() { + + const targetHero: Hero = { id: 2, name: 'Mr. Nice' }; + + it('has the right number of heroes', () => { + let page = getPageElts(); + expect(page.heroes.count()).toEqual(3); + }); + + it('has no hero details initially', function () { + let page = getPageElts(); + expect(page.heroDetail.isPresent()).toBeFalsy('no hero detail'); + }); + + it('shows selected hero details', async () => { + await element(by.cssContainingText('li', targetHero.name)).click(); + let page = getPageElts(); + let hero = await heroFromDetail(page.heroDetail); + expect(hero.id).toEqual(targetHero.id); + expect(hero.name).toEqual(targetHero.name); + }); + + it(`shows updated hero name in details`, async () => { + let input = element.all(by.css('input')).first(); + input.sendKeys(nameSuffix); + let page = getPageElts(); + let hero = await heroFromDetail(page.heroDetail); + let newName = targetHero.name + nameSuffix; + expect(hero.id).toEqual(targetHero.id); + expect(hero.name).toEqual(newName); + }); +} + +function salesTaxTests() { + it('has no sales tax initially', function () { + let page = getPageElts(); + expect(page.salesTaxDetail.isPresent()).toBeFalsy('no sales tax info'); + }); + + it('shows sales tax', async function () { + let page = getPageElts(); + page.salesTaxAmountInput.sendKeys('10', protractor.Key.ENTER); + expect(page.salesTaxDetail.getText()).toEqual('The sales tax is $1.00'); + }); +} + +// Helper functions + +function getPageElts() { + return { + heroes: element.all(by.css('my-app li')), + heroDetail: element(by.css('my-app hero-detail')), + salesTaxAmountInput: element(by.css('my-app sales-tax input')), + salesTaxDetail: element(by.css('my-app sales-tax div')) + }; +} + +async function heroFromDetail(detail: ElementFinder): Promise { + // Get hero id from the first
+ // let _id = await detail.all(by.css('div')).first().getText(); + let _id = await detail.all(by.css('div')).first().getText(); + // Get name from the h2 + // let _name = await detail.element(by.css('h4')).getText(); + let _name = await detail.element(by.css('h4')).getText(); + return { + id: +_id.substr(_id.indexOf(' ') + 1), + name: _name.substr(0, _name.lastIndexOf(' ')) + }; +} diff --git a/aio/content/examples/architecture/ts/example-config.json b/aio/content/examples/architecture/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/architecture/ts/plnkr.json b/aio/content/examples/architecture/ts/plnkr.json new file mode 100644 index 0000000000..b2f47131df --- /dev/null +++ b/aio/content/examples/architecture/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Intro to Angular", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!app/hero-list.component.1.*" + ] +} diff --git a/aio/content/examples/architecture/ts/plnkr.no-link.html b/aio/content/examples/architecture/ts/plnkr.no-link.html new file mode 100644 index 0000000000..cf21ec2759 --- /dev/null +++ b/aio/content/examples/architecture/ts/plnkr.no-link.html @@ -0,0 +1,481 @@ +
\ No newline at end of file diff --git a/aio/content/examples/architecture/ts/src/app/app.component.ts b/aio/content/examples/architecture/ts/src/app/app.component.ts new file mode 100644 index 0000000000..b987f17e44 --- /dev/null +++ b/aio/content/examples/architecture/ts/src/app/app.component.ts @@ -0,0 +1,14 @@ +// #docregion import +import { Component } from '@angular/core'; +// #enddocregion import + +@Component({ + selector: 'my-app', + template: ` + + + ` +}) +// #docregion export +export class AppComponent { } +// #enddocregion export diff --git a/aio/content/examples/architecture/ts/src/app/app.module.ts b/aio/content/examples/architecture/ts/src/app/app.module.ts new file mode 100644 index 0000000000..f6e64beecd --- /dev/null +++ b/aio/content/examples/architecture/ts/src/app/app.module.ts @@ -0,0 +1,36 @@ +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; +// #docregion imports +import { NgModule } from '@angular/core'; +import { AppComponent } from './app.component'; +// #enddocregion imports +import { HeroDetailComponent } from './hero-detail.component'; +import { HeroListComponent } from './hero-list.component'; +import { SalesTaxComponent } from './sales-tax.component'; +import { HeroService } from './hero.service'; +import { BackendService } from './backend.service'; +import { Logger } from './logger.service'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule + ], + declarations: [ + AppComponent, + HeroDetailComponent, + HeroListComponent, + SalesTaxComponent + ], +// #docregion providers + providers: [ + BackendService, + HeroService, + Logger + ], +// #enddocregion providers + bootstrap: [ AppComponent ] +}) +// #docregion export +export class AppModule { } +// #enddocregion export diff --git a/aio/content/examples/architecture/ts/src/app/backend.service.ts b/aio/content/examples/architecture/ts/src/app/backend.service.ts new file mode 100644 index 0000000000..e47cfc8ace --- /dev/null +++ b/aio/content/examples/architecture/ts/src/app/backend.service.ts @@ -0,0 +1,25 @@ +import { Injectable, Type } from '@angular/core'; + +import { Logger } from './logger.service'; +import { Hero } from './hero'; + +const HEROES = [ + new Hero('Windstorm', 'Weather mastery'), + new Hero('Mr. Nice', 'Killing them with kindness'), + new Hero('Magneta', 'Manipulates metalic objects') + ]; + +@Injectable() +export class BackendService { + constructor(private logger: Logger) {} + + getAll(type: Type): PromiseLike { + if (type === Hero) { + // TODO get from the database + return Promise.resolve(HEROES); + } + let err = new Error('Cannot get object of this type'); + this.logger.error(err); + throw err; + } +} diff --git a/aio/content/examples/architecture/ts/src/app/hero-detail.component.html b/aio/content/examples/architecture/ts/src/app/hero-detail.component.html new file mode 100644 index 0000000000..224de8bb86 --- /dev/null +++ b/aio/content/examples/architecture/ts/src/app/hero-detail.component.html @@ -0,0 +1,9 @@ +
+

{{hero.name}} Detail

+
Id: {{hero.id}}
+
Name: + + + +
+
Power:
diff --git a/aio/content/examples/architecture/ts/src/app/hero-detail.component.ts b/aio/content/examples/architecture/ts/src/app/hero-detail.component.ts new file mode 100644 index 0000000000..44a702ad49 --- /dev/null +++ b/aio/content/examples/architecture/ts/src/app/hero-detail.component.ts @@ -0,0 +1,12 @@ +import { Component, Input } from '@angular/core'; + +import { Hero } from './hero'; + +@Component({ + moduleId: module.id, + selector: 'hero-detail', + templateUrl: './hero-detail.component.html' +}) +export class HeroDetailComponent { + @Input() hero: Hero; +} diff --git a/aio/content/examples/architecture/ts/src/app/hero-list.component.1.html b/aio/content/examples/architecture/ts/src/app/hero-list.component.1.html new file mode 100644 index 0000000000..c6e6dd5133 --- /dev/null +++ b/aio/content/examples/architecture/ts/src/app/hero-list.component.1.html @@ -0,0 +1,9 @@ + +
  • {{hero.name}}
  • + +
  • + + + +
  • + diff --git a/aio/content/examples/architecture/ts/src/app/hero-list.component.html b/aio/content/examples/architecture/ts/src/app/hero-list.component.html new file mode 100644 index 0000000000..b46a307bd3 --- /dev/null +++ b/aio/content/examples/architecture/ts/src/app/hero-list.component.html @@ -0,0 +1,11 @@ + +

    Hero List

    + +

    Pick a hero from the list

    +
      +
    • + {{hero.name}} +
    • +
    + + diff --git a/aio/content/examples/architecture/ts/src/app/hero-list.component.ts b/aio/content/examples/architecture/ts/src/app/hero-list.component.ts new file mode 100644 index 0000000000..f72f57eef4 --- /dev/null +++ b/aio/content/examples/architecture/ts/src/app/hero-list.component.ts @@ -0,0 +1,30 @@ +import { Component, OnInit } from '@angular/core'; + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; + +// #docregion metadata, providers +@Component({ + moduleId: module.id, + selector: 'hero-list', + templateUrl: './hero-list.component.html', + providers: [ HeroService ] +}) +// #enddocregion providers +// #docregion class +export class HeroListComponent implements OnInit { + // #enddocregion metadata + heroes: Hero[]; + selectedHero: Hero; + + // #docregion ctor + constructor(private service: HeroService) { } + // #enddocregion ctor + + ngOnInit() { + this.heroes = this.service.getHeroes(); + } + + selectHero(hero: Hero) { this.selectedHero = hero; } + // #docregion metadata +} diff --git a/aio/content/examples/architecture/ts/src/app/hero.service.ts b/aio/content/examples/architecture/ts/src/app/hero.service.ts new file mode 100644 index 0000000000..493f064e40 --- /dev/null +++ b/aio/content/examples/architecture/ts/src/app/hero.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core'; + +import { Hero } from './hero'; +import { BackendService } from './backend.service'; +import { Logger } from './logger.service'; + +@Injectable() +// #docregion class +export class HeroService { + private heroes: Hero[] = []; + + constructor( + private backend: BackendService, + private logger: Logger) { } + + getHeroes() { + this.backend.getAll(Hero).then( (heroes: Hero[]) => { + this.logger.log(`Fetched ${heroes.length} heroes.`); + this.heroes.push(...heroes); // fill cache + }); + return this.heroes; + } +} diff --git a/aio/content/examples/architecture/ts/src/app/hero.ts b/aio/content/examples/architecture/ts/src/app/hero.ts new file mode 100644 index 0000000000..b89557aa71 --- /dev/null +++ b/aio/content/examples/architecture/ts/src/app/hero.ts @@ -0,0 +1,10 @@ +let nextId = 1; + +export class Hero { + id: number; + constructor( + public name: string, + public power?: string) { + this.id = nextId++; + } +} diff --git a/aio/content/examples/architecture/ts/src/app/logger.service.ts b/aio/content/examples/architecture/ts/src/app/logger.service.ts new file mode 100644 index 0000000000..9277ee8bc0 --- /dev/null +++ b/aio/content/examples/architecture/ts/src/app/logger.service.ts @@ -0,0 +1,9 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +// #docregion class +export class Logger { + log(msg: any) { console.log(msg); } + error(msg: any) { console.error(msg); } + warn(msg: any) { console.warn(msg); } +} diff --git a/aio/content/examples/architecture/ts/src/app/mini-app.ts b/aio/content/examples/architecture/ts/src/app/mini-app.ts new file mode 100644 index 0000000000..b064428be3 --- /dev/null +++ b/aio/content/examples/architecture/ts/src/app/mini-app.ts @@ -0,0 +1,45 @@ +// #docplaster +// A mini-application +import { Injectable } from '@angular/core'; + +@Injectable() +export class Logger { + log(message: string) { console.log(message); } +} + +// #docregion import-core-component +import { Component } from '@angular/core'; +// #enddocregion import-core-component + +@Component({ + selector: 'my-app', + template: 'Welcome to Angular' +}) +export class AppComponent { + constructor(logger: Logger) { + logger.log('Let the fun begin!'); + } +} + +// #docregion module +import { NgModule } from '@angular/core'; +// #docregion import-browser-module +import { BrowserModule } from '@angular/platform-browser'; +// #enddocregion import-browser-module +@NgModule({ +// #docregion ngmodule-imports + imports: [ BrowserModule ], +// #enddocregion ngmodule-imports + providers: [ Logger ], + declarations: [ AppComponent ], + exports: [ AppComponent ], + bootstrap: [ AppComponent ] +}) +// #docregion export +export class AppModule { } +// #enddocregion export +// #enddocregion module + +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/architecture/ts/src/app/sales-tax.component.ts b/aio/content/examples/architecture/ts/src/app/sales-tax.component.ts new file mode 100644 index 0000000000..02201afe05 --- /dev/null +++ b/aio/content/examples/architecture/ts/src/app/sales-tax.component.ts @@ -0,0 +1,25 @@ +import { Component } from '@angular/core'; + +import { SalesTaxService } from './sales-tax.service'; +import { TaxRateService } from './tax-rate.service'; + +@Component({ + selector: 'sales-tax', + template: ` +

    Sales Tax Calculator

    + Amount: + +
    + The sales tax is + {{ getTax(amountBox.value) | currency:'USD':true:'1.2-2' }} +
    + `, + providers: [SalesTaxService, TaxRateService] +}) +export class SalesTaxComponent { + constructor(private salesTaxService: SalesTaxService) { } + + getTax(value: string | number) { + return this.salesTaxService.getVAT(value); + } +} diff --git a/aio/content/examples/architecture/ts/src/app/sales-tax.service.ts b/aio/content/examples/architecture/ts/src/app/sales-tax.service.ts new file mode 100644 index 0000000000..d859dc1595 --- /dev/null +++ b/aio/content/examples/architecture/ts/src/app/sales-tax.service.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@angular/core'; + +import { TaxRateService } from './tax-rate.service'; + +@Injectable() +export class SalesTaxService { + constructor(private rateService: TaxRateService) { } + + getVAT(value: string | number) { + let amount = (typeof value === 'string') ? + parseFloat(value) : value; + return (amount || 0) * this.rateService.getRate('VAT'); + } +} diff --git a/aio/content/examples/architecture/ts/src/app/tax-rate.service.ts b/aio/content/examples/architecture/ts/src/app/tax-rate.service.ts new file mode 100644 index 0000000000..fff2f4df8f --- /dev/null +++ b/aio/content/examples/architecture/ts/src/app/tax-rate.service.ts @@ -0,0 +1,6 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class TaxRateService { + getRate(rateName: string) { return 0.10; } // 10% everywhere +} diff --git a/aio/content/examples/architecture/ts/src/index.html b/aio/content/examples/architecture/ts/src/index.html new file mode 100644 index 0000000000..9aadf6e109 --- /dev/null +++ b/aio/content/examples/architecture/ts/src/index.html @@ -0,0 +1,26 @@ + + + + Architecture of Angular + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/architecture/ts/src/main.ts b/aio/content/examples/architecture/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/aio/content/examples/architecture/ts/src/main.ts @@ -0,0 +1,5 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/attribute-directives/e2e-spec.ts b/aio/content/examples/attribute-directives/e2e-spec.ts new file mode 100644 index 0000000000..25a0cf258e --- /dev/null +++ b/aio/content/examples/attribute-directives/e2e-spec.ts @@ -0,0 +1,31 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('Attribute directives', function () { + + let _title = 'My First Attribute Directive'; + + beforeAll(function () { + browser.get(''); + }); + + it(`should display correct title: ${_title}`, function () { + expect(element(by.css('h1')).getText()).toEqual(_title); + }); + + it('should be able to select green highlight', function () { + let highlightedEle = element(by.cssContainingText('p', 'Highlight me')); + let lightGreen = 'rgba(144, 238, 144, 1)'; + + expect(highlightedEle.getCssValue('background-color')).not.toEqual(lightGreen); + // let greenRb = element(by.cssContainingText('input', 'Green')); + let greenRb = element.all(by.css('input')).get(0); + greenRb.click().then(function() { + // TypeScript Todo: find the right type for highlightedEle + browser.actions().mouseMove(highlightedEle as any).perform(); + expect(highlightedEle.getCssValue('background-color')).toEqual(lightGreen); + }); + + }); +}); diff --git a/aio/content/examples/attribute-directives/ts/example-config.json b/aio/content/examples/attribute-directives/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/attribute-directives/ts/plnkr.json b/aio/content/examples/attribute-directives/ts/plnkr.json new file mode 100644 index 0000000000..112e1de6f4 --- /dev/null +++ b/aio/content/examples/attribute-directives/ts/plnkr.json @@ -0,0 +1,10 @@ +{ + "description": "Attribute Directive", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!app/*.[1,2,3].*" + ], + "tags": ["attribute", "directive"] +} diff --git a/aio/content/examples/attribute-directives/ts/plnkr.no-link.html b/aio/content/examples/attribute-directives/ts/plnkr.no-link.html new file mode 100644 index 0000000000..b4e52a09a3 --- /dev/null +++ b/aio/content/examples/attribute-directives/ts/plnkr.no-link.html @@ -0,0 +1,253 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/attribute-directives/ts/src/app/app.component.1.html b/aio/content/examples/attribute-directives/ts/src/app/app.component.1.html new file mode 100644 index 0000000000..9f8c13f077 --- /dev/null +++ b/aio/content/examples/attribute-directives/ts/src/app/app.component.1.html @@ -0,0 +1,20 @@ + +

    My First Attribute Directive

    + +

    Highlight me!

    + + + + +

    Highlighted in yellow

    +

    Highlighted in orange

    + + + + +

    Highlighted with parent component's color

    + + + +

    I am green with envy!

    + diff --git a/aio/content/examples/attribute-directives/ts/src/app/app.component.1.ts b/aio/content/examples/attribute-directives/ts/src/app/app.component.1.ts new file mode 100644 index 0000000000..d65cb57850 --- /dev/null +++ b/aio/content/examples/attribute-directives/ts/src/app/app.component.1.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: './app.component.1.html' +}) +// #docregion class +export class AppComponent { + color = 'yellow'; +} +// #enddocregion class diff --git a/aio/content/examples/attribute-directives/ts/src/app/app.component.html b/aio/content/examples/attribute-directives/ts/src/app/app.component.html new file mode 100644 index 0000000000..0ef41b925d --- /dev/null +++ b/aio/content/examples/attribute-directives/ts/src/app/app.component.html @@ -0,0 +1,27 @@ + + +

    My First Attribute Directive

    + +

    Pick a highlight color

    +
    + Green + Yellow + Cyan +
    + +

    Highlight me!

    + + + + +

    + Highlight me too! +

    + + + +
    +

    Mouse over the following lines to see fixed highlights

    + +

    Highlighted in yellow

    +

    Highlighted in orange

    diff --git a/aio/content/examples/attribute-directives/ts/src/app/app.component.ts b/aio/content/examples/attribute-directives/ts/src/app/app.component.ts new file mode 100644 index 0000000000..a88e8e49a3 --- /dev/null +++ b/aio/content/examples/attribute-directives/ts/src/app/app.component.ts @@ -0,0 +1,14 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: './app.component.html' +}) +// #docregion class +export class AppComponent { + color: string; +} +// #enddocregion class +// #enddocregion diff --git a/aio/content/examples/attribute-directives/ts/src/app/app.module.ts b/aio/content/examples/attribute-directives/ts/src/app/app.module.ts new file mode 100644 index 0000000000..ca35d560fb --- /dev/null +++ b/aio/content/examples/attribute-directives/ts/src/app/app.module.ts @@ -0,0 +1,16 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; +import { HighlightDirective } from './highlight.directive'; + +@NgModule({ + imports: [ BrowserModule ], + declarations: [ + AppComponent, + HighlightDirective + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/attribute-directives/ts/src/app/dummy.module.1.ts b/aio/content/examples/attribute-directives/ts/src/app/dummy.module.1.ts new file mode 100644 index 0000000000..7ba41d53bb --- /dev/null +++ b/aio/content/examples/attribute-directives/ts/src/app/dummy.module.1.ts @@ -0,0 +1,17 @@ +// Not used. Keep away from plunker +// Keeps ATLS from complaining about undeclared directives. +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component.1'; +import { HighlightDirective as HLD1 } from './highlight.directive.1'; +import { HighlightDirective as HLD2 } from './highlight.directive.2'; +import { HighlightDirective as HLD3 } from './highlight.directive.3'; + +@NgModule({ + imports: [ BrowserModule ], + declarations: [ + AppComponent, HLD1, HLD2, HLD3 + ] +}) +export class DummyModule { } diff --git a/aio/content/examples/attribute-directives/ts/src/app/highlight.directive.1.ts b/aio/content/examples/attribute-directives/ts/src/app/highlight.directive.1.ts new file mode 100644 index 0000000000..4cacc0d22e --- /dev/null +++ b/aio/content/examples/attribute-directives/ts/src/app/highlight.directive.1.ts @@ -0,0 +1,10 @@ +/* tslint:disable:no-unused-variable */ +// #docregion +import { Directive, ElementRef, Input } from '@angular/core'; + +@Directive({ selector: '[myHighlight]' }) +export class HighlightDirective { + constructor(el: ElementRef) { + el.nativeElement.style.backgroundColor = 'yellow'; + } +} diff --git a/aio/content/examples/attribute-directives/ts/src/app/highlight.directive.2.ts b/aio/content/examples/attribute-directives/ts/src/app/highlight.directive.2.ts new file mode 100644 index 0000000000..156fabaaa8 --- /dev/null +++ b/aio/content/examples/attribute-directives/ts/src/app/highlight.directive.2.ts @@ -0,0 +1,45 @@ +/* tslint:disable:no-unused-variable member-ordering */ +// #docplaster +// #docregion +import { Directive, ElementRef, HostListener, Input } from '@angular/core'; + +@Directive({ + selector: '[myHighlight]' +}) +export class HighlightDirective { + // #docregion ctor + constructor(private el: ElementRef) { } + // #enddocregion ctor + // #enddocregion + + // #docregion color + @Input() highlightColor: string; + // #enddocregion color + + // #docregion color-2 + @Input() myHighlight: string; + // #enddocregion color-2 + + // #docregion + + // #docregion mouse-methods, host + @HostListener('mouseenter') onMouseEnter() { + // #enddocregion host + this.highlight('yellow'); + // #docregion host + } + + @HostListener('mouseleave') onMouseLeave() { + // #enddocregion host + this.highlight(null); + // #docregion host + } + // #enddocregion host + + private highlight(color: string) { + this.el.nativeElement.style.backgroundColor = color; + } + // #enddocregion mouse-methods + +} +// #enddocregion diff --git a/aio/content/examples/attribute-directives/ts/src/app/highlight.directive.3.ts b/aio/content/examples/attribute-directives/ts/src/app/highlight.directive.3.ts new file mode 100644 index 0000000000..bf76769c93 --- /dev/null +++ b/aio/content/examples/attribute-directives/ts/src/app/highlight.directive.3.ts @@ -0,0 +1,27 @@ +/* tslint:disable:member-ordering */ +// #docregion +import { Directive, ElementRef, HostListener, Input } from '@angular/core'; + +@Directive({ + selector: '[myHighlight]' +}) +export class HighlightDirective { + + constructor(private el: ElementRef) { } + + @Input('myHighlight') highlightColor: string; + + // #docregion mouse-enter + @HostListener('mouseenter') onMouseEnter() { + this.highlight(this.highlightColor || 'red'); + } + // #enddocregion mouse-enter + + @HostListener('mouseleave') onMouseLeave() { + this.highlight(null); + } + + private highlight(color: string) { + this.el.nativeElement.style.backgroundColor = color; + } +} diff --git a/aio/content/examples/attribute-directives/ts/src/app/highlight.directive.ts b/aio/content/examples/attribute-directives/ts/src/app/highlight.directive.ts new file mode 100644 index 0000000000..68c9f0cc73 --- /dev/null +++ b/aio/content/examples/attribute-directives/ts/src/app/highlight.directive.ts @@ -0,0 +1,36 @@ +/* tslint:disable:member-ordering */ +// #docplaster +// #docregion +// #docregion imports +import { Directive, ElementRef, HostListener, Input } from '@angular/core'; +// #enddocregion imports + +@Directive({ + selector: '[myHighlight]' +}) +export class HighlightDirective { + + constructor(private el: ElementRef) { } + + // #docregion defaultColor + @Input() defaultColor: string; + // #enddocregion defaultColor + + // #docregion color + @Input('myHighlight') highlightColor: string; + // #enddocregion color + + // #docregion mouse-enter + @HostListener('mouseenter') onMouseEnter() { + this.highlight(this.highlightColor || this.defaultColor || 'red'); + } + // #enddocregion mouse-enter + + @HostListener('mouseleave') onMouseLeave() { + this.highlight(null); + } + + private highlight(color: string) { + this.el.nativeElement.style.backgroundColor = color; + } +} diff --git a/aio/content/examples/attribute-directives/ts/src/index.html b/aio/content/examples/attribute-directives/ts/src/index.html new file mode 100644 index 0000000000..97662437f0 --- /dev/null +++ b/aio/content/examples/attribute-directives/ts/src/index.html @@ -0,0 +1,25 @@ + + + + + + Attribute Directives + + + + + + + + + + + + + + + loading... + + diff --git a/aio/content/examples/attribute-directives/ts/src/main.ts b/aio/content/examples/attribute-directives/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/aio/content/examples/attribute-directives/ts/src/main.ts @@ -0,0 +1,5 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/cb-ajs-quick-reference/e2e-spec.ts b/aio/content/examples/cb-ajs-quick-reference/e2e-spec.ts new file mode 100644 index 0000000000..81a5faa5e7 --- /dev/null +++ b/aio/content/examples/cb-ajs-quick-reference/e2e-spec.ts @@ -0,0 +1,115 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('AngularJS to Angular Quick Reference Tests', function () { + + beforeAll(function () { + browser.get(''); + }); + + it('should display no poster images after bootstrap', function () { + testImagesAreDisplayed(false); + }); + + it('should display proper movie data', function () { + // We check only a few samples + let expectedSamples: any[] = [ + {row: 0, column: 0, element: 'img', attr: 'src', value: 'images/hero.png', contains: true}, + {row: 0, column: 2, value: 'Celeritas'}, + {row: 1, column: 3, matches: /Dec 1[678], 2015/}, // absorb timezone dif; we care about date format + {row: 1, column: 5, value: '$14.95'}, + {row: 2, column: 4, value: 'PG-13'}, + {row: 2, column: 7, value: '100%'}, + {row: 2, column: 0, element: 'img', attr: 'src', value: 'images/ng-logo.png', contains: true}, + ]; + + // Go through the samples + let movieRows = getMovieRows(); + for (let i = 0; i < expectedSamples.length; i++) { + let sample = expectedSamples[i]; + let tableCell = movieRows.get(sample.row) + .all(by.tagName('td')).get(sample.column); + // Check the cell or its nested element + let elementToCheck = sample.element + ? tableCell.element(by.tagName(sample.element)) + : tableCell; + + // Check element attribute or text + let valueToCheck = sample.attr + ? elementToCheck.getAttribute(sample.attr) + : elementToCheck.getText(); + + // Test for equals/contains/match + if (sample.contains) { + expect(valueToCheck).toContain(sample.value); + } else if (sample.matches) { + expect(valueToCheck).toMatch(sample.matches); + } else { + expect(valueToCheck).toEqual(sample.value); + } + } + }); + + it('should display images after Show Poster', function () { + testPosterButtonClick('Show Poster', true); + }); + + it('should hide images after Hide Poster', function () { + testPosterButtonClick('Hide Poster', false); + }); + + it('should display no movie when no favorite hero is specified', function () { + testFavoriteHero(null, 'Please enter your favorite hero.'); + }); + + it('should display no movie for Magneta', function () { + testFavoriteHero('Magneta', 'No movie, sorry!'); + }); + + it('should display a movie for Mr. Nice', function () { + testFavoriteHero('Mr. Nice', 'Excellent choice!'); + }); + + function testImagesAreDisplayed(isDisplayed: boolean) { + let expectedMovieCount = 3; + + let movieRows = getMovieRows(); + expect(movieRows.count()).toBe(expectedMovieCount); + for (let i = 0; i < expectedMovieCount; i++) { + let movieImage = movieRows.get(i).element(by.css('td > img')); + expect(movieImage.isDisplayed()).toBe(isDisplayed); + } + } + + function testPosterButtonClick(expectedButtonText: string, isDisplayed: boolean) { + let posterButton = element(by.css('movie-list tr > th > button')); + expect(posterButton.getText()).toBe(expectedButtonText); + + posterButton.click().then(function () { + testImagesAreDisplayed(isDisplayed); + }); + } + + function getMovieRows() { + return element.all(by.css('movie-list tbody > tr')); + } + + function testFavoriteHero(heroName: string, expectedLabel: string) { + let movieListComp = element(by.tagName('movie-list')); + let heroInput = movieListComp.element(by.tagName('input')); + let favoriteHeroLabel = movieListComp.element(by.tagName('h3')); + let resultLabel = movieListComp.element(by.css('span > p')); + + heroInput.clear().then(function () { + heroInput.sendKeys(heroName || ''); + expect(resultLabel.getText()).toBe(expectedLabel); + if (heroName) { + expect(favoriteHeroLabel.isDisplayed()).toBe(true); + expect(favoriteHeroLabel.getText()).toContain(heroName); + } else { + expect(favoriteHeroLabel.isDisplayed()).toBe(false); + } + }); + } +}); diff --git a/aio/content/examples/cb-ajs-quick-reference/ts/example-config.json b/aio/content/examples/cb-ajs-quick-reference/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/cb-ajs-quick-reference/ts/plnkr.json b/aio/content/examples/cb-ajs-quick-reference/ts/plnkr.json new file mode 100644 index 0000000000..1ff34275f7 --- /dev/null +++ b/aio/content/examples/cb-ajs-quick-reference/ts/plnkr.json @@ -0,0 +1,10 @@ +{ + "description": "AngularJS to Angular Quick Reference", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1].*" + ], + "tags":["cookbook", "angularjs"] +} diff --git a/aio/content/examples/cb-ajs-quick-reference/ts/plnkr.no-link.html b/aio/content/examples/cb-ajs-quick-reference/ts/plnkr.no-link.html new file mode 100644 index 0000000000..69d78ced3d --- /dev/null +++ b/aio/content/examples/cb-ajs-quick-reference/ts/plnkr.no-link.html @@ -0,0 +1,597 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/cb-ajs-quick-reference/ts/src/app/app-routing.module.ts b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/app-routing.module.ts new file mode 100644 index 0000000000..a7cbe8a74d --- /dev/null +++ b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/app-routing.module.ts @@ -0,0 +1,16 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { MovieListComponent } from './movie-list.component'; + +const routes: Routes = [ + { path: '', redirectTo: '/movies', pathMatch: 'full' }, + { path: 'movies', component: MovieListComponent } +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +export class AppRoutingModule {} diff --git a/aio/content/examples/cb-ajs-quick-reference/ts/src/app/app.component.css b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/app.component.css new file mode 100644 index 0000000000..e454e9ea87 --- /dev/null +++ b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/app.component.css @@ -0,0 +1,9 @@ +.active {font-style: italic;} +.shazam {font-weight: bold;} + +img {height: 100px;} + +table td { + padding: 4px; + border: 1px solid #e0e0e0; +} diff --git a/aio/content/examples/cb-ajs-quick-reference/ts/src/app/app.component.html b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/app.component.html new file mode 100644 index 0000000000..72fd3de86f --- /dev/null +++ b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/app.component.html @@ -0,0 +1,112 @@ + +

    {{title}}

    + +

    Routed Movies

    + + + +
    + +

    Example Snippets

    + + +
    + + [ngClass] active +
    + +
    + + [ngClass] active and boldly important +
    + +
    + + [class.active] +
    + +

    + +Angular Docs + + +

    +
    + + + + +

    Image toggle event type was {{eventType}}

    +
    + +

    +
    + + + +
    + +

    + +
    + + color preference #1 +
    + +
    + + color preference #2 +
    + +

    Movie as JSON

    + +
    {{movie | json}}
    + + +

    Movie Titles via local variable

    + + + + + + +
    {{movie.title}}
    + +

    Sliced Movies with pipes

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {{movie.title | uppercase}}{{movie.title | lowercase}}{{movie.releaseDate | date}}{{movie.price | currency:'USD':true}}{{movie.starRating | number}}{{movie.starRating | number:'1.1-2'}}{{movie.approvalRating | percent: '1.0-2'}}
    diff --git a/aio/content/examples/cb-ajs-quick-reference/ts/src/app/app.component.ts b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/app.component.ts new file mode 100644 index 0000000000..4d708509a1 --- /dev/null +++ b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/app.component.ts @@ -0,0 +1,33 @@ +import { Component } from '@angular/core'; + +import { MovieService } from './movie.service'; +import { IMovie } from './movie'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: './app.component.html', + styleUrls: [ './app.component.css' ], + providers: [ MovieService ] +}) +export class AppComponent { + + angularDocsUrl = 'https://angular.io/'; + colorPreference = 'red'; + eventType = ''; + isActive = true; + isImportant = true; + movie: IMovie = null; + movies: IMovie[] = []; + showImage = true; + title: string = 'AngularJS to Angular Quick Ref Cookbook'; + toggleImage(event: UIEvent) { + this.showImage = !this.showImage; + this.eventType = (event && event.type) || 'not provided'; + } + + constructor(movieService: MovieService) { + this.movies = movieService.getMovies(); + this.movie = this.movies[0]; + } +} diff --git a/aio/content/examples/cb-ajs-quick-reference/ts/src/app/app.module.1.ts b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/app.module.1.ts new file mode 100644 index 0000000000..5b24020186 --- /dev/null +++ b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/app.module.1.ts @@ -0,0 +1,12 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ BrowserModule ], + declarations: [ AppComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/cb-ajs-quick-reference/ts/src/app/app.module.ts b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/app.module.ts new file mode 100644 index 0000000000..1dc46ad17c --- /dev/null +++ b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/app.module.ts @@ -0,0 +1,22 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; + +import { AppComponent } from './app.component'; +import { MovieListComponent } from './movie-list.component'; +import { AppRoutingModule } from './app-routing.module'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule, + AppRoutingModule + ], + declarations: [ + AppComponent, + MovieListComponent + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/cb-ajs-quick-reference/ts/src/app/date.pipe.ts b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/date.pipe.ts new file mode 100644 index 0000000000..e1421fa530 --- /dev/null +++ b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/date.pipe.ts @@ -0,0 +1,14 @@ +import { Injectable, Pipe, PipeTransform } from '@angular/core'; +import { DatePipe } from '@angular/common'; + +@Injectable() +// #docregion date-pipe +@Pipe({name: 'date', pure: true}) +export class StringSafeDatePipe extends DatePipe implements PipeTransform { + transform(value: any, format: string): string { + value = typeof value === 'string' ? + Date.parse(value) : value; + return super.transform(value, format); + } +} +// #enddocregion date-pipe diff --git a/aio/content/examples/cb-ajs-quick-reference/ts/src/app/movie-list.component.css b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/movie-list.component.css new file mode 100644 index 0000000000..a3d5bf8161 --- /dev/null +++ b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/movie-list.component.css @@ -0,0 +1,57 @@ +div { + font-family:Arial, Helvetica, sans-serif; + margin:20px; +} + +table { + font-family:Arial, Helvetica, sans-serif; + color:#666; + font-size:14px; + text-shadow: 1px 1px 0 #fff; + margin:20px; + border:#ccc 1px solid; + + -moz-border-radius:3px; + -webkit-border-radius:3px; + border-radius:3px; +} +table th { + padding:21px 25px 22px 25px; + border-top:1px solid #fafafa; + border-bottom:1px solid #e0e0e0; + border-left: 1px solid #e0e0e0; + font-weight: bold; +} +table th:first-child { + text-align: left; + padding-left:20px; + border-left: 0; +} +table tr { + text-align: center; + padding-left:20px; +} +table td:first-child { + text-align: left; + padding-left:20px; + border-left: 0; +} +table td { + padding:18px; + border-top: 1px solid #ffffff; + border-bottom:1px solid #e0e0e0; + border-left: 1px solid #e0e0e0; +} +table tr:last-child td { + border-bottom:0; +} +table tr:last-child td:first-child { + -moz-border-radius-bottomleft:3px; + -webkit-border-bottom-left-radius:3px; + border-bottom-left-radius:3px; +} +table tr:last-child td:last-child { + -moz-border-radius-bottomright:3px; + -webkit-border-bottom-right-radius:3px; + border-bottom-right-radius:3px; +} diff --git a/aio/content/examples/cb-ajs-quick-reference/ts/src/app/movie-list.component.html b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/movie-list.component.html new file mode 100644 index 0000000000..9de98806d7 --- /dev/null +++ b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/movie-list.component.html @@ -0,0 +1,78 @@ + +
    +

    Movie List

    +
    Who is your favorite hero?
    +
    + + + + + + +

    + Excellent choice! +

    +

    + No movie, sorry! +

    +

    + Please enter your favorite hero. +

    +
    + +
    +
    + + +

    + + Your favorite hero is: {{favoriteHero}} + +

    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + TitleHeroRelease DateRatingPriceStar ratingApproval rating
    + + {{movie.title}}{{movie.hero}}{{movie.releaseDate | date}}{{movie.mpaa | uppercase}}{{movie.price | currency:'USD':true}}{{movie.starRating | number:'1.1-2'}}{{movie.approvalRating | percent: '1.0-0'}}
    +
    diff --git a/aio/content/examples/cb-ajs-quick-reference/ts/src/app/movie-list.component.ts b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/movie-list.component.ts new file mode 100644 index 0000000000..c194bc50bd --- /dev/null +++ b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/movie-list.component.ts @@ -0,0 +1,43 @@ +/* tslint:disable:no-unused-variable */ +// #docplaster +// #docregion import +import { Component } from '@angular/core'; +// #enddocregion import +import { IMovie } from './movie'; +import { MovieService } from './movie.service'; + +// #docregion component +@Component({ + moduleId: module.id, + selector: 'movie-list', + templateUrl: './movie-list.component.html', +// #docregion style-url + styleUrls: [ './movie-list.component.css' ], +// #enddocregion style-url +}) +// #enddocregion component +// #docregion class +export class MovieListComponent { +// #enddocregion class + favoriteHero: string; + showImage: boolean = false; + movies: IMovie[]; + +// #docregion di + constructor(movieService: MovieService) { +// #enddocregion di + this.movies = movieService.getMovies(); +// #docregion di + } +// #enddocregion di + + toggleImage(): void { + this.showImage = !this.showImage; + } + + checkMovieHero(value: string): boolean { + return this.movies.filter(movie => movie.hero === value).length > 0 ; + } +// #docregion class +} +// #enddocregion class diff --git a/aio/content/examples/cb-ajs-quick-reference/ts/src/app/movie.service.ts b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/movie.service.ts new file mode 100644 index 0000000000..404fd4454c --- /dev/null +++ b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/movie.service.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@angular/core'; + +import { IMovie } from './movie'; + +@Injectable() +export class MovieService { + getMovies(): IMovie[] { + return [ + { + hero: 'Celeritas', + imageurl: 'images/hero.png', + movieId: 1, + mpaa: 'pg-13', + releaseDate: '2015-12-19T00:00:00', + title: 'Celeritas Reigns', + price: 12.95, + starRating: 4.925, + approvalRating: .97 + }, + { + hero: 'Mr. Nice', + imageurl: 'images/villain.png', + movieId: 2, + mpaa: 'pg-13', + releaseDate: '2015-12-18T00:00:00', + title: 'No More Mr. Nice Guy', + price: 14.95, + starRating: 4.6, + approvalRating: .94 + }, + { + hero: 'Angular', + imageurl: 'images/ng-logo.png', + movieId: 3, + mpaa: 'pg-13', + releaseDate: '2015-12-17T00:00:00', + title: 'Angular to the Rescue', + price: 15.95, + starRating: 4.98, + approvalRating: .9995 + } + ]; + } +} diff --git a/aio/content/examples/cb-ajs-quick-reference/ts/src/app/movie.ts b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/movie.ts new file mode 100644 index 0000000000..0e6f321520 --- /dev/null +++ b/aio/content/examples/cb-ajs-quick-reference/ts/src/app/movie.ts @@ -0,0 +1,12 @@ +/* Defines the movie entity */ +export interface IMovie { + approvalRating: number; + hero: string; + imageurl: string; + movieId: number; + mpaa: string; + price: number; + releaseDate: string; + starRating: number; + title: string; +} diff --git a/aio/content/examples/cb-ajs-quick-reference/ts/src/images/hero.png b/aio/content/examples/cb-ajs-quick-reference/ts/src/images/hero.png new file mode 100644 index 0000000000..2a128ac367 Binary files /dev/null and b/aio/content/examples/cb-ajs-quick-reference/ts/src/images/hero.png differ diff --git a/aio/content/examples/cb-ajs-quick-reference/ts/src/images/ng-logo.png b/aio/content/examples/cb-ajs-quick-reference/ts/src/images/ng-logo.png new file mode 100644 index 0000000000..1e488b1a49 Binary files /dev/null and b/aio/content/examples/cb-ajs-quick-reference/ts/src/images/ng-logo.png differ diff --git a/aio/content/examples/cb-ajs-quick-reference/ts/src/images/villain.png b/aio/content/examples/cb-ajs-quick-reference/ts/src/images/villain.png new file mode 100644 index 0000000000..26697d1a42 Binary files /dev/null and b/aio/content/examples/cb-ajs-quick-reference/ts/src/images/villain.png differ diff --git a/aio/content/examples/cb-ajs-quick-reference/ts/src/index.html b/aio/content/examples/cb-ajs-quick-reference/ts/src/index.html new file mode 100644 index 0000000000..829f080ae3 --- /dev/null +++ b/aio/content/examples/cb-ajs-quick-reference/ts/src/index.html @@ -0,0 +1,28 @@ + + + + + + AngularJS to Angular Quick Reference + + + + + + + + + + + + + + + + + Loading app... + + + diff --git a/aio/content/examples/cb-ajs-quick-reference/ts/src/main.ts b/aio/content/examples/cb-ajs-quick-reference/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/aio/content/examples/cb-ajs-quick-reference/ts/src/main.ts @@ -0,0 +1,5 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/cb-aot-compiler/e2e-spec.ts b/aio/content/examples/cb-aot-compiler/e2e-spec.ts new file mode 100644 index 0000000000..ab91490604 --- /dev/null +++ b/aio/content/examples/cb-aot-compiler/e2e-spec.ts @@ -0,0 +1,27 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +/* tslint:disable:quotemark */ +describe('AOT Compilation', function () { + + beforeAll(function () { + browser.get(''); + }); + + it('should load page and click button', function (done) { + let headingSelector = element.all(by.css('h1')).get(0); + expect(headingSelector.getText()).toEqual('Hello Angular'); + + expect(element.all(by.xpath('//div[text()="Magneta"]')).get(0).isPresent()).toBe(true); + expect(element.all(by.xpath('//div[text()="Bombasto"]')).get(0).isPresent()).toBe(true); + expect(element.all(by.xpath('//div[text()="Magma"]')).get(0).isPresent()).toBe(true); + expect(element.all(by.xpath('//div[text()="Tornado"]')).get(0).isPresent()).toBe(true); + + let toggleButton = element.all(by.css('button')).get(0); + toggleButton.click().then(function() { + expect(headingSelector.isPresent()).toBe(false); + done(); + }); + }); +}); diff --git a/aio/content/examples/cb-aot-compiler/ts/.gitignore b/aio/content/examples/cb-aot-compiler/ts/.gitignore new file mode 100644 index 0000000000..91da4c79a2 --- /dev/null +++ b/aio/content/examples/cb-aot-compiler/ts/.gitignore @@ -0,0 +1,7 @@ +**/*.ngfactory.ts +**/*.ngsummary.json +**/*.shim.ngstyle.ts +**/*.metadata.json +dist +!app/tsconfig.json +!rollup-config.js diff --git a/aio/content/examples/cb-aot-compiler/ts/example-config.json b/aio/content/examples/cb-aot-compiler/ts/example-config.json new file mode 100644 index 0000000000..473b80a572 --- /dev/null +++ b/aio/content/examples/cb-aot-compiler/ts/example-config.json @@ -0,0 +1,3 @@ +{ + "build": "build:aot" +} diff --git a/aio/content/examples/cb-aot-compiler/ts/rollup-config.js b/aio/content/examples/cb-aot-compiler/ts/rollup-config.js new file mode 100644 index 0000000000..0c9088fe54 --- /dev/null +++ b/aio/content/examples/cb-aot-compiler/ts/rollup-config.js @@ -0,0 +1,36 @@ +// #docregion +import rollup from 'rollup' +import nodeResolve from 'rollup-plugin-node-resolve' +import commonjs from 'rollup-plugin-commonjs'; +import uglify from 'rollup-plugin-uglify' + +// #docregion config +export default { + entry: 'src/main.js', + dest: 'src/build.js', // output a single application bundle + sourceMap: false, + format: 'iife', + onwarn: function(warning) { + // Skip certain warnings + + // should intercept ... but doesn't in some rollup versions + if ( warning.code === 'THIS_IS_UNDEFINED' ) { return; } + // intercepts in some rollup versions + if ( warning.indexOf("The 'this' keyword is equivalent to 'undefined'") > -1 ) { return; } + + // console.warn everything else + console.warn( warning.message ); + }, + plugins: [ + nodeResolve({jsnext: true, module: true}), + // #docregion commonjs + commonjs({ + include: 'node_modules/rxjs/**', + }), + // #enddocregion commonjs + // #docregion uglify + uglify() + // #enddocregion uglify + ] +} +// #enddocregion config diff --git a/aio/content/examples/cb-aot-compiler/ts/src/app/app.component.html b/aio/content/examples/cb-aot-compiler/ts/src/app/app.component.html new file mode 100644 index 0000000000..1d3a8de932 --- /dev/null +++ b/aio/content/examples/cb-aot-compiler/ts/src/app/app.component.html @@ -0,0 +1,7 @@ + + +

    Hello Angular

    + +

    List of Heroes

    +
    {{hero}}
    + diff --git a/aio/content/examples/cb-aot-compiler/ts/src/app/app.component.ts b/aio/content/examples/cb-aot-compiler/ts/src/app/app.component.ts new file mode 100644 index 0000000000..680c07d682 --- /dev/null +++ b/aio/content/examples/cb-aot-compiler/ts/src/app/app.component.ts @@ -0,0 +1,16 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: './app.component.html' +}) +export class AppComponent { + showHeading = true; + heroes = ['Magneta', 'Bombasto', 'Magma', 'Tornado']; + + toggleHeading() { + this.showHeading = !this.showHeading; + } +} diff --git a/aio/content/examples/cb-aot-compiler/ts/src/app/app.module.ts b/aio/content/examples/cb-aot-compiler/ts/src/app/app.module.ts new file mode 100644 index 0000000000..b4fc185c24 --- /dev/null +++ b/aio/content/examples/cb-aot-compiler/ts/src/app/app.module.ts @@ -0,0 +1,12 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ BrowserModule ], + declarations: [ AppComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/cb-aot-compiler/ts/src/index-jit.html b/aio/content/examples/cb-aot-compiler/ts/src/index-jit.html new file mode 100644 index 0000000000..713a04970e --- /dev/null +++ b/aio/content/examples/cb-aot-compiler/ts/src/index-jit.html @@ -0,0 +1,24 @@ + + + + + Ahead of time compilation (JIT) + + + + + + + + + + + + + + + Loading... + + diff --git a/aio/content/examples/cb-aot-compiler/ts/src/index.html b/aio/content/examples/cb-aot-compiler/ts/src/index.html new file mode 100644 index 0000000000..9016f3cbe1 --- /dev/null +++ b/aio/content/examples/cb-aot-compiler/ts/src/index.html @@ -0,0 +1,23 @@ + + + + + Ahead of time compilation + + + + + + + + + + + + + Loading... + + + + + diff --git a/aio/content/examples/cb-aot-compiler/ts/src/main-jit.ts b/aio/content/examples/cb-aot-compiler/ts/src/main-jit.ts new file mode 100644 index 0000000000..88e2c16ed5 --- /dev/null +++ b/aio/content/examples/cb-aot-compiler/ts/src/main-jit.ts @@ -0,0 +1,6 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +console.log('Running JIT compiled'); +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/cb-aot-compiler/ts/src/main.ts b/aio/content/examples/cb-aot-compiler/ts/src/main.ts new file mode 100644 index 0000000000..4446bc07d1 --- /dev/null +++ b/aio/content/examples/cb-aot-compiler/ts/src/main.ts @@ -0,0 +1,6 @@ +// #docregion +import { platformBrowser } from '@angular/platform-browser'; +import { AppModuleNgFactory } from '../aot/src/app/app.module.ngfactory'; + +console.log('Running AOT compiled'); +platformBrowser().bootstrapModuleFactory(AppModuleNgFactory); diff --git a/aio/content/examples/cb-aot-compiler/ts/tsconfig-aot.json b/aio/content/examples/cb-aot-compiler/ts/tsconfig-aot.json new file mode 100644 index 0000000000..d0a3d00ad4 --- /dev/null +++ b/aio/content/examples/cb-aot-compiler/ts/tsconfig-aot.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "es2015", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": ["es2015", "dom"], + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true + }, + + "files": [ + "src/app/app.module.ts", + "src/main.ts" + ], + + "angularCompilerOptions": { + "genDir": "aot", + "skipMetadataEmit" : true + } +} diff --git a/aio/content/examples/cb-aot-compiler/ts/tsconfig-aot.json.annotated b/aio/content/examples/cb-aot-compiler/ts/tsconfig-aot.json.annotated new file mode 100644 index 0000000000..ada86a323e --- /dev/null +++ b/aio/content/examples/cb-aot-compiler/ts/tsconfig-aot.json.annotated @@ -0,0 +1,25 @@ +// #docregion +{ + "compilerOptions": { + "target": "es5", + "module": "es2015", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": ["es2015", "dom"], + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true + }, + + "files": [ + "src/app/app.module.ts", + "src/main.ts" + ], + + "angularCompilerOptions": { + "genDir": "aot", + "skipMetadataEmit" : true + } +} +// #enddocregion diff --git a/aio/content/examples/cb-component-communication/e2e-spec.ts b/aio/content/examples/cb-component-communication/e2e-spec.ts new file mode 100644 index 0000000000..d78e0784d9 --- /dev/null +++ b/aio/content/examples/cb-component-communication/e2e-spec.ts @@ -0,0 +1,232 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('Component Communication Cookbook Tests', function () { + + // Note: '?e2e' which app can read to know it is running in protractor + // e.g. `if (!/e2e/.test(location.search)) { ...` + beforeAll(function () { + browser.get('?e2e'); + }); + + describe('Parent-to-child communication', function() { + // #docregion parent-to-child + // ... + let _heroNames = ['Mr. IQ', 'Magneta', 'Bombasto']; + let _masterName = 'Master'; + + it('should pass properties to children properly', function () { + let parent = element.all(by.tagName('hero-parent')).get(0); + let heroes = parent.all(by.tagName('hero-child')); + + for (let i = 0; i < _heroNames.length; i++) { + let childTitle = heroes.get(i).element(by.tagName('h3')).getText(); + let childDetail = heroes.get(i).element(by.tagName('p')).getText(); + expect(childTitle).toEqual(_heroNames[i] + ' says:'); + expect(childDetail).toContain(_masterName); + } + }); + // ... + // #enddocregion parent-to-child + }); + + describe('Parent-to-child communication with setter', function() { + // #docregion parent-to-child-setter + // ... + it('should display trimmed, non-empty names', function () { + let _nonEmptyNameIndex = 0; + let _nonEmptyName = '"Mr. IQ"'; + let parent = element.all(by.tagName('name-parent')).get(0); + let hero = parent.all(by.tagName('name-child')).get(_nonEmptyNameIndex); + + let displayName = hero.element(by.tagName('h3')).getText(); + expect(displayName).toEqual(_nonEmptyName); + }); + + it('should replace empty name with default name', function () { + let _emptyNameIndex = 1; + let _defaultName = '""'; + let parent = element.all(by.tagName('name-parent')).get(0); + let hero = parent.all(by.tagName('name-child')).get(_emptyNameIndex); + + let displayName = hero.element(by.tagName('h3')).getText(); + expect(displayName).toEqual(_defaultName); + }); + // ... + // #enddocregion parent-to-child-setter + }); + + describe('Parent-to-child communication with ngOnChanges', function() { + // #docregion parent-to-child-onchanges + // ... + // Test must all execute in this exact order + it('should set expected initial values', function () { + let actual = getActual(); + + let initialLabel = 'Version 1.23'; + let initialLog = 'Initial value of major set to 1, Initial value of minor set to 23'; + + expect(actual.label).toBe(initialLabel); + expect(actual.count).toBe(1); + expect(actual.logs.get(0).getText()).toBe(initialLog); + }); + + it('should set expected values after clicking \'Minor\' twice', function () { + let repoTag = element(by.tagName('version-parent')); + let newMinorButton = repoTag.all(by.tagName('button')).get(0); + + newMinorButton.click().then(function() { + newMinorButton.click().then(function() { + let actual = getActual(); + + let labelAfter2Minor = 'Version 1.25'; + let logAfter2Minor = 'minor changed from 24 to 25'; + + expect(actual.label).toBe(labelAfter2Minor); + expect(actual.count).toBe(3); + expect(actual.logs.get(2).getText()).toBe(logAfter2Minor); + }); + }); + }); + + it('should set expected values after clicking \'Major\' once', function () { + let repoTag = element(by.tagName('version-parent')); + let newMajorButton = repoTag.all(by.tagName('button')).get(1); + + newMajorButton.click().then(function() { + let actual = getActual(); + + let labelAfterMajor = 'Version 2.0'; + let logAfterMajor = 'major changed from 1 to 2, minor changed from 25 to 0'; + + expect(actual.label).toBe(labelAfterMajor); + expect(actual.count).toBe(4); + expect(actual.logs.get(3).getText()).toBe(logAfterMajor); + }); + }); + + function getActual() { + let versionTag = element(by.tagName('version-child')); + let label = versionTag.element(by.tagName('h3')).getText(); + let ul = versionTag.element((by.tagName('ul'))); + let logs = ul.all(by.tagName('li')); + + return { + label: label, + logs: logs, + count: logs.count() + }; + } + // ... + // #enddocregion parent-to-child-onchanges + + }); + + describe('Child-to-parent communication', function() { + // #docregion child-to-parent + // ... + it('should not emit the event initially', function () { + let voteLabel = element(by.tagName('vote-taker')) + .element(by.tagName('h3')).getText(); + expect(voteLabel).toBe('Agree: 0, Disagree: 0'); + }); + + it('should process Agree vote', function () { + let agreeButton1 = element.all(by.tagName('my-voter')).get(0) + .all(by.tagName('button')).get(0); + agreeButton1.click().then(function() { + let voteLabel = element(by.tagName('vote-taker')) + .element(by.tagName('h3')).getText(); + expect(voteLabel).toBe('Agree: 1, Disagree: 0'); + }); + }); + + it('should process Disagree vote', function () { + let agreeButton1 = element.all(by.tagName('my-voter')).get(1) + .all(by.tagName('button')).get(1); + agreeButton1.click().then(function() { + let voteLabel = element(by.tagName('vote-taker')) + .element(by.tagName('h3')).getText(); + expect(voteLabel).toBe('Agree: 1, Disagree: 1'); + }); + }); + // ... + // #enddocregion child-to-parent + }); + + // Can't run timer tests in protractor because + // interaction w/ zones causes all tests to freeze & timeout. + xdescribe('Parent calls child via local var', function() { + countDownTimerTests('countdown-parent-lv'); + }); + + xdescribe('Parent calls ViewChild', function() { + countDownTimerTests('countdown-parent-vc'); + }); + + function countDownTimerTests(parentTag: string) { + // #docregion countdown-timer-tests + // ... + it('timer and parent seconds should match', function () { + let parent = element(by.tagName(parentTag)); + let message = parent.element(by.tagName('countdown-timer')).getText(); + browser.sleep(10); // give `seconds` a chance to catchup with `message` + let seconds = parent.element(by.className('seconds')).getText(); + expect(message).toContain(seconds); + }); + + it('should stop the countdown', function () { + let parent = element(by.tagName(parentTag)); + let stopButton = parent.all(by.tagName('button')).get(1); + + stopButton.click().then(function() { + let message = parent.element(by.tagName('countdown-timer')).getText(); + expect(message).toContain('Holding'); + }); + }); + // ... + // #enddocregion countdown-timer-tests + } + + + describe('Parent and children communicate via a service', function() { + // #docregion bidirectional-service + // ... + it('should announce a mission', function () { + let missionControl = element(by.tagName('mission-control')); + let announceButton = missionControl.all(by.tagName('button')).get(0); + announceButton.click().then(function () { + let history = missionControl.all(by.tagName('li')); + expect(history.count()).toBe(1); + expect(history.get(0).getText()).toMatch(/Mission.* announced/); + }); + }); + + it('should confirm the mission by Lovell', function () { + testConfirmMission(1, 2, 'Lovell'); + }); + + it('should confirm the mission by Haise', function () { + testConfirmMission(3, 3, 'Haise'); + }); + + it('should confirm the mission by Swigert', function () { + testConfirmMission(2, 4, 'Swigert'); + }); + + function testConfirmMission(buttonIndex: number, expectedLogCount: number, astronaut: string) { + let _confirmedLog = ' confirmed the mission'; + let missionControl = element(by.tagName('mission-control')); + let confirmButton = missionControl.all(by.tagName('button')).get(buttonIndex); + confirmButton.click().then(function () { + let history = missionControl.all(by.tagName('li')); + expect(history.count()).toBe(expectedLogCount); + expect(history.get(expectedLogCount - 1).getText()).toBe(astronaut + _confirmedLog); + }); + } + // ... + // #enddocregion bidirectional-service + }); + +}); diff --git a/aio/content/examples/cb-component-communication/ts/.gitignore b/aio/content/examples/cb-component-communication/ts/.gitignore new file mode 100644 index 0000000000..2cb7d2a2e9 --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/.gitignore @@ -0,0 +1 @@ +**/*.js diff --git a/aio/content/examples/cb-component-communication/ts/example-config.json b/aio/content/examples/cb-component-communication/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/cb-component-communication/ts/plnkr.json b/aio/content/examples/cb-component-communication/ts/plnkr.json new file mode 100644 index 0000000000..03bd55cd1a --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Component Communication Cookbook samples", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js" + ], + "tags":["cookbook", "component"] +} diff --git a/aio/content/examples/cb-component-communication/ts/plnkr.no-link.html b/aio/content/examples/cb-component-communication/ts/plnkr.no-link.html new file mode 100644 index 0000000000..82eab6a1f3 --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/plnkr.no-link.html @@ -0,0 +1,757 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/cb-component-communication/ts/src/app/app.component.html b/aio/content/examples/cb-component-communication/ts/src/app/app.component.html new file mode 100644 index 0000000000..ef94020efb --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/src/app/app.component.html @@ -0,0 +1,51 @@ +

    Component Communication Cookbook

    + +Pass data from parent to child with input binding ("Heroes")
    +Intercept input property changes with a setter ("Master")
    +Intercept input property changes with ngOnChanges ("Source code version")
    +Parent listens for child event ("Colonize Universe")
    +Parent to child via local variable("Countdown to Liftoff")
    +Parent calls ViewChild("Countdown to Liftoff")
    +Parent and children communicate via a service ("Mission Control")
    + +
    + +
    +Back to Top + +
    +
    + +
    +Back to Top + +
    +
    + +
    +Back to Top + +
    +
    + +
    +Back to Top +
    + +
    + +
    +Back to Top +
    + +
    + +
    +Back to Top +
    + +
    + +
    +Back to Top +
    diff --git a/aio/content/examples/cb-component-communication/ts/src/app/app.component.ts b/aio/content/examples/cb-component-communication/ts/src/app/app.component.ts new file mode 100644 index 0000000000..ab2d3d9c33 --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/src/app/app.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: './app.component.html' +}) +export class AppComponent { } diff --git a/aio/content/examples/cb-component-communication/ts/src/app/app.module.ts b/aio/content/examples/cb-component-communication/ts/src/app/app.module.ts new file mode 100644 index 0000000000..a008fc5486 --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/src/app/app.module.ts @@ -0,0 +1,54 @@ +import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; +import { AstronautComponent } from './astronaut.component'; +import { CountdownLocalVarParentComponent, CountdownViewChildParentComponent } from './countdown-parent.component'; +import { CountdownTimerComponent } from './countdown-timer.component'; +import { HeroChildComponent } from './hero-child.component'; +import { HeroParentComponent } from './hero-parent.component'; +import { MissionControlComponent } from './missioncontrol.component'; +import { NameChildComponent } from './name-child.component'; +import { NameParentComponent } from './name-parent.component'; +import { VersionChildComponent } from './version-child.component'; +import { VersionParentComponent } from './version-parent.component'; +import { VoterComponent } from './voter.component'; +import { VoteTakerComponent } from './votetaker.component'; + +let directives: any[] = [ + AppComponent, + AstronautComponent, + CountdownTimerComponent, + HeroChildComponent, + HeroParentComponent, + MissionControlComponent, + NameChildComponent, + NameParentComponent, + VersionChildComponent, + VersionParentComponent, + VoterComponent, + VoteTakerComponent + ]; + +let schemas: any[] = []; + +// Include Countdown examples +// unless in e2e tests which they break. +if (!/e2e/.test(location.search)) { + console.log('adding countdown timer examples'); + directives.push(CountdownLocalVarParentComponent); + directives.push(CountdownViewChildParentComponent); +} else { + // In e2e test use CUSTOM_ELEMENTS_SCHEMA to supress unknown element errors + schemas.push(CUSTOM_ELEMENTS_SCHEMA); +} + +@NgModule({ + imports: [ + BrowserModule + ], + declarations: directives, + bootstrap: [ AppComponent ], + schemas: schemas +}) +export class AppModule { } diff --git a/aio/content/examples/cb-component-communication/ts/src/app/astronaut.component.ts b/aio/content/examples/cb-component-communication/ts/src/app/astronaut.component.ts new file mode 100644 index 0000000000..bc24964f86 --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/src/app/astronaut.component.ts @@ -0,0 +1,46 @@ +// #docregion +import { Component, Input, OnDestroy } from '@angular/core'; + +import { MissionService } from './mission.service'; +import { Subscription } from 'rxjs/Subscription'; + +@Component({ + selector: 'my-astronaut', + template: ` +

    + {{astronaut}}: {{mission}} + +

    + ` +}) +export class AstronautComponent implements OnDestroy { + @Input() astronaut: string; + mission = ''; + confirmed = false; + announced = false; + subscription: Subscription; + + constructor(private missionService: MissionService) { + this.subscription = missionService.missionAnnounced$.subscribe( + mission => { + this.mission = mission; + this.announced = true; + this.confirmed = false; + }); + } + + confirm() { + this.confirmed = true; + this.missionService.confirmMission(this.astronaut); + } + + ngOnDestroy() { + // prevent memory leak when component destroyed + this.subscription.unsubscribe(); + } +} +// #enddocregion diff --git a/aio/content/examples/cb-component-communication/ts/src/app/countdown-parent.component.ts b/aio/content/examples/cb-component-communication/ts/src/app/countdown-parent.component.ts new file mode 100644 index 0000000000..5bcf0645c9 --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/src/app/countdown-parent.component.ts @@ -0,0 +1,57 @@ +// #docplaster +// #docregion vc +import { AfterViewInit, ViewChild } from '@angular/core'; +// #docregion lv +import { Component } from '@angular/core'; +import { CountdownTimerComponent } from './countdown-timer.component'; + +// #enddocregion lv +// #enddocregion vc + +//// Local variable, #timer, version +// #docregion lv +@Component({ + selector: 'countdown-parent-lv', + template: ` +

    Countdown to Liftoff (via local variable)

    + + +
    {{timer.seconds}}
    + + `, + styleUrls: ['demo.css'] +}) +export class CountdownLocalVarParentComponent { } +// #enddocregion lv + +//// View Child version +// #docregion vc +@Component({ + selector: 'countdown-parent-vc', + template: ` +

    Countdown to Liftoff (via ViewChild)

    + + +
    {{ seconds() }}
    + + `, + styleUrls: ['demo.css'] +}) +export class CountdownViewChildParentComponent implements AfterViewInit { + + @ViewChild(CountdownTimerComponent) + private timerComponent: CountdownTimerComponent; + + seconds() { return 0; } + + ngAfterViewInit() { + // Redefine `seconds()` to get from the `CountdownTimerComponent.seconds` ... + // but wait a tick first to avoid one-time devMode + // unidirectional-data-flow-violation error + setTimeout(() => this.seconds = () => this.timerComponent.seconds, 0); + } + + start() { this.timerComponent.start(); } + stop() { this.timerComponent.stop(); } +} +// #enddocregion vc diff --git a/aio/content/examples/cb-component-communication/ts/src/app/countdown-timer.component.ts b/aio/content/examples/cb-component-communication/ts/src/app/countdown-timer.component.ts new file mode 100644 index 0000000000..a9a1a2fa7c --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/src/app/countdown-timer.component.ts @@ -0,0 +1,37 @@ +// #docregion +import { Component, OnDestroy, OnInit } from '@angular/core'; + +@Component({ + selector: 'countdown-timer', + template: '

    {{message}}

    ' +}) +export class CountdownTimerComponent implements OnInit, OnDestroy { + + intervalId = 0; + message = ''; + seconds = 11; + + clearTimer() { clearInterval(this.intervalId); } + + ngOnInit() { this.start(); } + ngOnDestroy() { this.clearTimer(); } + + start() { this.countDown(); } + stop() { + this.clearTimer(); + this.message = `Holding at T-${this.seconds} seconds`; + } + + private countDown() { + this.clearTimer(); + this.intervalId = window.setInterval(() => { + this.seconds -= 1; + if (this.seconds === 0) { + this.message = 'Blast off!'; + } else { + if (this.seconds < 0) { this.seconds = 10; } // reset + this.message = `T-${this.seconds} seconds and counting`; + } + }, 1000); + } +} diff --git a/aio/content/examples/cb-component-communication/ts/src/app/hero-child.component.ts b/aio/content/examples/cb-component-communication/ts/src/app/hero-child.component.ts new file mode 100644 index 0000000000..7447542a74 --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/src/app/hero-child.component.ts @@ -0,0 +1,17 @@ +// #docregion +import { Component, Input } from '@angular/core'; + +import { Hero } from './hero'; + +@Component({ + selector: 'hero-child', + template: ` +

    {{hero.name}} says:

    +

    I, {{hero.name}}, am at your service, {{masterName}}.

    + ` +}) +export class HeroChildComponent { + @Input() hero: Hero; + @Input('master') masterName: string; +} +// #enddocregion diff --git a/aio/content/examples/cb-component-communication/ts/src/app/hero-parent.component.ts b/aio/content/examples/cb-component-communication/ts/src/app/hero-parent.component.ts new file mode 100644 index 0000000000..bf3861c455 --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/src/app/hero-parent.component.ts @@ -0,0 +1,20 @@ +// #docregion +import { Component } from '@angular/core'; + +import { HEROES } from './hero'; + +@Component({ + selector: 'hero-parent', + template: ` +

    {{master}} controls {{heroes.length}} heroes

    + + + ` +}) +export class HeroParentComponent { + heroes = HEROES; + master: string = 'Master'; +} +// #enddocregion diff --git a/aio/content/examples/cb-component-communication/ts/src/app/hero.ts b/aio/content/examples/cb-component-communication/ts/src/app/hero.ts new file mode 100644 index 0000000000..a7b70f48e8 --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/src/app/hero.ts @@ -0,0 +1,9 @@ +export class Hero { + name: string; +} + +export const HEROES = [ + {name: 'Mr. IQ'}, + {name: 'Magneta'}, + {name: 'Bombasto'} +]; diff --git a/aio/content/examples/cb-component-communication/ts/src/app/mission.service.ts b/aio/content/examples/cb-component-communication/ts/src/app/mission.service.ts new file mode 100644 index 0000000000..25c86866b0 --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/src/app/mission.service.ts @@ -0,0 +1,25 @@ +// #docregion +import { Injectable } from '@angular/core'; +import { Subject } from 'rxjs/Subject'; + +@Injectable() +export class MissionService { + + // Observable string sources + private missionAnnouncedSource = new Subject(); + private missionConfirmedSource = new Subject(); + + // Observable string streams + missionAnnounced$ = this.missionAnnouncedSource.asObservable(); + missionConfirmed$ = this.missionConfirmedSource.asObservable(); + + // Service message commands + announceMission(mission: string) { + this.missionAnnouncedSource.next(mission); + } + + confirmMission(astronaut: string) { + this.missionConfirmedSource.next(astronaut); + } +} +// #enddocregion diff --git a/aio/content/examples/cb-component-communication/ts/src/app/missioncontrol.component.ts b/aio/content/examples/cb-component-communication/ts/src/app/missioncontrol.component.ts new file mode 100644 index 0000000000..a27e9b16b1 --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/src/app/missioncontrol.component.ts @@ -0,0 +1,43 @@ +// #docregion +import { Component } from '@angular/core'; + +import { MissionService } from './mission.service'; + +@Component({ + selector: 'mission-control', + template: ` +

    Mission Control

    + + + +

    History

    +
      +
    • {{event}}
    • +
    + `, + providers: [MissionService] +}) +export class MissionControlComponent { + astronauts = ['Lovell', 'Swigert', 'Haise']; + history: string[] = []; + missions = ['Fly to the moon!', + 'Fly to mars!', + 'Fly to Vegas!']; + nextMission = 0; + + constructor(private missionService: MissionService) { + missionService.missionConfirmed$.subscribe( + astronaut => { + this.history.push(`${astronaut} confirmed the mission`); + }); + } + + announce() { + let mission = this.missions[this.nextMission++]; + this.missionService.announceMission(mission); + this.history.push(`Mission "${mission}" announced`); + if (this.nextMission >= this.missions.length) { this.nextMission = 0; } + } +} +// #enddocregion diff --git a/aio/content/examples/cb-component-communication/ts/src/app/name-child.component.ts b/aio/content/examples/cb-component-communication/ts/src/app/name-child.component.ts new file mode 100644 index 0000000000..bc6d3c6f59 --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/src/app/name-child.component.ts @@ -0,0 +1,18 @@ +// #docregion +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'name-child', + template: '

    "{{name}}"

    ' +}) +export class NameChildComponent { + private _name = ''; + + @Input() + set name(name: string) { + this._name = (name && name.trim()) || ''; + } + + get name(): string { return this._name; } +} +// #enddocregion diff --git a/aio/content/examples/cb-component-communication/ts/src/app/name-parent.component.ts b/aio/content/examples/cb-component-communication/ts/src/app/name-parent.component.ts new file mode 100644 index 0000000000..99753303aa --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/src/app/name-parent.component.ts @@ -0,0 +1,15 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'name-parent', + template: ` +

    Master controls {{names.length}} names

    + + ` +}) +export class NameParentComponent { + // Displays 'Mr. IQ', '', 'Bombasto' + names = ['Mr. IQ', ' ', ' Bombasto ']; +} +// #enddocregion diff --git a/aio/content/examples/cb-component-communication/ts/src/app/version-child.component.ts b/aio/content/examples/cb-component-communication/ts/src/app/version-child.component.ts new file mode 100644 index 0000000000..89d365cf9f --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/src/app/version-child.component.ts @@ -0,0 +1,35 @@ +/* tslint:disable:forin */ +// #docregion +import { Component, Input, OnChanges, SimpleChange } from '@angular/core'; + +@Component({ + selector: 'version-child', + template: ` +

    Version {{major}}.{{minor}}

    +

    Change log:

    +
      +
    • {{change}}
    • +
    + ` +}) +export class VersionChildComponent implements OnChanges { + @Input() major: number; + @Input() minor: number; + changeLog: string[] = []; + + ngOnChanges(changes: {[propKey: string]: SimpleChange}) { + let log: string[] = []; + for (let propName in changes) { + let changedProp = changes[propName]; + let to = JSON.stringify(changedProp.currentValue); + if (changedProp.isFirstChange()) { + log.push(`Initial value of ${propName} set to ${to}`); + } else { + let from = JSON.stringify(changedProp.previousValue); + log.push(`${propName} changed from ${from} to ${to}`); + } + } + this.changeLog.push(log.join(', ')); + } +} +// #enddocregion diff --git a/aio/content/examples/cb-component-communication/ts/src/app/version-parent.component.ts b/aio/content/examples/cb-component-communication/ts/src/app/version-parent.component.ts new file mode 100644 index 0000000000..bbc9101702 --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/src/app/version-parent.component.ts @@ -0,0 +1,26 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'version-parent', + template: ` +

    Source code version

    + + + + ` +}) +export class VersionParentComponent { + major: number = 1; + minor: number = 23; + + newMinor() { + this.minor++; + } + + newMajor() { + this.major++; + this.minor = 0; + } +} +// #enddocregion diff --git a/aio/content/examples/cb-component-communication/ts/src/app/voter.component.ts b/aio/content/examples/cb-component-communication/ts/src/app/voter.component.ts new file mode 100644 index 0000000000..c0cb23abc0 --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/src/app/voter.component.ts @@ -0,0 +1,22 @@ +// #docregion +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'my-voter', + template: ` +

    {{name}}

    + + + ` +}) +export class VoterComponent { + @Input() name: string; + @Output() onVoted = new EventEmitter(); + voted = false; + + vote(agreed: boolean) { + this.onVoted.emit(agreed); + this.voted = true; + } +} +// #enddocregion diff --git a/aio/content/examples/cb-component-communication/ts/src/app/votetaker.component.ts b/aio/content/examples/cb-component-communication/ts/src/app/votetaker.component.ts new file mode 100644 index 0000000000..87f06161f5 --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/src/app/votetaker.component.ts @@ -0,0 +1,24 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'vote-taker', + template: ` +

    Should mankind colonize the Universe?

    +

    Agree: {{agreed}}, Disagree: {{disagreed}}

    + + + ` +}) +export class VoteTakerComponent { + agreed = 0; + disagreed = 0; + voters = ['Mr. IQ', 'Ms. Universe', 'Bombasto']; + + onVoted(agreed: boolean) { + agreed ? this.agreed++ : this.disagreed++; + } +} +// #enddocregion diff --git a/aio/content/examples/cb-component-communication/ts/src/demo.css b/aio/content/examples/cb-component-communication/ts/src/demo.css new file mode 100644 index 0000000000..b63a8b38dd --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/src/demo.css @@ -0,0 +1,9 @@ +/* Component Communication cookbook specific styles */ +.seconds { + background-color: black; + color: red; + font-size: 3em; + margin: 0.3em 0; + text-align: center; + width: 1.5em; +} diff --git a/aio/content/examples/cb-component-communication/ts/src/index.html b/aio/content/examples/cb-component-communication/ts/src/index.html new file mode 100644 index 0000000000..64c8a3430f --- /dev/null +++ b/aio/content/examples/cb-component-communication/ts/src/index.html @@ -0,0 +1,29 @@ + + + + + Passing information from parent to child + + + + + + + + + + + + + + + + + loading... + + + diff --git a/aio/content/examples/cb-component-relative-paths/ts/app/main.ts b/aio/content/examples/cb-component-communication/ts/src/main.ts similarity index 74% rename from aio/content/examples/cb-component-relative-paths/ts/app/main.ts rename to aio/content/examples/cb-component-communication/ts/src/main.ts index 6af7a5b2ae..311c44b76d 100644 --- a/aio/content/examples/cb-component-relative-paths/ts/app/main.ts +++ b/aio/content/examples/cb-component-communication/ts/src/main.ts @@ -1,5 +1,5 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { AppModule } from './app.module'; +import { AppModule } from './app/app.module'; platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/cb-component-relative-paths/ts/plnkr.json b/aio/content/examples/cb-component-relative-paths/ts/plnkr.json index a4c33bbde3..21002e1d2f 100644 --- a/aio/content/examples/cb-component-relative-paths/ts/plnkr.json +++ b/aio/content/examples/cb-component-relative-paths/ts/plnkr.json @@ -1,5 +1,6 @@ { "description": "Module-relative Paths", + "basePath": "src/", "files": [ "!**/*.d.ts", "!**/*.js" diff --git a/aio/content/examples/cb-component-relative-paths/ts/plnkr.no-link.html b/aio/content/examples/cb-component-relative-paths/ts/plnkr.no-link.html new file mode 100644 index 0000000000..bd65444405 --- /dev/null +++ b/aio/content/examples/cb-component-relative-paths/ts/plnkr.no-link.html @@ -0,0 +1,277 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/cb-component-relative-paths/ts/app/app.component.ts b/aio/content/examples/cb-component-relative-paths/ts/src/app/app.component.ts similarity index 100% rename from aio/content/examples/cb-component-relative-paths/ts/app/app.component.ts rename to aio/content/examples/cb-component-relative-paths/ts/src/app/app.component.ts diff --git a/aio/content/examples/cb-component-relative-paths/ts/app/app.module.ts b/aio/content/examples/cb-component-relative-paths/ts/src/app/app.module.ts similarity index 100% rename from aio/content/examples/cb-component-relative-paths/ts/app/app.module.ts rename to aio/content/examples/cb-component-relative-paths/ts/src/app/app.module.ts diff --git a/aio/content/examples/cb-component-relative-paths/ts/app/some.component.css b/aio/content/examples/cb-component-relative-paths/ts/src/app/some.component.css similarity index 100% rename from aio/content/examples/cb-component-relative-paths/ts/app/some.component.css rename to aio/content/examples/cb-component-relative-paths/ts/src/app/some.component.css diff --git a/aio/content/examples/cb-component-relative-paths/ts/app/some.component.html b/aio/content/examples/cb-component-relative-paths/ts/src/app/some.component.html similarity index 100% rename from aio/content/examples/cb-component-relative-paths/ts/app/some.component.html rename to aio/content/examples/cb-component-relative-paths/ts/src/app/some.component.html diff --git a/aio/content/examples/cb-component-relative-paths/ts/app/some.component.ts b/aio/content/examples/cb-component-relative-paths/ts/src/app/some.component.ts similarity index 100% rename from aio/content/examples/cb-component-relative-paths/ts/app/some.component.ts rename to aio/content/examples/cb-component-relative-paths/ts/src/app/some.component.ts diff --git a/aio/content/examples/cb-component-relative-paths/ts/index.html b/aio/content/examples/cb-component-relative-paths/ts/src/index.html similarity index 85% rename from aio/content/examples/cb-component-relative-paths/ts/index.html rename to aio/content/examples/cb-component-relative-paths/ts/src/index.html index 9144b9cf26..e951f9f016 100644 --- a/aio/content/examples/cb-component-relative-paths/ts/index.html +++ b/aio/content/examples/cb-component-relative-paths/ts/src/index.html @@ -13,7 +13,7 @@ - + @@ -21,7 +21,7 @@ diff --git a/aio/content/examples/cb-component-relative-paths/ts/src/main.ts b/aio/content/examples/cb-component-relative-paths/ts/src/main.ts new file mode 100644 index 0000000000..311c44b76d --- /dev/null +++ b/aio/content/examples/cb-component-relative-paths/ts/src/main.ts @@ -0,0 +1,5 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/cb-dependency-injection/e2e-spec.ts b/aio/content/examples/cb-dependency-injection/e2e-spec.ts new file mode 100644 index 0000000000..8c9d163d5e --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/e2e-spec.ts @@ -0,0 +1,102 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('Dependency Injection Cookbook', function () { + + beforeAll(function () { + browser.get(''); + }); + + it('should render Logged in User example', function () { + let loggedInUser = element.all(by.xpath('//h3[text()="Logged in user"]')).get(0); + expect(loggedInUser).toBeDefined(); + }); + + it('"Bombasto" should be the logged in user', function () { + let loggedInUser = element.all(by.xpath('//div[text()="Name: Bombasto"]')).get(0); + expect(loggedInUser).toBeDefined(); + }); + + it('should render sorted heroes', function () { + let sortedHeroes = element.all(by.xpath('//h3[text()="Sorted Heroes" and position()=1]')).get(0); + expect(sortedHeroes).toBeDefined(); + }); + + it('Mr. Nice should be in sorted heroes', function () { + let sortedHero = element.all(by.xpath('//sorted-heroes/[text()="Mr. Nice" and position()=2]')).get(0); + expect(sortedHero).toBeDefined(); + }); + + it('RubberMan should be in sorted heroes', function () { + let sortedHero = element.all(by.xpath('//sorted-heroes/[text()="RubberMan" and position()=3]')).get(0); + expect(sortedHero).toBeDefined(); + }); + + it('Magma should be in sorted heroes', function () { + let sortedHero = element.all(by.xpath('//sorted-heroes/[text()="Magma"]')).get(0); + expect(sortedHero).toBeDefined(); + }); + + it('should render Hero of the Month', function () { + let heroOfTheMonth = element.all(by.xpath('//h3[text()="Hero of the month"]')).get(0); + expect(heroOfTheMonth).toBeDefined(); + }); + + it('should render Hero Bios', function () { + let heroBios = element.all(by.xpath('//h3[text()="Hero Bios"]')).get(0); + expect(heroBios).toBeDefined(); + }); + + it('should render Magma\'s description in Hero Bios', function () { + let magmaText = element.all(by.xpath('//textarea[text()="Hero of all trades"]')).get(0); + expect(magmaText).toBeDefined(); + }); + + it('should render Magma\'s phone in Hero Bios and Contacts', function () { + let magmaPhone = element.all(by.xpath('//div[text()="Phone #: 555-555-5555"]')).get(0); + expect(magmaPhone).toBeDefined(); + }); + + it('should render Hero-of-the-Month runner-ups', function () { + let runnersUp = element(by.id('rups1')).getText(); + expect(runnersUp).toContain('RubberMan, Mr. Nice'); + }); + + it('should render DateLogger log entry in Hero-of-the-Month', function () { + let logs = element.all(by.id('logs')).get(0).getText(); + expect(logs).toContain('INFO: starting up at'); + }); + + it('should highlight Hero Bios and Contacts container when mouseover', function () { + let target = element(by.css('div[myHighlight="yellow"]')); + let yellow = 'rgba(255, 255, 0, 1)'; + + expect(target.getCssValue('background-color')).not.toEqual(yellow); + browser.actions().mouseMove(target.getWebElement()).perform(); + expect(target.getCssValue('background-color')).toEqual(yellow); + }); + + describe('in Parent Finder', function () { + let cathy1 = element(by.css('alex cathy')); + let craig1 = element(by.css('alex craig')); + let carol1 = element(by.css('alex carol p')); + let carol2 = element(by.css('barry carol p')); + + it('"Cathy" should find "Alex" via the component class', function () { + expect(cathy1.getText()).toContain('Found Alex via the component'); + }); + + it('"Craig" should not find "Alex" via the base class', function () { + expect(craig1.getText()).toContain('Did not find Alex via the base'); + }); + + it('"Carol" within "Alex" should have "Alex" parent', function () { + expect(carol1.getText()).toContain('Alex'); + }); + + it('"Carol" within "Barry" should have "Barry" parent', function () { + expect(carol2.getText()).toContain('Barry'); + }); + }); +}); diff --git a/aio/content/examples/cb-dependency-injection/ts/example-config.json b/aio/content/examples/cb-dependency-injection/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/cb-dependency-injection/ts/plnkr.json b/aio/content/examples/cb-dependency-injection/ts/plnkr.json new file mode 100644 index 0000000000..ff0aedca01 --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/plnkr.json @@ -0,0 +1,10 @@ +{ + "description": "Dependency Injection", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1].*" + ], + "tags":["cookbook"] +} diff --git a/aio/content/examples/cb-dependency-injection/ts/plnkr.no-link.html b/aio/content/examples/cb-dependency-injection/ts/plnkr.no-link.html new file mode 100644 index 0000000000..cf460b3b95 --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/plnkr.no-link.html @@ -0,0 +1,989 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/cb-dependency-injection/ts/src/app/app-routing.module.ts b/aio/content/examples/cb-dependency-injection/ts/src/app/app-routing.module.ts new file mode 100644 index 0000000000..09a0592d00 --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/app/app-routing.module.ts @@ -0,0 +1,11 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +const routes: Routes = []; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + providers: [], + exports: [RouterModule] +}) +export class AppRoutingModule {} diff --git a/aio/content/examples/cb-dependency-injection/ts/src/app/app.component.html b/aio/content/examples/cb-dependency-injection/ts/src/app/app.component.html new file mode 100644 index 0000000000..a715e484fe --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/app/app.component.html @@ -0,0 +1,38 @@ +

    DI Cookbook

    +
    +

    Logged in user

    +
    Name: {{userContext.name}}
    +
    Role: {{userContext.role}}
    +
    + +
    +

    Hero Bios

    + +
    + + +
    +

    Hero Bios and Contacts

    +
    + +
    +
    + + +
    + +
    + +
    +

    Unsorted Heroes

    + +
    + +
    +

    Sorted Heroes

    + +
    + +
    + +
    diff --git a/aio/content/examples/cb-dependency-injection/ts/src/app/app.component.ts b/aio/content/examples/cb-dependency-injection/ts/src/app/app.component.ts new file mode 100644 index 0000000000..a3b938f7e5 --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/app/app.component.ts @@ -0,0 +1,30 @@ +// #docregion +import { Component } from '@angular/core'; + +// #docregion import-services +import { LoggerService } from './logger.service'; +import { UserContextService } from './user-context.service'; +import { UserService } from './user.service'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: './app.component.html', +// #docregion providers + providers: [ LoggerService, UserContextService, UserService ] +// #enddocregion providers +}) +export class AppComponent { +// #enddocregion import-services + + private userId: number = 1; + + // #docregion ctor + constructor(logger: LoggerService, public userContext: UserContextService) { + userContext.loadUser(this.userId); + logger.logInfo('AppComponent initialized'); + } + // #enddocregion ctor +// #docregion import-services +} +// #enddocregion import-services diff --git a/aio/content/examples/cb-dependency-injection/ts/src/app/app.module.ts b/aio/content/examples/cb-dependency-injection/ts/src/app/app.module.ts new file mode 100644 index 0000000000..a240e21f7c --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/app/app.module.ts @@ -0,0 +1,74 @@ +// #docregion +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; +import { HttpModule } from '@angular/http'; + +// import { AppRoutingModule } from './app-routing.module'; +import { LocationStrategy, + HashLocationStrategy } from '@angular/common'; +import { NgModule } from '@angular/core'; + +import { HeroData } from './hero-data'; +import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; + + +import { AppComponent } from './app.component'; +import { HeroBioComponent } from './hero-bio.component'; +import { HeroBiosComponent, + HeroBiosAndContactsComponent } from './hero-bios.component'; +import { HeroOfTheMonthComponent } from './hero-of-the-month.component'; +import { HeroContactComponent } from './hero-contact.component'; +import { HeroesBaseComponent, + SortedHeroesComponent } from './sorted-heroes.component'; +import { HighlightDirective } from './highlight.directive'; +import { ParentFinderComponent, + AlexComponent, + AliceComponent, + CarolComponent, + ChrisComponent, + CraigComponent, + CathyComponent, + BarryComponent, + BethComponent, + BobComponent } from './parent-finder.component'; + +const declarations = [ + AppComponent, + HeroBiosComponent, HeroBiosAndContactsComponent, HeroBioComponent, + HeroesBaseComponent, SortedHeroesComponent, + HeroOfTheMonthComponent, HeroContactComponent, + HighlightDirective, + ParentFinderComponent, +]; + +const a_components = [AliceComponent, AlexComponent ]; + +const b_components = [ BarryComponent, BethComponent, BobComponent ]; + +const c_components = [ + CarolComponent, ChrisComponent, CraigComponent, + CathyComponent +]; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule, + HttpModule, + InMemoryWebApiModule.forRoot(HeroData) + // AppRoutingModule TODO: add routes + ], + declarations: [ + declarations, + a_components, + b_components, + c_components, + ], + bootstrap: [ AppComponent ], + // #docregion providers + providers: [ + { provide: LocationStrategy, useClass: HashLocationStrategy } + ] + // #enddocregion providers +}) +export class AppModule { } diff --git a/aio/content/examples/cb-dependency-injection/ts/src/app/date-logger.service.ts b/aio/content/examples/cb-dependency-injection/ts/src/app/date-logger.service.ts new file mode 100644 index 0000000000..e5b597db02 --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/app/date-logger.service.ts @@ -0,0 +1,38 @@ +/* tslint:disable:one-line:check-open-brace*/ +// #docregion +import { Injectable } from '@angular/core'; + +import { LoggerService } from './logger.service'; + +// #docregion minimal-logger +// class used as a restricting interface (hides other public members) +export abstract class MinimalLogger { + logInfo: (msg: string) => void; + logs: string[]; +} +// #enddocregion minimal-logger + +/* +// Transpiles to: +// #docregion minimal-logger-transpiled + var MinimalLogger = (function () { + function MinimalLogger() {} + return MinimalLogger; + }()); + exports("MinimalLogger", MinimalLogger); +// #enddocregion minimal-logger-transpiled + */ + +// #docregion date-logger-service +@Injectable() +// #docregion date-logger-service-signature +export class DateLoggerService extends LoggerService implements MinimalLogger +// #enddocregion date-logger-service-signature +{ + logInfo(msg: any) { super.logInfo(stamp(msg)); } + logDebug(msg: any) { super.logInfo(stamp(msg)); } + logError(msg: any) { super.logError(stamp(msg)); } +} + +function stamp(msg: any) { return msg + ' at ' + new Date(); } +// #enddocregion date-logger-service diff --git a/aio/content/examples/cb-dependency-injection/ts/src/app/hero-bio.component.ts b/aio/content/examples/cb-dependency-injection/ts/src/app/hero-bio.component.ts new file mode 100644 index 0000000000..84f8e05e66 --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/app/hero-bio.component.ts @@ -0,0 +1,27 @@ +// #docregion +import { Component, Input, OnInit } from '@angular/core'; + +import { HeroCacheService } from './hero-cache.service'; + +// #docregion component +@Component({ + selector: 'hero-bio', + // #docregion template + template: ` +

    {{hero.name}}

    + + `, + // #enddocregion template + providers: [HeroCacheService] +}) + +export class HeroBioComponent implements OnInit { + @Input() heroId: number; + + constructor(private heroCache: HeroCacheService) { } + + ngOnInit() { this.heroCache.fetchCachedHero(this.heroId); } + + get hero() { return this.heroCache.hero; } +} +// #enddocregion component diff --git a/aio/content/examples/cb-dependency-injection/ts/src/app/hero-bios.component.ts b/aio/content/examples/cb-dependency-injection/ts/src/app/hero-bios.component.ts new file mode 100644 index 0000000000..217c5edcd0 --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/app/hero-bios.component.ts @@ -0,0 +1,48 @@ +// #docplaster +// #docregion +import { Component } from '@angular/core'; + +import { HeroService } from './hero.service'; +import { LoggerService } from './logger.service'; + +//////// HeroBiosComponent //// +// #docregion simple +@Component({ + selector: 'hero-bios', + template: ` + + + `, + providers: [HeroService] +}) +export class HeroBiosComponent { +// #enddocregion simple +// #docregion ctor + constructor(logger: LoggerService) { + logger.logInfo('Creating HeroBiosComponent'); + } +// #enddocregion ctor +// #docregion simple +} +// #enddocregion simple + +//////// HeroBiosAndContactsComponent //// +// #docregion hero-bios-and-contacts +@Component({ + selector: 'hero-bios-and-contacts', + // #docregion template + template: ` + + + `, + // #enddocregion template + // #docregion class-provider + providers: [HeroService] + // #enddocregion class-provider +}) +export class HeroBiosAndContactsComponent { + constructor(logger: LoggerService) { + logger.logInfo('Creating HeroBiosAndContactsComponent'); + } +} +// #enddocregion hero-bios-and-contacts diff --git a/aio/content/examples/cb-dependency-injection/ts/src/app/hero-cache.service.ts b/aio/content/examples/cb-dependency-injection/ts/src/app/hero-cache.service.ts new file mode 100644 index 0000000000..6dbc7a0c4f --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/app/hero-cache.service.ts @@ -0,0 +1,20 @@ +// #docregion +import { Injectable } from '@angular/core'; + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; + +// #docregion service +@Injectable() +export class HeroCacheService { + hero: Hero; + constructor(private heroService: HeroService) {} + + fetchCachedHero(id: number) { + if (!this.hero) { + this.hero = this.heroService.getHeroById(id); + } + return this.hero; + } +} +// #enddocregion service diff --git a/aio/content/examples/cb-dependency-injection/ts/src/app/hero-contact.component.ts b/aio/content/examples/cb-dependency-injection/ts/src/app/hero-contact.component.ts new file mode 100644 index 0000000000..add6df91c5 --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/app/hero-contact.component.ts @@ -0,0 +1,40 @@ +// #docplaster +// #docregion +import { Component, Host, Optional } from '@angular/core'; + +import { HeroCacheService } from './hero-cache.service'; +import { LoggerService } from './logger.service'; + +// #docregion component +@Component({ + selector: 'hero-contact', + template: ` +
    Phone #: {{phoneNumber}} + !!!
    ` +}) +export class HeroContactComponent { + + hasLogger = false; + + constructor( + // #docregion ctor-params + @Host() // limit to the host component's instance of the HeroCacheService + private heroCache: HeroCacheService, + + @Host() // limit search for logger; hides the application-wide logger + @Optional() // ok if the logger doesn't exist + private loggerService: LoggerService + // #enddocregion ctor-params + ) { + if (loggerService) { + this.hasLogger = true; + loggerService.logInfo('HeroContactComponent can log!'); + } + // #docregion ctor + } + // #enddocregion ctor + + get phoneNumber() { return this.heroCache.hero.phone; } + +} +// #enddocregion component diff --git a/aio/content/examples/cb-dependency-injection/ts/src/app/hero-data.ts b/aio/content/examples/cb-dependency-injection/ts/src/app/hero-data.ts new file mode 100644 index 0000000000..10cdbcaab1 --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/app/hero-data.ts @@ -0,0 +1,14 @@ +// #docregion +import { Hero } from './hero'; + +export class HeroData { + createDb() { + let heroes = [ + new Hero(1, 'Windstorm'), + new Hero(2, 'Bombasto'), + new Hero(3, 'Magneta'), + new Hero(4, 'Tornado') + ]; + return {heroes}; + } +} diff --git a/aio/content/examples/cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts b/aio/content/examples/cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts new file mode 100644 index 0000000000..c5bafca36d --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts @@ -0,0 +1,75 @@ +/* tslint:disable:one-line:check-open-brace*/ +// #docplaster +// #docregion opaque-token +import { OpaqueToken } from '@angular/core'; + +export const TITLE = new OpaqueToken('title'); +// #enddocregion opaque-token + +// #docregion hero-of-the-month +import { Component, Inject } from '@angular/core'; + +import { DateLoggerService, + MinimalLogger } from './date-logger.service'; +import { Hero } from './hero'; +import { HeroService } from './hero.service'; +import { LoggerService } from './logger.service'; +import { RUNNERS_UP, + runnersUpFactory } from './runners-up'; + +// #enddocregion hero-of-the-month +// #docregion some-hero +const someHero = new Hero(42, 'Magma', 'Had a great month!', '555-555-5555'); +// #enddocregion some-hero + +const template = ` +

    {{title}}

    +
    Winner: {{heroOfTheMonth.name}}
    +
    Reason for award: {{heroOfTheMonth.description}}
    +
    Runners-up: {{runnersUp}}
    + +

    Logs:

    +
    +
    {{log}}
    +
    + `; + +// #docregion hero-of-the-month +@Component({ + selector: 'hero-of-the-month', + template: template, + providers: [ + // #docregion use-value + { provide: Hero, useValue: someHero }, + // #docregion provide-opaque-token + { provide: TITLE, useValue: 'Hero of the Month' }, + // #enddocregion provide-opaque-token + // #enddocregion use-value + // #docregion use-class + { provide: HeroService, useClass: HeroService }, + { provide: LoggerService, useClass: DateLoggerService }, + // #enddocregion use-class + // #docregion use-existing + { provide: MinimalLogger, useExisting: LoggerService }, + // #enddocregion use-existing + // #docregion provide-opaque-token, use-factory + { provide: RUNNERS_UP, useFactory: runnersUpFactory(2), deps: [Hero, HeroService] } + // #enddocregion provide-opaque-token, use-factory + ] +}) +export class HeroOfTheMonthComponent { + logs: string[] = []; + +// #docregion ctor-signature + constructor( + logger: MinimalLogger, + public heroOfTheMonth: Hero, + @Inject(RUNNERS_UP) public runnersUp: string, + @Inject(TITLE) public title: string) +// #enddocregion ctor-signature + { + this.logs = logger.logs; + logger.logInfo('starting up'); + } +} +// #enddocregion hero-of-the-month diff --git a/aio/content/examples/cb-dependency-injection/ts/src/app/hero.service.ts b/aio/content/examples/cb-dependency-injection/ts/src/app/hero.service.ts new file mode 100644 index 0000000000..2063c30d7a --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/app/hero.service.ts @@ -0,0 +1,22 @@ +// #docregion +import { Injectable } from '@angular/core'; +import { Hero } from './hero'; + +@Injectable() +export class HeroService { + + // TODO move to database + private heroes: Array = [ + new Hero(1, 'RubberMan', 'Hero of many talents', '123-456-7899'), + new Hero(2, 'Magma', 'Hero of all trades', '555-555-5555'), + new Hero(3, 'Mr. Nice', 'The name says it all', '111-222-3333') + ]; + + getHeroById(id: number): Hero { + return this.heroes.find(hero => hero.id === id); + } + + getAllHeroes(): Array { + return this.heroes; + } +} diff --git a/aio/content/examples/cb-dependency-injection/ts/src/app/hero.ts b/aio/content/examples/cb-dependency-injection/ts/src/app/hero.ts new file mode 100644 index 0000000000..c17069e727 --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/app/hero.ts @@ -0,0 +1,9 @@ +// #docregion +export class Hero { + constructor( + public id: number, + public name: string, + public description?: string, + public phone?: string) { + } +} diff --git a/aio/content/examples/cb-dependency-injection/ts/src/app/highlight.directive.ts b/aio/content/examples/cb-dependency-injection/ts/src/app/highlight.directive.ts new file mode 100644 index 0000000000..e220114daa --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/app/highlight.directive.ts @@ -0,0 +1,29 @@ +// #docplaster +// #docregion +import { Directive, ElementRef, HostListener, Input } from '@angular/core'; + +@Directive({ + selector: '[myHighlight]' +}) +export class HighlightDirective { + + @Input('myHighlight') highlightColor: string; + + private el: HTMLElement; + + constructor(el: ElementRef) { + this.el = el.nativeElement; + } + + @HostListener('mouseenter') onMouseEnter() { + this.highlight(this.highlightColor || 'cyan'); + } + + @HostListener('mouseleave') onMouseLeave() { + this.highlight(null); + } + + private highlight(color: string) { + this.el.style.backgroundColor = color; + } +} diff --git a/aio/content/examples/cb-dependency-injection/ts/src/app/logger.service.ts b/aio/content/examples/cb-dependency-injection/ts/src/app/logger.service.ts new file mode 100644 index 0000000000..df8ee6b9c7 --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/app/logger.service.ts @@ -0,0 +1,16 @@ +// #docregion +import { Injectable } from '@angular/core'; + +@Injectable() +export class LoggerService { + logs: string[] = []; + + logInfo(msg: any) { this.log(`INFO: ${msg}`); } + logDebug(msg: any) { this.log(`DEBUG: ${msg}`); } + logError(msg: any) { this.log(`ERROR: ${msg}`, true); } + + private log(msg: any, isErr = false) { + this.logs.push(msg); + isErr ? console.error(msg) : console.log(msg); + } +} diff --git a/aio/content/examples/cb-dependency-injection/ts/src/app/parent-finder.component.ts b/aio/content/examples/cb-dependency-injection/ts/src/app/parent-finder.component.ts new file mode 100644 index 0000000000..c6f208f79b --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/app/parent-finder.component.ts @@ -0,0 +1,215 @@ +/* tslint:disable:no-unused-variable component-selector-name one-line check-open-brace */ +/* tslint:disable:*/ +// #docplaster +// #docregion +import { Component, forwardRef, Optional, SkipSelf } from '@angular/core'; + +// A component base class (see AlexComponent) +export abstract class Base { name = 'Count Basie'; } + +// Marker class, used as an interface +// #docregion parent +export abstract class Parent { name: string; } +// #enddocregion parent + +const DifferentParent = Parent; + +// #docregion provide-parent, provide-the-parent +// Helper method to provide the current component instance in the name of a `parentType`. +// #enddocregion provide-the-parent +// The `parentType` defaults to `Parent` when omitting the second parameter. +// #docregion provide-the-parent +const provideParent = +// #enddocregion provide-parent, provide-the-parent +// #docregion provide-parent + (component: any, parentType?: any) => { + return { provide: parentType || Parent, useExisting: forwardRef(() => component) }; + }; +// #enddocregion provide-parent + +// Simpler syntax version that always provides the component in the name of `Parent`. +const provideTheParent = +// #docregion provide-the-parent + (component: any) => { + return { provide: Parent, useExisting: forwardRef(() => component) }; + }; +// #enddocregion provide-the-parent + + +///////// C - Child ////////// +// #docregion carol +const templateC = ` +
    +

    {{name}}

    +

    My parent is {{parent?.name}}

    +
    `; + +@Component({ + selector: 'carol', + template: templateC +}) +// #docregion carol-class +export class CarolComponent { + name= 'Carol'; + // #docregion carol-ctor + constructor( @Optional() public parent: Parent ) { } + // #enddocregion carol-ctor +} +// #enddocregion carol-class +// #enddocregion carol + +@Component({ + selector: 'chris', + template: templateC +}) +export class ChrisComponent { + name= 'Chris'; + constructor( @Optional() public parent: Parent ) { } +} + +////// Craig /////////// +/** + * Show we cannot inject a parent by its base class. + */ +// #docregion craig +@Component({ + selector: 'craig', + template: ` +
    +

    Craig

    + {{alex ? 'Found' : 'Did not find'}} Alex via the base class. +
    ` +}) +export class CraigComponent { + constructor( @Optional() public alex: Base ) { } +} +// #enddocregion craig + +//////// B - Parent ///////// +// #docregion barry +const templateB = ` +
    +
    +

    {{name}}

    +

    My parent is {{parent?.name}}

    +
    + + +
    `; + +@Component({ + selector: 'barry', + template: templateB, + providers: [{ provide: Parent, useExisting: forwardRef(() => BarryComponent) }] +}) +export class BarryComponent implements Parent { + name = 'Barry'; +// #docregion barry-ctor + constructor( @SkipSelf() @Optional() public parent: Parent ) { } +// #enddocregion barry-ctor +} +// #enddocregion barry + +@Component({ + selector: 'bob', + template: templateB, + providers: [ provideParent(BobComponent) ] +}) +export class BobComponent implements Parent { + name= 'Bob'; + constructor( @SkipSelf() @Optional() public parent: Parent ) { } +} + +@Component({ + selector: 'beth', + template: templateB, +// #docregion beth-providers + providers: [ provideParent(BethComponent, DifferentParent) ] +// #enddocregion beth-providers +}) +export class BethComponent implements Parent { + name= 'Beth'; + constructor( @SkipSelf() @Optional() public parent: Parent ) { } +} + +///////// A - Grandparent ////// + +// #docregion alex, alex-1 +@Component({ + selector: 'alex', + template: ` +
    +

    {{name}}

    + + + +
    `, +// #enddocregion alex-1 +// #docregion alex-providers + providers: [{ provide: Parent, useExisting: forwardRef(() => AlexComponent) }], +// #enddocregion alex-providers +// #docregion alex-1 +}) +// #enddocregion alex-1 +// Todo: Add `... implements Parent` to class signature +// #docregion alex-1 +// #docregion alex-class-signature +export class AlexComponent extends Base +// #enddocregion alex-class-signature +{ + name= 'Alex'; +} +// #enddocregion alex, alex-1 + +///// + +// #docregion alice +@Component({ + selector: 'alice', + template: ` +
    +

    {{name}}

    + + + + +
    `, +// #docregion alice-providers + providers: [ provideParent(AliceComponent) ] +// #enddocregion alice-providers +}) +// #docregion alice-class-signature +export class AliceComponent implements Parent +// #enddocregion alice-class-signature +{ + name= 'Alice'; +} +// #enddocregion alice + +////// Cathy /////////// +/** + * Show we can inject a parent by component type + */ +// #docregion cathy +@Component({ + selector: 'cathy', + template: ` +
    +

    Cathy

    + {{alex ? 'Found' : 'Did not find'}} Alex via the component class.
    +
    ` +}) +export class CathyComponent { + constructor( @Optional() public alex: AlexComponent ) { } +} +// #enddocregion cathy + +///////// ParentFinder ////// +@Component({ + selector: 'parent-finder', + template: ` +

    Parent Finder

    + + ` +}) +export class ParentFinderComponent { } diff --git a/aio/content/examples/cb-dependency-injection/ts/src/app/runners-up.ts b/aio/content/examples/cb-dependency-injection/ts/src/app/runners-up.ts new file mode 100644 index 0000000000..6eb8b195d0 --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/app/runners-up.ts @@ -0,0 +1,26 @@ +// #docplaster +// #docregion +import { OpaqueToken } from '@angular/core'; + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; + +// #docregion runners-up +export const RUNNERS_UP = new OpaqueToken('RunnersUp'); +// #enddocregion runners-up + +// #docregion factory-synopsis +export function runnersUpFactory(take: number) { + return (winner: Hero, heroService: HeroService): string => { + /* ... */ +// #enddocregion factory-synopsis + return heroService + .getAllHeroes() + .filter((hero) => hero.name !== winner.name) + .map(hero => hero.name) + .slice(0, Math.max(0, take)) + .join(', '); +// #docregion factory-synopsis + }; +}; +// #enddocregion factory-synopsis diff --git a/aio/content/examples/cb-dependency-injection/ts/src/app/sorted-heroes.component.ts b/aio/content/examples/cb-dependency-injection/ts/src/app/sorted-heroes.component.ts new file mode 100644 index 0000000000..8cb6e3c69c --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/app/sorted-heroes.component.ts @@ -0,0 +1,52 @@ +// #docplaster +// #docregion +import { Component, OnInit } from '@angular/core'; + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; + +/////// HeroesBaseComponent ///// +// #docregion heroes-base, injection +@Component({ + selector: 'unsorted-heroes', + template: `
    {{hero.name}}
    `, + providers: [HeroService] +}) +export class HeroesBaseComponent implements OnInit { + constructor(private heroService: HeroService) { } +// #enddocregion injection + + heroes: Array; + + ngOnInit() { + this.heroes = this.heroService.getAllHeroes(); + this.afterGetHeroes(); + } + + // Post-process heroes in derived class override. + protected afterGetHeroes() {} + +// #docregion injection +} +// #enddocregion heroes-base,injection + +/////// SortedHeroesComponent ///// +// #docregion sorted-heroes +@Component({ + selector: 'sorted-heroes', + template: `
    {{hero.name}}
    `, + providers: [HeroService] +}) +export class SortedHeroesComponent extends HeroesBaseComponent { + constructor(heroService: HeroService) { + super(heroService); + } + + protected afterGetHeroes() { + this.heroes = this.heroes.sort((h1, h2) => { + return h1.name < h2.name ? -1 : + (h1.name > h2.name ? 1 : 0); + }); + } +} +// #enddocregion sorted-heroes diff --git a/aio/content/examples/cb-dependency-injection/ts/src/app/user-context.service.ts b/aio/content/examples/cb-dependency-injection/ts/src/app/user-context.service.ts new file mode 100644 index 0000000000..ed394fc734 --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/app/user-context.service.ts @@ -0,0 +1,33 @@ +// #docplaster +// #docregion +import { Injectable } from '@angular/core'; + +import { LoggerService } from './logger.service'; +import { UserService } from './user.service'; + +// #docregion injectables, injectable +@Injectable() +export class UserContextService { +// #enddocregion injectables, injectable + name: string; + role: string; + loggedInSince: Date; + + // #docregion ctor, injectables + constructor(private userService: UserService, private loggerService: LoggerService) { + // #enddocregion ctor, injectables + this.loggedInSince = new Date(); + // #docregion ctor, injectables + } + // #enddocregion ctor, injectables + + loadUser(userId: number) { + let user = this.userService.getUserById(userId); + this.name = user.name; + this.role = user.role; + + this.loggerService.logDebug('loaded User'); + } +// #docregion injectables, injectable +} +// #enddocregion injectables, injectable diff --git a/aio/content/examples/cb-dependency-injection/ts/src/app/user.service.ts b/aio/content/examples/cb-dependency-injection/ts/src/app/user.service.ts new file mode 100644 index 0000000000..c48b025a08 --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/app/user.service.ts @@ -0,0 +1,10 @@ +// #docregion +import { Injectable } from '@angular/core'; + +@Injectable() +export class UserService { + + getUserById(userId: number): any { + return {name: 'Bombasto', role: 'Admin'}; + } +} diff --git a/aio/content/examples/cb-dependency-injection/ts/src/index.html b/aio/content/examples/cb-dependency-injection/ts/src/index.html new file mode 100644 index 0000000000..0308c72155 --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/index.html @@ -0,0 +1,29 @@ + + + + + + Dependency Injection + + + + + + + + + + + + + + + + + + Loading app... + + + diff --git a/aio/content/examples/cb-dependency-injection/ts/src/main.ts b/aio/content/examples/cb-dependency-injection/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/main.ts @@ -0,0 +1,5 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/cb-dependency-injection/ts/src/sample.css b/aio/content/examples/cb-dependency-injection/ts/src/sample.css new file mode 100644 index 0000000000..a8b59efd05 --- /dev/null +++ b/aio/content/examples/cb-dependency-injection/ts/src/sample.css @@ -0,0 +1,26 @@ +.di-component{ + padding: 10px; + width:300px; + margin-bottom: 10px; +} +div[myHighlight] { + padding: 2px 8px; +} + +/* Parent Finder */ +.a, .b, .c { + margin: 6px 2px 6px; + padding: 4px 6px; +} +.a { + border: solid 2px black; +} +.b { + background: lightblue; + border: solid 1px darkblue; + display: flex; +} +.c { + background: pink; + border: solid 1px red; +} \ No newline at end of file diff --git a/aio/content/examples/cb-dynamic-component-loader/e2e-spec.ts b/aio/content/examples/cb-dynamic-component-loader/e2e-spec.ts new file mode 100644 index 0000000000..5036ac2a88 --- /dev/null +++ b/aio/content/examples/cb-dynamic-component-loader/e2e-spec.ts @@ -0,0 +1,21 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +/* tslint:disable:quotemark */ +describe('Dynamic Component Loader', function () { + + beforeEach(function () { + browser.get(''); + }); + + it('should load ad banner', function () { + let headline = element(by.xpath("//h4[text()='Featured Hero Profile']")); + let name = element(by.xpath("//h3[text()='Bombasto']")); + let bio = element(by.xpath("//p[text()='Brave as they come']")); + + expect(name).toBeDefined(); + expect(headline).toBeDefined(); + expect(bio).toBeDefined(); + }); +}); diff --git a/aio/content/examples/cb-dynamic-component-loader/ts/example-config.json b/aio/content/examples/cb-dynamic-component-loader/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/cb-dynamic-component-loader/ts/src/app/ad-banner.component.ts b/aio/content/examples/cb-dynamic-component-loader/ts/src/app/ad-banner.component.ts new file mode 100644 index 0000000000..293d31dea9 --- /dev/null +++ b/aio/content/examples/cb-dynamic-component-loader/ts/src/app/ad-banner.component.ts @@ -0,0 +1,55 @@ +// #docregion +import { Component, Input, AfterViewInit, ViewChild, ComponentFactoryResolver, OnDestroy } from '@angular/core'; + +import { AdDirective } from './ad.directive'; +import { AdItem } from './ad-item'; +import { AdComponent } from './ad.component'; + +@Component({ + selector: 'add-banner', + // #docregion ad-host + template: ` +
    +

    Advertisements

    + +
    + ` + // #enddocregion ad-host +}) +export class AdBannerComponent implements AfterViewInit, OnDestroy { + @Input() ads: AdItem[]; + currentAddIndex: number = -1; + @ViewChild(AdDirective) adHost: AdDirective; + subscription: any; + interval: any; + + constructor(private _componentFactoryResolver: ComponentFactoryResolver) { } + + ngAfterViewInit() { + this.loadComponent(); + this.getAds(); + } + + ngOnDestroy() { + clearInterval(this.interval); + } + + loadComponent() { + this.currentAddIndex = (this.currentAddIndex + 1) % this.ads.length; + let adItem = this.ads[this.currentAddIndex]; + + let componentFactory = this._componentFactoryResolver.resolveComponentFactory(adItem.component); + + let viewContainerRef = this.adHost.viewContainerRef; + viewContainerRef.clear(); + + let componentRef = viewContainerRef.createComponent(componentFactory); + (componentRef.instance).data = adItem.data; + } + + getAds() { + this.interval = setInterval(() => { + this.loadComponent(); + }, 3000); + } +} diff --git a/aio/content/examples/cb-dynamic-component-loader/ts/src/app/ad-item.ts b/aio/content/examples/cb-dynamic-component-loader/ts/src/app/ad-item.ts new file mode 100644 index 0000000000..ef8ca70577 --- /dev/null +++ b/aio/content/examples/cb-dynamic-component-loader/ts/src/app/ad-item.ts @@ -0,0 +1,6 @@ +// #docregion +import { Type } from '@angular/core'; + +export class AdItem { + constructor(public component: Type, public data: any) {} +} diff --git a/aio/content/examples/cb-dynamic-component-loader/ts/src/app/ad.component.ts b/aio/content/examples/cb-dynamic-component-loader/ts/src/app/ad.component.ts new file mode 100644 index 0000000000..dee3b47953 --- /dev/null +++ b/aio/content/examples/cb-dynamic-component-loader/ts/src/app/ad.component.ts @@ -0,0 +1,4 @@ +// #docregion +export interface AdComponent { + data: any; +} diff --git a/aio/content/examples/cb-dynamic-component-loader/ts/src/app/ad.directive.ts b/aio/content/examples/cb-dynamic-component-loader/ts/src/app/ad.directive.ts new file mode 100644 index 0000000000..312e605228 --- /dev/null +++ b/aio/content/examples/cb-dynamic-component-loader/ts/src/app/ad.directive.ts @@ -0,0 +1,10 @@ +// #docregion +import { Directive, ViewContainerRef } from '@angular/core'; + +@Directive({ + selector: '[ad-host]', +}) +export class AdDirective { + constructor(public viewContainerRef: ViewContainerRef) { } +} + diff --git a/aio/content/examples/cb-dynamic-component-loader/ts/src/app/ad.service.ts b/aio/content/examples/cb-dynamic-component-loader/ts/src/app/ad.service.ts new file mode 100644 index 0000000000..91b0758771 --- /dev/null +++ b/aio/content/examples/cb-dynamic-component-loader/ts/src/app/ad.service.ts @@ -0,0 +1,23 @@ +// #docregion +import { Injectable } from '@angular/core'; + +import { HeroJobAdComponent } from './hero-job-ad.component'; +import { HeroProfileComponent } from './hero-profile.component'; +import { AdItem } from './ad-item'; + +@Injectable() +export class AdService { + getAds() { + return [ + new AdItem(HeroProfileComponent, {name: 'Bombasto', bio: 'Brave as they come'}), + + new AdItem(HeroProfileComponent, {name: 'Dr IQ', bio: 'Smart as they come'}), + + new AdItem(HeroJobAdComponent, {headline: 'Hiring for several positions', + body: 'Submit your resume today!'}), + + new AdItem(HeroJobAdComponent, {headline: 'Openings in all departments', + body: 'Apply today'}), + ]; + } +} diff --git a/aio/content/examples/cb-dynamic-component-loader/ts/src/app/app.component.ts b/aio/content/examples/cb-dynamic-component-loader/ts/src/app/app.component.ts new file mode 100644 index 0000000000..89359ccdf6 --- /dev/null +++ b/aio/content/examples/cb-dynamic-component-loader/ts/src/app/app.component.ts @@ -0,0 +1,24 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; + +import { AdService } from './ad.service'; +import { AdItem } from './ad-item'; + +@Component({ + selector: 'my-app', + template: ` +
    + +
    + ` +}) +export class AppComponent implements OnInit { + ads: AdItem[]; + + constructor(private adService: AdService) {} + + ngOnInit() { + this.ads = this.adService.getAds(); + } +} + diff --git a/aio/content/examples/cb-dynamic-component-loader/ts/src/app/app.module.ts b/aio/content/examples/cb-dynamic-component-loader/ts/src/app/app.module.ts new file mode 100644 index 0000000000..a65d394709 --- /dev/null +++ b/aio/content/examples/cb-dynamic-component-loader/ts/src/app/app.module.ts @@ -0,0 +1,27 @@ +// #docregion +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { AppComponent } from './app.component'; +import { HeroJobAdComponent } from './hero-job-ad.component'; +import { AdBannerComponent } from './ad-banner.component'; +import { HeroProfileComponent } from './hero-profile.component'; +import { AdDirective } from './ad.directive'; +import { AdService } from './ad.service'; + +@NgModule({ + imports: [ BrowserModule ], + providers: [AdService], + declarations: [ AppComponent, + AdBannerComponent, + HeroJobAdComponent, + HeroProfileComponent, + AdDirective ], + // #docregion entry-components + entryComponents: [ HeroJobAdComponent, HeroProfileComponent ], + // #enddocregion entry-components + bootstrap: [ AppComponent ] +}) +export class AppModule { + constructor() {} +} + diff --git a/aio/content/examples/cb-dynamic-component-loader/ts/src/app/hero-job-ad.component.ts b/aio/content/examples/cb-dynamic-component-loader/ts/src/app/hero-job-ad.component.ts new file mode 100644 index 0000000000..675a03d0e0 --- /dev/null +++ b/aio/content/examples/cb-dynamic-component-loader/ts/src/app/hero-job-ad.component.ts @@ -0,0 +1,19 @@ +// #docregion +import { Component, Input } from '@angular/core'; + +import { AdComponent } from './ad.component'; + +@Component({ + template: ` +
    +

    {{data.headline}}

    + + {{data.body}} +
    + ` +}) +export class HeroJobAdComponent implements AdComponent { + @Input() data: any; + +} + diff --git a/aio/content/examples/cb-dynamic-component-loader/ts/src/app/hero-profile.component.ts b/aio/content/examples/cb-dynamic-component-loader/ts/src/app/hero-profile.component.ts new file mode 100644 index 0000000000..1c266db3c9 --- /dev/null +++ b/aio/content/examples/cb-dynamic-component-loader/ts/src/app/hero-profile.component.ts @@ -0,0 +1,22 @@ +// #docregion +import { Component, Input } from '@angular/core'; + +import { AdComponent } from './ad.component'; + +@Component({ + template: ` +
    +

    Featured Hero Profile

    +

    {{data.name}}

    + +

    {{data.bio}}

    + + Hire this hero today! +
    + ` +}) +export class HeroProfileComponent implements AdComponent { + @Input() data: any; +} + + diff --git a/aio/content/examples/cb-dynamic-component-loader/ts/src/index.html b/aio/content/examples/cb-dynamic-component-loader/ts/src/index.html new file mode 100644 index 0000000000..9239d91d9a --- /dev/null +++ b/aio/content/examples/cb-dynamic-component-loader/ts/src/index.html @@ -0,0 +1,26 @@ + + + + + + Dynamic Component Loader + + + + + + + + + + + + + + + Loading app... + + + diff --git a/aio/content/examples/cb-dynamic-component-loader/ts/src/main.ts b/aio/content/examples/cb-dynamic-component-loader/ts/src/main.ts new file mode 100644 index 0000000000..53d16fa43a --- /dev/null +++ b/aio/content/examples/cb-dynamic-component-loader/ts/src/main.ts @@ -0,0 +1,6 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); + diff --git a/aio/content/examples/cb-dynamic-component-loader/ts/src/sample.css b/aio/content/examples/cb-dynamic-component-loader/ts/src/sample.css new file mode 100644 index 0000000000..7a2ca1f2dc --- /dev/null +++ b/aio/content/examples/cb-dynamic-component-loader/ts/src/sample.css @@ -0,0 +1,23 @@ +.hero-profile { + border: 1px solid gray; + padding: 5px; + padding-bottom: 20px; + padding-left: 20px; + border-radius: 10px; + background-color: lightgreen; + color: black; +} + +.job-ad { + border: 1px solid gray; + padding: 5px; + padding-bottom: 20px; + padding-left: 20px; + border-radius: 10px; + background-color: lightblue; + color: black; +} + +.ad-banner { + width: 400px; +} \ No newline at end of file diff --git a/aio/content/examples/cb-dynamic-form/e2e-spec.ts b/aio/content/examples/cb-dynamic-form/e2e-spec.ts new file mode 100644 index 0000000000..408ac75766 --- /dev/null +++ b/aio/content/examples/cb-dynamic-form/e2e-spec.ts @@ -0,0 +1,29 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +/* tslint:disable:quotemark */ +describe('Dynamic Form', function () { + + beforeAll(function () { + browser.get(''); + }); + + it('should submit form', function () { + let firstNameElement = element.all(by.css('input[id=firstName]')).get(0); + expect(firstNameElement.getAttribute('value')).toEqual('Bombasto'); + + let emailElement = element.all(by.css('input[id=emailAddress]')).get(0); + let email = 'test@test.com'; + emailElement.sendKeys(email); + expect(emailElement.getAttribute('value')).toEqual(email); + + element(by.css('select option[value="solid"]')).click(); + + let saveButton = element.all(by.css('button')).get(0); + saveButton.click().then(function(){ + expect(element(by.xpath("//strong[contains(text(),'Saved the following values')]")).isPresent()).toBe(true); + }); + }); + +}); diff --git a/aio/content/examples/cb-dynamic-form/ts/example-config.json b/aio/content/examples/cb-dynamic-form/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/cb-dynamic-form/ts/plnkr.json b/aio/content/examples/cb-dynamic-form/ts/plnkr.json new file mode 100644 index 0000000000..1f50b4a992 --- /dev/null +++ b/aio/content/examples/cb-dynamic-form/ts/plnkr.json @@ -0,0 +1,10 @@ +{ + "description": "Dynamic Form", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1].*" + ], + "tags":["cookbook"] +} diff --git a/aio/content/examples/cb-dynamic-form/ts/plnkr.no-link.html b/aio/content/examples/cb-dynamic-form/ts/plnkr.no-link.html new file mode 100644 index 0000000000..93370216ad --- /dev/null +++ b/aio/content/examples/cb-dynamic-form/ts/plnkr.no-link.html @@ -0,0 +1,463 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/cb-dynamic-form/ts/src/app/app.component.ts b/aio/content/examples/cb-dynamic-form/ts/src/app/app.component.ts new file mode 100644 index 0000000000..582daced2e --- /dev/null +++ b/aio/content/examples/cb-dynamic-form/ts/src/app/app.component.ts @@ -0,0 +1,22 @@ +// #docregion +import { Component } from '@angular/core'; + +import { QuestionService } from './question.service'; + +@Component({ + selector: 'my-app', + template: ` +
    +

    Job Application for Heroes

    + +
    + `, + providers: [QuestionService] +}) +export class AppComponent { + questions: any[]; + + constructor(service: QuestionService) { + this.questions = service.getQuestions(); + } +} diff --git a/aio/content/examples/cb-dynamic-form/ts/src/app/app.module.ts b/aio/content/examples/cb-dynamic-form/ts/src/app/app.module.ts new file mode 100644 index 0000000000..7a68e45a92 --- /dev/null +++ b/aio/content/examples/cb-dynamic-form/ts/src/app/app.module.ts @@ -0,0 +1,18 @@ +// #docregion +import { BrowserModule } from '@angular/platform-browser'; +import { ReactiveFormsModule } from '@angular/forms'; +import { NgModule } from '@angular/core'; + +import { AppComponent } from './app.component'; +import { DynamicFormComponent } from './dynamic-form.component'; +import { DynamicFormQuestionComponent } from './dynamic-form-question.component'; + +@NgModule({ + imports: [ BrowserModule, ReactiveFormsModule ], + declarations: [ AppComponent, DynamicFormComponent, DynamicFormQuestionComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule { + constructor() { + } +} diff --git a/aio/content/examples/cb-dynamic-form/ts/src/app/dynamic-form-question.component.html b/aio/content/examples/cb-dynamic-form/ts/src/app/dynamic-form-question.component.html new file mode 100644 index 0000000000..9f1b8cd4a6 --- /dev/null +++ b/aio/content/examples/cb-dynamic-form/ts/src/app/dynamic-form-question.component.html @@ -0,0 +1,17 @@ + +
    + + +
    + + + + + +
    + +
    {{question.label}} is required
    +
    diff --git a/aio/content/examples/cb-dynamic-form/ts/src/app/dynamic-form-question.component.ts b/aio/content/examples/cb-dynamic-form/ts/src/app/dynamic-form-question.component.ts new file mode 100644 index 0000000000..24d0bd168b --- /dev/null +++ b/aio/content/examples/cb-dynamic-form/ts/src/app/dynamic-form-question.component.ts @@ -0,0 +1,16 @@ +// #docregion +import { Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +import { QuestionBase } from './question-base'; + +@Component({ + moduleId: module.id, + selector: 'df-question', + templateUrl: './dynamic-form-question.component.html' +}) +export class DynamicFormQuestionComponent { + @Input() question: QuestionBase; + @Input() form: FormGroup; + get isValid() { return this.form.controls[this.question.key].valid; } +} diff --git a/aio/content/examples/cb-dynamic-form/ts/src/app/dynamic-form.component.html b/aio/content/examples/cb-dynamic-form/ts/src/app/dynamic-form.component.html new file mode 100644 index 0000000000..717f09ff71 --- /dev/null +++ b/aio/content/examples/cb-dynamic-form/ts/src/app/dynamic-form.component.html @@ -0,0 +1,17 @@ + +
    +
    + +
    + +
    + +
    + +
    +
    + +
    + Saved the following values
    {{payLoad}} +
    +
    diff --git a/aio/content/examples/cb-dynamic-form/ts/src/app/dynamic-form.component.ts b/aio/content/examples/cb-dynamic-form/ts/src/app/dynamic-form.component.ts new file mode 100644 index 0000000000..32aaf73114 --- /dev/null +++ b/aio/content/examples/cb-dynamic-form/ts/src/app/dynamic-form.component.ts @@ -0,0 +1,29 @@ +// #docregion +import { Component, Input, OnInit } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +import { QuestionBase } from './question-base'; +import { QuestionControlService } from './question-control.service'; + +@Component({ + moduleId: module.id, + selector: 'dynamic-form', + templateUrl: './dynamic-form.component.html', + providers: [ QuestionControlService ] +}) +export class DynamicFormComponent implements OnInit { + + @Input() questions: QuestionBase[] = []; + form: FormGroup; + payLoad = ''; + + constructor(private qcs: QuestionControlService) { } + + ngOnInit() { + this.form = this.qcs.toFormGroup(this.questions); + } + + onSubmit() { + this.payLoad = JSON.stringify(this.form.value); + } +} diff --git a/aio/content/examples/cb-dynamic-form/ts/src/app/question-base.ts b/aio/content/examples/cb-dynamic-form/ts/src/app/question-base.ts new file mode 100644 index 0000000000..2b32b00f2a --- /dev/null +++ b/aio/content/examples/cb-dynamic-form/ts/src/app/question-base.ts @@ -0,0 +1,25 @@ +// #docregion +export class QuestionBase{ + value: T; + key: string; + label: string; + required: boolean; + order: number; + controlType: string; + + constructor(options: { + value?: T, + key?: string, + label?: string, + required?: boolean, + order?: number, + controlType?: string + } = {}) { + this.value = options.value; + this.key = options.key || ''; + this.label = options.label || ''; + this.required = !!options.required; + this.order = options.order === undefined ? 1 : options.order; + this.controlType = options.controlType || ''; + } +} diff --git a/aio/content/examples/cb-dynamic-form/ts/src/app/question-control.service.ts b/aio/content/examples/cb-dynamic-form/ts/src/app/question-control.service.ts new file mode 100644 index 0000000000..1378ba8490 --- /dev/null +++ b/aio/content/examples/cb-dynamic-form/ts/src/app/question-control.service.ts @@ -0,0 +1,20 @@ +// #docregion +import { Injectable } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; + +import { QuestionBase } from './question-base'; + +@Injectable() +export class QuestionControlService { + constructor() { } + + toFormGroup(questions: QuestionBase[] ) { + let group: any = {}; + + questions.forEach(question => { + group[question.key] = question.required ? new FormControl(question.value || '', Validators.required) + : new FormControl(question.value || ''); + }); + return new FormGroup(group); + } +} diff --git a/aio/content/examples/cb-dynamic-form/ts/src/app/question-dropdown.ts b/aio/content/examples/cb-dynamic-form/ts/src/app/question-dropdown.ts new file mode 100644 index 0000000000..35a9074c74 --- /dev/null +++ b/aio/content/examples/cb-dynamic-form/ts/src/app/question-dropdown.ts @@ -0,0 +1,12 @@ +// #docregion +import { QuestionBase } from './question-base'; + +export class DropdownQuestion extends QuestionBase { + controlType = 'dropdown'; + options: {key: string, value: string}[] = []; + + constructor(options: {} = {}) { + super(options); + this.options = options['options'] || []; + } +} diff --git a/aio/content/examples/cb-dynamic-form/ts/src/app/question-textbox.ts b/aio/content/examples/cb-dynamic-form/ts/src/app/question-textbox.ts new file mode 100644 index 0000000000..aaa7edf267 --- /dev/null +++ b/aio/content/examples/cb-dynamic-form/ts/src/app/question-textbox.ts @@ -0,0 +1,12 @@ +// #docregion +import { QuestionBase } from './question-base'; + +export class TextboxQuestion extends QuestionBase { + controlType = 'textbox'; + type: string; + + constructor(options: {} = {}) { + super(options); + this.type = options['type'] || ''; + } +} diff --git a/aio/content/examples/cb-dynamic-form/ts/src/app/question.service.ts b/aio/content/examples/cb-dynamic-form/ts/src/app/question.service.ts new file mode 100644 index 0000000000..bb452cf5e6 --- /dev/null +++ b/aio/content/examples/cb-dynamic-form/ts/src/app/question.service.ts @@ -0,0 +1,47 @@ +// #docregion +import { Injectable } from '@angular/core'; + +import { DropdownQuestion } from './question-dropdown'; +import { QuestionBase } from './question-base'; +import { TextboxQuestion } from './question-textbox'; + +@Injectable() +export class QuestionService { + + // Todo: get from a remote source of question metadata + // Todo: make asynchronous + getQuestions() { + + let questions: QuestionBase[] = [ + + new DropdownQuestion({ + key: 'brave', + label: 'Bravery Rating', + options: [ + {key: 'solid', value: 'Solid'}, + {key: 'great', value: 'Great'}, + {key: 'good', value: 'Good'}, + {key: 'unproven', value: 'Unproven'} + ], + order: 3 + }), + + new TextboxQuestion({ + key: 'firstName', + label: 'First name', + value: 'Bombasto', + required: true, + order: 1 + }), + + new TextboxQuestion({ + key: 'emailAddress', + label: 'Email', + type: 'email', + order: 2 + }) + ]; + + return questions.sort((a, b) => a.order - b.order); + } +} diff --git a/aio/content/examples/cb-dynamic-form/ts/src/index.html b/aio/content/examples/cb-dynamic-form/ts/src/index.html new file mode 100644 index 0000000000..01963f71e2 --- /dev/null +++ b/aio/content/examples/cb-dynamic-form/ts/src/index.html @@ -0,0 +1,28 @@ + + + + + + Dynamic Form + + + + + + + + + + + + + + + + + Loading app... + + + diff --git a/aio/content/examples/cb-dynamic-form/ts/src/main.ts b/aio/content/examples/cb-dynamic-form/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/aio/content/examples/cb-dynamic-form/ts/src/main.ts @@ -0,0 +1,5 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/cb-dynamic-form/ts/src/sample.css b/aio/content/examples/cb-dynamic-form/ts/src/sample.css new file mode 100644 index 0000000000..fe2cc28481 --- /dev/null +++ b/aio/content/examples/cb-dynamic-form/ts/src/sample.css @@ -0,0 +1,7 @@ +.errorMessage{ + color:red; +} + +.form-row{ + margin-top: 10px; +} \ No newline at end of file diff --git a/aio/content/examples/cb-form-validation/e2e-spec.ts b/aio/content/examples/cb-form-validation/e2e-spec.ts new file mode 100644 index 0000000000..8ffc01e250 --- /dev/null +++ b/aio/content/examples/cb-form-validation/e2e-spec.ts @@ -0,0 +1,182 @@ +'use strict'; // necessary for node! + +import { browser, element, by, protractor, ElementFinder, ElementArrayFinder } from 'protractor'; +import { appLang, describeIf } from '../protractor-helpers'; + +// THESE TESTS ARE INCOMPLETE +describeIf(appLang.appIsTs || appLang.appIsJs, 'Form Validation Tests', function () { + + beforeAll(function () { + browser.get(''); + }); + + describe('Hero Form 1', () => { + beforeAll(() => { + getPage('hero-form-template1'); + }); + + tests(); + }); + + describe('Hero Form 2', () => { + beforeAll(() => { + getPage('hero-form-template2'); + }); + + tests(); + bobTests(); + }); + + describe('Hero Form 3 (Reactive)', () => { + beforeAll(() => { + getPage('hero-form-reactive3'); + makeNameTooLong(); + }); + + tests(); + bobTests(); + }); +}); + +////////// + +const testName = 'Test Name'; + +let page: { + section: ElementFinder, + form: ElementFinder, + title: ElementFinder, + nameInput: ElementFinder, + alterEgoInput: ElementFinder, + powerSelect: ElementFinder, + errorMessages: ElementArrayFinder, + heroFormButtons: ElementArrayFinder, + heroSubmitted: ElementFinder +}; + +function getPage(sectionTag: string) { + let section = element(by.css(sectionTag)); + let buttons = section.all(by.css('button')); + + page = { + section: section, + form: section.element(by.css('form')), + title: section.element(by.css('h1')), + nameInput: section.element(by.css('#name')), + alterEgoInput: section.element(by.css('#alterEgo')), + powerSelect: section.element(by.css('#power')), + errorMessages: section.all(by.css('div.alert')), + heroFormButtons: buttons, + heroSubmitted: section.element(by.css('hero-submitted > div')) + }; +} + +function tests() { + it('should display correct title', function () { + expect(page.title.getText()).toContain('Hero Form'); + }); + + it('should not display submitted message before submit', function () { + expect(page.heroSubmitted.isElementPresent(by.css('h2'))).toBe(false); + }); + + it('should have form buttons', function () { + expect(page.heroFormButtons.count()).toEqual(2); + }); + + it('should have error at start', function () { + expectFormIsInvalid(); + }); + + // it('showForm', function () { + // page.form.getInnerHtml().then(html => console.log(html)); + // }); + + it('should have disabled submit button', function () { + expect(page.heroFormButtons.get(0).isEnabled()).toBe(false); + }); + + it('resetting name to valid name should clear errors', function () { + const ele = page.nameInput; + expect(ele.isPresent()).toBe(true, 'nameInput should exist'); + ele.clear(); + ele.sendKeys(testName); + expectFormIsValid(); + }); + + it('should produce "required" error after clearing name', function () { + page.nameInput.clear(); + // page.alterEgoInput.click(); // to blur ... didn't work + page.nameInput.sendKeys('x', protractor.Key.BACK_SPACE); // ugh! + expect(page.form.getAttribute('class')).toMatch('ng-invalid'); + expect(page.errorMessages.get(0).getText()).toContain('required'); + }); + + it('should produce "at least 4 characters" error when name="x"', function () { + page.nameInput.clear(); + page.nameInput.sendKeys('x'); // too short + expectFormIsInvalid(); + expect(page.errorMessages.get(0).getText()).toContain('at least 4 characters'); + }); + + it('resetting name to valid name again should clear errors', function () { + page.nameInput.sendKeys(testName); + expectFormIsValid(); + }); + + it('should have enabled submit button', function () { + const submitBtn = page.heroFormButtons.get(0); + expect(submitBtn.isEnabled()).toBe(true); + }); + + it('should hide form after submit', function () { + page.heroFormButtons.get(0).click(); + expect(page.title.isDisplayed()).toBe(false); + }); + + it('submitted form should be displayed', function () { + expect(page.heroSubmitted.isElementPresent(by.css('h2'))).toBe(true); + }); + + it('submitted form should have new hero name', function () { + expect(page.heroSubmitted.getText()).toContain(testName); + }); + + it('clicking edit button should reveal form again', function () { + const editBtn = page.heroSubmitted.element(by.css('button')); + editBtn.click(); + expect(page.heroSubmitted.isElementPresent(by.css('h2'))) + .toBe(false, 'submitted hidden again'); + expect(page.title.isDisplayed()).toBe(true, 'can see form title'); + }); +} + +function expectFormIsValid() { + expect(page.form.getAttribute('class')).toMatch('ng-valid'); +} + +function expectFormIsInvalid() { + expect(page.form.getAttribute('class')).toMatch('ng-invalid'); +} + +function bobTests() { + const emsg = 'Someone named "Bob" cannot be a hero.'; + + it('should produce "no bob" error after setting name to "Bobby"', function () { + page.nameInput.clear(); + page.nameInput.sendKeys('Bobby'); + expectFormIsInvalid(); + expect(page.errorMessages.get(0).getText()).toBe(emsg); + }); + + it('should be ok again with valid name', function () { + page.nameInput.clear(); + page.nameInput.sendKeys(testName); + expectFormIsValid(); + }); +} + +function makeNameTooLong() { + // make the first name invalid + page.nameInput.sendKeys('ThisHeroNameHasWayWayTooManyLetters'); +} diff --git a/aio/content/examples/cb-form-validation/ts/example-config.json b/aio/content/examples/cb-form-validation/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/cb-form-validation/ts/plnkr.json b/aio/content/examples/cb-form-validation/ts/plnkr.json new file mode 100644 index 0000000000..c5656d77f7 --- /dev/null +++ b/aio/content/examples/cb-form-validation/ts/plnkr.json @@ -0,0 +1,8 @@ +{ + "description": "Validation", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js" + ] +} diff --git a/aio/content/examples/cb-form-validation/ts/plnkr.no-link.html b/aio/content/examples/cb-form-validation/ts/plnkr.no-link.html new file mode 100644 index 0000000000..626da53295 --- /dev/null +++ b/aio/content/examples/cb-form-validation/ts/plnkr.no-link.html @@ -0,0 +1,768 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/cb-form-validation/ts/src/app/app.component.ts b/aio/content/examples/cb-form-validation/ts/src/app/app.component.ts new file mode 100644 index 0000000000..2da4dc4d0a --- /dev/null +++ b/aio/content/examples/cb-form-validation/ts/src/app/app.component.ts @@ -0,0 +1,12 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: ` +
    + +
    + ` +}) +export class AppComponent { } diff --git a/aio/content/examples/cb-form-validation/ts/src/app/app.module.ts b/aio/content/examples/cb-form-validation/ts/src/app/app.module.ts new file mode 100644 index 0000000000..72b4e3a770 --- /dev/null +++ b/aio/content/examples/cb-form-validation/ts/src/app/app.module.ts @@ -0,0 +1,18 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; +import { HeroFormTemplateModule } from './template/hero-form-template.module'; +import { HeroFormReactiveModule } from './reactive/hero-form-reactive.module'; + +@NgModule({ + imports: [ + BrowserModule, + HeroFormTemplateModule, + HeroFormReactiveModule + ], + declarations: [ AppComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.html b/aio/content/examples/cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.html new file mode 100644 index 0000000000..149537bd3e --- /dev/null +++ b/aio/content/examples/cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.html @@ -0,0 +1,47 @@ + +
    +
    +

    Hero Form 3 (Reactive)

    + +
    + +
    + + + + + +
    + {{ formErrors.name }} +
    + +
    + +
    + + +
    + +
    + + + +
    + {{ formErrors.power }} +
    +
    + + + +
    +
    + + +
    diff --git a/aio/content/examples/cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.ts b/aio/content/examples/cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.ts new file mode 100644 index 0000000000..4bbefadb96 --- /dev/null +++ b/aio/content/examples/cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.ts @@ -0,0 +1,117 @@ +/* tslint:disable: member-ordering forin */ +// #docplaster +// #docregion +import { Component, OnInit } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; + +import { Hero } from '../shared/hero'; +import { forbiddenNameValidator } from '../shared/forbidden-name.directive'; + +@Component({ + moduleId: module.id, + selector: 'hero-form-reactive3', + templateUrl: './hero-form-reactive.component.html' +}) +export class HeroFormReactiveComponent implements OnInit { + + powers = ['Really Smart', 'Super Flexible', 'Weather Changer']; + + hero = new Hero(18, 'Dr. WhatIsHisName', this.powers[0], 'Dr. What'); + + submitted = false; + + // #docregion on-submit + onSubmit() { + this.submitted = true; + this.hero = this.heroForm.value; + } + // #enddocregion on-submit +// #enddocregion + + // Reset the form with a new hero AND restore 'pristine' class state + // by toggling 'active' flag which causes the form + // to be removed/re-added in a tick via NgIf + // TODO: Workaround until NgForm has a reset method (#6822) + active = true; +// #docregion class + // #docregion add-hero + addHero() { + this.hero = new Hero(42, '', ''); + this.buildForm(); + // #enddocregion add-hero +// #enddocregion class + + this.active = false; + setTimeout(() => this.active = true, 0); +// #docregion + // #docregion add-hero + } + // #enddocregion add-hero + + // #docregion form-builder + heroForm: FormGroup; + constructor(private fb: FormBuilder) { } + + ngOnInit(): void { + this.buildForm(); + } + + buildForm(): void { + this.heroForm = this.fb.group({ + // #docregion name-validators + 'name': [this.hero.name, [ + Validators.required, + Validators.minLength(4), + Validators.maxLength(24), + forbiddenNameValidator(/bob/i) + ] + ], + // #enddocregion name-validators + 'alterEgo': [this.hero.alterEgo], + 'power': [this.hero.power, Validators.required] + }); + + this.heroForm.valueChanges + .subscribe(data => this.onValueChanged(data)); + + this.onValueChanged(); // (re)set validation messages now + } + + // #enddocregion form-builder + + onValueChanged(data?: any) { + if (!this.heroForm) { return; } + const form = this.heroForm; + + for (const field in this.formErrors) { + // clear previous error message (if any) + this.formErrors[field] = ''; + const control = form.get(field); + + if (control && control.dirty && !control.valid) { + const messages = this.validationMessages[field]; + for (const key in control.errors) { + this.formErrors[field] += messages[key] + ' '; + } + } + } + } + + formErrors = { + 'name': '', + 'power': '' + }; + + validationMessages = { + 'name': { + 'required': 'Name is required.', + 'minlength': 'Name must be at least 4 characters long.', + 'maxlength': 'Name cannot be more than 24 characters long.', + 'forbiddenName': 'Someone named "Bob" cannot be a hero.' + }, + 'power': { + 'required': 'Power is required.' + } + }; +} +// #enddocregion diff --git a/aio/content/examples/cb-form-validation/ts/src/app/reactive/hero-form-reactive.module.ts b/aio/content/examples/cb-form-validation/ts/src/app/reactive/hero-form-reactive.module.ts new file mode 100644 index 0000000000..6ff9265e92 --- /dev/null +++ b/aio/content/examples/cb-form-validation/ts/src/app/reactive/hero-form-reactive.module.ts @@ -0,0 +1,13 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; + +import { SharedModule } from '../shared/shared.module'; +import { HeroFormReactiveComponent } from './hero-form-reactive.component'; + +@NgModule({ + imports: [ SharedModule, ReactiveFormsModule ], + declarations: [ HeroFormReactiveComponent ], + exports: [ HeroFormReactiveComponent ] +}) +export class HeroFormReactiveModule { } diff --git a/aio/content/examples/cb-form-validation/ts/src/app/shared/forbidden-name.directive.ts b/aio/content/examples/cb-form-validation/ts/src/app/shared/forbidden-name.directive.ts new file mode 100644 index 0000000000..870f514842 --- /dev/null +++ b/aio/content/examples/cb-form-validation/ts/src/app/shared/forbidden-name.directive.ts @@ -0,0 +1,43 @@ +// #docregion +import { Directive, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { AbstractControl, NG_VALIDATORS, Validator, ValidatorFn, Validators } from '@angular/forms'; + +// #docregion custom-validator +/** A hero's name can't match the given regular expression */ +export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn { + return (control: AbstractControl): {[key: string]: any} => { + const name = control.value; + const no = nameRe.test(name); + return no ? {'forbiddenName': {name}} : null; + }; +} +// #enddocregion custom-validator + +// #docregion directive +@Directive({ + selector: '[forbiddenName]', + // #docregion directive-providers + providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}] + // #enddocregion directive-providers +}) +export class ForbiddenValidatorDirective implements Validator, OnChanges { + @Input() forbiddenName: string; + private valFn = Validators.nullValidator; + + ngOnChanges(changes: SimpleChanges): void { + const change = changes['forbiddenName']; + if (change) { + const val: string | RegExp = change.currentValue; + const re = val instanceof RegExp ? val : new RegExp(val, 'i'); + this.valFn = forbiddenNameValidator(re); + } else { + this.valFn = Validators.nullValidator; + } + } + + validate(control: AbstractControl): {[key: string]: any} { + return this.valFn(control); + } +} +// #enddocregion directive + diff --git a/aio/content/examples/cb-form-validation/ts/src/app/shared/hero.ts b/aio/content/examples/cb-form-validation/ts/src/app/shared/hero.ts new file mode 100644 index 0000000000..fe2b55e51a --- /dev/null +++ b/aio/content/examples/cb-form-validation/ts/src/app/shared/hero.ts @@ -0,0 +1,9 @@ +// #docregion +export class Hero { + constructor( + public id: number, + public name: string, + public power: string, + public alterEgo?: string + ) { } +} diff --git a/aio/content/examples/cb-form-validation/ts/src/app/shared/shared.module.ts b/aio/content/examples/cb-form-validation/ts/src/app/shared/shared.module.ts new file mode 100644 index 0000000000..2b0ada59bd --- /dev/null +++ b/aio/content/examples/cb-form-validation/ts/src/app/shared/shared.module.ts @@ -0,0 +1,14 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { ForbiddenValidatorDirective } from './forbidden-name.directive'; +import { SubmittedComponent } from './submitted.component'; + +@NgModule({ + imports: [ CommonModule], + declarations: [ ForbiddenValidatorDirective, SubmittedComponent ], + exports: [ ForbiddenValidatorDirective, SubmittedComponent, + CommonModule ] +}) +export class SharedModule { } diff --git a/aio/content/examples/cb-form-validation/ts/src/app/shared/submitted.component.ts b/aio/content/examples/cb-form-validation/ts/src/app/shared/submitted.component.ts new file mode 100644 index 0000000000..18cea6563f --- /dev/null +++ b/aio/content/examples/cb-form-validation/ts/src/app/shared/submitted.component.ts @@ -0,0 +1,32 @@ +// #docregion +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +import { Hero } from './hero'; + +@Component({ + selector: 'hero-submitted', + template: ` +
    +

    You submitted the following:

    +
    +
    Name
    +
    {{ hero.name }}
    +
    +
    +
    Alter Ego
    +
    {{ hero.alterEgo }}
    +
    +
    +
    Power
    +
    {{ hero.power }}
    +
    +
    + +
    ` +}) +export class SubmittedComponent { + @Input() hero: Hero; + @Input() submitted = false; + @Output() submittedChange = new EventEmitter(); + onClick() { this.submittedChange.emit(false); } +} diff --git a/aio/content/examples/cb-form-validation/ts/src/app/template/hero-form-template.module.ts b/aio/content/examples/cb-form-validation/ts/src/app/template/hero-form-template.module.ts new file mode 100644 index 0000000000..042c019d5e --- /dev/null +++ b/aio/content/examples/cb-form-validation/ts/src/app/template/hero-form-template.module.ts @@ -0,0 +1,14 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; + +import { SharedModule } from '../shared/shared.module'; +import { HeroFormTemplate1Component } from './hero-form-template1.component'; +import { HeroFormTemplate2Component } from './hero-form-template2.component'; + +@NgModule({ + imports: [ SharedModule, FormsModule ], + declarations: [ HeroFormTemplate1Component, HeroFormTemplate2Component ], + exports: [ HeroFormTemplate1Component, HeroFormTemplate2Component ] +}) +export class HeroFormTemplateModule { } diff --git a/aio/content/examples/cb-form-validation/ts/src/app/template/hero-form-template1.component.html b/aio/content/examples/cb-form-validation/ts/src/app/template/hero-form-template1.component.html new file mode 100644 index 0000000000..22b374b622 --- /dev/null +++ b/aio/content/examples/cb-form-validation/ts/src/app/template/hero-form-template1.component.html @@ -0,0 +1,61 @@ + +
    +
    +

    Hero Form 1 (Template)

    + +
    + +
    + + + + + +
    +
    + Name is required +
    +
    + Name must be at least 4 characters long. +
    +
    + Name cannot be more than 24 characters long. +
    +
    + +
    + +
    + + +
    + +
    + + + +
    +
    Power is required
    +
    +
    + + + +
    +
    + + +
    diff --git a/aio/content/examples/cb-form-validation/ts/src/app/template/hero-form-template1.component.ts b/aio/content/examples/cb-form-validation/ts/src/app/template/hero-form-template1.component.ts new file mode 100644 index 0000000000..b096ca8cf4 --- /dev/null +++ b/aio/content/examples/cb-form-validation/ts/src/app/template/hero-form-template1.component.ts @@ -0,0 +1,48 @@ +/* tslint:disable: member-ordering */ +// #docplaster +// #docregion +import { Component } from '@angular/core'; + + +import { Hero } from '../shared/hero'; + +@Component({ + moduleId: module.id, + selector: 'hero-form-template1', + templateUrl: './hero-form-template1.component.html' +}) +// #docregion class +export class HeroFormTemplate1Component { + + powers = ['Really Smart', 'Super Flexible', 'Weather Changer']; + + hero = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What'); + + submitted = false; + + onSubmit() { + this.submitted = true; + } +// #enddocregion class +// #enddocregion + // Reset the form with a new hero AND restore 'pristine' class state + // by toggling 'active' flag which causes the form + // to be removed/re-added in a tick via NgIf + // TODO: Workaround until NgForm has a reset method (#6822) + active = true; +// #docregion +// #docregion class + + addHero() { + this.hero = new Hero(42, '', ''); +// #enddocregion class +// #enddocregion + + this.active = false; + setTimeout(() => this.active = true, 0); +// #docregion +// #docregion class + } +} +// #enddocregion class +// #enddocregion diff --git a/aio/content/examples/cb-form-validation/ts/src/app/template/hero-form-template2.component.html b/aio/content/examples/cb-form-validation/ts/src/app/template/hero-form-template2.component.html new file mode 100644 index 0000000000..8bb7066541 --- /dev/null +++ b/aio/content/examples/cb-form-validation/ts/src/app/template/hero-form-template2.component.html @@ -0,0 +1,52 @@ + +
    +
    +

    Hero Form 2 (Template & Messages)

    + +
    + +
    + + + + + + + +
    + {{ formErrors.name }} +
    + +
    + +
    + + +
    + +
    + + + +
    + {{ formErrors.power }} +
    +
    + + + +
    +
    + + +
    diff --git a/aio/content/examples/cb-form-validation/ts/src/app/template/hero-form-template2.component.ts b/aio/content/examples/cb-form-validation/ts/src/app/template/hero-form-template2.component.ts new file mode 100644 index 0000000000..8b3e150347 --- /dev/null +++ b/aio/content/examples/cb-form-validation/ts/src/app/template/hero-form-template2.component.ts @@ -0,0 +1,100 @@ +/* tslint:disable: member-ordering forin */ +// #docplaster +// #docregion +import { Component, AfterViewChecked, ViewChild } from '@angular/core'; +import { NgForm } from '@angular/forms'; + +import { Hero } from '../shared/hero'; + +@Component({ + moduleId: module.id, + selector: 'hero-form-template2', + templateUrl: './hero-form-template2.component.html' +}) +export class HeroFormTemplate2Component implements AfterViewChecked { + + powers = ['Really Smart', 'Super Flexible', 'Weather Changer']; + + hero = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What'); + + submitted = false; + + onSubmit() { + this.submitted = true; + } +// #enddocregion + + // Reset the form with a new hero AND restore 'pristine' class state + // by toggling 'active' flag which causes the form + // to be removed/re-added in a tick via NgIf + // TODO: Workaround until NgForm has a reset method (#6822) + active = true; +// #docregion + + addHero() { + this.hero = new Hero(42, '', ''); +// #enddocregion + + this.active = false; + setTimeout(() => this.active = true, 0); +// #docregion + } + + // #docregion view-child + heroForm: NgForm; + @ViewChild('heroForm') currentForm: NgForm; + + ngAfterViewChecked() { + this.formChanged(); + } + + formChanged() { + if (this.currentForm === this.heroForm) { return; } + this.heroForm = this.currentForm; + if (this.heroForm) { + this.heroForm.valueChanges + .subscribe(data => this.onValueChanged(data)); + } + } + // #enddocregion view-child + + // #docregion handler + onValueChanged(data?: any) { + if (!this.heroForm) { return; } + const form = this.heroForm.form; + + for (const field in this.formErrors) { + // clear previous error message (if any) + this.formErrors[field] = ''; + const control = form.get(field); + + if (control && control.dirty && !control.valid) { + const messages = this.validationMessages[field]; + for (const key in control.errors) { + this.formErrors[field] += messages[key] + ' '; + } + } + } + } + + formErrors = { + 'name': '', + 'power': '' + }; + // #enddocregion handler + + // #docregion messages + validationMessages = { + 'name': { + 'required': 'Name is required.', + 'minlength': 'Name must be at least 4 characters long.', + 'maxlength': 'Name cannot be more than 24 characters long.', + 'forbiddenName': 'Someone named "Bob" cannot be a hero.' + }, + 'power': { + 'required': 'Power is required.' + } + }; + // #enddocregion messages +} +// #enddocregion diff --git a/aio/content/examples/cb-form-validation/ts/src/forms.css b/aio/content/examples/cb-form-validation/ts/src/forms.css new file mode 100644 index 0000000000..67ad13037b --- /dev/null +++ b/aio/content/examples/cb-form-validation/ts/src/forms.css @@ -0,0 +1,7 @@ +.ng-valid[required], .ng-valid.required { + border-left: 5px solid #42A948; /* green */ +} + +.ng-invalid:not(form) { + border-left: 5px solid #a94442; /* red */ +} diff --git a/aio/content/examples/cb-form-validation/ts/src/index.html b/aio/content/examples/cb-form-validation/ts/src/index.html new file mode 100644 index 0000000000..e7201508d7 --- /dev/null +++ b/aio/content/examples/cb-form-validation/ts/src/index.html @@ -0,0 +1,29 @@ + + + Hero Form with Validation + + + + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/cb-form-validation/ts/src/main.ts b/aio/content/examples/cb-form-validation/ts/src/main.ts new file mode 100644 index 0000000000..f332d1d245 --- /dev/null +++ b/aio/content/examples/cb-form-validation/ts/src/main.ts @@ -0,0 +1,6 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/cb-i18n/e2e-spec.ts b/aio/content/examples/cb-i18n/e2e-spec.ts new file mode 100644 index 0000000000..3dcca5a670 --- /dev/null +++ b/aio/content/examples/cb-i18n/e2e-spec.ts @@ -0,0 +1,33 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('i18n E2E Tests', () => { + + beforeEach(function () { + browser.get(''); + }); + + it('should display i18n translated welcome: ¡Hola i18n!', function () { + expect(element(by.css('h1')).getText()).toEqual('¡Hola i18n!'); + }); + + it('should display the node texts without elements', function () { + expect(element(by.css('my-app')).getText()).toContain('No genero ningún elemento'); + expect(element(by.css('my-app')).getText()).toContain('Yo tampoco genero ningún elemento'); + }); + + it('should display the translated title attribute', function () { + const title = element(by.css('img')).getAttribute('title'); + expect(title).toBe('Logo de Angular'); + }); + + it('should display the plural of: a horde of wolves', function () { + expect(element.all(by.css('span')).get(0).getText()).toBe('ningún lobo'); + }); + + it('should display the select of gender', function () { + expect(element.all(by.css('span')).get(1).getText()).toBe('El heroe es mujer'); + }); + +}); diff --git a/aio/content/examples/cb-i18n/ts/.gitignore b/aio/content/examples/cb-i18n/ts/.gitignore new file mode 100644 index 0000000000..d38bb50aa9 --- /dev/null +++ b/aio/content/examples/cb-i18n/ts/.gitignore @@ -0,0 +1,6 @@ +**/*.ngfactory.ts +**/*.metadata.json +dist +!app/tsconfig.json +!rollup.js +!src/systemjs-text-plugin.js diff --git a/aio/content/examples/cb-i18n/ts/example-config.json b/aio/content/examples/cb-i18n/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/cb-i18n/ts/messages.xlf b/aio/content/examples/cb-i18n/ts/messages.xlf new file mode 100644 index 0000000000..dee8b65aca --- /dev/null +++ b/aio/content/examples/cb-i18n/ts/messages.xlf @@ -0,0 +1,47 @@ + + + + + + Hello i18n! + + An introduction header for this sample + User welcome + + + I don't output any element + + + + +I don't output any element either + + + optional description + optional meaning + + + Angular logo + + + + + + + + + + + + + + The hero is + + + + + + + + + diff --git a/aio/content/examples/cb-i18n/ts/plnkr.json b/aio/content/examples/cb-i18n/ts/plnkr.json new file mode 100644 index 0000000000..e2cbd40afe --- /dev/null +++ b/aio/content/examples/cb-i18n/ts/plnkr.json @@ -0,0 +1,18 @@ +{ + "description": "i18n", + "basePath": "src/", + "files": [ + "app/**/*.css", + "app/**/*.html", + "app/**/*.ts", + "messages.xlf", + "locale/messages.*.xlf", + + "!**/*.[1].*", + + "styles.css", + "systemjs-text-plugin.js", + "index.html" + ], + "tags": ["i18n"] +} diff --git a/aio/content/examples/cb-i18n/ts/plnkr.no-link.html b/aio/content/examples/cb-i18n/ts/plnkr.no-link.html new file mode 100644 index 0000000000..0a3fd393ec --- /dev/null +++ b/aio/content/examples/cb-i18n/ts/plnkr.no-link.html @@ -0,0 +1,329 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/cb-i18n/ts/src/app/app.component.1.html b/aio/content/examples/cb-i18n/ts/src/app/app.component.1.html new file mode 100644 index 0000000000..cc7d4f1155 --- /dev/null +++ b/aio/content/examples/cb-i18n/ts/src/app/app.component.1.html @@ -0,0 +1,15 @@ + +

    Hello i18n!

    + + + +

    Hello i18n!

    + + + +

    Hello i18n!

    + + + + + diff --git a/aio/content/examples/cb-i18n/ts/src/app/app.component.html b/aio/content/examples/cb-i18n/ts/src/app/app.component.html new file mode 100644 index 0000000000..39ace24f79 --- /dev/null +++ b/aio/content/examples/cb-i18n/ts/src/app/app.component.html @@ -0,0 +1,34 @@ + + +

    Hello i18n!

    + + + +I don't output any element + + +
    + + + +I don't output any element either + + + +
    + + + + +
    + + +{wolves, plural, =0 {no wolves} =1 {one wolf} =2 {two wolves} other {a wolf pack}} + +({{wolves}}) +

    + + +The hero is {gender, select, m {male} f {female}} + +
    diff --git a/aio/content/examples/cb-i18n/ts/src/app/app.component.ts b/aio/content/examples/cb-i18n/ts/src/app/app.component.ts new file mode 100644 index 0000000000..09adc99d2a --- /dev/null +++ b/aio/content/examples/cb-i18n/ts/src/app/app.component.ts @@ -0,0 +1,20 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: './app.component.html' +}) +export class AppComponent { + wolves = 0; + gender = 'f'; + fly = true; + logo = 'https://angular.io/resources/images/logos/angular2/angular.png'; + inc(i: number) { + this.wolves = Math.min(5, Math.max(0, this.wolves + i)); + } + male() { this.gender = 'm'; } + female() { this.gender = 'f'; } +} + diff --git a/aio/content/examples/cb-i18n/ts/src/app/app.module.ts b/aio/content/examples/cb-i18n/ts/src/app/app.module.ts new file mode 100644 index 0000000000..64ad44075b --- /dev/null +++ b/aio/content/examples/cb-i18n/ts/src/app/app.module.ts @@ -0,0 +1,13 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ BrowserModule ], + declarations: [ AppComponent ], + bootstrap: [ AppComponent ] +}) + +export class AppModule { } diff --git a/aio/content/examples/cb-i18n/ts/src/app/i18n-providers.ts b/aio/content/examples/cb-i18n/ts/src/app/i18n-providers.ts new file mode 100644 index 0000000000..f0bb662dc3 --- /dev/null +++ b/aio/content/examples/cb-i18n/ts/src/app/i18n-providers.ts @@ -0,0 +1,33 @@ +// #docregion +import { TRANSLATIONS, TRANSLATIONS_FORMAT, LOCALE_ID } from '@angular/core'; + +export function getTranslationProviders(): Promise { + + // Get the locale id from the global + const locale = document['locale'] as string; + + // return no providers if fail to get translation file for locale + const noProviders: Object[] = []; + + // No locale or U.S. English: no translation providers + if (!locale || locale === 'en-US') { + return Promise.resolve(noProviders); + } + + // Ex: 'locale/messages.es.xlf` + const translationFile = `./locale/messages.${locale}.xlf`; + + return getTranslationsWithSystemJs(translationFile) + .then( (translations: string ) => [ + { provide: TRANSLATIONS, useValue: translations }, + { provide: TRANSLATIONS_FORMAT, useValue: 'xlf' }, + { provide: LOCALE_ID, useValue: locale } + ]) + .catch(() => noProviders); // ignore if file not found +} + +declare var System: any; + +function getTranslationsWithSystemJs(file: string) { + return System.import(file + '!text'); // relies on text plugin +} diff --git a/aio/content/examples/cb-i18n/ts/src/index.html b/aio/content/examples/cb-i18n/ts/src/index.html new file mode 100644 index 0000000000..ce90cd24c4 --- /dev/null +++ b/aio/content/examples/cb-i18n/ts/src/index.html @@ -0,0 +1,39 @@ + + + + + Angular i18n example + + + + + + + + + + + + + + + + + + + Loading... + + diff --git a/aio/content/examples/cb-i18n/ts/src/locale/messages.es.xlf b/aio/content/examples/cb-i18n/ts/src/locale/messages.es.xlf new file mode 100644 index 0000000000..7b813c38dd --- /dev/null +++ b/aio/content/examples/cb-i18n/ts/src/locale/messages.es.xlf @@ -0,0 +1,41 @@ + + + + + + Hello i18n! + ¡Hola i18n! + An introduction header for this sample + User welcome + + + I don't output any element + No genero ningún elemento + + + +I don't output any element either + + Yo tampoco genero ningún elemento + optional description + optional meaning + + + Angular logo + Logo de Angular + + + + {wolves, plural, =0 {ningún lobo} =1 {un lobo} =2 {dos lobos} other {una horda de lobos}} + + + The hero is + El heroe es + + + + {gender, select, m {hombre} f {mujer}} + + + + diff --git a/aio/content/examples/cb-i18n/ts/src/locale/messages.es.xlf.html b/aio/content/examples/cb-i18n/ts/src/locale/messages.es.xlf.html new file mode 100644 index 0000000000..a6cdccc6c1 --- /dev/null +++ b/aio/content/examples/cb-i18n/ts/src/locale/messages.es.xlf.html @@ -0,0 +1,54 @@ + + + + + + + + + Hello i18n! + ¡Hola i18n! + An introduction header for this sample + User welcome + + + + + I don't output any element + No genero ningún elemento + + + I don't output any element either + Yo tampoco genero ningún elemento + optional description + optional meaning + + + Angular logo + Logo de Angular + + + + + + {wolves, plural, =0 {ningún lobo} =1 {un lobo} =2 {dos lobos} other {una horda de lobos}} + + + + + + The hero is + El heroe es + + + + + + {gender, select, m {hombre} f {mujer}} + + + + + + + diff --git a/aio/content/examples/cb-i18n/ts/src/main.1.ts b/aio/content/examples/cb-i18n/ts/src/main.1.ts new file mode 100644 index 0000000000..f332d1d245 --- /dev/null +++ b/aio/content/examples/cb-i18n/ts/src/main.1.ts @@ -0,0 +1,6 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/cb-i18n/ts/src/main.ts b/aio/content/examples/cb-i18n/ts/src/main.ts new file mode 100644 index 0000000000..894cecfb10 --- /dev/null +++ b/aio/content/examples/cb-i18n/ts/src/main.ts @@ -0,0 +1,10 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { getTranslationProviders } from './app/i18n-providers'; + +import { AppModule } from './app/app.module'; + +getTranslationProviders().then(providers => { + const options = { providers }; + platformBrowserDynamic().bootstrapModule(AppModule, options); +}); diff --git a/aio/content/examples/cb-i18n/ts/src/systemjs-text-plugin.js b/aio/content/examples/cb-i18n/ts/src/systemjs-text-plugin.js new file mode 100644 index 0000000000..d5ca508fe0 --- /dev/null +++ b/aio/content/examples/cb-i18n/ts/src/systemjs-text-plugin.js @@ -0,0 +1,14 @@ +// #docregion +/* + SystemJS Text plugin from + https://github.com/systemjs/plugin-text/blob/master/text.js +*/ +exports.translate = function(load) { + if (this.builder && this.transpiler) { + load.metadata.format = 'esm'; + return 'exp' + 'ort var __useDefault = true; exp' + 'ort default ' + JSON.stringify(load.source) + ';'; + } + + load.metadata.format = 'amd'; + return 'def' + 'ine(function() {\nreturn ' + JSON.stringify(load.source) + ';\n});'; +} diff --git a/aio/content/examples/cb-set-document-title/e2e-spec.ts b/aio/content/examples/cb-set-document-title/e2e-spec.ts new file mode 100644 index 0000000000..801b732995 --- /dev/null +++ b/aio/content/examples/cb-set-document-title/e2e-spec.ts @@ -0,0 +1,31 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by, ElementFinder } from 'protractor'; + +// gulp run-e2e-tests --filter=cb-set-document-title +describe('Set Document Title', function () { + + beforeAll(function () { + browser.get(''); + }); + + it('should set the document title', function () { + + let titles = [ + 'Good morning!', + 'Good afternoon!', + 'Good evening!' + ]; + + element.all( by.css( 'ul li a' ) ).each( + function iterator( element: ElementFinder, i: number ) { + + element.click(); + expect( browser.getTitle() ).toEqual( titles[ i ] ); + + } + ); + + }); + +}); diff --git a/aio/content/examples/cb-set-document-title/ts/example-config.json b/aio/content/examples/cb-set-document-title/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/cb-set-document-title/ts/plnkr.json b/aio/content/examples/cb-set-document-title/ts/plnkr.json new file mode 100644 index 0000000000..020d5a1db2 --- /dev/null +++ b/aio/content/examples/cb-set-document-title/ts/plnkr.json @@ -0,0 +1,10 @@ +{ + "description": "Set The Document Title In Angular", + "basePath": "src/", + "files": [ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1].*" + ], + "tags": [ "cookbook" ] +} diff --git a/aio/content/examples/cb-set-document-title/ts/plnkr.no-link.html b/aio/content/examples/cb-set-document-title/ts/plnkr.no-link.html new file mode 100644 index 0000000000..915ea603b5 --- /dev/null +++ b/aio/content/examples/cb-set-document-title/ts/plnkr.no-link.html @@ -0,0 +1,228 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/cb-set-document-title/ts/src/app/app.component.ts b/aio/content/examples/cb-set-document-title/ts/src/app/app.component.ts new file mode 100644 index 0000000000..f1905e635d --- /dev/null +++ b/aio/content/examples/cb-set-document-title/ts/src/app/app.component.ts @@ -0,0 +1,29 @@ +// #docplaster +// #docregion +// Import the native Angular services. +import { Component } from '@angular/core'; +import { Title } from '@angular/platform-browser'; + +@Component({ +selector: 'my-app', +template: + `

    + Select a title to set on the current HTML document: +

    + + + ` +}) +// #docregion class +export class AppComponent { + public constructor(private titleService: Title ) { } + + public setTitle( newTitle: string) { + this.titleService.setTitle( newTitle ); + } +} +// #enddocregion class diff --git a/aio/content/examples/cb-set-document-title/ts/src/app/app.module.ts b/aio/content/examples/cb-set-document-title/ts/src/app/app.module.ts new file mode 100644 index 0000000000..81f13c244c --- /dev/null +++ b/aio/content/examples/cb-set-document-title/ts/src/app/app.module.ts @@ -0,0 +1,19 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule, Title } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ + BrowserModule + ], + declarations: [ + AppComponent + ], + providers: [ + Title + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/cb-set-document-title/ts/src/index.html b/aio/content/examples/cb-set-document-title/ts/src/index.html new file mode 100644 index 0000000000..6b00948530 --- /dev/null +++ b/aio/content/examples/cb-set-document-title/ts/src/index.html @@ -0,0 +1,39 @@ + + + + + + + + + Setting The Document Title Using The Title Service + + + + + + + + + + + + + + + + + + +

    + Setting The Document Title Using The Title Service +

    + + Loading app... + + + diff --git a/aio/content/examples/cb-set-document-title/ts/src/main.ts b/aio/content/examples/cb-set-document-title/ts/src/main.ts new file mode 100644 index 0000000000..f332d1d245 --- /dev/null +++ b/aio/content/examples/cb-set-document-title/ts/src/main.ts @@ -0,0 +1,6 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/cb-ts-to-js/e2e-spec.ts b/aio/content/examples/cb-ts-to-js/e2e-spec.ts new file mode 100644 index 0000000000..bc67bac8f0 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/e2e-spec.ts @@ -0,0 +1,77 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('TypeScript to Javascript tests', function () { + + beforeAll(function () { + browser.get(''); + }); + + it('should display the basic component example', function () { + testTag('hero-view', 'Hero Detail: Windstorm'); + }); + + it('should display the component example with lifecycle methods', function () { + testTag('hero-lifecycle', 'Hero: Windstorm'); + }); + + it('should display component with DI example', function () { + testTag('hero-di', 'Hero: Windstorm'); + }); + + it('should display component with DI using @Inject example', function () { + testTag('hero-di-inject', 'Hero: Windstorm'); + }); + + it('should support optional, attribute, and query injections', function () { + let app = element(by.css('hero-di-inject-additional')); + let h1 = app.element(by.css('h1')); + let okMsg = app.element(by.css('p')); + + expect(h1.getText()).toBe('Tour of Heroes'); + app.element(by.buttonText('OK')).click(); + expect(okMsg.getText()).toBe('OK!'); + }); + + it('should support component with inputs and outputs', function () { + let app = element(by.css('hero-io')); + let confirmComponent = app.element(by.css('app-confirm')); + + confirmComponent.element(by.buttonText('OK')).click(); + expect(app.element(by.cssContainingText('span', 'OK clicked')).isPresent()).toBe(true); + + confirmComponent.element(by.buttonText('Cancel')).click(); + expect(app.element(by.cssContainingText('span', 'Cancel clicked')).isPresent()).toBe(true); + }); + + it('should support host bindings and host listeners', function() { + let app = element(by.css('hero-host')); + let h1 = app.element(by.css('h1')); + + expect(app.getAttribute('class')).toBe('heading'); + expect(app.getAttribute('title')).toContain('Tooltip'); + + h1.click(); + expect(h1.getAttribute('class')).toBe('active'); + + h1.click(); + browser.actions().doubleClick(h1.getWebElement()).perform(); + expect(h1.getAttribute('class')).toBe('active'); + }); + + it('should support content and view queries', function() { + let app = element(by.css('hero-queries')); + let windstorm = app.element(by.css('view-child:first-child')); + + app.element(by.css('button')).click(); + expect(windstorm.element(by.css('h2')).getAttribute('class')).toBe('active'); + expect(windstorm.element(by.css('content-child')).getText()).toBe('Active'); + }); + + function testTag(selector: string, expectedText: string) { + let component = element(by.css(selector)); + expect(component.getText()).toBe(expectedText); + } + +}); diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/example-config.json b/aio/content/examples/cb-ts-to-js/js-es6-decorators/example-config.json new file mode 100644 index 0000000000..81f31aaf0d --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/example-config.json @@ -0,0 +1,3 @@ +{ + "build": "build:babel" +} diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/plnkr.json b/aio/content/examples/cb-ts-to-js/js-es6-decorators/plnkr.json new file mode 100644 index 0000000000..447fc5f605 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "TypeScript to JavaScript", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js" + ], + "tags":["cookbook"] +} diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/plnkr.no-link.html b/aio/content/examples/cb-ts-to-js/js-es6-decorators/plnkr.no-link.html new file mode 100644 index 0000000000..618489fc11 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/plnkr.no-link.html @@ -0,0 +1,209 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/.babelrc b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/.babelrc new file mode 100644 index 0000000000..3aaf507508 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/.babelrc @@ -0,0 +1,6 @@ +{ + "presets": [ + "es2015", + "angular2" + ] +} diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/app.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/app.component.es6 new file mode 100644 index 0000000000..8e65fff539 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/app.component.es6 @@ -0,0 +1,15 @@ +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: './app.component.html', + styles: [ + // See hero-di-inject-additional.component + 'hero-host, hero-host-meta { border: 1px dashed black; display: block; padding: 4px;}', + '.heading {font-style: italic}' + ] +}) +export class AppComponent { + title = 'ES6 JavaScript with Decorators'; +} diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/app.component.html b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/app.component.html new file mode 100644 index 0000000000..995645073a --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/app.component.html @@ -0,0 +1,31 @@ + +

    {{title}}

    +Classes and Class Metadata
    +Input and Output Decorators
    +Dependency Injection
    +Host Metadata
    +View and Child Metadata
    + +
    +

    Classes and Class Metadata

    + + + +
    +

    Input and Output Metadata

    + + +
    +

    Dependency Injection

    + + + + +
    +

    Host Metadata

    + + + +
    +

    View and Child Metadata

    + diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/app.module.es6 b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/app.module.es6 new file mode 100644 index 0000000000..9c248a7ad3 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/app.module.es6 @@ -0,0 +1,55 @@ +import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; +import { ConfirmComponent } from './confirm.component'; +// #docregion appimport +import { HeroComponent } from './hero.component'; +// #enddocregion appimport +import { HeroComponent as HeroDIComponent } from './hero-di.component'; +import { HeroComponent as HeroDIInjectComponent } from './hero-di-inject.component'; +import { HeroComponent as HeroDIInjectAdditionalComponent } from './hero-di-inject-additional.component'; +import { HeroHostComponent } from './hero-host.component'; +import { HeroHostMetaComponent } from './hero-host-meta.component'; +import { HeroIOComponent } from './hero-io.component'; +import { HeroComponent as HeroLifecycleComponent } from './hero-lifecycle.component'; +import { HeroQueriesComponent, ViewChildComponent, ContentChildComponent } from './hero-queries.component'; +import { HeroTitleComponent } from './hero-title.component'; + +import { DataService } from './data.service'; + +@NgModule({ + imports: [ + BrowserModule + ], + declarations: [ + AppComponent, + ConfirmComponent, + HeroComponent, + HeroDIComponent, + HeroDIInjectComponent, + HeroDIInjectAdditionalComponent, + HeroHostComponent, HeroHostMetaComponent, + HeroIOComponent, + HeroLifecycleComponent, + HeroQueriesComponent, ViewChildComponent, ContentChildComponent, + HeroTitleComponent + ], + providers: [ + DataService, + { provide: 'heroName', useValue: 'Windstorm' } + ], + bootstrap: [ AppComponent ], + + // schemas: [ NO_ERRORS_SCHEMA ] // helpful for debugging +}) +export class AppModule { } + +/* tslint:disable no-unused-variable */ +// #docregion ng2import +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { + LocationStrategy, + HashLocationStrategy +} from '@angular/common'; +// #enddocregion ng2import diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/confirm.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/confirm.component.es6 new file mode 100644 index 0000000000..c9be28c44c --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/confirm.component.es6 @@ -0,0 +1,22 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +// #docregion +@Component({ + moduleId: module.id, + selector: 'app-confirm', + templateUrl: './confirm.component.html' +}) +export class ConfirmComponent { + @Input() okMsg = ''; + @Input('cancelMsg') notOkMsg = ''; + @Output() ok = new EventEmitter(); + @Output('cancel') notOk = new EventEmitter(); + + onOkClick() { + this.ok.emit(true); + } + onNotOkClick() { + this.notOk.emit(true); + } +} +// #enddocregion diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/confirm.component.html b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/confirm.component.html new file mode 100644 index 0000000000..45275d218a --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/confirm.component.html @@ -0,0 +1,6 @@ + + diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/data.service.es6 b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/data.service.es6 new file mode 100644 index 0000000000..cd7f9e1aae --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/data.service.es6 @@ -0,0 +1,10 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class DataService { + constructor() { } + + getHeroName() { + return 'Windstorm'; + } +} diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-di-inject-additional.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-di-inject-additional.component.es6 new file mode 100644 index 0000000000..ec460a9dbc --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-di-inject-additional.component.es6 @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'hero-di-inject-additional', + template: `` +}) +export class HeroComponent { } diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-di-inject.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-di-inject.component.es6 new file mode 100644 index 0000000000..94b42f956a --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-di-inject.component.es6 @@ -0,0 +1,13 @@ +import { Component, Inject } from '@angular/core'; + +// #docregion +@Component({ + selector: 'hero-di-inject', + template: `

    Hero: {{name}}

    ` +}) +export class HeroComponent { + constructor(@Inject('heroName') name) { + this.name = name; + } +} +// #enddocregion diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-di.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-di.component.es6 new file mode 100644 index 0000000000..3a17abd281 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-di.component.es6 @@ -0,0 +1,15 @@ +// #docregion +import { Component } from '@angular/core'; +import { DataService } from './data.service'; + +@Component({ + selector: 'hero-di', + template: `

    Hero: {{name}}

    ` +}) +export class HeroComponent { + name = ''; + constructor(dataService: DataService) { + this.name = dataService.getHeroName(); + } +} +// #enddocregion diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-host-meta.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-host-meta.component.es6 new file mode 100644 index 0000000000..fefe4a5470 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-host-meta.component.es6 @@ -0,0 +1,44 @@ +import { Component } from '@angular/core'; + +// #docregion +@Component({ + selector: 'hero-host-meta', + template: ` +

    Hero Host in Metadata

    +
    Heading clicks: {{clicks}}
    + `, + host: { + // HostBindings to the element + '[title]': 'title', + '[class.heading]': 'headingClass', + + // HostListeners on the entire element + '(click)': 'clicked()', + '(mouseenter)': 'enter($event)', + '(mouseleave)': 'leave($event)' + }, + // Styles within (but excluding) the element + styles: ['.active {background-color: coral;}'] +}) +export class HeroHostMetaComponent { + title = 'Hero Host in Metadata Tooltip'; + headingClass = true; + + active = false; + clicks = 0; + + clicked() { + this.clicks += 1; + } + + enter(event: Event) { + this.active = true; + this.headingClass = false; + } + + leave(event: Event) { + this.active = false; + this.headingClass = true; + } +} +// #enddocregion diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-host.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-host.component.es6 new file mode 100644 index 0000000000..e8d72233c8 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-host.component.es6 @@ -0,0 +1,39 @@ +import { Component, HostBinding, HostListener } from '@angular/core'; + +// #docregion +@Component({ + selector: 'hero-host', + template: ` +

    Hero Host in Decorators

    +
    Heading clicks: {{clicks}}
    + `, + // Styles within (but excluding) the element + styles: ['.active {background-color: yellow;}'] +}) +export class HeroHostComponent { + // HostBindings to the element + @HostBinding() title = 'Hero Host in Decorators Tooltip'; + @HostBinding('class.heading') headingClass = true; + + active = false; + clicks = 0; + + // HostListeners on the entire element + @HostListener('click') + clicked() { + this.clicks += 1; + } + + @HostListener('mouseenter', ['$event']) + enter(event: Event) { + this.active = true; + this.headingClass = false; + } + + @HostListener('mouseleave', ['$event']) + leave(event: Event) { + this.active = false; + this.headingClass = true; + } +} +// #enddocregion diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-io.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-io.component.es6 new file mode 100644 index 0000000000..4b36411e78 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-io.component.es6 @@ -0,0 +1,26 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'hero-io', + template: ` + + + OK clicked + Cancel clicked + ` +}) +export class HeroIOComponent { + okClicked = false; + cancelClicked = false; + + onOk() { + this.okClicked = true; + } + + onCancel() { + this.cancelClicked = true; + } +} diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-lifecycle.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-lifecycle.component.es6 new file mode 100644 index 0000000000..2539266597 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-lifecycle.component.es6 @@ -0,0 +1,14 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'hero-lifecycle', + template: `

    Hero: {{name}}

    ` +}) +export class HeroComponent { + name = ''; + ngOnInit() { + // todo: fetch from server async + setTimeout(() => this.name = 'Windstorm', 0); + } +} diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6 new file mode 100644 index 0000000000..fced43d4d7 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6 @@ -0,0 +1,81 @@ +import { + Component, + ContentChild, + Input, + QueryList, + ViewChildren +} from '@angular/core'; + +@Component({ + selector: 'content-child', + template: ` + + Active + ` +}) +export class ContentChildComponent { + active = false; + + activate() { + this.active = true; + } +} + +//////////////////// + +// #docregion content +@Component({ + selector: 'view-child', + template: ` +

    + {{hero.name}} + +

    `, + styles: ['.active {font-weight: bold; background-color: skyblue;}'] +}) +export class ViewChildComponent { + @Input() hero; + active = false; + + @ContentChild(ContentChildComponent) content; + + activate() { + this.active = !this.active; + this.content.activate(); + } +} +// #enddocregion content + +//////////////////// + +// #docregion view +@Component({ + selector: 'hero-queries', + template: ` + + + + + ` +}) +export class HeroQueriesComponent { + active = false; + heroData = [ + {id: 1, name: 'Windstorm'}, + {id: 2, name: 'LaughingGas'} + ]; + + @ViewChildren(ViewChildComponent) views; + + activate() { + this.active = !this.active; + this.views.forEach( + view => view.activate() + ); + } + + get buttonLabel() { + return this.active ? 'Deactivate' : 'Activate'; + } +} +// #enddocregion view diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-title.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-title.component.es6 new file mode 100644 index 0000000000..32988dcdda --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-title.component.es6 @@ -0,0 +1,26 @@ +import { Attribute, Component, Inject, Optional } from '@angular/core'; + +// #docregion +// #docregion templateUrl +@Component({ + moduleId: module.id, + selector: 'hero-title', + templateUrl: './hero-title.component.html' +}) +// #enddocregion templateUrl +export class HeroTitleComponent { + msg = ''; + constructor( + @Inject('titlePrefix') @Optional() titlePrefix, + @Attribute('title') title + ) { + this.titlePrefix = titlePrefix; + this.title = title; + } + + ok() { + this.msg = 'OK!'; + } +} +// #enddocregion + diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-title.component.html b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-title.component.html new file mode 100644 index 0000000000..164683cb7c --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero-title.component.html @@ -0,0 +1,4 @@ + +

    {{titlePrefix}} {{title}}

    + +

    {{ msg }}

    diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero.component.es6 new file mode 100644 index 0000000000..2976ec605e --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/app/hero.component.es6 @@ -0,0 +1,15 @@ +// #docregion +// #docregion metadata +import { Component } from '@angular/core'; + +@Component({ + selector: 'hero-view', + template: '

    {{title}}: {{getName()}}

    ' +}) +// #docregion appexport, class +export class HeroComponent { + title = 'Hero Detail'; + getName() {return 'Windstorm'; } +} +// #enddocregion appexport, class +// #enddocregion metadata diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/index.html b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/index.html new file mode 100644 index 0000000000..2a94db38cc --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/index.html @@ -0,0 +1,26 @@ + + + + + + + + TypeScript to JavaScript + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/main.es6 b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/main.es6 new file mode 100644 index 0000000000..f22933ba8e --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6-decorators/src/main.es6 @@ -0,0 +1,4 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/cb-ts-to-js/js-es6/example-config.json b/aio/content/examples/cb-ts-to-js/js-es6/example-config.json new file mode 100644 index 0000000000..81f31aaf0d --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/example-config.json @@ -0,0 +1,3 @@ +{ + "build": "build:babel" +} diff --git a/aio/content/examples/cb-ts-to-js/js-es6/plnkr.json b/aio/content/examples/cb-ts-to-js/js-es6/plnkr.json new file mode 100644 index 0000000000..447fc5f605 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "TypeScript to JavaScript", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js" + ], + "tags":["cookbook"] +} diff --git a/aio/content/examples/cb-ts-to-js/js-es6/plnkr.no-link.html b/aio/content/examples/cb-ts-to-js/js-es6/plnkr.no-link.html new file mode 100644 index 0000000000..f47b7742b6 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/plnkr.no-link.html @@ -0,0 +1,208 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/cb-ts-to-js/js-es6/src/.babelrc b/aio/content/examples/cb-ts-to-js/js-es6/src/.babelrc new file mode 100644 index 0000000000..3c078e9f99 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/src/.babelrc @@ -0,0 +1,5 @@ +{ + "presets": [ + "es2015" + ] +} diff --git a/aio/content/examples/cb-ts-to-js/js-es6/src/app/app.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6/src/app/app.component.es6 new file mode 100644 index 0000000000..3084439b2a --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/src/app/app.component.es6 @@ -0,0 +1,20 @@ +import { Component } from '@angular/core'; + +export class AppComponent { + constructor() { + this.title = 'Plain ES6 JavaScript'; + } +} + +AppComponent.annotations = [ + new Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: './app.component.html', + styles: [ + // See hero-di-inject-additional.component + 'hero-host { border: 1px dashed black; display: block; padding: 4px;}', + '.heading {font-style: italic}' + ] + }) +]; diff --git a/aio/content/examples/cb-ts-to-js/js-es6/src/app/app.component.html b/aio/content/examples/cb-ts-to-js/js-es6/src/app/app.component.html new file mode 100644 index 0000000000..52b9b4580e --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/src/app/app.component.html @@ -0,0 +1,30 @@ + +

    {{title}}

    +Classes and Class Metadata
    +Input and Output Metadata
    +Dependency Injection
    +Host Metadata
    +View and Child Metadata
    + +
    +

    Classes and Class Metadata

    + + + +
    +

    Input and Output Metadata

    + + +
    +

    Dependency Injection

    + + + + +
    +

    Host Metadata

    + + +
    +

    View and Child Metadata

    + diff --git a/aio/content/examples/cb-ts-to-js/js-es6/src/app/app.module.es6 b/aio/content/examples/cb-ts-to-js/js-es6/src/app/app.module.es6 new file mode 100644 index 0000000000..f31141e78f --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/src/app/app.module.es6 @@ -0,0 +1,56 @@ +import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; +import { ConfirmComponent } from './confirm.component'; +// #docregion appimport +import { HeroComponent } from './hero.component'; + +// #enddocregion appimport +import { HeroComponent as HeroDIComponent } from './hero-di.component'; +import { HeroComponent as HeroDIInjectComponent } from './hero-di-inject.component'; +import { HeroComponent as HeroDIInjectAdditionalComponent } from './hero-di-inject-additional.component'; +import { HeroHostComponent } from './hero-host.component'; +import { HeroIOComponent } from './hero-io.component'; +import { HeroComponent as HeroLifecycleComponent } from './hero-lifecycle.component'; +import { HeroQueriesComponent, ViewChildComponent, ContentChildComponent } from './hero-queries.component'; +import { HeroTitleComponent } from './hero-title.component'; + +import { DataService } from './data.service'; + +export class AppModule { } + +AppModule.annotations = [ + new NgModule({ + imports: [ BrowserModule], + declarations: [ + AppComponent, + ConfirmComponent, + HeroComponent, + HeroDIComponent, + HeroDIInjectComponent, + HeroDIInjectAdditionalComponent, + HeroHostComponent, + HeroIOComponent, + HeroLifecycleComponent, + HeroQueriesComponent, ViewChildComponent, ContentChildComponent, + HeroTitleComponent + ], + providers: [ + DataService, + { provide: 'heroName', useValue: 'Windstorm' } + ], + bootstrap: [ AppComponent ], + + // schemas: [ NO_ERRORS_SCHEMA ] // helpful for debugging + }) +] + +/* tslint:disable no-unused-variable */ +// #docregion ng2import +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { + LocationStrategy, + HashLocationStrategy +} from '@angular/common'; +// #enddocregion ng2import diff --git a/aio/content/examples/cb-ts-to-js/js-es6/src/app/confirm.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6/src/app/confirm.component.es6 new file mode 100644 index 0000000000..3e72036952 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/src/app/confirm.component.es6 @@ -0,0 +1,32 @@ +import { Component, EventEmitter } from '@angular/core'; + +// #docregion +export class ConfirmComponent { + constructor(){ + this.ok = new EventEmitter(); + this.notOk = new EventEmitter(); + } + onOkClick() { + this.ok.emit(true); + } + onNotOkClick() { + this.notOk.emit(true); + } +} + +ConfirmComponent.annotations = [ + new Component({ + moduleId: module.id, + selector: 'app-confirm', + templateUrl: './confirm.component.html', + inputs: [ + 'okMsg', + 'notOkMsg: cancelMsg' + ], + outputs: [ + 'ok', + 'notOk: cancel' + ] + }) +]; +// #enddocregion diff --git a/aio/content/examples/cb-ts-to-js/js-es6/src/app/confirm.component.html b/aio/content/examples/cb-ts-to-js/js-es6/src/app/confirm.component.html new file mode 100644 index 0000000000..45275d218a --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/src/app/confirm.component.html @@ -0,0 +1,6 @@ + + diff --git a/aio/content/examples/cb-ts-to-js/js-es6/src/app/data.service.es6 b/aio/content/examples/cb-ts-to-js/js-es6/src/app/data.service.es6 new file mode 100644 index 0000000000..de023ce5a0 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/src/app/data.service.es6 @@ -0,0 +1,13 @@ +import { Injectable } from '@angular/core'; + +export class DataService { + constructor() { + } + getHeroName() { + return 'Windstorm'; + } +} + +DataService.annotations = [ + new Injectable() +]; diff --git a/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-di-inject-additional.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-di-inject-additional.component.es6 new file mode 100644 index 0000000000..5eb9bab815 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-di-inject-additional.component.es6 @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +export class HeroComponent { } + +HeroComponent.annotations = [ + new Component({ + selector: 'hero-di-inject-additional', + template: `` + }) +]; diff --git a/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-di-inject.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-di-inject.component.es6 new file mode 100644 index 0000000000..2f95a0b981 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-di-inject.component.es6 @@ -0,0 +1,20 @@ +import { Component, Inject } from '@angular/core'; + +// #docregion +export class HeroComponent { + constructor(name) { + this.name = name; + } +} + +HeroComponent.annotations = [ + new Component({ + selector: 'hero-di-inject', + template: `

    Hero: {{name}}

    ` + }) +]; + +HeroComponent.parameters = [ + [new Inject('heroName')] +]; +// #enddocregion diff --git a/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-di.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-di.component.es6 new file mode 100644 index 0000000000..a18b1ba777 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-di.component.es6 @@ -0,0 +1,21 @@ +// #docregion +import { Component } from '@angular/core'; +import { DataService } from './data.service'; + +export class HeroComponent { + constructor(dataService) { + this.name = dataService.getHeroName(); + } +} + +HeroComponent.annotations = [ + new Component({ + selector: 'hero-di', + template: `

    Hero: {{name}}

    ` + }) +]; + +HeroComponent.parameters = [ + [DataService] +]; +// #enddocregion diff --git a/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-host.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-host.component.es6 new file mode 100644 index 0000000000..b06701ee8d --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-host.component.es6 @@ -0,0 +1,50 @@ +import { Component } from '@angular/core'; + +// #docregion +export class HeroHostComponent { + constructor() { + this.active = false; + this.clicks = 0; + this.headingClass = true; + this.title = 'Hero Host Tooltip'; + } + + clicked() { + this.clicks += 1; + } + + enter(event) { + this.active = true; + this.headingClass = false; + } + + leave(event) { + this.active = false; + this.headingClass = true; + } +} + +// #docregion metadata +HeroHostComponent.annotations = [ + new Component({ + selector: 'hero-host', + template: ` +

    Hero Host

    +
    Heading clicks: {{clicks}}
    + `, + host: { + // HostBindings to the element + '[title]': 'title', + '[class.heading]': 'headingClass', + '(click)': 'clicked()', + + // HostListeners on the entire element + '(mouseenter)': 'enter($event)', + '(mouseleave)': 'leave($event)' + }, + // Styles within (but excluding) the element + styles: ['.active {background-color: yellow;}'] + }) +]; +// #enddocregion metadata +// #enddocregion diff --git a/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-io.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-io.component.es6 new file mode 100644 index 0000000000..0dc8c08f2d --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-io.component.es6 @@ -0,0 +1,31 @@ +import { Component } from '@angular/core'; + +export class HeroIOComponent { + constructor() { + this.okClicked = false; + this.cancelClicked = false; + } + + onOk() { + this.okClicked = true; + } + + onCancel() { + this.cancelClicked = true; + } +} + +HeroIOComponent.annotations = [ + new Component({ + selector: 'hero-io', + template: ` + + + OK clicked + Cancel clicked + ` + }) +]; diff --git a/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-lifecycle.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-lifecycle.component.es6 new file mode 100644 index 0000000000..b394ff59aa --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-lifecycle.component.es6 @@ -0,0 +1,15 @@ +// #docregion +import { Component } from '@angular/core'; +export class HeroComponent { + ngOnInit() { + // todo: fetch from server async + setTimeout(() => this.name = 'Windstorm', 0); + } +} + +HeroComponent.annotations = [ + new Component({ + selector: 'hero-lifecycle', + template: `

    Hero: {{name}}

    ` + }) +]; diff --git a/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-queries.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-queries.component.es6 new file mode 100644 index 0000000000..bf3b914406 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-queries.component.es6 @@ -0,0 +1,97 @@ +import { + Component, + ContentChild, + Input, + QueryList, + ViewChildren +} from '@angular/core'; + +export class ContentChildComponent { + constructor() { + this.active = false; + } + + activate() { + this.active = !this.active; + } +} + +ContentChildComponent.annotations = [ + new Component({ + selector: 'content-child', + template: ` + + Active + ` + }) +]; + +//////////////////// + +// #docregion content +export class ViewChildComponent { + constructor() { + this.active = false; + } + + activate() { + this.active = !this.active; + this.content.activate(); + } +} + +ViewChildComponent.annotations = [ + new Component({ + selector: 'view-child', + template: `

    + {{hero.name}} + +

    `, + styles: ['.active {font-weight: bold; background-color: skyblue;}'], + inputs: ['hero'], + queries: { + content: new ContentChild(ContentChildComponent) + } + }) +]; +// #enddocregion content + +//////////////////// + +// #docregion view +export class HeroQueriesComponent { + constructor(){ + this.active = false; + this.heroData = [ + {id: 1, name: 'Windstorm'}, + {id: 2, name: 'LaughingGas'} + ]; + } + + activate() { + this.active = !this.active; + this.views.forEach( + view => view.activate() + ); + } + + get buttonLabel() { + return this.active ? 'Deactivate' : 'Activate'; + } +} + +HeroQueriesComponent.annotations = [ + new Component({ + selector: 'hero-queries', + template: ` + + + + + `, + queries: { + views: new ViewChildren(ViewChildComponent) + } + }) +]; +// #enddocregion view diff --git a/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-title.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-title.component.es6 new file mode 100644 index 0000000000..1656226602 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-title.component.es6 @@ -0,0 +1,29 @@ +import { Attribute, Component, Inject, Optional } from '@angular/core'; + +// #docregion +export class HeroTitleComponent { + constructor(titlePrefix, title) { + this.titlePrefix = titlePrefix; + this.title = title; + this.msg = ''; + } + + ok() { + this.msg = 'OK!'; + } +} + +// #docregion templateUrl +HeroTitleComponent.annotations = [ + new Component({ + moduleId: module.id, + selector: 'hero-title', + templateUrl: './hero-title.component.html' + }) +]; +// #enddocregion templateUrl + +HeroTitleComponent.parameters = [ + [new Optional(), new Inject('titlePrefix')], + [new Attribute('title')] +]; diff --git a/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-title.component.html b/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-title.component.html new file mode 100644 index 0000000000..164683cb7c --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero-title.component.html @@ -0,0 +1,4 @@ + +

    {{titlePrefix}} {{title}}

    + +

    {{ msg }}

    diff --git a/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero.component.es6 b/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero.component.es6 new file mode 100644 index 0000000000..350f946460 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/src/app/hero.component.es6 @@ -0,0 +1,21 @@ +// #docplaster +// #docregion +// #docregion metadata +import { Component } from '@angular/core'; + +// #docregion appexport, class +export class HeroComponent { + constructor() { + this.title = 'Hero Detail'; + } + getName() {return 'Windstorm'; } +} +// #enddocregion appexport, class + +HeroComponent.annotations = [ + new Component({ + selector: 'hero-view', + template: '

    {{title}}: {{getName()}}

    ' + }) +]; +// #enddocregion metadata diff --git a/aio/content/examples/cb-ts-to-js/js-es6/src/index.html b/aio/content/examples/cb-ts-to-js/js-es6/src/index.html new file mode 100644 index 0000000000..2a94db38cc --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/src/index.html @@ -0,0 +1,26 @@ + + + + + + + + TypeScript to JavaScript + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/cb-ts-to-js/js-es6/src/main.es6 b/aio/content/examples/cb-ts-to-js/js-es6/src/main.es6 new file mode 100644 index 0000000000..f22933ba8e --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js-es6/src/main.es6 @@ -0,0 +1,4 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/cb-ts-to-js/js/example-config.json b/aio/content/examples/cb-ts-to-js/js/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/cb-ts-to-js/js/plnkr.json b/aio/content/examples/cb-ts-to-js/js/plnkr.json new file mode 100644 index 0000000000..5e1eb188be --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/plnkr.json @@ -0,0 +1,8 @@ +{ + "description": "TypeScript to JavaScript", + "basePath": "src/", + "files":[ + "!**/karma*.*" + ], + "tags":["cookbook"] +} diff --git a/aio/content/examples/cb-ts-to-js/js/plnkr.no-link.html b/aio/content/examples/cb-ts-to-js/js/plnkr.no-link.html new file mode 100644 index 0000000000..d01f4940df --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/plnkr.no-link.html @@ -0,0 +1,1022 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/cb-ts-to-js/js/src/app/app.component.html b/aio/content/examples/cb-ts-to-js/js/src/app/app.component.html new file mode 100644 index 0000000000..4ca26f2657 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/src/app/app.component.html @@ -0,0 +1,47 @@ + +

    {{title}}

    +Classes and Class Metadata
    +Interfaces
    +Input and Output Metadata
    +Dependency Injection
    +Host Metadata
    +View and Child Metadata
    + +
    +

    Classes and Class Metadata

    + +

    Classes and Class Metadata (DSL)

    + + +
    +

    Interfaces

    + +

    Interfaces (DSL)

    + + +
    +

    Input and Output Metadata

    + +

    Input and Output Metadata (DSL)

    + + +
    +

    Dependency Injection

    + + + + +

    Dependency Injection (DSL)

    + + + + +
    +

    Host Metadata

    + +

    Host Metadata (DSL)

    + + +
    +

    View and Child Metadata (DSL)

    + diff --git a/aio/content/examples/cb-ts-to-js/js/src/app/app.component.js b/aio/content/examples/cb-ts-to-js/js/src/app/app.component.js new file mode 100644 index 0000000000..b6d1ec29b0 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/src/app/app.component.js @@ -0,0 +1,20 @@ +(function(app) { + +app.AppComponent = AppComponent; +function AppComponent() { + this.title = 'ES5 JavaScript'; +} + +AppComponent.annotations = [ + new ng.core.Component({ + selector: 'my-app', + templateUrl: 'app/app.component.html', + styles: [ + // See hero-di-inject-additional.component + 'hero-host, hero-host-dsl { border: 1px dashed black; display: block; padding: 4px;}', + '.heading {font-style: italic}' + ] + }) +]; + +})(window.app = window.app || {}); diff --git a/aio/content/examples/cb-ts-to-js/js/src/app/app.module.js b/aio/content/examples/cb-ts-to-js/js/src/app/app.module.js new file mode 100644 index 0000000000..fc2300a89b --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/src/app/app.module.js @@ -0,0 +1,46 @@ +(function(app) { + +app.AppModule = AppModule; +function AppModule() { } + +AppModule.annotations = [ + new ng.core.NgModule({ + imports: [ ng.platformBrowser.BrowserModule ], + declarations: [ + app.AppComponent, + app.ConfirmComponent, app.ConfirmDslComponent, + app.HeroComponent, app.HeroDslComponent, + app.HeroDIComponent, app.HeroDIDslComponent, + app.HeroDIInjectComponent, app.HeroDIInjectDslComponent, + app.HeroDIInjectAdditionalComponent, app.HeroDIInjectAdditionalDslComponent, + app.HeroHostComponent, app.HeroHostDslComponent, + app.HeroIOComponent, app.HeroIODslComponent, + app.HeroLifecycleComponent, app.HeroLifecycleDslComponent, + app.heroQueries.HeroQueriesComponent, app.heroQueries.ViewChildComponent, app.heroQueries.ContentChildComponent, + app.HeroTitleComponent, app.HeroTitleDslComponent + ], + providers: [ + app.DataService, + { provide: 'heroName', useValue: 'Windstorm' } + ], + bootstrap: [ app.AppComponent ], + + // schemas: [ ng.core.NO_ERRORS_SCHEMA ] // helpful for debugging! + }) +] + +})(window.app = window.app || {}); + + +///// For documentation only ///// +(function () { + // #docregion appimport + var HeroComponent = app.HeroComponent; + // #enddocregion appimport + + // #docregion ng2import + var platformBrowserDynamic = ng.platformBrowserDynamic.platformBrowserDynamic; + var LocationStrategy = ng.common.LocationStrategy; + var HashLocationStrategy = ng.common.HashLocationStrategy; + // #enddocregion ng2import +}) diff --git a/aio/content/examples/cb-ts-to-js/js/src/app/confirm.component.html b/aio/content/examples/cb-ts-to-js/js/src/app/confirm.component.html new file mode 100644 index 0000000000..45275d218a --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/src/app/confirm.component.html @@ -0,0 +1,6 @@ + + diff --git a/aio/content/examples/cb-ts-to-js/js/src/app/confirm.component.js b/aio/content/examples/cb-ts-to-js/js/src/app/confirm.component.js new file mode 100644 index 0000000000..b76d3f54aa --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/src/app/confirm.component.js @@ -0,0 +1,75 @@ +(function(app) { + + // #docregion + app.ConfirmComponent = ConfirmComponent; + + ConfirmComponent.annotations = [ + new ng.core.Component({ + selector: 'app-confirm', + templateUrl: 'app/confirm.component.html', + inputs: [ + 'okMsg', + 'notOkMsg: cancelMsg' + ], + outputs: [ + 'ok', + 'notOk: cancel' + ] + }) + ]; + + function ConfirmComponent() { + this.ok = new ng.core.EventEmitter(); + this.notOk = new ng.core.EventEmitter(); + } + + ConfirmComponent.prototype.onOkClick = function() { + this.ok.emit(true); + } + + ConfirmComponent.prototype.onNotOkClick = function() { + this.notOk.emit(true); + } + // #enddocregion + +})(window.app = window.app || {}); + +/////// DSL version //////// + +(function(app) { + + var old = app.ConfirmComponent; + + // #docregion dsl + app.ConfirmComponent = ng.core.Component({ + selector: 'app-confirm-dsl', + templateUrl: 'app/confirm.component.html', + inputs: [ + 'okMsg', + 'notOkMsg: cancelMsg' + ], + outputs: [ + 'ok', + 'notOk: cancel' + ] + }) + .Class({ + constructor: function ConfirmComponent() { + this.ok = new ng.core.EventEmitter(); + this.notOk = new ng.core.EventEmitter(); + }, + + onOkClick: function() { + this.ok.emit(true); + }, + + onNotOkClick: function() { + this.notOk.emit(true); + } + }); + // #enddocregion dsl + + app.ConfirmDslComponent = app.ConfirmComponent; + app.ConfirmComponent = old; + +})(window.app = window.app || {}); diff --git a/aio/content/examples/cb-ts-to-js/js/src/app/data.service.js b/aio/content/examples/cb-ts-to-js/js/src/app/data.service.js new file mode 100644 index 0000000000..643bb57dca --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/src/app/data.service.js @@ -0,0 +1,10 @@ +(function(app) { + + app.DataService = DataService; + function DataService() { } + + DataService.prototype.getHeroName = function() { + return 'Windstorm'; + }; + +})(window.app = window.app || {}); diff --git a/aio/content/examples/cb-ts-to-js/js/src/app/hero-di-inject-additional.component.js b/aio/content/examples/cb-ts-to-js/js/src/app/hero-di-inject-additional.component.js new file mode 100644 index 0000000000..450cc53847 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/src/app/hero-di-inject-additional.component.js @@ -0,0 +1,36 @@ +(function(app) { + + var old = app.HeroComponent; + + app.HeroComponent = HeroComponent; + + HeroComponent.annotations = [ + new ng.core.Component({ + selector: 'hero-di-inject-additional', + template: '' + }) + ]; + + function HeroComponent() {} + + app.HeroDIInjectAdditionalComponent = app.HeroComponent; + app.HeroComponent = old; + +})(window.app = window.app || {}); + +////// DSL Version ///////// +(function(app) { + + var old = app.HeroComponent; + + app.HeroComponent = ng.core.Component({ + selector: 'hero-di-inject-additional-dsl', + template: '' + }).Class({ + constructor: function HeroComponent() { } + }); + + app.HeroDIInjectAdditionalDslComponent = app.HeroComponent; + app.HeroComponent = old; + +})(window.app = window.app || {}); diff --git a/aio/content/examples/cb-ts-to-js/js/src/app/hero-di-inject.component.js b/aio/content/examples/cb-ts-to-js/js/src/app/hero-di-inject.component.js new file mode 100644 index 0000000000..879365208c --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/src/app/hero-di-inject.component.js @@ -0,0 +1,51 @@ +(function(app) { + + var old = app.HeroComponent; + + // #docregion + app.HeroComponent = HeroComponent; + + HeroComponent.annotations = [ + new ng.core.Component({ + selector: 'hero-di-inject', + template: '

    Hero: {{name}}

    ' + }) + ]; + + HeroComponent.parameters = [ 'heroName' ]; + + function HeroComponent(name) { + this.name = name; + } + // #enddocregion + + app.HeroDIInjectComponent = app.HeroComponent; + app.HeroComponent = old; + +})(window.app = window.app || {}); + +/////// DSL version //////// + +(function(app) { + + var old = app.HeroComponent; + + // #docregion dsl + app.HeroComponent = ng.core.Component({ + selector: 'hero-di-inject-dsl', + template: '

    Hero: {{name}}

    ' + }) + .Class({ + constructor: [ + new ng.core.Inject('heroName'), + function HeroComponent(name) { + this.name = name; + } + ] + }); + // #enddocregion dsl + + app.HeroDIInjectDslComponent = app.HeroComponent; + app.HeroComponent = old; + +})(window.app = window.app || {}); diff --git a/aio/content/examples/cb-ts-to-js/js/src/app/hero-di.component.js b/aio/content/examples/cb-ts-to-js/js/src/app/hero-di.component.js new file mode 100644 index 0000000000..2f49a85079 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/src/app/hero-di.component.js @@ -0,0 +1,51 @@ +(function(app) { + + var old = app.HeroComponent; + + // #docregion + app.HeroComponent = HeroComponent; + + HeroComponent.annotations = [ + new ng.core.Component({ + selector: 'hero-di', + template: '

    Hero: {{name}}

    ' + }) + ]; + + HeroComponent.parameters = [ app.DataService ]; + + function HeroComponent(dataService) { + this.name = dataService.getHeroName(); + } + // #enddocregion + + app.HeroDIComponent = app.HeroComponent; + app.HeroComponent = old; + +})(window.app = window.app || {}); + +////// DSL Version ///// + +(function(app) { + + var old = app.HeroComponent; + + // #docregion dsl + app.HeroComponent = ng.core.Component({ + selector: 'hero-di-dsl', + template: '

    Hero: {{name}}

    ' + }) + .Class({ + constructor: [ + app.DataService, + function HeroComponent(service) { + this.name = service.getHeroName(); + } + ] + }); + // #enddocregion dsl + + app.HeroDIDslComponent = app.HeroComponent; + app.HeroComponent = old; + +})(window.app = window.app || {}); diff --git a/aio/content/examples/cb-ts-to-js/js/src/app/hero-host.component.js b/aio/content/examples/cb-ts-to-js/js/src/app/hero-host.component.js new file mode 100644 index 0000000000..b73e50aae0 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/src/app/hero-host.component.js @@ -0,0 +1,107 @@ +(function(app) { + + var old = app.HeroComponent + + // #docregion + app.HeroComponent = HeroComponent; + + HeroComponent.annotations = [ + new ng.core.Component({ + selector: 'hero-host', + template: + '

    Hero Host

    ' + + '
    Heading clicks: {{clicks}}
    ', + host: { + // HostBindings to the element + '[title]': 'title', + '[class.heading]': 'headingClass', + '(click)': 'clicked()', + + // HostListeners on the entire element + '(mouseenter)': 'enter($event)', + '(mouseleave)': 'leave($event)' + }, + // Styles within (but excluding) the element + styles: ['.active {background-color: yellow;}'] + }) + ]; + + function HeroComponent() { + this.clicks = 0; + this.headingClass = true; + this.title = 'Hero Host Tooltip content'; + } + + HeroComponent.prototype.clicked = function() { + this.clicks += 1; + } + + HeroComponent.prototype.enter = function(event) { + this.active = true; + this.headingClass = false; + } + + HeroComponent.prototype.leave = function(event) { + this.active = false; + this.headingClass = true; + } + // #enddocregion + + app.HeroHostComponent = app.HeroComponent; + app.HeroComponent = old; + +})(window.app = window.app || {}); + +//// DSL Version //// + +(function(app) { + + var old = app.HeroComponent; + + // #docregion dsl + app.HeroComponent = ng.core.Component({ + selector: 'hero-host-dsl', + template: ` +

    Hero Host (DSL)

    +
    Heading clicks: {{clicks}}
    + `, + host: { + // HostBindings to the element + '[title]': 'title', + '[class.heading]': 'headingClass', + '(click)': 'clicked()', + + // HostListeners on the entire element + '(mouseenter)': 'enter($event)', + '(mouseleave)': 'leave($event)' + }, + // Styles within (but excluding) the element + styles: ['.active {background-color: coral;}'] + }) + .Class({ + constructor: function HeroComponent() { + this.clicks = 0; + this.headingClass = true; + this.title = 'Hero Host Tooltip DSL content'; + }, + + clicked() { + this.clicks += 1; + }, + + enter(event) { + this.active = true; + this.headingClass = false; + }, + + leave(event) { + this.active = false; + this.headingClass = true; + } + }); + // #enddocregion dsl + + app.HeroHostDslComponent = app.HeroComponent; + app.HeroComponent = old; + +})(window.app = window.app || {}); diff --git a/aio/content/examples/cb-ts-to-js/js/src/app/hero-io-dsl.component.html b/aio/content/examples/cb-ts-to-js/js/src/app/hero-io-dsl.component.html new file mode 100644 index 0000000000..c8023ccb45 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/src/app/hero-io-dsl.component.html @@ -0,0 +1,7 @@ + + +OK clicked +Cancel clicked diff --git a/aio/content/examples/cb-ts-to-js/js/src/app/hero-io.component.html b/aio/content/examples/cb-ts-to-js/js/src/app/hero-io.component.html new file mode 100644 index 0000000000..215ddccf92 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/src/app/hero-io.component.html @@ -0,0 +1,7 @@ + + +OK clicked +Cancel clicked diff --git a/aio/content/examples/cb-ts-to-js/js/src/app/hero-io.component.js b/aio/content/examples/cb-ts-to-js/js/src/app/hero-io.component.js new file mode 100644 index 0000000000..b09208ce68 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/src/app/hero-io.component.js @@ -0,0 +1,52 @@ +(function(app) { + + var old = app.HeroComponent + + app.HeroComponent = HeroComponent; + + HeroComponent.annotations = [ + new ng.core.Component({ + selector: 'hero-io', + templateUrl: 'app/hero-io.component.html' + }) + ]; + + function HeroComponent() { } + + HeroComponent.prototype.onOk = function() { + this.okClicked = true; + } + + HeroComponent.prototype.onCancel = function() { + this.cancelClicked = true; + } + + app.HeroIOComponent = app.HeroComponent; + app.HeroComponent = old; + +})(window.app = window.app || {}); + +///// DSL Version //// + +(function(app) { + + var old = app.HeroComponent + + app.HeroComponent = ng.core.Component({ + selector: 'hero-io-dsl', + templateUrl: 'app/hero-io-dsl.component.html' + }) + .Class({ + constructor: function HeroComponent() { }, + onOk: function() { + this.okClicked = true; + }, + onCancel: function() { + this.cancelClicked = true; + } + }); + + app.HeroIODslComponent = app.HeroComponent; + app.HeroComponent = old; + +})(window.app = window.app || {}); diff --git a/aio/content/examples/cb-ts-to-js/js/src/app/hero-lifecycle.component.js b/aio/content/examples/cb-ts-to-js/js/src/app/hero-lifecycle.component.js new file mode 100644 index 0000000000..2a68288f92 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/src/app/hero-lifecycle.component.js @@ -0,0 +1,52 @@ +// #docplaster +(function(app) { + + var old = app.HeroComponent; + + // #docregion + app.HeroComponent = HeroComponent; + + HeroComponent.annotations = [ + new ng.core.Component({ + selector: 'hero-lifecycle', + template: '

    Hero: {{name}}

    ' + }) + ]; + + function HeroComponent() { } + + HeroComponent.prototype.ngOnInit = function() { + // todo: fetch from server async + setTimeout(() => this.name = 'Windstorm', 0); + }; + // #enddocregion + + app.HeroLifecycleComponent = app.HeroComponent; + app.HeroComponent = old; + +})(window.app = window.app || {}); + +/////// DSL version //// + +(function(app) { + + var old = app.HeroComponent; + + // #docregion dsl + app.HeroComponent = ng.core.Component({ + selector: 'hero-lifecycle-dsl', + template: '

    Hero: {{name}}

    ' + }) + .Class({ + constructor: function HeroComponent() { }, + ngOnInit: function() { + // todo: fetch from server async + setTimeout(() => this.name = 'Windstorm', 0); + } + }); + // #enddocregion dsl + + app.HeroLifecycleDslComponent = app.HeroComponent; + app.HeroComponent = old; + +})(window.app = window.app || {}); diff --git a/aio/content/examples/cb-ts-to-js/js/src/app/hero-queries.component.js b/aio/content/examples/cb-ts-to-js/js/src/app/hero-queries.component.js new file mode 100644 index 0000000000..5e8de58d41 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/src/app/hero-queries.component.js @@ -0,0 +1,92 @@ +(function(app) { + + app.heroQueries = app.heroQueries || {}; + + app.heroQueries.ContentChildComponent = ng.core.Component({ + selector: 'content-child', + template: + '' + + 'Active' + + '' + }).Class({ + constructor: function ContentChildComponent() { + this.active = false; + }, + + activate: function() { + this.active = !this.active; + } + }); + + //////////////////// + + // #docregion content + app.heroQueries.ViewChildComponent = ng.core.Component({ + selector: 'view-child', + template: + '

    ' + + '{{hero.name}} ' + + '' + + '

    ', + styles: ['.active {font-weight: bold; background-color: skyblue;}'], + inputs: ['hero'], + queries: { + content: new ng.core.ContentChild(app.heroQueries.ContentChildComponent) + } + }) + .Class({ + constructor: function HeroQueriesHeroComponent() { + this.active = false; + }, + + activate: function() { + this.active = !this.active; + this.content.activate(); + } + }); + // #enddocregion content + + //////////////////// + + // #docregion view + app.heroQueries.HeroQueriesComponent = ng.core.Component({ + selector: 'hero-queries', + template: + '' + + '' + + '' + + '', + queries: { + views: new ng.core.ViewChildren(app.heroQueries.ViewChildComponent) + } + }) + .Class({ + constructor: function HeroQueriesComponent() { + this.active = false; + this.heroData = [ + {id: 1, name: 'Windstorm'}, + {id: 2, name: 'LaughingGas'} + ]; + }, + + activate: function() { + this.active = !this.active; + this.views.forEach(function(view) { + view.activate(); + }); + }, + }); + + // #docregion defined-property + // add prototype property w/ getter outside the DSL + var proto = app.heroQueries.HeroQueriesComponent.prototype; + Object.defineProperty(proto, "buttonLabel", { + get: function () { + return this.active ? 'Deactivate' : 'Activate'; + }, + enumerable: true + }); + // #enddocregion defined-property + // #enddocregion view + +})(window.app = window.app || {}); diff --git a/aio/content/examples/cb-ts-to-js/js/src/app/hero-title.component.html b/aio/content/examples/cb-ts-to-js/js/src/app/hero-title.component.html new file mode 100644 index 0000000000..164683cb7c --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/src/app/hero-title.component.html @@ -0,0 +1,4 @@ + +

    {{titlePrefix}} {{title}}

    + +

    {{ msg }}

    diff --git a/aio/content/examples/cb-ts-to-js/js/src/app/hero-title.component.js b/aio/content/examples/cb-ts-to-js/js/src/app/hero-title.component.js new file mode 100644 index 0000000000..f0770e1228 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/src/app/hero-title.component.js @@ -0,0 +1,64 @@ +(function(app) { + + // #docregion + app.HeroTitleComponent = HeroTitleComponent; + + // #docregion templateUrl + HeroTitleComponent.annotations = [ + new ng.core.Component({ + selector: 'hero-title', + templateUrl: 'app/hero-title.component.html' + }) + ]; + // #enddocregion templateUrl + + function HeroTitleComponent(titlePrefix, title) { + this.titlePrefix = titlePrefix; + this.title = title; + this.msg = ''; + } + + HeroTitleComponent.prototype.ok = function() { + this.msg = 'OK!'; + } + + HeroTitleComponent.parameters = [ + [new ng.core.Optional(), new ng.core.Inject('titlePrefix')], + [new ng.core.Attribute('title')] + ]; + // #enddocregion + +})(window.app = window.app || {}); + +////////// DSL version //////////// + +(function(app) { + + var old = app.HeroTitleComponent; + + // #docregion dsl + app.HeroTitleComponent = ng.core.Component({ + selector: 'hero-title-dsl', + templateUrl: 'app/hero-title.component.html' + }) + .Class({ + constructor: [ + [ new ng.core.Optional(), new ng.core.Inject('titlePrefix') ], + new ng.core.Attribute('title'), + function HeroTitleComponent(titlePrefix, title) { + this.titlePrefix = titlePrefix; + this.title = title; + this.msg = ''; + } + ], + + ok: function() { + this.msg = 'OK!'; + } + }); + // #enddocregion dsl + + app.HeroTitleDslComponent = app.HeroTitleComponent; + app.HeroTitleComponent = old; + +})(window.app = window.app || {}); diff --git a/aio/content/examples/cb-ts-to-js/js/src/app/hero.component.js b/aio/content/examples/cb-ts-to-js/js/src/app/hero.component.js new file mode 100644 index 0000000000..e1407b2635 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/src/app/hero.component.js @@ -0,0 +1,53 @@ +// #docplaster +(function(app) { + +// #docregion +// #docregion appexport +// #docregion metadata +app.HeroComponent = HeroComponent; // "export" + +HeroComponent.annotations = [ + new ng.core.Component({ + selector: 'hero-view', + template: '

    {{title}}: {{getName()}}

    ' + }) +]; + +// #docregion constructorproto +function HeroComponent() { + this.title = "Hero Detail"; +} + +HeroComponent.prototype.getName = function() { return 'Windstorm'; }; +// #enddocregion constructorproto + +// #enddocregion metadata +// #enddocregion appexport +// #enddocregion + +})(window.app = window.app || {}); + +//////////// DSL version /////////// + +(function(app) { + + var old = app.HeroComponent; + + // #docregion dsl + app.HeroComponent = ng.core.Component({ + selector: 'hero-view-dsl', + template: '

    {{title}}: {{getName()}}

    ', + }) + .Class({ + constructor: function HeroComponent() { + this.title = "Hero Detail"; + }, + + getName: function() { return 'Windstorm'; } + }); + // #enddocregion dsl + + app.HeroDslComponent = app.HeroComponent; + app.HeroComponent = old; + +})(window.app = window.app || {}); diff --git a/aio/content/examples/cb-ts-to-js/js/src/index.html b/aio/content/examples/cb-ts-to-js/js/src/index.html new file mode 100644 index 0000000000..00338aa55a --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/src/index.html @@ -0,0 +1,43 @@ + + + + + + + TypeScript to JavaScript + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/cb-ts-to-js/js/src/main.js b/aio/content/examples/cb-ts-to-js/js/src/main.js new file mode 100644 index 0000000000..fd3e737c9e --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/js/src/main.js @@ -0,0 +1,9 @@ +(function(app) { + +var platformBrowserDynamic = ng.platformBrowserDynamic.platformBrowserDynamic; + +document.addEventListener('DOMContentLoaded', function() { + platformBrowserDynamic().bootstrapModule(app.AppModule); +}); + +})(window.app = window.app || {}); diff --git a/aio/content/examples/cb-ts-to-js/ts/example-config.json b/aio/content/examples/cb-ts-to-js/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/cb-ts-to-js/ts/plnkr.json b/aio/content/examples/cb-ts-to-js/ts/plnkr.json new file mode 100644 index 0000000000..447fc5f605 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "TypeScript to JavaScript", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js" + ], + "tags":["cookbook"] +} diff --git a/aio/content/examples/cb-ts-to-js/ts/plnkr.no-link.html b/aio/content/examples/cb-ts-to-js/ts/plnkr.no-link.html new file mode 100644 index 0000000000..56a3e2e5c4 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/plnkr.no-link.html @@ -0,0 +1,652 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/cb-ts-to-js/ts/src/app/app.component.html b/aio/content/examples/cb-ts-to-js/ts/src/app/app.component.html new file mode 100644 index 0000000000..995645073a --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/src/app/app.component.html @@ -0,0 +1,31 @@ + +

    {{title}}

    +Classes and Class Metadata
    +Input and Output Decorators
    +Dependency Injection
    +Host Metadata
    +View and Child Metadata
    + +
    +

    Classes and Class Metadata

    + + + +
    +

    Input and Output Metadata

    + + +
    +

    Dependency Injection

    + + + + +
    +

    Host Metadata

    + + + +
    +

    View and Child Metadata

    + diff --git a/aio/content/examples/cb-ts-to-js/ts/src/app/app.component.ts b/aio/content/examples/cb-ts-to-js/ts/src/app/app.component.ts new file mode 100644 index 0000000000..8e7afad0df --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/src/app/app.component.ts @@ -0,0 +1,15 @@ +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: './app.component.html', + styles: [ + // See hero-di-inject-additional.component + 'hero-host, hero-host-meta { border: 1px dashed black; display: block; padding: 4px;}', + '.heading {font-style: italic}' + ] +}) +export class AppComponent { + title = 'TypeScript'; +} diff --git a/aio/content/examples/cb-ts-to-js/ts/src/app/app.module.ts b/aio/content/examples/cb-ts-to-js/ts/src/app/app.module.ts new file mode 100644 index 0000000000..992c3c3514 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/src/app/app.module.ts @@ -0,0 +1,56 @@ +/* tslint:disable-next-line:no-unused-variable */ +import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; +import { ConfirmComponent } from './confirm.component'; +// #docregion appimport +import { HeroComponent } from './hero.component'; +// #enddocregion appimport +import { HeroComponent as HeroDIComponent } from './hero-di.component'; +import { HeroComponent as HeroDIInjectComponent } from './hero-di-inject.component'; +import { HeroComponent as HeroDIInjectAdditionalComponent } from './hero-di-inject-additional.component'; +import { HeroHostComponent } from './hero-host.component'; +import { HeroHostMetaComponent } from './hero-host-meta.component'; +import { HeroIOComponent } from './hero-io.component'; +import { HeroComponent as HeroLifecycleComponent } from './hero-lifecycle.component'; +import { HeroQueriesComponent, ViewChildComponent, ContentChildComponent } from './hero-queries.component'; +import { HeroTitleComponent } from './hero-title.component'; + +import { DataService } from './data.service'; + +@NgModule({ + imports: [ + BrowserModule + ], + declarations: [ + AppComponent, + ConfirmComponent, + HeroComponent, + HeroDIComponent, + HeroDIInjectComponent, + HeroDIInjectAdditionalComponent, + HeroHostComponent, HeroHostMetaComponent, + HeroIOComponent, + HeroLifecycleComponent, + HeroQueriesComponent, ViewChildComponent, ContentChildComponent, + HeroTitleComponent + ], + providers: [ + DataService, + { provide: 'heroName', useValue: 'Windstorm' } + ], + bootstrap: [ AppComponent ], + + // schemas: [ NO_ERRORS_SCHEMA ] // helpful for debugging! +}) +export class AppModule { } + +/* tslint:disable no-unused-variable */ +// #docregion ng2import +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { + LocationStrategy, + HashLocationStrategy +} from '@angular/common'; +// #enddocregion ng2import diff --git a/aio/content/examples/cb-ts-to-js/ts/src/app/confirm.component.html b/aio/content/examples/cb-ts-to-js/ts/src/app/confirm.component.html new file mode 100644 index 0000000000..45275d218a --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/src/app/confirm.component.html @@ -0,0 +1,6 @@ + + diff --git a/aio/content/examples/cb-ts-to-js/ts/src/app/confirm.component.ts b/aio/content/examples/cb-ts-to-js/ts/src/app/confirm.component.ts new file mode 100644 index 0000000000..c9be28c44c --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/src/app/confirm.component.ts @@ -0,0 +1,22 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +// #docregion +@Component({ + moduleId: module.id, + selector: 'app-confirm', + templateUrl: './confirm.component.html' +}) +export class ConfirmComponent { + @Input() okMsg = ''; + @Input('cancelMsg') notOkMsg = ''; + @Output() ok = new EventEmitter(); + @Output('cancel') notOk = new EventEmitter(); + + onOkClick() { + this.ok.emit(true); + } + onNotOkClick() { + this.notOk.emit(true); + } +} +// #enddocregion diff --git a/aio/content/examples/cb-ts-to-js/ts/src/app/data.service.ts b/aio/content/examples/cb-ts-to-js/ts/src/app/data.service.ts new file mode 100644 index 0000000000..cd7f9e1aae --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/src/app/data.service.ts @@ -0,0 +1,10 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class DataService { + constructor() { } + + getHeroName() { + return 'Windstorm'; + } +} diff --git a/aio/content/examples/cb-ts-to-js/ts/src/app/hero-di-inject-additional.component.ts b/aio/content/examples/cb-ts-to-js/ts/src/app/hero-di-inject-additional.component.ts new file mode 100644 index 0000000000..ec460a9dbc --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/src/app/hero-di-inject-additional.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'hero-di-inject-additional', + template: `` +}) +export class HeroComponent { } diff --git a/aio/content/examples/cb-ts-to-js/ts/src/app/hero-di-inject.component.ts b/aio/content/examples/cb-ts-to-js/ts/src/app/hero-di-inject.component.ts new file mode 100644 index 0000000000..c583a1b0f6 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/src/app/hero-di-inject.component.ts @@ -0,0 +1,11 @@ +import { Component, Inject } from '@angular/core'; + +// #docregion +@Component({ + selector: 'hero-di-inject', + template: `

    Hero: {{name}}

    ` +}) +export class HeroComponent { + constructor(@Inject('heroName') private name: string) { } +} +// #enddocregion diff --git a/aio/content/examples/cb-ts-to-js/ts/src/app/hero-di.component.ts b/aio/content/examples/cb-ts-to-js/ts/src/app/hero-di.component.ts new file mode 100644 index 0000000000..3a17abd281 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/src/app/hero-di.component.ts @@ -0,0 +1,15 @@ +// #docregion +import { Component } from '@angular/core'; +import { DataService } from './data.service'; + +@Component({ + selector: 'hero-di', + template: `

    Hero: {{name}}

    ` +}) +export class HeroComponent { + name = ''; + constructor(dataService: DataService) { + this.name = dataService.getHeroName(); + } +} +// #enddocregion diff --git a/aio/content/examples/cb-ts-to-js/ts/src/app/hero-host-meta.component.ts b/aio/content/examples/cb-ts-to-js/ts/src/app/hero-host-meta.component.ts new file mode 100644 index 0000000000..fefe4a5470 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/src/app/hero-host-meta.component.ts @@ -0,0 +1,44 @@ +import { Component } from '@angular/core'; + +// #docregion +@Component({ + selector: 'hero-host-meta', + template: ` +

    Hero Host in Metadata

    +
    Heading clicks: {{clicks}}
    + `, + host: { + // HostBindings to the element + '[title]': 'title', + '[class.heading]': 'headingClass', + + // HostListeners on the entire element + '(click)': 'clicked()', + '(mouseenter)': 'enter($event)', + '(mouseleave)': 'leave($event)' + }, + // Styles within (but excluding) the element + styles: ['.active {background-color: coral;}'] +}) +export class HeroHostMetaComponent { + title = 'Hero Host in Metadata Tooltip'; + headingClass = true; + + active = false; + clicks = 0; + + clicked() { + this.clicks += 1; + } + + enter(event: Event) { + this.active = true; + this.headingClass = false; + } + + leave(event: Event) { + this.active = false; + this.headingClass = true; + } +} +// #enddocregion diff --git a/aio/content/examples/cb-ts-to-js/ts/src/app/hero-host.component.ts b/aio/content/examples/cb-ts-to-js/ts/src/app/hero-host.component.ts new file mode 100644 index 0000000000..e8d72233c8 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/src/app/hero-host.component.ts @@ -0,0 +1,39 @@ +import { Component, HostBinding, HostListener } from '@angular/core'; + +// #docregion +@Component({ + selector: 'hero-host', + template: ` +

    Hero Host in Decorators

    +
    Heading clicks: {{clicks}}
    + `, + // Styles within (but excluding) the element + styles: ['.active {background-color: yellow;}'] +}) +export class HeroHostComponent { + // HostBindings to the element + @HostBinding() title = 'Hero Host in Decorators Tooltip'; + @HostBinding('class.heading') headingClass = true; + + active = false; + clicks = 0; + + // HostListeners on the entire element + @HostListener('click') + clicked() { + this.clicks += 1; + } + + @HostListener('mouseenter', ['$event']) + enter(event: Event) { + this.active = true; + this.headingClass = false; + } + + @HostListener('mouseleave', ['$event']) + leave(event: Event) { + this.active = false; + this.headingClass = true; + } +} +// #enddocregion diff --git a/aio/content/examples/cb-ts-to-js/ts/src/app/hero-io.component.ts b/aio/content/examples/cb-ts-to-js/ts/src/app/hero-io.component.ts new file mode 100644 index 0000000000..4b36411e78 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/src/app/hero-io.component.ts @@ -0,0 +1,26 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'hero-io', + template: ` + + + OK clicked + Cancel clicked + ` +}) +export class HeroIOComponent { + okClicked = false; + cancelClicked = false; + + onOk() { + this.okClicked = true; + } + + onCancel() { + this.cancelClicked = true; + } +} diff --git a/aio/content/examples/cb-ts-to-js/ts/src/app/hero-lifecycle.component.ts b/aio/content/examples/cb-ts-to-js/ts/src/app/hero-lifecycle.component.ts new file mode 100644 index 0000000000..2629c85a1a --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/src/app/hero-lifecycle.component.ts @@ -0,0 +1,14 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'hero-lifecycle', + template: `

    Hero: {{name}}

    ` +}) +export class HeroComponent implements OnInit { + name: string; + ngOnInit() { + // todo: fetch from server async + setTimeout(() => this.name = 'Windstorm', 0); + } +} diff --git a/aio/content/examples/cb-ts-to-js/ts/src/app/hero-queries.component.ts b/aio/content/examples/cb-ts-to-js/ts/src/app/hero-queries.component.ts new file mode 100644 index 0000000000..8b2d91ea85 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/src/app/hero-queries.component.ts @@ -0,0 +1,83 @@ +import { + Component, + ContentChild, + Input, + QueryList, + ViewChildren +} from '@angular/core'; + +@Component({ + selector: 'content-child', + template: ` + + Active + ` +}) +export class ContentChildComponent { + active = false; + + activate() { + this.active = !this.active; + } +} + +//////////////////// + +// #docregion content +@Component({ + selector: 'view-child', + template: ` +

    + {{hero.name}} + +

    `, + styles: ['.active {font-weight: bold; background-color: skyblue;}'] +}) +export class ViewChildComponent { + @Input() hero: any; + active = false; + + @ContentChild(ContentChildComponent) content: ContentChildComponent; + + activate() { + this.active = !this.active; + this.content.activate(); + } +} +// #enddocregion content + +//////////////////// + +// #docregion view +@Component({ + selector: 'hero-queries', + template: ` + + + + + ` +}) +export class HeroQueriesComponent { + active = false; + heroData = [ + {id: 1, name: 'Windstorm'}, + {id: 2, name: 'LaughingGas'} + ]; + + @ViewChildren(ViewChildComponent) views: QueryList; + + activate() { + this.active = !this.active; + this.views.forEach( + view => view.activate() + ); + } + + // #docregion defined-property + get buttonLabel() { + return this.active ? 'Deactivate' : 'Activate'; + } + // #enddocregion defined-property +} +// #enddocregion view diff --git a/aio/content/examples/cb-ts-to-js/ts/src/app/hero-title.component.html b/aio/content/examples/cb-ts-to-js/ts/src/app/hero-title.component.html new file mode 100644 index 0000000000..164683cb7c --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/src/app/hero-title.component.html @@ -0,0 +1,4 @@ + +

    {{titlePrefix}} {{title}}

    + +

    {{ msg }}

    diff --git a/aio/content/examples/cb-ts-to-js/ts/src/app/hero-title.component.ts b/aio/content/examples/cb-ts-to-js/ts/src/app/hero-title.component.ts new file mode 100644 index 0000000000..e8e68684e2 --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/src/app/hero-title.component.ts @@ -0,0 +1,22 @@ +import { Attribute, Component, Inject, Optional } from '@angular/core'; + +// #docregion +// #docregion templateUrl +@Component({ + moduleId: module.id, + selector: 'hero-title', + templateUrl: './hero-title.component.html' +}) +// #enddocregion templateUrl +export class HeroTitleComponent { + msg: string = ''; + constructor( + @Inject('titlePrefix') @Optional() private titlePrefix: string, + @Attribute('title') private title: string + ) { } + + ok() { + this.msg = 'OK!'; + } +} +// #enddocregion diff --git a/aio/content/examples/cb-ts-to-js/ts/src/app/hero.component.ts b/aio/content/examples/cb-ts-to-js/ts/src/app/hero.component.ts new file mode 100644 index 0000000000..2976ec605e --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/src/app/hero.component.ts @@ -0,0 +1,15 @@ +// #docregion +// #docregion metadata +import { Component } from '@angular/core'; + +@Component({ + selector: 'hero-view', + template: '

    {{title}}: {{getName()}}

    ' +}) +// #docregion appexport, class +export class HeroComponent { + title = 'Hero Detail'; + getName() {return 'Windstorm'; } +} +// #enddocregion appexport, class +// #enddocregion metadata diff --git a/aio/content/examples/cb-ts-to-js/ts/src/index.html b/aio/content/examples/cb-ts-to-js/ts/src/index.html new file mode 100644 index 0000000000..d9ad1f7aef --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/src/index.html @@ -0,0 +1,26 @@ + + + + + + + + TypeScript to JavaScript + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/cb-ts-to-js/ts/src/main.ts b/aio/content/examples/cb-ts-to-js/ts/src/main.ts new file mode 100644 index 0000000000..f22933ba8e --- /dev/null +++ b/aio/content/examples/cb-ts-to-js/ts/src/main.ts @@ -0,0 +1,4 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/cb-visual-studio-2015/ts/.gitignore b/aio/content/examples/cb-visual-studio-2015/ts/.gitignore new file mode 100644 index 0000000000..ca3f2dbd5e --- /dev/null +++ b/aio/content/examples/cb-visual-studio-2015/ts/.gitignore @@ -0,0 +1 @@ +!tsconfig.json diff --git a/aio/content/examples/cb-visual-studio-2015/ts/tsconfig.json b/aio/content/examples/cb-visual-studio-2015/ts/tsconfig.json new file mode 100644 index 0000000000..2624488890 --- /dev/null +++ b/aio/content/examples/cb-visual-studio-2015/ts/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "removeComments": false, + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true, + "typeRoots": [ + "node_modules/@types" + ] + }, + "compileOnSave": true +} diff --git a/aio/content/examples/cli-quickstart/e2e-spec.ts b/aio/content/examples/cli-quickstart/e2e-spec.ts new file mode 100644 index 0000000000..59816e13c2 --- /dev/null +++ b/aio/content/examples/cli-quickstart/e2e-spec.ts @@ -0,0 +1,14 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('cli-quickstart App', () => { + beforeEach(() => { + return browser.get('/'); + }); + + it('should display message saying app works', () => { + let pageTitle = element(by.css('app-root h1')).getText(); + expect(pageTitle).toEqual('My First Angular App'); + }); +}); diff --git a/aio/content/examples/cli-quickstart/ts/.gitignore b/aio/content/examples/cli-quickstart/ts/.gitignore new file mode 100644 index 0000000000..fd172dea98 --- /dev/null +++ b/aio/content/examples/cli-quickstart/ts/.gitignore @@ -0,0 +1,42 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp + +# dependencies +/node_modules + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ + +# IDE - VSCode +.vscode/ +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# misc +/.sass-cache +/connect.lock +/coverage/* +/libpeerconnection.log +npm-debug.log +testem.log +/typings + +# e2e +/e2e/*.js +/e2e/*.map + +#System Files +.DS_Store +Thumbs.db + +!src/styles.css diff --git a/aio/content/examples/cli-quickstart/ts/README.md b/aio/content/examples/cli-quickstart/ts/README.md new file mode 100644 index 0000000000..ae9b1a3643 --- /dev/null +++ b/aio/content/examples/cli-quickstart/ts/README.md @@ -0,0 +1,31 @@ +# MyApp + +This project was generated with [angular-cli](https://github.com/angular/angular-cli) version 1.0.0-beta.25.5. + +## Development server +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). +Before running the tests make sure you are serving the app via `ng serve`. + +## Deploying to GitHub Pages + +Run `ng github-pages:deploy` to deploy to GitHub Pages. + +## Further help + +To get more help on the `angular-cli` use `ng help` or go check out the [Angular-CLI README](https://github.com/angular/angular-cli/blob/master/README.md). diff --git a/aio/content/examples/cli-quickstart/ts/angular-cli.json b/aio/content/examples/cli-quickstart/ts/angular-cli.json new file mode 100644 index 0000000000..9f23ed85fb --- /dev/null +++ b/aio/content/examples/cli-quickstart/ts/angular-cli.json @@ -0,0 +1,59 @@ +{ + "project": { + "version": "1.0.0-beta.25.5", + "name": "my-app" + }, + "apps": [ + { + "root": "src", + "outDir": "dist", + "assets": [ + "assets", + "favicon.ico" + ], + "index": "index.html", + "main": "main.ts", + "test": "test.ts", + "tsconfig": "tsconfig.json", + "prefix": "app", + "mobile": false, + "styles": [ + "styles.css" + ], + "scripts": [], + "environments": { + "source": "environments/environment.ts", + "dev": "environments/environment.ts", + "prod": "environments/environment.prod.ts" + } + } + ], + "addons": [], + "packages": [], + "e2e": { + "protractor": { + "config": "./protractor.conf.js" + } + }, + "test": { + "karma": { + "config": "./karma.conf.js" + } + }, + "defaults": { + "styleExt": "css", + "prefixInterfaces": false, + "inline": { + "style": false, + "template": false + }, + "spec": { + "class": false, + "component": true, + "directive": true, + "module": false, + "pipe": true, + "service": true + } + } +} diff --git a/aio/content/examples/cli-quickstart/ts/bs-config.cli.json b/aio/content/examples/cli-quickstart/ts/bs-config.cli.json new file mode 100644 index 0000000000..ac61d35f83 --- /dev/null +++ b/aio/content/examples/cli-quickstart/ts/bs-config.cli.json @@ -0,0 +1,11 @@ +{ + "open": false, + "logLevel": "silent", + "port": 8080, + "server": { + "baseDir": "dist", + "middleware": { + "0": null + } + } +} diff --git a/aio/content/examples/cli-quickstart/ts/e2e/app.e2e-spec.ts b/aio/content/examples/cli-quickstart/ts/e2e/app.e2e-spec.ts new file mode 100644 index 0000000000..4346d120c8 --- /dev/null +++ b/aio/content/examples/cli-quickstart/ts/e2e/app.e2e-spec.ts @@ -0,0 +1,14 @@ +import { MyAppPage } from './app.po'; + +describe('my-app App', function() { + let page: MyAppPage; + + beforeEach(() => { + page = new MyAppPage(); + }); + + it('should display message saying app works', () => { + page.navigateTo(); + expect(page.getParagraphText()).toEqual('app works!'); + }); +}); diff --git a/aio/content/examples/cli-quickstart/ts/e2e/app.po.ts b/aio/content/examples/cli-quickstart/ts/e2e/app.po.ts new file mode 100644 index 0000000000..5d82157326 --- /dev/null +++ b/aio/content/examples/cli-quickstart/ts/e2e/app.po.ts @@ -0,0 +1,11 @@ +import { browser, element, by } from 'protractor'; + +export class MyAppPage { + navigateTo() { + return browser.get('/'); + } + + getParagraphText() { + return element(by.css('app-root h1')).getText(); + } +} diff --git a/aio/content/examples/cli-quickstart/ts/example-config.json b/aio/content/examples/cli-quickstart/ts/example-config.json new file mode 100644 index 0000000000..313764c3c6 --- /dev/null +++ b/aio/content/examples/cli-quickstart/ts/example-config.json @@ -0,0 +1,4 @@ +{ + "build": "build:cli", + "run": "serve:cli" +} diff --git a/aio/content/examples/cli-quickstart/ts/src/app/app.component.css b/aio/content/examples/cli-quickstart/ts/src/app/app.component.css new file mode 100644 index 0000000000..a2b21fae82 --- /dev/null +++ b/aio/content/examples/cli-quickstart/ts/src/app/app.component.css @@ -0,0 +1,6 @@ +/* #docregion */ +h1 { + color: #369; + font-family: Arial, Helvetica, sans-serif; + font-size: 250%; +} diff --git a/aio/content/examples/cli-quickstart/ts/src/app/app.component.html b/aio/content/examples/cli-quickstart/ts/src/app/app.component.html new file mode 100644 index 0000000000..b6931b538a --- /dev/null +++ b/aio/content/examples/cli-quickstart/ts/src/app/app.component.html @@ -0,0 +1,3 @@ +

    + {{title}} +

    diff --git a/aio/content/examples/cli-quickstart/ts/src/app/app.component.spec.ts b/aio/content/examples/cli-quickstart/ts/src/app/app.component.spec.ts new file mode 100644 index 0000000000..72ee705d0b --- /dev/null +++ b/aio/content/examples/cli-quickstart/ts/src/app/app.component.spec.ts @@ -0,0 +1,35 @@ +/* tslint:disable:no-unused-variable */ + +import { TestBed, async } from '@angular/core/testing'; + +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ + AppComponent + ], + }); + TestBed.compileComponents(); + }); + + it('should create the app', async(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app).toBeTruthy(); + })); + + it(`should have as title 'app works!'`, async(() => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app.title).toEqual('app works!'); + })); + + it('should render title in a h1 tag', async(() => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain('app works!'); + })); +}); diff --git a/aio/content/examples/cli-quickstart/ts/src/app/app.component.ts b/aio/content/examples/cli-quickstart/ts/src/app/app.component.ts new file mode 100644 index 0000000000..4c1b4a9bf7 --- /dev/null +++ b/aio/content/examples/cli-quickstart/ts/src/app/app.component.ts @@ -0,0 +1,16 @@ +// #docregion import +import { Component } from '@angular/core'; +// #enddocregion import + +// #docregion metadata +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'] +}) +// #enddocregion metadata +// #docregion title, class +export class AppComponent { + title = 'My First Angular App'; +} +// #enddocregion title, class diff --git a/aio/content/examples/cli-quickstart/ts/src/app/app.module.ts b/aio/content/examples/cli-quickstart/ts/src/app/app.module.ts new file mode 100644 index 0000000000..33ec868232 --- /dev/null +++ b/aio/content/examples/cli-quickstart/ts/src/app/app.module.ts @@ -0,0 +1,20 @@ +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { HttpModule } from '@angular/http'; + +import { AppComponent } from './app.component'; + +@NgModule({ + declarations: [ + AppComponent + ], + imports: [ + BrowserModule, + FormsModule, + HttpModule + ], + providers: [], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/aio/content/examples/cli-quickstart/ts/src/assets/.gitkeep b/aio/content/examples/cli-quickstart/ts/src/assets/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/cli-quickstart/ts/src/environments/environment.prod.ts b/aio/content/examples/cli-quickstart/ts/src/environments/environment.prod.ts new file mode 100644 index 0000000000..3612073bc3 --- /dev/null +++ b/aio/content/examples/cli-quickstart/ts/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true +}; diff --git a/aio/content/examples/cli-quickstart/ts/src/environments/environment.ts b/aio/content/examples/cli-quickstart/ts/src/environments/environment.ts new file mode 100644 index 0000000000..00313f1664 --- /dev/null +++ b/aio/content/examples/cli-quickstart/ts/src/environments/environment.ts @@ -0,0 +1,8 @@ +// The file contents for the current environment will overwrite these during build. +// The build system defaults to the dev environment which uses `environment.ts`, but if you do +// `ng build --env=prod` then `environment.prod.ts` will be used instead. +// The list of which env maps to which file can be found in `angular-cli.json`. + +export const environment = { + production: false +}; diff --git a/aio/content/examples/cli-quickstart/ts/src/favicon.ico b/aio/content/examples/cli-quickstart/ts/src/favicon.ico new file mode 100644 index 0000000000..8081c7ceaf Binary files /dev/null and b/aio/content/examples/cli-quickstart/ts/src/favicon.ico differ diff --git a/aio/content/examples/cli-quickstart/ts/src/index.html b/aio/content/examples/cli-quickstart/ts/src/index.html new file mode 100644 index 0000000000..8a1a7b32b3 --- /dev/null +++ b/aio/content/examples/cli-quickstart/ts/src/index.html @@ -0,0 +1,14 @@ + + + + + MyApp + + + + + + + Loading... + + diff --git a/aio/content/examples/cli-quickstart/ts/src/main.ts b/aio/content/examples/cli-quickstart/ts/src/main.ts new file mode 100644 index 0000000000..ac78a713c2 --- /dev/null +++ b/aio/content/examples/cli-quickstart/ts/src/main.ts @@ -0,0 +1,12 @@ +import './polyfills.ts'; + +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { enableProdMode } from '@angular/core'; +import { environment } from './environments/environment'; +import { AppModule } from './app/app.module'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/cli-quickstart/ts/src/polyfills.ts b/aio/content/examples/cli-quickstart/ts/src/polyfills.ts new file mode 100644 index 0000000000..4749399c2d --- /dev/null +++ b/aio/content/examples/cli-quickstart/ts/src/polyfills.ts @@ -0,0 +1,19 @@ +// This file includes polyfills needed by Angular and is loaded before +// the app. You can add your own extra polyfills to this file. +import 'core-js/es6/symbol'; +import 'core-js/es6/object'; +import 'core-js/es6/function'; +import 'core-js/es6/parse-int'; +import 'core-js/es6/parse-float'; +import 'core-js/es6/number'; +import 'core-js/es6/math'; +import 'core-js/es6/string'; +import 'core-js/es6/date'; +import 'core-js/es6/array'; +import 'core-js/es6/regexp'; +import 'core-js/es6/map'; +import 'core-js/es6/set'; +import 'core-js/es6/reflect'; + +import 'core-js/es7/reflect'; +import 'zone.js/dist/zone'; diff --git a/aio/content/examples/cli-quickstart/ts/src/styles.css b/aio/content/examples/cli-quickstart/ts/src/styles.css new file mode 100644 index 0000000000..d81835d0cd --- /dev/null +++ b/aio/content/examples/cli-quickstart/ts/src/styles.css @@ -0,0 +1,116 @@ +/* #docregion , quickstart, toh */ +/* Master Styles */ +h1 { + color: #369; + font-family: Arial, Helvetica, sans-serif; + font-size: 250%; +} +h2, h3 { + color: #444; + font-family: Arial, Helvetica, sans-serif; + font-weight: lighter; +} +body { + margin: 2em; +} +/* #enddocregion quickstart */ +body, input[text], button { + color: #888; + font-family: Cambria, Georgia; +} +/* #enddocregion toh */ +a { + cursor: pointer; + cursor: hand; +} +button { + font-family: Arial; + background-color: #eee; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; + cursor: hand; +} +button:hover { + background-color: #cfd8dc; +} +button:disabled { + background-color: #eee; + color: #aaa; + cursor: auto; +} + +/* Navigation link styles */ +nav a { + padding: 5px 10px; + text-decoration: none; + margin-right: 10px; + margin-top: 10px; + display: inline-block; + background-color: #eee; + border-radius: 4px; +} +nav a:visited, a:link { + color: #607D8B; +} +nav a:hover { + color: #039be5; + background-color: #CFD8DC; +} +nav a.active { + color: #039be5; +} + +/* items class */ +.items { + margin: 0 0 2em 0; + list-style-type: none; + padding: 0; + width: 24em; +} +.items li { + cursor: pointer; + position: relative; + left: 0; + background-color: #EEE; + margin: .5em; + padding: .3em 0; + height: 1.6em; + border-radius: 4px; +} +.items li:hover { + color: #607D8B; + background-color: #DDD; + left: .1em; +} +.items li.selected { + background-color: #CFD8DC; + color: white; +} +.items li.selected:hover { + background-color: #BBD8DC; +} +.items .text { + position: relative; + top: -3px; +} +.items .badge { + display: inline-block; + font-size: small; + color: white; + padding: 0.8em 0.7em 0 0.7em; + background-color: #607D8B; + line-height: 1em; + position: relative; + left: -1px; + top: -4px; + height: 1.8em; + margin-right: .8em; + border-radius: 4px 0 0 4px; +} +/* #docregion toh */ +/* everywhere else */ +* { + font-family: Arial, Helvetica, sans-serif; +} diff --git a/aio/content/examples/cli-quickstart/ts/src/test.ts b/aio/content/examples/cli-quickstart/ts/src/test.ts new file mode 100644 index 0000000000..f9d51efd05 --- /dev/null +++ b/aio/content/examples/cli-quickstart/ts/src/test.ts @@ -0,0 +1,32 @@ +import './polyfills.ts'; + +import 'zone.js/dist/long-stack-trace-zone'; +import 'zone.js/dist/proxy.js'; +import 'zone.js/dist/sync-test'; +import 'zone.js/dist/jasmine-patch'; +import 'zone.js/dist/async-test'; +import 'zone.js/dist/fake-async-test'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. +declare var __karma__: any; +declare var require: any; + +// Prevent Karma from running prematurely. +__karma__.loaded = function () {}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting() +); +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); +// Finally, start Karma to run the tests. +__karma__.start(); diff --git a/aio/content/examples/component-styles/e2e-spec.ts b/aio/content/examples/component-styles/e2e-spec.ts new file mode 100644 index 0000000000..28a44221a4 --- /dev/null +++ b/aio/content/examples/component-styles/e2e-spec.ts @@ -0,0 +1,73 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('Component Style Tests', function () { + + beforeAll(function () { + browser.get(''); + }); + + it('scopes component styles to component view', function() { + let componentH1 = element(by.css('hero-app > h1')); + let externalH1 = element(by.css('body > h1')); + + expect(componentH1.getCssValue('fontWeight')).toEqual('normal'); + expect(externalH1.getCssValue('fontWeight')).not.toEqual('normal'); + }); + + + it('allows styling :host element', function() { + let host = element(by.css('hero-details')); + + expect(host.getCssValue('borderWidth')).toEqual('1px'); + }); + + it('supports :host() in function form', function() { + let host = element(by.css('hero-details')); + + host.element(by.buttonText('Activate')).click(); + expect(host.getCssValue('borderWidth')).toEqual('3px'); + }); + + it('allows conditional :host-context() styling', function() { + let h2 = element(by.css('hero-details h2')); + + expect(h2.getCssValue('backgroundColor')).toEqual('rgba(238, 238, 255, 1)'); // #eeeeff + }); + + it('styles both view and content children with /deep/', function() { + let viewH3 = element(by.css('hero-team h3')); + let contentH3 = element(by.css('hero-controls h3')); + + expect(viewH3.getCssValue('fontStyle')).toEqual('italic'); + expect(contentH3.getCssValue('fontStyle')).toEqual('italic'); + }); + + it('includes styles loaded with CSS @import', function() { + let host = element(by.css('hero-details')); + + expect(host.getCssValue('padding')).toEqual('10px'); + }); + + it('processes template inline styles', function() { + let button = element(by.css('hero-controls button')); + let externalButton = element(by.css('body > button')); + expect(button.getCssValue('backgroundColor')).toEqual('rgba(255, 255, 255, 1)'); // #ffffff + expect(externalButton.getCssValue('backgroundColor')).not.toEqual('rgba(255, 255, 255, 1)'); + }); + + it('processes template s', function() { + let li = element(by.css('hero-team li:first-child')); + let externalLi = element(by.css('body > ul li')); + + expect(li.getCssValue('listStyleType')).toEqual('square'); + expect(externalLi.getCssValue('listStyleType')).not.toEqual('square'); + }); + + it('supports relative loading with moduleId', function() { + let host = element(by.css('quest-summary')); + expect(host.getCssValue('color')).toEqual('rgba(255, 255, 255, 1)'); // #ffffff + }); + +}); diff --git a/aio/content/examples/component-styles/ts/example-config.json b/aio/content/examples/component-styles/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/component-styles/ts/plnkr.json b/aio/content/examples/component-styles/ts/plnkr.json new file mode 100644 index 0000000000..e045ebcb38 --- /dev/null +++ b/aio/content/examples/component-styles/ts/plnkr.json @@ -0,0 +1,10 @@ +{ + "description": "Component Styles", + "basePath": "src/", + "files": [ + "!**/*.d.ts", + "!**/*.js", + "!**/*.native.*" + ], + "tags": ["CSS"] +} diff --git a/aio/content/examples/component-styles/ts/plnkr.no-link.html b/aio/content/examples/component-styles/ts/plnkr.no-link.html new file mode 100644 index 0000000000..12978b4077 --- /dev/null +++ b/aio/content/examples/component-styles/ts/plnkr.no-link.html @@ -0,0 +1,409 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/component-styles/ts/src/app/app.module.ts b/aio/content/examples/component-styles/ts/src/app/app.module.ts new file mode 100644 index 0000000000..31c72cbbf2 --- /dev/null +++ b/aio/content/examples/component-styles/ts/src/app/app.module.ts @@ -0,0 +1,23 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { HeroAppComponent } from './hero-app.component'; +import { HeroAppMainComponent } from './hero-app-main.component'; +import { HeroDetailsComponent } from './hero-details.component'; +import { HeroControlsComponent } from './hero-controls.component'; +import { QuestSummaryComponent } from './quest-summary.component'; +import { HeroTeamComponent } from './hero-team.component'; + +@NgModule({ + imports: [ BrowserModule ], + declarations: [ + HeroAppComponent, + HeroAppMainComponent, + HeroDetailsComponent, + HeroControlsComponent, + QuestSummaryComponent, + HeroTeamComponent + ], + bootstrap: [ HeroAppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/component-styles/ts/src/app/hero-app-main.component.ts b/aio/content/examples/component-styles/ts/src/app/hero-app-main.component.ts new file mode 100644 index 0000000000..aebb1f729e --- /dev/null +++ b/aio/content/examples/component-styles/ts/src/app/hero-app-main.component.ts @@ -0,0 +1,16 @@ +import { Component, Input } from '@angular/core'; + +import { Hero } from './hero'; + +@Component({ + selector: 'hero-app-main', + template: ` + + + + + ` +}) +export class HeroAppMainComponent { + @Input() hero: Hero; +} diff --git a/aio/content/examples/component-styles/ts/src/app/hero-app.component.ts b/aio/content/examples/component-styles/ts/src/app/hero-app.component.ts new file mode 100644 index 0000000000..5f1923e6f3 --- /dev/null +++ b/aio/content/examples/component-styles/ts/src/app/hero-app.component.ts @@ -0,0 +1,24 @@ +import { Component, HostBinding } from '@angular/core'; +import { Hero } from './hero'; + +// #docregion +@Component({ + selector: 'hero-app', + template: ` +

    Tour of Heroes

    + `, + styles: ['h1 { font-weight: normal; }'] +}) +export class HeroAppComponent { +// #enddocregion + hero = new Hero( + 'Human Torch', + ['Mister Fantastic', 'Invisible Woman', 'Thing'] + ); + + @HostBinding('class') get themeClass() { + return 'theme-light'; + } +// #docregion +} +// #enddocregion diff --git a/aio/content/examples/component-styles/ts/src/app/hero-controls.component.ts b/aio/content/examples/component-styles/ts/src/app/hero-controls.component.ts new file mode 100644 index 0000000000..5d293596d2 --- /dev/null +++ b/aio/content/examples/component-styles/ts/src/app/hero-controls.component.ts @@ -0,0 +1,25 @@ +import { Component, Input } from '@angular/core'; +import { Hero } from './hero'; + +// #docregion inlinestyles +@Component({ + selector: 'hero-controls', + template: ` + +

    Controls

    + + ` +}) +// #enddocregion inlinestyles +export class HeroControlsComponent { + @Input() hero: Hero; + + activate() { + this.hero.active = true; + } +} diff --git a/aio/content/examples/component-styles/ts/src/app/hero-details-box.css b/aio/content/examples/component-styles/ts/src/app/hero-details-box.css new file mode 100644 index 0000000000..f240073005 --- /dev/null +++ b/aio/content/examples/component-styles/ts/src/app/hero-details-box.css @@ -0,0 +1,3 @@ +:host { + padding: 10px; +} diff --git a/aio/content/examples/component-styles/ts/src/app/hero-details.component.css b/aio/content/examples/component-styles/ts/src/app/hero-details.component.css new file mode 100644 index 0000000000..fd938ca55c --- /dev/null +++ b/aio/content/examples/component-styles/ts/src/app/hero-details.component.css @@ -0,0 +1,28 @@ +/* #docregion import */ +@import 'hero-details-box.css'; +/* #enddocregion import */ + +/* #docregion host */ +:host { + display: block; + border: 1px solid black; +} +/* #enddocregion host */ + +/* #docregion hostfunction */ +:host(.active) { + border-width: 3px; +} +/* #enddocregion hostfunction */ + +/* #docregion hostcontext */ +:host-context(.theme-light) h2 { + background-color: #eef; +} +/* #enddocregion hostcontext */ + +/* #docregion deep */ +:host /deep/ h3 { + font-style: italic; +} +/* #enddocregion deep */ diff --git a/aio/content/examples/component-styles/ts/src/app/hero-details.component.ts b/aio/content/examples/component-styles/ts/src/app/hero-details.component.ts new file mode 100644 index 0000000000..bd86a63e04 --- /dev/null +++ b/aio/content/examples/component-styles/ts/src/app/hero-details.component.ts @@ -0,0 +1,18 @@ +import { Component, Input } from '@angular/core'; +import { Hero } from './hero'; + +// #docregion styleurls +@Component({ + selector: 'hero-details', + template: ` +

    {{hero.name}}

    + + + `, + styleUrls: ['app/hero-details.component.css'] +}) +export class HeroDetailsComponent { + // #enddocregion styleurls + @Input() hero: Hero; + // #docregion styleurls +} diff --git a/aio/content/examples/component-styles/ts/src/app/hero-team.component.css b/aio/content/examples/component-styles/ts/src/app/hero-team.component.css new file mode 100644 index 0000000000..b87679886b --- /dev/null +++ b/aio/content/examples/component-styles/ts/src/app/hero-team.component.css @@ -0,0 +1,3 @@ +li { + list-style-type: square; +} diff --git a/aio/content/examples/component-styles/ts/src/app/hero-team.component.ts b/aio/content/examples/component-styles/ts/src/app/hero-team.component.ts new file mode 100644 index 0000000000..4f092d2827 --- /dev/null +++ b/aio/content/examples/component-styles/ts/src/app/hero-team.component.ts @@ -0,0 +1,19 @@ +import { Component, Input } from '@angular/core'; +import { Hero } from './hero'; + +// #docregion stylelink +@Component({ + selector: 'hero-team', + template: ` + +

    Team

    +
      +
    • + {{member}} +
    • +
    ` +}) +// #enddocregion stylelink +export class HeroTeamComponent { + @Input() hero: Hero; +} diff --git a/aio/content/examples/component-styles/ts/src/app/hero.ts b/aio/content/examples/component-styles/ts/src/app/hero.ts new file mode 100644 index 0000000000..7f8969e682 --- /dev/null +++ b/aio/content/examples/component-styles/ts/src/app/hero.ts @@ -0,0 +1,7 @@ +export class Hero { + active: boolean; + + constructor(public name: string, + public team: string[]) { + } +} diff --git a/aio/content/examples/component-styles/ts/src/app/quest-summary.component.css b/aio/content/examples/component-styles/ts/src/app/quest-summary.component.css new file mode 100644 index 0000000000..207fa981dd --- /dev/null +++ b/aio/content/examples/component-styles/ts/src/app/quest-summary.component.css @@ -0,0 +1,5 @@ +:host { + display: block; + background-color: green; + color: white; +} diff --git a/aio/content/examples/component-styles/ts/src/app/quest-summary.component.html b/aio/content/examples/component-styles/ts/src/app/quest-summary.component.html new file mode 100644 index 0000000000..ace27d2a1c --- /dev/null +++ b/aio/content/examples/component-styles/ts/src/app/quest-summary.component.html @@ -0,0 +1 @@ +

    No quests in progress

    diff --git a/aio/content/examples/component-styles/ts/src/app/quest-summary.component.ts b/aio/content/examples/component-styles/ts/src/app/quest-summary.component.ts new file mode 100644 index 0000000000..c43239e368 --- /dev/null +++ b/aio/content/examples/component-styles/ts/src/app/quest-summary.component.ts @@ -0,0 +1,21 @@ +/* tslint:disable:no-unused-variable */ +// #docplaster +import { Component, ViewEncapsulation } from '@angular/core'; + +// #docregion +@Component({ + moduleId: module.id, + selector: 'quest-summary', + // #docregion urls + templateUrl: './quest-summary.component.html', + styleUrls: ['./quest-summary.component.css'] + // #enddocregion urls +}) +export class QuestSummaryComponent { } +// #enddocregion +/* + // #docregion encapsulation.native + // warning: few browsers support shadow DOM encapsulation at this time + encapsulation: ViewEncapsulation.Native + // #enddocregion encapsulation.native +*/ diff --git a/aio/content/examples/component-styles/ts/src/index.html b/aio/content/examples/component-styles/ts/src/index.html new file mode 100644 index 0000000000..6e9dc51b32 --- /dev/null +++ b/aio/content/examples/component-styles/ts/src/index.html @@ -0,0 +1,31 @@ + + + + + Component Styles + + + + + + + + + + + + + + + +

    External H1 Title for E2E test

    + + +
      +
    • External list for E2E test
    • +
    + + + diff --git a/aio/content/examples/component-styles/ts/src/main.ts b/aio/content/examples/component-styles/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/aio/content/examples/component-styles/ts/src/main.ts @@ -0,0 +1,5 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/dependency-injection/e2e-spec.ts b/aio/content/examples/dependency-injection/e2e-spec.ts new file mode 100644 index 0000000000..28fe22a9cb --- /dev/null +++ b/aio/content/examples/dependency-injection/e2e-spec.ts @@ -0,0 +1,199 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by, ElementFinder } from 'protractor'; + +describe('Dependency Injection Tests', function () { + + let expectedMsg: string; + let expectedMsgRx: RegExp; + + beforeAll(function () { + browser.get(''); + }); + + describe('Cars:', function() { + + it('DI car displays as expected', function () { + expectedMsg = 'DI car with 4 cylinders and Flintstone tires.'; + expect(element(by.css('#di')).getText()).toEqual(expectedMsg); + }); + + it('No DI car displays as expected', function () { + expectedMsg = 'No DI car with 4 cylinders and Flintstone tires.'; + expect(element(by.css('#nodi')).getText()).toEqual(expectedMsg); + }); + + it('Injector car displays as expected', function () { + expectedMsg = 'Injector car with 4 cylinders and Flintstone tires.'; + expect(element(by.css('#injector')).getText()).toEqual(expectedMsg); + }); + + it('Factory car displays as expected', function () { + expectedMsg = 'Factory car with 4 cylinders and Flintstone tires.'; + expect(element(by.css('#factory')).getText()).toEqual(expectedMsg); + }); + + it('Simple car displays as expected', function () { + expectedMsg = 'Simple car with 4 cylinders and Flintstone tires.'; + expect(element(by.css('#simple')).getText()).toEqual(expectedMsg); + }); + + it('Super car displays as expected', function () { + expectedMsg = 'Super car with 12 cylinders and Flintstone tires.'; + expect(element(by.css('#super')).getText()).toEqual(expectedMsg); + }); + + it('Test car displays as expected', function () { + expectedMsg = 'Test car with 8 cylinders and YokoGoodStone tires.'; + expect(element(by.css('#test')).getText()).toEqual(expectedMsg); + }); + }); + + describe('Other Injections:', function() { + it('DI car displays as expected', function () { + expectedMsg = 'DI car with 4 cylinders and Flintstone tires.'; + expect(element(by.css('#car')).getText()).toEqual(expectedMsg); + }); + + it('Hero displays as expected', function () { + expectedMsg = 'Mr. Nice'; + expect(element(by.css('#hero')).getText()).toEqual(expectedMsg); + }); + + it('Optional injection displays as expected', function () { + expectedMsg = 'R.O.U.S.\'s? I don\'t think they exist!'; + expect(element(by.css('#rodent')).getText()).toEqual(expectedMsg); + }); + }); + + describe('Tests:', function() { + + it('Tests display as expected', function () { + expectedMsgRx = /Tests passed/; + expect(element(by.css('#tests')).getText()).toMatch(expectedMsgRx); + }); + + }); + + describe('Provider variations:', function() { + + it('P1 (class) displays as expected', function () { + expectedMsg = 'Hello from logger provided with Logger class'; + expect(element(by.css('#p1')).getText()).toEqual(expectedMsg); + }); + + it('P3 (provide) displays as expected', function () { + expectedMsg = 'Hello from logger provided with useClass:Logger'; + expect(element(by.css('#p3')).getText()).toEqual(expectedMsg); + }); + + it('P4 (useClass:BetterLogger) displays as expected', function () { + expectedMsg = 'Hello from logger provided with useClass:BetterLogger'; + expect(element(by.css('#p4')).getText()).toEqual(expectedMsg); + }); + + it('P5 (useClass:EvenBetterLogger - dependency) displays as expected', function () { + expectedMsg = 'Message to Bob: Hello from EvenBetterlogger'; + expect(element(by.css('#p5')).getText()).toEqual(expectedMsg); + }); + + it('P6a (no alias) displays as expected', function () { + expectedMsg = 'Hello OldLogger (but we want NewLogger)'; + expect(element(by.css('#p6a')).getText()).toEqual(expectedMsg); + }); + + it('P6b (alias) displays as expected', function () { + expectedMsg = 'Hello from NewLogger (via aliased OldLogger)'; + expect(element(by.css('#p6b')).getText()).toEqual(expectedMsg); + }); + + it('P7 (useValue) displays as expected', function () { + expectedMsg = 'Silent logger says "Shhhhh!". Provided via "useValue"'; + expect(element(by.css('#p7')).getText()).toEqual(expectedMsg); + }); + + it('P8 (useFactory) displays as expected', function () { + expectedMsg = 'Hero service injected successfully via heroServiceProvider'; + expect(element(by.css('#p8')).getText()).toEqual(expectedMsg); + }); + + it('P9 (OpaqueToken) displays as expected', function () { + expectedMsg = 'APP_CONFIG Application title is Dependency Injection'; + expect(element(by.css('#p9')).getText()).toEqual(expectedMsg); + }); + + it('P10 (optional dependency) displays as expected', function () { + expectedMsg = 'Optional logger was not available'; + expect(element(by.css('#p10')).getText()).toEqual(expectedMsg); + }); + }); + + describe('User/Heroes:', function() { + it('User is Bob - unauthorized', function () { + expectedMsgRx = /Bob, is not authorized/; + expect(element(by.css('#user')).getText()).toMatch(expectedMsgRx); + }); + + it('should have button', function () { + expect(element.all(by.cssContainingText('button', 'Next User')) + .get(0).isDisplayed()).toBe(true, '\'Next User\' button should be displayed'); + }); + + it('unauthorized user should have multiple unauthorized heroes', function () { + let heroes = element.all(by.css('#unauthorized hero-list div')); + expect(heroes.count()).toBeGreaterThan(0); + }); + + it('unauthorized user should have no secret heroes', function () { + let heroes = element.all(by.css('#unauthorized hero-list div')); + expect(heroes.count()).toBeGreaterThan(0); + + let filteredHeroes = heroes.filter((elem: ElementFinder, index: number) => { + return elem.getText().then((text: string) => { + return /secret/.test(text); + }); + }); + + expect(filteredHeroes.count()).toEqual(0); + }); + + it('unauthorized user should have no authorized heroes listed', function () { + expect(element.all(by.css('#authorized hero-list div')).count()).toEqual(0); + }); + + describe('after button click', function() { + + beforeAll(function (done) { + let buttonEle = element.all(by.cssContainingText('button', 'Next User')).get(0); + buttonEle.click().then(done, done); + }); + + it('User is Alice - authorized', function () { + expectedMsgRx = /Alice, is authorized/; + expect(element(by.css('#user')).getText()).toMatch(expectedMsgRx); + }); + + it('authorized user should have multiple authorized heroes ', function () { + let heroes = element.all(by.css('#authorized hero-list div')); + expect(heroes.count()).toBeGreaterThan(0); + }); + + it('authorized user should have secret heroes', function () { + let heroes = element.all(by.css('#authorized hero-list div')); + expect(heroes.count()).toBeGreaterThan(0); + + let filteredHeroes = heroes.filter(function(elem: ElementFinder, index: number){ + return elem.getText().then(function(text: string) { + return /secret/.test(text); + }); + }); + + expect(filteredHeroes.count()).toBeGreaterThan(0); + }); + + it('authorized user should have no unauthorized heroes listed', function () { + expect(element.all(by.css('#unauthorized hero-list div')).count()).toEqual(0); + }); + }); + }); +}); diff --git a/aio/content/examples/dependency-injection/ts/example-config.json b/aio/content/examples/dependency-injection/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/dependency-injection/ts/plnkr.json b/aio/content/examples/dependency-injection/ts/plnkr.json new file mode 100644 index 0000000000..e8d1ab24b2 --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/plnkr.json @@ -0,0 +1,10 @@ +{ + "description": "Dependency Injection", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1,2].*" + ], + "tags": ["dependency", "di"] +} diff --git a/aio/content/examples/dependency-injection/ts/plnkr.no-link.html b/aio/content/examples/dependency-injection/ts/plnkr.no-link.html new file mode 100644 index 0000000000..d4219166e9 --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/plnkr.no-link.html @@ -0,0 +1,986 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/dependency-injection/ts/src/app/app.component.1.ts b/aio/content/examples/dependency-injection/ts/src/app/app.component.1.ts new file mode 100644 index 0000000000..b398ebeb57 --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/app.component.1.ts @@ -0,0 +1,17 @@ +// Early versions + +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: ` +

    {{title}}

    + + + ` +}) + +export class AppComponent { + title = 'Dependency Injection'; +} diff --git a/aio/content/examples/dependency-injection/ts/src/app/app.component.2.ts b/aio/content/examples/dependency-injection/ts/src/app/app.component.2.ts new file mode 100644 index 0000000000..d24df5568c --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/app.component.2.ts @@ -0,0 +1,25 @@ +// #docregion +// #docregion imports +import { Component } from '@angular/core'; +import { Inject } from '@angular/core'; + +import { APP_CONFIG, AppConfig } from './app.config'; +// #enddocregion imports + +@Component({ + selector: 'my-app', + template: ` +

    {{title}}

    + + + ` +}) +export class AppComponent { + title: string; + + // #docregion ctor + constructor(@Inject(APP_CONFIG) config: AppConfig) { + this.title = config.title; + } + // #enddocregion ctor +} diff --git a/aio/content/examples/dependency-injection/ts/src/app/app.component.ts b/aio/content/examples/dependency-injection/ts/src/app/app.component.ts new file mode 100644 index 0000000000..dd83963a4b --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/app.component.ts @@ -0,0 +1,48 @@ +// #docplaster +// #docregion +// #docregion imports +import { Component, Inject } from '@angular/core'; + +import { APP_CONFIG, AppConfig } from './app.config'; +import { Logger } from './logger.service'; +import { UserService } from './user.service'; +// #enddocregion imports + +@Component({ + selector: 'my-app', + template: ` +

    {{title}}

    + + + +

    User

    +

    + {{userInfo}} + +

    + + + + `, + providers: [Logger] +}) +export class AppComponent { + title: string; + + // #docregion ctor + constructor( + @Inject(APP_CONFIG) config: AppConfig, + private userService: UserService) { + this.title = config.title; + } + // #enddocregion ctor + + get isAuthorized() { return this.user.isAuthorized; } + nextUser() { this.userService.getNewUser(); } + get user() { return this.userService.user; } + + get userInfo() { + return `Current user, ${this.user.name}, is ` + + `${this.isAuthorized ? '' : 'not'} authorized. `; + } +} diff --git a/aio/content/examples/dependency-injection/ts/src/app/app.config.ts b/aio/content/examples/dependency-injection/ts/src/app/app.config.ts new file mode 100644 index 0000000000..7626c46e5b --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/app.config.ts @@ -0,0 +1,16 @@ +// #docregion token +import { OpaqueToken } from '@angular/core'; + +export let APP_CONFIG = new OpaqueToken('app.config'); +// #enddocregion token + +// #docregion config +export interface AppConfig { + apiEndpoint: string; + title: string; +} + +export const HERO_DI_CONFIG: AppConfig = { + apiEndpoint: 'api.heroes.com', + title: 'Dependency Injection' +}; diff --git a/aio/content/examples/dependency-injection/ts/src/app/app.module.ts b/aio/content/examples/dependency-injection/ts/src/app/app.module.ts new file mode 100644 index 0000000000..67ae8ae913 --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/app.module.ts @@ -0,0 +1,60 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; +import { CarComponent } from './car/car.component'; +import { HeroesComponent } from './heroes/heroes.component'; +import { HeroListComponent } from './heroes/hero-list.component'; +import { InjectorComponent } from './injector.component'; +import { TestComponent } from './test.component'; +import { APP_CONFIG, HERO_DI_CONFIG } from './app.config'; +import { UserService } from './user.service'; +import { + ProvidersComponent, + Provider1Component, + Provider3Component, + Provider4Component, + Provider5Component, + Provider6aComponent, + Provider6bComponent, + Provider7Component, + Provider8Component, + Provider9Component, + Provider10Component, +} from './providers.component'; + +// #docregion ngmodule +@NgModule({ + imports: [ + BrowserModule + ], + declarations: [ + AppComponent, + CarComponent, + HeroesComponent, + // #enddocregion ngmodule + HeroListComponent, + InjectorComponent, + TestComponent, + ProvidersComponent, + Provider1Component, + Provider3Component, + Provider4Component, + Provider5Component, + Provider6aComponent, + Provider6bComponent, + Provider7Component, + Provider8Component, + Provider9Component, + Provider10Component, + // #docregion ngmodule + ], + // #docregion ngmodule-providers + providers: [ + UserService, + { provide: APP_CONFIG, useValue: HERO_DI_CONFIG } + ], + // #enddocregion ngmodule-providers + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/dependency-injection/ts/src/app/car/car-creations.ts b/aio/content/examples/dependency-injection/ts/src/app/car/car-creations.ts new file mode 100644 index 0000000000..c758c72951 --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/car/car-creations.ts @@ -0,0 +1,46 @@ +// Examples with car and engine variations + +// #docplaster +import { Car, Engine, Tires } from './car'; + +///////// example 1 //////////// +export function simpleCar() { + // #docregion car-ctor-instantiation + // Simple car with 4 cylinders and Flintstone tires. + let car = new Car(new Engine(), new Tires()); + // #enddocregion car-ctor-instantiation + car.description = 'Simple'; + return car; +} + + +///////// example 2 //////////// +// #docregion car-ctor-instantiation-with-param + class Engine2 { + constructor(public cylinders: number) { } + } +// #enddocregion car-ctor-instantiation-with-param +export function superCar() { +// #docregion car-ctor-instantiation-with-param + // Super car with 12 cylinders and Flintstone tires. + let bigCylinders = 12; + let car = new Car(new Engine2(bigCylinders), new Tires()); +// #enddocregion car-ctor-instantiation-with-param + car.description = 'Super'; + return car; +} + +/////////// example 3 ////////// + // #docregion car-ctor-instantiation-with-mocks + class MockEngine extends Engine { cylinders = 8; } + class MockTires extends Tires { make = 'YokoGoodStone'; } + + // #enddocregion car-ctor-instantiation-with-mocks +export function testCar() { + // #docregion car-ctor-instantiation-with-mocks + // Test car with 8 cylinders and YokoGoodStone tires. + let car = new Car(new MockEngine(), new MockTires()); + // #enddocregion car-ctor-instantiation-with-mocks + car.description = 'Test'; + return car; +} diff --git a/aio/content/examples/dependency-injection/ts/src/app/car/car-factory.ts b/aio/content/examples/dependency-injection/ts/src/app/car/car-factory.ts new file mode 100644 index 0000000000..06daafe63b --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/car/car-factory.ts @@ -0,0 +1,19 @@ +// #docregion +import { Engine, Tires, Car } from './car'; + +// BAD pattern! +export class CarFactory { + createCar() { + let car = new Car(this.createEngine(), this.createTires()); + car.description = 'Factory'; + return car; + } + + createEngine() { + return new Engine(); + } + + createTires() { + return new Tires(); + } +} diff --git a/aio/content/examples/dependency-injection/ts/src/app/car/car-injector.ts b/aio/content/examples/dependency-injection/ts/src/app/car/car-injector.ts new file mode 100644 index 0000000000..4f7498ee4e --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/car/car-injector.ts @@ -0,0 +1,27 @@ +import { ReflectiveInjector } from '@angular/core'; + +import { Car, Engine, Tires } from './car'; +import { Logger } from '../logger.service'; + +// #docregion injector +export function useInjector() { + let injector: ReflectiveInjector; + // #enddocregion injector + /* + // #docregion injector-no-new + // Cannot instantiate an ReflectiveInjector like this! + let injector = new ReflectiveInjector([Car, Engine, Tires]); + // #enddocregion injector-no-new + */ + // #docregion injector, injector-create-and-call + injector = ReflectiveInjector.resolveAndCreate([Car, Engine, Tires]); + // #docregion injector-call + let car = injector.get(Car); + // #enddocregion injector-call, injector-create-and-call + car.description = 'Injector'; + + injector = ReflectiveInjector.resolveAndCreate([Logger]); + let logger = injector.get(Logger); + logger.log('Injector car.drive() said: ' + car.drive()); + return car; +} diff --git a/aio/content/examples/dependency-injection/ts/src/app/car/car-no-di.ts b/aio/content/examples/dependency-injection/ts/src/app/car/car-no-di.ts new file mode 100644 index 0000000000..9026edebc2 --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/car/car-no-di.ts @@ -0,0 +1,24 @@ +// Car without DI +import { Engine, Tires } from './car'; + +// #docregion car +export class Car { + + // #docregion car-ctor + public engine: Engine; + public tires: Tires; + public description = 'No DI'; + + constructor() { + this.engine = new Engine(); + this.tires = new Tires(); + } + // #enddocregion car-ctor + + // Method using the engine and tires + drive() { + return `${this.description} car with ` + + `${this.engine.cylinders} cylinders and ${this.tires.make} tires.`; + } +} +// #enddocregion car diff --git a/aio/content/examples/dependency-injection/ts/src/app/car/car.component.ts b/aio/content/examples/dependency-injection/ts/src/app/car/car.component.ts new file mode 100644 index 0000000000..9a835ef124 --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/car/car.component.ts @@ -0,0 +1,38 @@ +// #docregion +import { Component } from '@angular/core'; + +import { Car, Engine, Tires } from './car'; +import { Car as CarNoDi } from './car-no-di'; +import { CarFactory } from './car-factory'; + +import { testCar, + simpleCar, + superCar } from './car-creations'; + +import { useInjector } from './car-injector'; + + +@Component({ + selector: 'my-car', + template: ` +

    Cars

    +
    {{car.drive()}}
    +
    {{noDiCar.drive()}}
    +
    {{injectorCar.drive()}}
    +
    {{factoryCar.drive()}}
    +
    {{simpleCar.drive()}}
    +
    {{superCar.drive()}}
    +
    {{testCar.drive()}}
    + `, + providers: [Car, Engine, Tires] +}) +export class CarComponent { + factoryCar = (new CarFactory).createCar(); + injectorCar = useInjector(); + noDiCar = new CarNoDi; + simpleCar = simpleCar(); + superCar = superCar(); + testCar = testCar(); + + constructor(public car: Car) {} +} diff --git a/aio/content/examples/dependency-injection/ts/src/app/car/car.ts b/aio/content/examples/dependency-injection/ts/src/app/car/car.ts new file mode 100644 index 0000000000..37162c570b --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/car/car.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@angular/core'; + +export class Engine { + public cylinders = 4; +} + +export class Tires { + public make = 'Flintstone'; + public model = 'Square'; +} + +@Injectable() +export class Car { + // #docregion car-ctor + public description = 'DI'; + + constructor(public engine: Engine, public tires: Tires) { } + // #enddocregion car-ctor + + // Method using the engine and tires + drive() { + return `${this.description} car with ` + + `${this.engine.cylinders} cylinders and ${this.tires.make} tires.`; + } +} diff --git a/aio/content/examples/dependency-injection/ts/src/app/heroes/hero-list.component.1.ts b/aio/content/examples/dependency-injection/ts/src/app/heroes/hero-list.component.1.ts new file mode 100644 index 0000000000..ba32366aa9 --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/heroes/hero-list.component.1.ts @@ -0,0 +1,16 @@ +// #docregion +import { Component } from '@angular/core'; + +import { HEROES } from './mock-heroes'; + +@Component({ + selector: 'hero-list', + template: ` +
    + {{hero.id}} - {{hero.name}} +
    + ` +}) +export class HeroListComponent { + heroes = HEROES; +} diff --git a/aio/content/examples/dependency-injection/ts/src/app/heroes/hero-list.component.2.ts b/aio/content/examples/dependency-injection/ts/src/app/heroes/hero-list.component.2.ts new file mode 100644 index 0000000000..cb23d3257c --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/heroes/hero-list.component.2.ts @@ -0,0 +1,31 @@ +// #docplaster +// #docregion +import { Component } from '@angular/core'; + +import { Hero } from './hero'; +// #enddocregion +import { HeroService } from './hero.service.1'; +/* +// #docregion +import { HeroService } from './hero.service'; +// #enddocregion +*/ +// #docregion + +@Component({ + selector: 'hero-list', + template: ` +
    + {{hero.id}} - {{hero.name}} +
    + ` +}) +export class HeroListComponent { + heroes: Hero[]; + + // #docregion ctor + constructor(heroService: HeroService) { + this.heroes = heroService.getHeroes(); + } + // #enddocregion ctor +} diff --git a/aio/content/examples/dependency-injection/ts/src/app/heroes/hero-list.component.ts b/aio/content/examples/dependency-injection/ts/src/app/heroes/hero-list.component.ts new file mode 100644 index 0000000000..db3a325bdb --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/heroes/hero-list.component.ts @@ -0,0 +1,26 @@ +/* tslint:disable:one-line */ +// #docregion +import { Component } from '@angular/core'; + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; + +@Component({ + selector: 'hero-list', + template: ` +
    + {{hero.id}} - {{hero.name}} + ({{hero.isSecret ? 'secret' : 'public'}}) +
    + `, +}) +export class HeroListComponent { + heroes: Hero[]; + + // #docregion ctor-signature + constructor(heroService: HeroService) + // #enddocregion ctor-signature + { + this.heroes = heroService.getHeroes(); + } +} diff --git a/aio/content/examples/dependency-injection/ts/src/app/heroes/hero.service.1.ts b/aio/content/examples/dependency-injection/ts/src/app/heroes/hero.service.1.ts new file mode 100644 index 0000000000..2e0e3ca734 --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/heroes/hero.service.1.ts @@ -0,0 +1,9 @@ +// #docregion +import { Injectable } from '@angular/core'; + +import { HEROES } from './mock-heroes'; + +@Injectable() +export class HeroService { + getHeroes() { return HEROES; } +} diff --git a/aio/content/examples/dependency-injection/ts/src/app/heroes/hero.service.2.ts b/aio/content/examples/dependency-injection/ts/src/app/heroes/hero.service.2.ts new file mode 100644 index 0000000000..6b3a98306a --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/heroes/hero.service.2.ts @@ -0,0 +1,18 @@ +// #docregion +import { Injectable } from '@angular/core'; + +import { HEROES } from './mock-heroes'; +import { Logger } from '../logger.service'; + +@Injectable() +export class HeroService { + + // #docregion ctor + constructor(private logger: Logger) { } + // #enddocregion ctor + + getHeroes() { + this.logger.log('Getting heroes ...'); + return HEROES; + } +} diff --git a/aio/content/examples/dependency-injection/ts/src/app/heroes/hero.service.provider.ts b/aio/content/examples/dependency-injection/ts/src/app/heroes/hero.service.provider.ts new file mode 100644 index 0000000000..6de4ebee90 --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/heroes/hero.service.provider.ts @@ -0,0 +1,19 @@ +/* tslint:disable:one-line */ +// #docregion +import { HeroService } from './hero.service'; +import { Logger } from '../logger.service'; +import { UserService } from '../user.service'; + +// #docregion factory +let heroServiceFactory = (logger: Logger, userService: UserService) => { + return new HeroService(logger, userService.user.isAuthorized); +}; +// #enddocregion factory + +// #docregion provider +export let heroServiceProvider = + { provide: HeroService, + useFactory: heroServiceFactory, + deps: [Logger, UserService] + }; +// #enddocregion provider diff --git a/aio/content/examples/dependency-injection/ts/src/app/heroes/hero.service.ts b/aio/content/examples/dependency-injection/ts/src/app/heroes/hero.service.ts new file mode 100644 index 0000000000..fb03ec1de6 --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/heroes/hero.service.ts @@ -0,0 +1,20 @@ +// #docregion +import { Injectable } from '@angular/core'; + +import { HEROES } from './mock-heroes'; +import { Logger } from '../logger.service'; + +@Injectable() +export class HeroService { + // #docregion internals + constructor( + private logger: Logger, + private isAuthorized: boolean) { } + + getHeroes() { + let auth = this.isAuthorized ? 'authorized ' : 'unauthorized'; + this.logger.log(`Getting heroes for ${auth} user.`); + return HEROES.filter(hero => this.isAuthorized || !hero.isSecret); + } + // #enddocregion internals +} diff --git a/aio/content/examples/dependency-injection/ts/src/app/heroes/hero.ts b/aio/content/examples/dependency-injection/ts/src/app/heroes/hero.ts new file mode 100644 index 0000000000..5c49328241 --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/heroes/hero.ts @@ -0,0 +1,6 @@ +// #docregion +export class Hero { + id: number; + name: string; + isSecret = false; +} diff --git a/aio/content/examples/dependency-injection/ts/src/app/heroes/heroes.component.1.ts b/aio/content/examples/dependency-injection/ts/src/app/heroes/heroes.component.1.ts new file mode 100644 index 0000000000..e0e9deae08 --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/heroes/heroes.component.1.ts @@ -0,0 +1,21 @@ +// #docplaster +// #docregion full, v1 +import { Component } from '@angular/core'; +// #enddocregion v1 + +import { HeroService } from './hero.service'; +// #enddocregion full + +// #docregion full, v1 + +@Component({ + selector: 'my-heroes', + // #enddocregion v1 + providers: [HeroService], + // #docregion v1 + template: ` +

    Heroes

    + + ` +}) +export class HeroesComponent { } diff --git a/aio/content/examples/dependency-injection/ts/src/app/heroes/heroes.component.ts b/aio/content/examples/dependency-injection/ts/src/app/heroes/heroes.component.ts new file mode 100644 index 0000000000..5923f7590b --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/heroes/heroes.component.ts @@ -0,0 +1,14 @@ +// #docregion +import { Component } from '@angular/core'; + +import { heroServiceProvider } from './hero.service.provider'; + +@Component({ + selector: 'my-heroes', + template: ` +

    Heroes

    + + `, + providers: [heroServiceProvider] +}) +export class HeroesComponent { } diff --git a/aio/content/examples/dependency-injection/ts/src/app/heroes/mock-heroes.ts b/aio/content/examples/dependency-injection/ts/src/app/heroes/mock-heroes.ts new file mode 100644 index 0000000000..79a91dc03a --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/heroes/mock-heroes.ts @@ -0,0 +1,15 @@ +// #docregion +import { Hero } from './hero'; + +export var HEROES: Hero[] = [ + { id: 11, isSecret: false, name: 'Mr. Nice' }, + { id: 12, isSecret: false, name: 'Narco' }, + { id: 13, isSecret: false, name: 'Bombasto' }, + { id: 14, isSecret: false, name: 'Celeritas' }, + { id: 15, isSecret: false, name: 'Magneta' }, + { id: 16, isSecret: false, name: 'RubberMan' }, + { id: 17, isSecret: false, name: 'Dynama' }, + { id: 18, isSecret: true, name: 'Dr IQ' }, + { id: 19, isSecret: true, name: 'Magma' }, + { id: 20, isSecret: true, name: 'Tornado' } +]; diff --git a/aio/content/examples/dependency-injection/ts/src/app/injector.component.ts b/aio/content/examples/dependency-injection/ts/src/app/injector.component.ts new file mode 100644 index 0000000000..7c9e8a56e5 --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/injector.component.ts @@ -0,0 +1,43 @@ +// #docplaster +// #docregion +import { Component, Injector } from '@angular/core'; + +import { Car, Engine, Tires } from './car/car'; +import { Hero } from './heroes/hero'; +import { HeroService } from './heroes/hero.service'; +import { heroServiceProvider } from './heroes/hero.service.provider'; +import { Logger } from './logger.service'; + +// #docregion injector +@Component({ + selector: 'my-injectors', + template: ` +

    Other Injections

    +
    {{car.drive()}}
    +
    {{hero.name}}
    +
    {{rodent}}
    + `, + providers: [Car, Engine, Tires, heroServiceProvider, Logger] +}) +export class InjectorComponent { + car: Car = this.injector.get(Car); + + // #docregion get-hero-service + heroService: HeroService = this.injector.get(HeroService); + // #enddocregion get-hero-service + hero: Hero = this.heroService.getHeroes()[0]; + + constructor(private injector: Injector) { } + + get rodent() { + let rousDontExist = `R.O.U.S.'s? I don't think they exist!`; + return this.injector.get(ROUS, rousDontExist); + } +} +// #enddocregion injector + +/** + * R.O.U.S. - Rodents Of Unusual Size + * // https://www.youtube.com/watch?v=BOv5ZjAOpC8 + */ +class ROUS { } diff --git a/aio/content/examples/dependency-injection/ts/src/app/logger.service.ts b/aio/content/examples/dependency-injection/ts/src/app/logger.service.ts new file mode 100644 index 0000000000..e943523ad2 --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/logger.service.ts @@ -0,0 +1,12 @@ +// #docregion +import { Injectable } from '@angular/core'; + +@Injectable() +export class Logger { + logs: string[] = []; // capture logs for testing + + log(message: string) { + this.logs.push(message); + console.log(message); + } +} diff --git a/aio/content/examples/dependency-injection/ts/src/app/providers.component.ts b/aio/content/examples/dependency-injection/ts/src/app/providers.component.ts new file mode 100644 index 0000000000..a96bfce99e --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/providers.component.ts @@ -0,0 +1,267 @@ +/* tslint:disable:one-line:check-open-brace*/ +// Examples of provider arrays +// #docplaster +import { Component, Inject, Injectable, OnInit } from '@angular/core'; + +import { APP_CONFIG, AppConfig, + HERO_DI_CONFIG } from './app.config'; + +import { HeroService } from './heroes/hero.service'; +import { heroServiceProvider } from './heroes/hero.service.provider'; +import { Logger } from './logger.service'; +import { UserService } from './user.service'; + +let template = '{{log}}'; + +////////////////////////////////////////// +@Component({ + selector: 'provider-1', + template: template, + // #docregion providers-1, providers-logger + providers: [Logger] + // #enddocregion providers-1, providers-logger +}) +export class Provider1Component { + log: string; + constructor(logger: Logger) { + logger.log('Hello from logger provided with Logger class'); + this.log = logger.logs[0]; + } +} + +////////////////////////////////////////// +@Component({ + selector: 'provider-3', + template: template, + providers: + // #docregion providers-3 + [{ provide: Logger, useClass: Logger }] + // #enddocregion providers-3 +}) +export class Provider3Component { + log: string; + constructor(logger: Logger) { + logger.log('Hello from logger provided with useClass:Logger'); + this.log = logger.logs[0]; + } +} + +////////////////////////////////////////// +class BetterLogger extends Logger {} + +@Component({ + selector: 'provider-4', + template: template, + providers: + // #docregion providers-4 + [{ provide: Logger, useClass: BetterLogger }] + // #enddocregion providers-4 +}) +export class Provider4Component { + log: string; + constructor(logger: Logger) { + logger.log('Hello from logger provided with useClass:BetterLogger'); + this.log = logger.logs[0]; + } +} + +////////////////////////////////////////// +// #docregion EvenBetterLogger +@Injectable() +class EvenBetterLogger extends Logger { + constructor(private userService: UserService) { super(); } + + log(message: string) { + let name = this.userService.user.name; + super.log(`Message to ${name}: ${message}`); + } +} +// #enddocregion EvenBetterLogger + +@Component({ + selector: 'provider-5', + template: template, + providers: + // #docregion providers-5 + [ UserService, + { provide: Logger, useClass: EvenBetterLogger }] + // #enddocregion providers-5 +}) +export class Provider5Component { + log: string; + constructor(logger: Logger) { + logger.log('Hello from EvenBetterlogger'); + this.log = logger.logs[0]; + } +} + +////////////////////////////////////////// +class NewLogger extends Logger {} +class OldLogger { + logs: string[] = []; + log(message: string) { + throw new Error('Should not call the old logger!'); + }; +} + +@Component({ + selector: 'provider-6a', + template: template, + providers: + // #docregion providers-6a + [ NewLogger, + // Not aliased! Creates two instances of `NewLogger` + { provide: OldLogger, useClass: NewLogger}] + // #enddocregion providers-6a +}) +export class Provider6aComponent { + log: string; + constructor(newLogger: NewLogger, oldLogger: OldLogger) { + if (newLogger === oldLogger){ + throw new Error('expected the two loggers to be different instances'); + } + oldLogger.log('Hello OldLogger (but we want NewLogger)'); + // The newLogger wasn't called so no logs[] + // display the logs of the oldLogger. + this.log = newLogger.logs[0] || oldLogger.logs[0]; + } +} + +@Component({ + selector: 'provider-6b', + template: template, + providers: + // #docregion providers-6b + [ NewLogger, + // Alias OldLogger w/ reference to NewLogger + { provide: OldLogger, useExisting: NewLogger}] + // #enddocregion providers-6b +}) +export class Provider6bComponent { + log: string; + constructor(newLogger: NewLogger, oldLogger: OldLogger) { + if (newLogger !== oldLogger){ + throw new Error('expected the two loggers to be the same instance'); + } + oldLogger.log('Hello from NewLogger (via aliased OldLogger)'); + this.log = newLogger.logs[0]; + } +} + +////////////////////////////////////////// +// #docregion silent-logger +// An object in the shape of the logger service +let silentLogger = { + logs: ['Silent logger says "Shhhhh!". Provided via "useValue"'], + log: () => {} +}; +// #enddocregion silent-logger + +@Component({ + selector: 'provider-7', + template: template, + providers: + // #docregion providers-7 + [{ provide: Logger, useValue: silentLogger }] + // #enddocregion providers-7 +}) +export class Provider7Component { + log: string; + constructor(logger: Logger) { + logger.log('Hello from logger provided with useValue'); + this.log = logger.logs[0]; + } +} +///////////////// + +@Component({ + selector: 'provider-8', + template: template, + providers: [heroServiceProvider, Logger, UserService] +}) +export class Provider8Component { + // must be true else this component would have blown up at runtime + log = 'Hero service injected successfully via heroServiceProvider'; + + // #docregion provider-8-ctor + constructor(heroService: HeroService) { } + // #enddocregion provider-8-ctor +} + +///////////////// +@Component({ + selector: 'provider-9', + template: template, + /* + // #docregion providers-9-interface + // FAIL! Can't use interface as provider token + [{ provide: AppConfig, useValue: HERO_DI_CONFIG })] + // #enddocregion providers-9-interface + */ + // #docregion providers-9 + providers: [{ provide: APP_CONFIG, useValue: HERO_DI_CONFIG }] + // #enddocregion providers-9 +}) +export class Provider9Component implements OnInit { + log: string; + /* + // #docregion provider-9-ctor-interface + // FAIL! Can't inject using the interface as the parameter type + constructor(private config: AppConfig){ } + // #enddocregion provider-9-ctor-interface + */ + // #docregion provider-9-ctor + constructor(@Inject(APP_CONFIG) private config: AppConfig) { } + // #enddocregion provider-9-ctor + + ngOnInit() { + this.log = 'APP_CONFIG Application title is ' + this.config.title; + } +} +////////////////////////////////////////// +// Sample providers 1 to 7 illustrate a required logger dependency. +// Optional logger, can be null +// #docregion import-optional +import { Optional } from '@angular/core'; +// #enddocregion import-optional + +let some_message = 'Hello from the injected logger'; + +@Component({ + selector: 'provider-10', + template: template, + providers: [{ provide: Logger, useValue: null }] +}) +export class Provider10Component implements OnInit { + log: string; + // #docregion provider-10-ctor + constructor(@Optional() private logger: Logger) { + if (this.logger) { + this.logger.log(some_message); + } + } + // #enddocregion provider-10-ctor + + ngOnInit() { + this.log = this.logger ? this.logger.logs[0] : 'Optional logger was not available'; + } +} + +///////////////// +@Component({ + selector: 'my-providers', + template: ` +

    Provider variations

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + ` +}) +export class ProvidersComponent { } diff --git a/aio/content/examples/dependency-injection/ts/src/app/test.component.ts b/aio/content/examples/dependency-injection/ts/src/app/test.component.ts new file mode 100644 index 0000000000..fc0fef75a8 --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/test.component.ts @@ -0,0 +1,55 @@ +/* tslint:disable */ +// Simulate a simple test +// Reader should look to the testing chapter for the real thing + +import { Component } from '@angular/core'; + +import { HeroService } from './heroes/hero.service'; +import { HeroListComponent } from './heroes/hero-list.component'; + +@Component({ + selector: 'my-tests', + template: ` +

    Tests

    +

    Tests {{results.pass}}: {{results.message}}

    + ` +}) +export class TestComponent { + results = runTests(); +} + +///////////////////////////////////// +function runTests() { + + // #docregion spec + let expectedHeroes = [{name: 'A'}, {name: 'B'}] + let mockService = {getHeroes: () => expectedHeroes } + + it('should have heroes when HeroListComponent created', () => { + let hlc = new HeroListComponent(mockService); + expect(hlc.heroes.length).toEqual(expectedHeroes.length); + }); + // #enddocregion spec + + return testResults; +} + +////////////////////////////////// +// Fake Jasmine infrastructure +var testName: string; +var testResults: {pass: string; message: string}; + +function expect(actual: any) { + return { + toEqual: function(expected: any){ + testResults = actual === expected ? + {pass: 'passed', message: testName} : + {pass: 'failed', message: `${testName}; expected ${actual} to equal ${expected}.`}; + } + }; +} + +function it(label: string, test: () => void) { + testName = label; + test(); +} diff --git a/aio/content/examples/dependency-injection/ts/src/app/user.service.ts b/aio/content/examples/dependency-injection/ts/src/app/user.service.ts new file mode 100644 index 0000000000..8fdda925db --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/app/user.service.ts @@ -0,0 +1,22 @@ +// #docregion +import { Injectable } from '@angular/core'; + +export class User { + constructor( + public name: string, + public isAuthorized = false) { } +} + +// Todo: get the user; don't 'new' it. +let alice = new User('Alice', true); +let bob = new User('Bob', false); + +@Injectable() +export class UserService { + user = bob; // initial user is Bob + + // swap users + getNewUser() { + return this.user = this.user === bob ? alice : bob; + } +} diff --git a/aio/content/examples/dependency-injection/ts/src/index.html b/aio/content/examples/dependency-injection/ts/src/index.html new file mode 100644 index 0000000000..87a9d7e694 --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/index.html @@ -0,0 +1,27 @@ + + + + + Dependency Injection + + + + + + + + + + + + + + + + + Loading my-app ... + + + diff --git a/aio/content/examples/dependency-injection/ts/src/main.ts b/aio/content/examples/dependency-injection/ts/src/main.ts new file mode 100644 index 0000000000..1a1d481719 --- /dev/null +++ b/aio/content/examples/dependency-injection/ts/src/main.ts @@ -0,0 +1,6 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +// #docregion bootstrap +platformBrowserDynamic().bootstrapModule(AppModule); +// #enddocregion bootstrap diff --git a/aio/content/examples/deployment/ts/.gitignore b/aio/content/examples/deployment/ts/.gitignore new file mode 100644 index 0000000000..7f794a0b16 --- /dev/null +++ b/aio/content/examples/deployment/ts/.gitignore @@ -0,0 +1 @@ +!systemjs.config.server.js diff --git a/aio/content/examples/deployment/ts/example-config.json b/aio/content/examples/deployment/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/deployment/ts/src/app/app.component.ts b/aio/content/examples/deployment/ts/src/app/app.component.ts new file mode 100644 index 0000000000..47731c13a4 --- /dev/null +++ b/aio/content/examples/deployment/ts/src/app/app.component.ts @@ -0,0 +1,15 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: ` +

    Simple Deployment

    + + + ` +}) +export class AppComponent { } diff --git a/aio/content/examples/deployment/ts/src/app/app.module.ts b/aio/content/examples/deployment/ts/src/app/app.module.ts new file mode 100644 index 0000000000..a885bc2918 --- /dev/null +++ b/aio/content/examples/deployment/ts/src/app/app.module.ts @@ -0,0 +1,29 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule, Routes } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { CrisisListComponent } from './crisis-list.component'; +import { HeroListComponent } from './hero-list.component'; + +const appRoutes: Routes = [ + { path: 'crisis-center', component: CrisisListComponent }, + { path: 'heroes', component: HeroListComponent }, + + { path: '', redirectTo: '/heroes', pathMatch: 'full' } +]; + +@NgModule({ + imports: [ + BrowserModule, + RouterModule.forRoot(appRoutes) + ], + declarations: [ + AppComponent, + CrisisListComponent, + HeroListComponent + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/deployment/ts/src/app/crisis-list.component.ts b/aio/content/examples/deployment/ts/src/app/crisis-list.component.ts new file mode 100644 index 0000000000..62ef9e7555 --- /dev/null +++ b/aio/content/examples/deployment/ts/src/app/crisis-list.component.ts @@ -0,0 +1,9 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + template: ` +

    CRISIS CENTER

    +

    Get your crisis here

    ` +}) +export class CrisisListComponent { } diff --git a/aio/content/examples/deployment/ts/src/app/hero-list.component.ts b/aio/content/examples/deployment/ts/src/app/hero-list.component.ts new file mode 100644 index 0000000000..479f73b508 --- /dev/null +++ b/aio/content/examples/deployment/ts/src/app/hero-list.component.ts @@ -0,0 +1,10 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + template: ` +

    HEROES

    +

    Get your heroes here

    + ` +}) +export class HeroListComponent { } diff --git a/aio/content/examples/deployment/ts/src/index.html b/aio/content/examples/deployment/ts/src/index.html new file mode 100644 index 0000000000..ba6ba3dea6 --- /dev/null +++ b/aio/content/examples/deployment/ts/src/index.html @@ -0,0 +1,38 @@ + + + + + + + + + Simple Deployment + + + + + + + + + + + + + + + + + + + + + + + loading... + + + diff --git a/aio/content/examples/deployment/ts/src/main.ts b/aio/content/examples/deployment/ts/src/main.ts new file mode 100644 index 0000000000..6f82bbc745 --- /dev/null +++ b/aio/content/examples/deployment/ts/src/main.ts @@ -0,0 +1,15 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; + +// #docregion enableProdMode +import { enableProdMode } from '@angular/core'; + +// Enable production mode unless running locally +if (!/localhost/.test(document.location.host)) { + enableProdMode(); +} +// #enddocregion enableProdMode + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/deployment/ts/src/systemjs.config.server.js b/aio/content/examples/deployment/ts/src/systemjs.config.server.js new file mode 100644 index 0000000000..40c4426064 --- /dev/null +++ b/aio/content/examples/deployment/ts/src/systemjs.config.server.js @@ -0,0 +1,46 @@ +// #docregion +/** + * System configuration for deployment without installing node_modules + * Loads umd packages from the web instead + * Adjust as necessary for your application needs. + */ +(function (global) { + System.config({ + // #docregion paths + paths: { + 'npm:': 'https://unpkg.com/' // path serves as alias + }, + // #enddocregion paths + // map tells the System loader where to look for things + map: { + app: 'app', // location of transpiled app files + + // angular minimized umd bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.min.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.min.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.min.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.min.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.min.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.min.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.min.js', + '@angular/router/upgrade': 'npm:@angular/router/bundles/router-upgrade.umd.min.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.min.js', + '@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.min.js', + '@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.min.js', + + // other libraries + 'rxjs': 'npm:rxjs@5.0.1', + 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js' + }, + // packages tells the System loader how to load when no filename and/or no extension + packages: { + app: { + main: './main.js', + defaultExtension: 'js' + }, + rxjs: { + defaultExtension: 'js' + } + } + }); + })(this); diff --git a/aio/content/examples/displaying-data/e2e-spec.ts b/aio/content/examples/displaying-data/e2e-spec.ts new file mode 100644 index 0000000000..96c52c5d00 --- /dev/null +++ b/aio/content/examples/displaying-data/e2e-spec.ts @@ -0,0 +1,29 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('Displaying Data Tests', function () { + let _title = 'Tour of Heroes'; + let _defaultHero = 'Windstorm'; + + beforeAll(function () { + browser.get(''); + }); + + it('should display correct title: ' + _title, function () { + expect(element(by.css('h1')).getText()).toEqual(_title); + }); + + it('should have correct default hero: ' + _defaultHero, function () { + expect(element(by.css('h2')).getText()).toContain(_defaultHero); + }); + + it('should have heroes', function () { + let heroEls = element.all(by.css('li')); + expect(heroEls.count()).not.toBe(0, 'should have heroes'); + }); + + it('should display "there are many heroes!"', function () { + expect(element(by.css('ul ~ p')).getText()).toContain('There are many heroes!'); + }); +}); diff --git a/aio/content/examples/displaying-data/ts/example-config.json b/aio/content/examples/displaying-data/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/displaying-data/ts/plnkr.json b/aio/content/examples/displaying-data/ts/plnkr.json new file mode 100644 index 0000000000..b4572f0fb6 --- /dev/null +++ b/aio/content/examples/displaying-data/ts/plnkr.json @@ -0,0 +1,11 @@ +{ + "description": "Displaying Data", + "basePath": "src/", + "files": [ + "!**/*.d.ts", + "!**/*.js", + "!**/app-ctor.component.ts", + "!**/*.[1,2,3].*" + ], + "tags": ["Template"] +} diff --git a/aio/content/examples/displaying-data/ts/plnkr.no-link.html b/aio/content/examples/displaying-data/ts/plnkr.no-link.html new file mode 100644 index 0000000000..067e72dea2 --- /dev/null +++ b/aio/content/examples/displaying-data/ts/plnkr.no-link.html @@ -0,0 +1,228 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/displaying-data/ts/src/app/app-ctor.component.ts b/aio/content/examples/displaying-data/ts/src/app/app-ctor.component.ts new file mode 100644 index 0000000000..b275baa8e6 --- /dev/null +++ b/aio/content/examples/displaying-data/ts/src/app/app-ctor.component.ts @@ -0,0 +1,19 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app-ctor', + template: ` +

    {{title}} [Ctor version]

    +

    My favorite hero is: {{myHero}}

    + ` +}) +// #docregion class +export class AppCtorComponent { + title: string; + myHero: string; + + constructor() { + this.title = 'Tour of Heroes'; + this.myHero = 'Windstorm'; + } +} diff --git a/aio/content/examples/displaying-data/ts/src/app/app.component.1.ts b/aio/content/examples/displaying-data/ts/src/app/app.component.1.ts new file mode 100644 index 0000000000..1cbeb0f731 --- /dev/null +++ b/aio/content/examples/displaying-data/ts/src/app/app.component.1.ts @@ -0,0 +1,16 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + // #docregion template + template: ` +

    {{title}}

    +

    My favorite hero is: {{myHero}}

    + ` + // #enddocregion template +}) +export class AppComponent { + title = 'Tour of Heroes'; + myHero = 'Windstorm'; +} diff --git a/aio/content/examples/displaying-data/ts/src/app/app.component.2.ts b/aio/content/examples/displaying-data/ts/src/app/app.component.2.ts new file mode 100644 index 0000000000..da7a653973 --- /dev/null +++ b/aio/content/examples/displaying-data/ts/src/app/app.component.2.ts @@ -0,0 +1,26 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + // #docregion template + template: ` +

    {{title}}

    +

    My favorite hero is: {{myHero}}

    +

    Heroes:

    +
      + // #docregion li +
    • + {{ hero }} +
    • + // #enddocregion li +
    + ` + // #enddocregion template +}) +// #docregion class +export class AppComponent { + title = 'Tour of Heroes'; + heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado']; + myHero = this.heroes[0]; +} diff --git a/aio/content/examples/displaying-data/ts/src/app/app.component.3.ts b/aio/content/examples/displaying-data/ts/src/app/app.component.3.ts new file mode 100644 index 0000000000..06ab060557 --- /dev/null +++ b/aio/content/examples/displaying-data/ts/src/app/app.component.3.ts @@ -0,0 +1,35 @@ +// #docregion +import { Component } from '@angular/core'; + +// #docregion import +import { Hero } from './hero'; +// #enddocregion import + +@Component({ + selector: 'my-app', + // #docregion template + template: ` +

    {{title}}

    +

    My favorite hero is: {{myHero.name}}

    +

    Heroes:

    +
      +
    • + {{ hero.name }} +
    • +
    + ` + // #enddocregion template +}) +// #docregion class +export class AppComponent { + title = 'Tour of Heroes'; + // #docregion heroes + heroes = [ + new Hero(1, 'Windstorm'), + new Hero(13, 'Bombasto'), + new Hero(15, 'Magneta'), + new Hero(20, 'Tornado') + ]; + myHero = this.heroes[0]; + // #enddocregion heroes +} diff --git a/aio/content/examples/displaying-data/ts/src/app/app.component.ts b/aio/content/examples/displaying-data/ts/src/app/app.component.ts new file mode 100644 index 0000000000..7234959265 --- /dev/null +++ b/aio/content/examples/displaying-data/ts/src/app/app.component.ts @@ -0,0 +1,32 @@ +// #docplaster +// #docregion final +import { Component } from '@angular/core'; + +import { Hero } from './hero'; + +@Component({ + selector: 'my-app', + template: ` +

    {{title}}

    +

    My favorite hero is: {{myHero.name}}

    +

    Heroes:

    +
      +
    • + {{ hero.name }} +
    • +
    + // #docregion message +

    There are many heroes!

    + // #enddocregion message +` +}) +export class AppComponent { + title = 'Tour of Heroes'; + heroes = [ + new Hero(1, 'Windstorm'), + new Hero(13, 'Bombasto'), + new Hero(15, 'Magneta'), + new Hero(20, 'Tornado') + ]; + myHero = this.heroes[0]; +} diff --git a/aio/content/examples/displaying-data/ts/src/app/app.module.ts b/aio/content/examples/displaying-data/ts/src/app/app.module.ts new file mode 100644 index 0000000000..362f3401fa --- /dev/null +++ b/aio/content/examples/displaying-data/ts/src/app/app.module.ts @@ -0,0 +1,16 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ + BrowserModule + ], + declarations: [ + AppComponent + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/displaying-data/ts/src/app/hero.ts b/aio/content/examples/displaying-data/ts/src/app/hero.ts new file mode 100644 index 0000000000..f89d26ad63 --- /dev/null +++ b/aio/content/examples/displaying-data/ts/src/app/hero.ts @@ -0,0 +1,9 @@ +// #docregion +export class Hero { + constructor( + // #docregion id + public id: number, + // #enddocregion id + public name: string) { } +} +// #enddocregion diff --git a/aio/content/examples/displaying-data/ts/src/index.html b/aio/content/examples/displaying-data/ts/src/index.html new file mode 100644 index 0000000000..ddcbade46b --- /dev/null +++ b/aio/content/examples/displaying-data/ts/src/index.html @@ -0,0 +1,28 @@ + + + + Displaying Data + + + + + + + + + + + + + + + + + + loading... + + + + diff --git a/aio/content/examples/displaying-data/ts/src/main.ts b/aio/content/examples/displaying-data/ts/src/main.ts new file mode 100644 index 0000000000..f332d1d245 --- /dev/null +++ b/aio/content/examples/displaying-data/ts/src/main.ts @@ -0,0 +1,6 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/forms/e2e-spec.ts b/aio/content/examples/forms/e2e-spec.ts new file mode 100644 index 0000000000..2afd370103 --- /dev/null +++ b/aio/content/examples/forms/e2e-spec.ts @@ -0,0 +1,63 @@ +import { browser, element, by } from 'protractor'; +import { appLang, describeIf } from '../protractor-helpers'; + +describeIf(appLang.appIsTs || appLang.appIsJs, 'Forms Tests', function () { + + beforeEach(function () { + browser.get(''); + }); + + it('should display correct title', function () { + expect(element.all(by.css('h1')).get(0).getText()).toEqual('Hero Form'); + }); + + + it('should not display message before submit', function () { + let ele = element(by.css('h2')); + expect(ele.isDisplayed()).toBe(false); + }); + + it('should hide form after submit', function () { + let ele = element.all(by.css('h1')).get(0); + expect(ele.isDisplayed()).toBe(true); + let b = element.all(by.css('button[type=submit]')).get(0); + b.click().then(function() { + expect(ele.isDisplayed()).toBe(false); + }); + }); + + it('should display message after submit', function () { + let b = element.all(by.css('button[type=submit]')).get(0); + b.click().then(function() { + expect(element(by.css('h2')).getText()).toContain('You submitted the following'); + }); + }); + + it('should hide form after submit', function () { + let alterEgoEle = element.all(by.css('input[name=alterEgo]')).get(0); + expect(alterEgoEle.isDisplayed()).toBe(true); + let submitButtonEle = element.all(by.css('button[type=submit]')).get(0); + submitButtonEle.click().then(function() { + expect(alterEgoEle.isDisplayed()).toBe(false); + }); + }); + + it('should reflect submitted data after submit', function () { + let test = 'testing 1 2 3'; + let newValue: string; + let alterEgoEle = element.all(by.css('input[name=alterEgo]')).get(0); + alterEgoEle.getAttribute('value').then(function(value: string) { + alterEgoEle.sendKeys(test); + newValue = value + test; + expect(alterEgoEle.getAttribute('value')).toEqual(newValue); + let b = element.all(by.css('button[type=submit]')).get(0); + return b.click(); + }).then(function() { + let alterEgoTextEle = element(by.cssContainingText('div', 'Alter Ego')); + expect(alterEgoTextEle.isPresent()).toBe(true, 'cannot locate "Alter Ego" label'); + let divEle = element(by.cssContainingText('div', newValue)); + expect(divEle.isPresent()).toBe(true, 'cannot locate div with this text: ' + newValue); + }); + }); +}); + diff --git a/aio/content/examples/forms/js/example-config.json b/aio/content/examples/forms/js/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/forms/js/plnkr.json b/aio/content/examples/forms/js/plnkr.json new file mode 100644 index 0000000000..946cbb88f6 --- /dev/null +++ b/aio/content/examples/forms/js/plnkr.json @@ -0,0 +1,5 @@ +{ + "description": "Forms", + "basePath": "src/", + "files":["app/**/*.js", "**/*.html", "**/*.css"] +} diff --git a/aio/content/examples/forms/js/plnkr.no-link.html b/aio/content/examples/forms/js/plnkr.no-link.html new file mode 100644 index 0000000000..67697d6b1a --- /dev/null +++ b/aio/content/examples/forms/js/plnkr.no-link.html @@ -0,0 +1,440 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/forms/js/src/app/app.component.js b/aio/content/examples/forms/js/src/app/app.component.js new file mode 100644 index 0000000000..56bd982416 --- /dev/null +++ b/aio/content/examples/forms/js/src/app/app.component.js @@ -0,0 +1,11 @@ +// #docregion +(function(app) { + app.AppComponent = ng.core + .Component({ + selector: 'my-app', + template: '' + }) + .Class({ + constructor: function() {} + }); +})(window.app || (window.app = {})); diff --git a/aio/content/examples/forms/js/src/app/app.module.js b/aio/content/examples/forms/js/src/app/app.module.js new file mode 100644 index 0000000000..92c7f8b9e5 --- /dev/null +++ b/aio/content/examples/forms/js/src/app/app.module.js @@ -0,0 +1,19 @@ +// #docplaster +// #docregion +(function(app) { + app.AppModule = + ng.core.NgModule({ + imports: [ + ng.platformBrowser.BrowserModule, + ng.forms.FormsModule + ], + declarations: [ + app.AppComponent, + app.HeroFormComponent + ], + bootstrap: [ app.AppComponent ] + }) + .Class({ + constructor: function() {} + }); +})(window.app || (window.app = {})); diff --git a/aio/content/examples/forms/js/src/app/hero-form.component.html b/aio/content/examples/forms/js/src/app/hero-form.component.html new file mode 100644 index 0000000000..279ded0866 --- /dev/null +++ b/aio/content/examples/forms/js/src/app/hero-form.component.html @@ -0,0 +1,196 @@ + + +
    + +
    +

    Hero Form

    + +
    + + +
    + + + +
    + Name is required +
    + +
    + +
    + + +
    + +
    + + +
    + Power is required +
    +
    + + + + +
    +
    + + +
    +

    You submitted the following:

    +
    +
    Name
    +
    {{ model.name }}
    +
    +
    +
    Alter Ego
    +
    {{ model.alterEgo }}
    +
    +
    +
    Power
    +
    {{ model.power }}
    +
    +
    + +
    + +
    + + + +
    +
    + + + + +
    +
    + + + +
    + + +
    + +
    +

    Hero Form

    +
    +
    + + +
    + +
    + + +
    + + + +
    + + +
    + + + + +
    +
    + + + + +
    + +
    +

    Hero Form

    +
    + + {{diagnostic()}} +
    + + +
    + +
    + + +
    + +
    + + +
    + + + + +
    +
    + + + +
    + + + TODO: remove this: {{model.name}} + +
    + + + TODO: remove this: {{model.name}} + +
    +
    + + + +
    + + +
    TODO: remove this: {{spy.className}} + +
    + +
    +
    + Name via form.controls = {{showFormControls(heroForm)}} +
    + +
    diff --git a/aio/content/examples/forms/js/src/app/hero-form.component.js b/aio/content/examples/forms/js/src/app/hero-form.component.js new file mode 100644 index 0000000000..505993a1fd --- /dev/null +++ b/aio/content/examples/forms/js/src/app/hero-form.component.js @@ -0,0 +1,52 @@ +// #docplaster +// #docregion +// #docregion first, final +(function(app) { + app.HeroFormComponent = ng.core + .Component({ + selector: 'hero-form', + templateUrl: 'app/hero-form.component.html' + }) + .Class({ + // #docregion submitted + constructor: [function() { + // #enddocregion submitted + this.powers = ['Really Smart', 'Super Flexible', + 'Super Hot', 'Weather Changer' + ]; + + this.model = new app.Hero(18, 'Dr IQ', this.powers[0], + 'Chuck Overstreet'); + + // #docregion submitted + this.submitted = false; + }], + onSubmit: function() { + this.submitted = true; + }, + // #enddocregion submitted + + // #enddocregion final + // TODO: Remove this when we're done + diagnostic: function() { + return JSON.stringify(this.model); + }, + // #enddocregion first + + + //////// DO NOT SHOW IN DOCS //////// + + // Reveal in html: + // AlterEgo via form.controls = {{showFormControls(hf)}} + showFormControls: function(form) { + return form.controls['alterEgo'] && + // #docregion form-controls + form.controls['name'].value; // Dr. IQ + // #enddocregion form-controls + }, + ///////////////////////////// + + // #docregion first, final + }); +})(window.app || (window.app = {})); +// #enddocregion first, final diff --git a/aio/content/examples/forms/js/src/app/hero.js b/aio/content/examples/forms/js/src/app/hero.js new file mode 100644 index 0000000000..9c2449c922 --- /dev/null +++ b/aio/content/examples/forms/js/src/app/hero.js @@ -0,0 +1,11 @@ +// #docregion +(function(app) { + app.Hero = Hero; + + function Hero(id, name, power, alterEgo) { + this.id = id; + this.name = name; + this.power = power; + this.alterEgo = alterEgo; + } +})(window.app || (window.app = {})); diff --git a/aio/content/examples/forms/js/src/forms.css b/aio/content/examples/forms/js/src/forms.css new file mode 100644 index 0000000000..d7e11405b1 --- /dev/null +++ b/aio/content/examples/forms/js/src/forms.css @@ -0,0 +1,9 @@ +/* #docregion */ +.ng-valid[required] { + border-left: 5px solid #42A948; /* green */ +} + +.ng-invalid { + border-left: 5px solid #a94442; /* red */ +} +/* #enddocregion */ \ No newline at end of file diff --git a/aio/content/examples/forms/js/src/index.html b/aio/content/examples/forms/js/src/index.html new file mode 100644 index 0000000000..9715d2476e --- /dev/null +++ b/aio/content/examples/forms/js/src/index.html @@ -0,0 +1,47 @@ + + + + + + Hero Form + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/forms/js/src/main.js b/aio/content/examples/forms/js/src/main.js new file mode 100644 index 0000000000..785823fa84 --- /dev/null +++ b/aio/content/examples/forms/js/src/main.js @@ -0,0 +1,8 @@ +// #docregion +(function(app) { + document.addEventListener('DOMContentLoaded', function() { + ng.platformBrowserDynamic + .platformBrowserDynamic() + .bootstrapModule(app.AppModule); + }); +})(window.app || (window.app = {})); diff --git a/aio/content/examples/forms/ts/example-config.json b/aio/content/examples/forms/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/forms/ts/plnkr.json b/aio/content/examples/forms/ts/plnkr.json new file mode 100644 index 0000000000..3f0abbfc3d --- /dev/null +++ b/aio/content/examples/forms/ts/plnkr.json @@ -0,0 +1,8 @@ +{ + "description": "Forms", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js" + ] +} diff --git a/aio/content/examples/forms/ts/plnkr.no-link.html b/aio/content/examples/forms/ts/plnkr.no-link.html new file mode 100644 index 0000000000..7ce8c13e4e --- /dev/null +++ b/aio/content/examples/forms/ts/plnkr.no-link.html @@ -0,0 +1,461 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/forms/ts/src/app/app.component.ts b/aio/content/examples/forms/ts/src/app/app.component.ts new file mode 100644 index 0000000000..454f7e03db --- /dev/null +++ b/aio/content/examples/forms/ts/src/app/app.component.ts @@ -0,0 +1,8 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: '' +}) +export class AppComponent { } diff --git a/aio/content/examples/forms/ts/src/app/app.module.ts b/aio/content/examples/forms/ts/src/app/app.module.ts new file mode 100644 index 0000000000..f214c02714 --- /dev/null +++ b/aio/content/examples/forms/ts/src/app/app.module.ts @@ -0,0 +1,20 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; + +import { AppComponent } from './app.component'; +import { HeroFormComponent } from './hero-form.component'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule + ], + declarations: [ + AppComponent, + HeroFormComponent + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/forms/ts/src/app/hero-form.component.html b/aio/content/examples/forms/ts/src/app/hero-form.component.html new file mode 100644 index 0000000000..2efb9bc7dd --- /dev/null +++ b/aio/content/examples/forms/ts/src/app/hero-form.component.html @@ -0,0 +1,210 @@ + + +
    + +
    +

    Hero Form

    + +
    + +
    + + + + +
    + + Name is required +
    + +
    + +
    + + +
    + +
    + + +
    + Power is required +
    +
    + + + + + + + + + with reset + +    + + + + without reset + + +
    +
    + Name via form.controls = {{showFormControls(heroForm)}} +
    + + +
    +
    + + +
    +

    You submitted the following:

    +
    +
    Name
    +
    {{ model.name }}
    +
    +
    +
    Alter Ego
    +
    {{ model.alterEgo }}
    +
    +
    +
    Power
    +
    {{ model.power }}
    +
    +
    + +
    + +
    + + + +
    +
    + + + + +
    +
    + + + +
    + + +
    + +
    +

    Hero Form

    +
    +
    + + +
    + +
    + + +
    + + + +
    + + +
    + + + + + +
    +
    + + + + +
    + +
    +

    Hero Form

    +
    + + {{diagnostic}} +
    + + +
    + +
    + + +
    + +
    + + +
    + + + + +
    +
    + + + +
    + + + TODO: remove this: {{model.name}} + +
    + + + TODO: remove this: {{model.name}} + +
    + + +
    TODO: remove this: {{spy.className}} + + +
    diff --git a/aio/content/examples/forms/ts/src/app/hero-form.component.ts b/aio/content/examples/forms/ts/src/app/hero-form.component.ts new file mode 100644 index 0000000000..b2db8aaafd --- /dev/null +++ b/aio/content/examples/forms/ts/src/app/hero-form.component.ts @@ -0,0 +1,60 @@ +// #docplaster +// #docregion , v1, final +import { Component } from '@angular/core'; + +import { Hero } from './hero'; + +@Component({ + moduleId: module.id, + selector: 'hero-form', + templateUrl: './hero-form.component.html' +}) +export class HeroFormComponent { + + powers = ['Really Smart', 'Super Flexible', + 'Super Hot', 'Weather Changer']; + + model = new Hero(18, 'Dr IQ', this.powers[0], 'Chuck Overstreet'); + + // #docregion submitted + submitted = false; + + onSubmit() { this.submitted = true; } + // #enddocregion submitted + + // #enddocregion final + // TODO: Remove this when we're done + get diagnostic() { return JSON.stringify(this.model); } + // #enddocregion v1 + + // #docregion final, new-hero + newHero() { + this.model = new Hero(42, '', ''); + } + // #enddocregion final, new-hero + + skyDog(): Hero { + // #docregion SkyDog + let myHero = new Hero(42, 'SkyDog', + 'Fetch any object at any distance', + 'Leslie Rollover'); + console.log('My hero is called ' + myHero.name); // "My hero is called SkyDog" + // #enddocregion SkyDog + return myHero; + } + + //////// NOT SHOWN IN DOCS //////// + + // Reveal in html: + // Name via form.controls = {{showFormControls(heroForm)}} + showFormControls(form: any) { + return form && form.controls['name'] && + // #docregion form-controls + form.controls['name'].value; // Dr. IQ + // #enddocregion form-controls + } + + ///////////////////////////// + + // #docregion v1, final +} diff --git a/aio/content/examples/forms/ts/src/app/hero.ts b/aio/content/examples/forms/ts/src/app/hero.ts new file mode 100644 index 0000000000..c128626452 --- /dev/null +++ b/aio/content/examples/forms/ts/src/app/hero.ts @@ -0,0 +1,11 @@ +// #docregion +export class Hero { + + constructor( + public id: number, + public name: string, + public power: string, + public alterEgo?: string + ) { } + +} diff --git a/aio/content/examples/forms/ts/src/forms.css b/aio/content/examples/forms/ts/src/forms.css new file mode 100644 index 0000000000..13ffbe1203 --- /dev/null +++ b/aio/content/examples/forms/ts/src/forms.css @@ -0,0 +1,9 @@ +/* #docregion */ +.ng-valid[required], .ng-valid.required { + border-left: 5px solid #42A948; /* green */ +} + +.ng-invalid:not(form) { + border-left: 5px solid #a94442; /* red */ +} +/* #enddocregion */ diff --git a/aio/content/examples/forms/ts/src/index.html b/aio/content/examples/forms/ts/src/index.html new file mode 100644 index 0000000000..032888ca3e --- /dev/null +++ b/aio/content/examples/forms/ts/src/index.html @@ -0,0 +1,35 @@ + + + + + Hero Form + + + + + + + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/forms/ts/src/main.ts b/aio/content/examples/forms/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/aio/content/examples/forms/ts/src/main.ts @@ -0,0 +1,5 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/hierarchical-dependency-injection/e2e-spec.ts b/aio/content/examples/hierarchical-dependency-injection/e2e-spec.ts new file mode 100644 index 0000000000..4630453f78 --- /dev/null +++ b/aio/content/examples/hierarchical-dependency-injection/e2e-spec.ts @@ -0,0 +1,103 @@ +'use strict'; // necessary for es6 output in node + +import { browser, by, element } from 'protractor'; + +describe('Hierarchical dependency injection', () => { + + beforeAll(() => { + browser.get(''); + }); + + describe('Heroes Scenario', () => { + let page = { + heroName: '', + income: '', + + // queries + heroEl: element.all(by.css('heroes-list li')).get(0), // first hero + heroCardEl: element(by.css('heroes-list hero-tax-return')), // first hero tax-return + taxReturnNameEl: element.all(by.css('heroes-list hero-tax-return #name')).get(0), + incomeInputEl: element.all(by.css('heroes-list hero-tax-return input')).get(0), + cancelButtonEl: element(by.cssContainingText('heroes-list hero-tax-return button', 'Cancel')), + closeButtonEl: element(by.cssContainingText('heroes-list hero-tax-return button', 'Close')), + saveButtonEl: element(by.cssContainingText('heroes-list hero-tax-return button', 'Save')) + }; + + it('should list multiple heroes', () => { + expect(element.all(by.css('heroes-list li')).count()).toBeGreaterThan(1); + }); + + it('should show no hero tax-returns at the start', () => { + expect(element.all(by.css('heroes-list li hero-tax-return')).count()).toBe(0); + }); + + it('should open first hero in hero-tax-return view after click', () => { + page.heroEl.getText() + .then(val => { + page.heroName = val; + }) + .then(() => page.heroEl.click()) + .then(() => { + expect(page.heroCardEl.isDisplayed()).toBe(true); + }); + }); + + it('hero tax-return should have first hero\'s name', () => { + // Not `page.tax-returnNameInputEl.getAttribute('value')` although later that is essential + expect(page.taxReturnNameEl.getText()).toEqual(page.heroName); + }); + + it('should be able to cancel change', () => { + page.incomeInputEl.clear() + .then(() => page.incomeInputEl.sendKeys('777')) + .then(() => { + expect(page.incomeInputEl.getAttribute('value')).toBe('777', 'income should be 777'); + return page.cancelButtonEl.click(); + }) + .then(() => { + expect(page.incomeInputEl.getAttribute('value')).not.toBe('777', 'income should not be 777'); + }); + }); + + it('should be able to save change', () => { + page.incomeInputEl.clear() + .then(() => page.incomeInputEl.sendKeys('999')) + .then(() => { + expect(page.incomeInputEl.getAttribute('value')).toBe('999', 'income should be 999'); + return page.saveButtonEl.click(); + }) + .then(() => { + expect(page.incomeInputEl.getAttribute('value')).toBe('999', 'income should still be 999'); + }); + }); + + it('should be able to close tax-return', () => { + page.saveButtonEl.click() + .then(() => { + expect(element.all(by.css('heroes-list li hero-tax-return')).count()).toBe(0); + }); + }); + + }); + + describe('Villains Scenario', () => { + it('should list multiple villains', () => { + expect(element.all(by.css('villains-list li')).count()).toBeGreaterThan(1); + }); + }); + + describe('Cars Scenario', () => { + + it('A-component should use expected services', () => { + expect(element(by.css('a-car')).getText()).toContain('C1-E1-T1'); + }); + + it('B-component should use expected services', () => { + expect(element(by.css('b-car')).getText()).toContain('C2-E2-T1'); + }); + + it('C-component should use expected services', () => { + expect(element(by.css('c-car')).getText()).toContain('C3-E2-T1'); + }); + }); +}); diff --git a/aio/content/examples/hierarchical-dependency-injection/ts/example-config.json b/aio/content/examples/hierarchical-dependency-injection/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/hierarchical-dependency-injection/ts/plnkr.json b/aio/content/examples/hierarchical-dependency-injection/ts/plnkr.json new file mode 100644 index 0000000000..ca92b93b06 --- /dev/null +++ b/aio/content/examples/hierarchical-dependency-injection/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Hierachical Dependency Injection", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js" + ], + "tags": ["dependency", "injection"] +} diff --git a/aio/content/examples/hierarchical-dependency-injection/ts/plnkr.no-link.html b/aio/content/examples/hierarchical-dependency-injection/ts/plnkr.no-link.html new file mode 100644 index 0000000000..797e470d2e --- /dev/null +++ b/aio/content/examples/hierarchical-dependency-injection/ts/plnkr.no-link.html @@ -0,0 +1,744 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/hierarchical-dependency-injection/ts/src/app/app.component.ts b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/app.component.ts new file mode 100644 index 0000000000..34b5dd0a5a --- /dev/null +++ b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/app.component.ts @@ -0,0 +1,21 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: ` + + + + +

    Hierarchical Dependency Injection

    + + + + + ` +}) +export class AppComponent { + showCars = true; + showHeroes = true; + showVillains = true; +} diff --git a/aio/content/examples/hierarchical-dependency-injection/ts/src/app/app.module.ts b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/app.module.ts new file mode 100644 index 0000000000..6ea18655af --- /dev/null +++ b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/app.module.ts @@ -0,0 +1,33 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; + +import { AppComponent } from './app.component'; +import { HeroTaxReturnComponent } from './hero-tax-return.component'; +import { HeroesListComponent } from './heroes-list.component'; +import { HeroesService } from './heroes.service'; +import { VillainsListComponent } from './villains-list.component'; + +import { carComponents, carServices } from './car.components'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule + ], + providers: [ + carServices, + HeroesService + ], + declarations: [ + AppComponent, + carComponents, + HeroesListComponent, + HeroTaxReturnComponent, + VillainsListComponent + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } + diff --git a/aio/content/examples/hierarchical-dependency-injection/ts/src/app/car.components.ts b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/car.components.ts new file mode 100644 index 0000000000..5b4df19696 --- /dev/null +++ b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/car.components.ts @@ -0,0 +1,74 @@ +import { Component } from '@angular/core'; + +import { + CarService, CarService2, CarService3, + EngineService, EngineService2, TiresService +} from './car.services'; + +////////// CCarComponent //////////// +@Component({ + selector: 'c-car', + template: `
    C: {{description}}
    `, + providers: [ + { provide: CarService, useClass: CarService3 } + ] +}) +export class CCarComponent { + description: string; + constructor(carService: CarService) { + this.description = `${carService.getCar().description} (${carService.name})`; + } +} + +////////// BCarComponent //////////// +@Component({ + selector: 'b-car', + template: ` +
    B: {{description}}
    + + `, + providers: [ + { provide: CarService, useClass: CarService2 }, + { provide: EngineService, useClass: EngineService2 } + ] +}) +export class BCarComponent { + description: string; + constructor(carService: CarService) { + this.description = `${carService.getCar().description} (${carService.name})`; + } +} + +////////// ACarComponent //////////// +@Component({ + selector: 'a-car', + template: ` +
    A: {{description}}
    + ` +}) +export class ACarComponent { + description: string; + constructor(carService: CarService) { + this.description = `${carService.getCar().description} (${carService.name})`; + } +} +////////// CarsComponent //////////// +@Component({ + selector: 'my-cars', + template: ` +

    Cars

    + ` +}) +export class CarsComponent { } + +//////////////// + +export const carComponents = [ + CarsComponent, + ACarComponent, BCarComponent, CCarComponent +]; + +// generic car-related services +export const carServices = [ + CarService, EngineService, TiresService +]; diff --git a/aio/content/examples/hierarchical-dependency-injection/ts/src/app/car.services.ts b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/car.services.ts new file mode 100644 index 0000000000..03c79270b0 --- /dev/null +++ b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/car.services.ts @@ -0,0 +1,95 @@ +import { Injectable } from '@angular/core'; + +/// Model /// +export class Car { + name = 'Avocado Motors'; + constructor(public engine: Engine, public tires: Tires) { } + + get description() { + return `${this.name} car with ` + + `${this.engine.cylinders} cylinders and ${this.tires.make} tires.`; + } +} + +export class Engine { + cylinders = 4; +} + +export class Tires { + make = 'Flintstone'; + model = 'Square'; +} + +//// Engine services /// +@Injectable() +export class EngineService { + id = 'E1'; + getEngine() { return new Engine(); } +} + +@Injectable() +export class EngineService2 { + id = 'E2'; + getEngine() { + const eng = new Engine(); + eng.cylinders = 8; + return eng; + } +} + +//// Tire services /// +@Injectable() +export class TiresService { + id = 'T1'; + getTires() { return new Tires(); } +} + +/// Car Services /// +@Injectable() +export class CarService { + id = 'C1'; + constructor( + protected engineService: EngineService, + protected tiresService: TiresService) { } + + getCar() { + return new Car( + this.engineService.getEngine(), + this.tiresService.getTires()); + } + + get name() { + return `${this.id}-${this.engineService.id}-${this.tiresService.id}`; + } +} + +@Injectable() +export class CarService2 extends CarService { + id = 'C2'; + constructor( + protected engineService: EngineService, + protected tiresService: TiresService) { + super(engineService, tiresService); + } + getCar() { + const car = super.getCar(); + car.name = 'BamBam Motors, BroVan 2000'; + return car; + } +} + +@Injectable() +export class CarService3 extends CarService2 { + id = 'C3'; + constructor( + protected engineService: EngineService, + protected tiresService: TiresService) { + super(engineService, tiresService); + } + getCar() { + const car = super.getCar(); + car.name = 'Chizzamm Motors, Calico UltraMax Supreme'; + return car; + } +} + diff --git a/aio/content/examples/hierarchical-dependency-injection/ts/src/app/hero-tax-return.component.css b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/hero-tax-return.component.css new file mode 100644 index 0000000000..1d29a1d168 --- /dev/null +++ b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/hero-tax-return.component.css @@ -0,0 +1,22 @@ +.tax-return { border: thin dashed green; margin: 1em; padding: 1em; width: 18em; position: relative } +#name { font-weight: bold;} +#tid { float: right; } +input { font-size: 100%; padding-left: 2px; width: 6em; } +input.num { text-align: right; padding-left: 0; padding-right: 4px; width: 4em;} +fieldset { border: 0 none;} + +.msg { + color: white; + font-size: 150%; + position: absolute; + /*opacity: 0.3;*/ + left: 2px; + top: 3em; + width: 98%; + background-color: green; + text-align: center; +} +.msg.canceled { + color: white; + background-color: red; +} diff --git a/aio/content/examples/hierarchical-dependency-injection/ts/src/app/hero-tax-return.component.html b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/hero-tax-return.component.html new file mode 100644 index 0000000000..ebf2dcbaaa --- /dev/null +++ b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/hero-tax-return.component.html @@ -0,0 +1,20 @@ +
    +
    {{message}}
    +
    + {{taxReturn.name}} + +
    +
    + +
    +
    + +
    +
    + + + +
    +
    diff --git a/aio/content/examples/hierarchical-dependency-injection/ts/src/app/hero-tax-return.component.ts b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/hero-tax-return.component.ts new file mode 100644 index 0000000000..78d0f97763 --- /dev/null +++ b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/hero-tax-return.component.ts @@ -0,0 +1,44 @@ +// #docregion +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { HeroTaxReturn } from './hero'; +import { HeroTaxReturnService } from './hero-tax-return.service'; + +@Component({ + moduleId: module.id, + selector: 'hero-tax-return', + templateUrl: './hero-tax-return.component.html', + styleUrls: [ './hero-tax-return.component.css' ], + // #docregion providers + providers: [ HeroTaxReturnService ] + // #enddocregion providers +}) +export class HeroTaxReturnComponent { + message = ''; + @Output() close = new EventEmitter(); + @Input() + get taxReturn(): HeroTaxReturn { + return this.heroTaxReturnService.taxReturn; + } + set taxReturn (htr: HeroTaxReturn) { + this.heroTaxReturnService.taxReturn = htr; + } + + constructor(private heroTaxReturnService: HeroTaxReturnService ) { } + + onCanceled() { + this.flashMessage('Canceled'); + this.heroTaxReturnService.restoreTaxReturn(); + }; + + onClose() { this.close.emit(); }; + + onSaved() { + this.flashMessage('Saved'); + this.heroTaxReturnService.saveTaxReturn(); + } + + flashMessage(msg: string) { + this.message = msg; + setTimeout(() => this.message = '', 500); + } +} diff --git a/aio/content/examples/hierarchical-dependency-injection/ts/src/app/hero-tax-return.service.ts b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/hero-tax-return.service.ts new file mode 100644 index 0000000000..d6ff0f7fff --- /dev/null +++ b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/hero-tax-return.service.ts @@ -0,0 +1,30 @@ +// #docregion +import { Injectable } from '@angular/core'; +import { HeroTaxReturn } from './hero'; +import { HeroesService } from './heroes.service'; + +@Injectable() +export class HeroTaxReturnService { + private currentTaxReturn: HeroTaxReturn; + private originalTaxReturn: HeroTaxReturn; + + constructor(private heroService: HeroesService) { } + + set taxReturn (htr: HeroTaxReturn) { + this.originalTaxReturn = htr; + this.currentTaxReturn = htr.clone(); + } + + get taxReturn (): HeroTaxReturn { + return this.currentTaxReturn; + } + + restoreTaxReturn() { + this.taxReturn = this.originalTaxReturn; + } + + saveTaxReturn() { + this.taxReturn = this.currentTaxReturn; + this.heroService.saveTaxReturn(this.currentTaxReturn).subscribe(); + } +} diff --git a/aio/content/examples/hierarchical-dependency-injection/ts/src/app/hero.ts b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/hero.ts new file mode 100644 index 0000000000..4ad6ccd8eb --- /dev/null +++ b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/hero.ts @@ -0,0 +1,31 @@ +// #docregion + +export class Hero { + id: number; + name: string; + tid: string; // tax id +} + +//// HeroTaxReturn //// +let nextId = 100; + +export class HeroTaxReturn { + constructor( + public id = nextId++, + public hero: Hero, + public income = 0 ) { + if (id === 0) { id = nextId++; } + } + + get name() { return this.hero.name; } + get tax() { return this.income ? .10 * this.income : 0; } + get tid() { return this.hero.tid; } + + toString() { + return `${this.hero.name}`; + } + + clone() { + return new HeroTaxReturn(this.id, this.hero, this.income); + } +} diff --git a/aio/content/examples/hierarchical-dependency-injection/ts/src/app/heroes-list.component.ts b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/heroes-list.component.ts new file mode 100644 index 0000000000..36cb5ec1c3 --- /dev/null +++ b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/heroes-list.component.ts @@ -0,0 +1,48 @@ +// #docregion +import { Component } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; + +import { Hero, HeroTaxReturn } from './hero'; +import { HeroesService } from './heroes.service'; + +@Component({ + selector: 'heroes-list', + template: ` +
    +

    Hero Tax Returns

    +
      +
    • {{hero.name}} +
    • +
    + + +
    + `, + styles: [ 'li {cursor: pointer;}' ] +}) +export class HeroesListComponent { + heroes: Observable; + selectedTaxReturns: HeroTaxReturn[] = []; + + constructor(private heroesService: HeroesService) { + this.heroes = heroesService.getHeroes(); + } + + showTaxReturn(hero: Hero) { + this.heroesService.getTaxReturn(hero) + .subscribe(htr => { + // show if not currently shown + if (!this.selectedTaxReturns.find(tr => tr.id === htr.id)) { + this.selectedTaxReturns.push(htr); + } + }); + } + + closeTaxReturn(ix: number) { + this.selectedTaxReturns.splice(ix, 1); + } +} diff --git a/aio/content/examples/hierarchical-dependency-injection/ts/src/app/heroes.service.ts b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/heroes.service.ts new file mode 100644 index 0000000000..85b33c89a0 --- /dev/null +++ b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/heroes.service.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@angular/core'; + +import { Observable } from 'rxjs/Observable'; +import { Observer } from 'rxjs/Observer'; + +import { Hero, HeroTaxReturn } from './hero'; + +@Injectable() +export class HeroesService { + heroes: Hero[] = [ + { id: 1, name: 'RubberMan', tid: '082-27-5678'}, + { id: 2, name: 'Tornado', tid: '099-42-4321'} + ]; + + heroTaxReturns: HeroTaxReturn[] = [ + new HeroTaxReturn(10, this.heroes[0], 35000), + new HeroTaxReturn(20, this.heroes[1], 1250000) + ]; + + getHeroes(): Observable { + return new Observable((observer: Observer) => { + observer.next(this.heroes); + observer.complete(); + }); + } + + getTaxReturn(hero: Hero): Observable { + return new Observable((observer: Observer) => { + const htr = this.heroTaxReturns.find(t => t.hero.id === hero.id); + observer.next(htr || new HeroTaxReturn(0, hero)); + observer.complete(); + }); + } + + saveTaxReturn(heroTaxReturn: HeroTaxReturn): Observable { + return new Observable((observer: Observer) => { + const htr = this.heroTaxReturns.find(t => t.id === heroTaxReturn.id); + if (htr) { + heroTaxReturn = Object.assign(htr, heroTaxReturn); // demo: mutate + } else { + this.heroTaxReturns.push(heroTaxReturn); + } + observer.next(heroTaxReturn); + observer.complete(); + }); + } +} diff --git a/aio/content/examples/hierarchical-dependency-injection/ts/src/app/villains-list.component.html b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/villains-list.component.html new file mode 100644 index 0000000000..4d1fb55482 --- /dev/null +++ b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/villains-list.component.html @@ -0,0 +1,6 @@ +
    +

    Villains

    +
      +
    • {{villain.name}}
    • +
    +
    diff --git a/aio/content/examples/hierarchical-dependency-injection/ts/src/app/villains-list.component.ts b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/villains-list.component.ts new file mode 100644 index 0000000000..105aaca14f --- /dev/null +++ b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/villains-list.component.ts @@ -0,0 +1,21 @@ +// #docregion +import { Component } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; + +import { Villain, VillainsService } from './villains.service'; + +// #docregion metadata +@Component({ + moduleId: module.id, + selector: 'villains-list', + templateUrl: './villains-list.component.html', + providers: [ VillainsService ] +}) +// #enddocregion metadata +export class VillainsListComponent { + villaines: Observable; + + constructor(private villainesService: VillainsService) { + this.villaines = villainesService.getVillains(); + } +} diff --git a/aio/content/examples/hierarchical-dependency-injection/ts/src/app/villains.service.ts b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/villains.service.ts new file mode 100644 index 0000000000..3d480c20af --- /dev/null +++ b/aio/content/examples/hierarchical-dependency-injection/ts/src/app/villains.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; + +import { of } from 'rxjs/observable/of'; + +export interface Villain { id: number; name: string; } + +@Injectable() +export class VillainsService { + villains: Villain[] = [ + { id: 1, name: 'Dr. Evil'}, + { id: 2, name: 'Moriarty'} + ]; + + getVillains() { + return of(this.villains); + } +} diff --git a/aio/content/examples/hierarchical-dependency-injection/ts/src/index.html b/aio/content/examples/hierarchical-dependency-injection/ts/src/index.html new file mode 100644 index 0000000000..0ea32679e6 --- /dev/null +++ b/aio/content/examples/hierarchical-dependency-injection/ts/src/index.html @@ -0,0 +1,26 @@ + + + + Hierarchical Injectors + + + + + + + + + + + + + + + + + loading... + + + diff --git a/aio/content/examples/hierarchical-dependency-injection/ts/src/main.ts b/aio/content/examples/hierarchical-dependency-injection/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/aio/content/examples/hierarchical-dependency-injection/ts/src/main.ts @@ -0,0 +1,5 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/homepage-hello-world/e2e-spec.ts b/aio/content/examples/homepage-hello-world/e2e-spec.ts new file mode 100644 index 0000000000..c4c6464937 --- /dev/null +++ b/aio/content/examples/homepage-hello-world/e2e-spec.ts @@ -0,0 +1,30 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('Homepage Hello World', function () { + + beforeAll(function () { + browser.get(''); + }); + + // Does it even launch? + let expectedLabel = 'Name:'; + it(`should display the label: ${expectedLabel}`, function () { + expect(element(by.css('label')).getText()).toEqual(expectedLabel); + }); + + it('should display entered name', function () { + let testName = 'Bobby Joe'; + let nameEle = element.all(by.css('input')).get(0); + nameEle.getAttribute('value').then(function(value: string) { + nameEle.sendKeys(testName); + let newValue = value + testName; // old input box value + new name + expect(nameEle.getAttribute('value')).toEqual(newValue); + }).then(function() { + // Check the interpolated message built from name + let helloEle = element.all(by.css('h1')).get(0); + expect(helloEle.getText()).toEqual('Hello ' + testName + '!'); + }); + }); +}); diff --git a/aio/content/examples/homepage-hello-world/ts/example-config.json b/aio/content/examples/homepage-hello-world/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/homepage-hello-world/ts/plnkr.json b/aio/content/examples/homepage-hello-world/ts/plnkr.json new file mode 100644 index 0000000000..bfe6aecc8b --- /dev/null +++ b/aio/content/examples/homepage-hello-world/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Hello World", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1].*" + ] +} diff --git a/aio/content/examples/homepage-hello-world/ts/plnkr.no-link.html b/aio/content/examples/homepage-hello-world/ts/plnkr.no-link.html new file mode 100644 index 0000000000..be5787c68f --- /dev/null +++ b/aio/content/examples/homepage-hello-world/ts/plnkr.no-link.html @@ -0,0 +1,217 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/homepage-hello-world/ts/src/app/app.module.ts b/aio/content/examples/homepage-hello-world/ts/src/app/app.module.ts new file mode 100644 index 0000000000..55a2ef3693 --- /dev/null +++ b/aio/content/examples/homepage-hello-world/ts/src/app/app.module.ts @@ -0,0 +1,16 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; + +import { HelloWorldComponent } from './hello_world'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule + ], + declarations: [ HelloWorldComponent ], + bootstrap: [ HelloWorldComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/homepage-hello-world/ts/src/app/hello_world.html b/aio/content/examples/homepage-hello-world/ts/src/app/hello_world.html new file mode 100644 index 0000000000..13af8fb34e --- /dev/null +++ b/aio/content/examples/homepage-hello-world/ts/src/app/hello_world.html @@ -0,0 +1,7 @@ + + + + +
    + +

    Hello {{yourName}}!

    \ No newline at end of file diff --git a/aio/content/examples/homepage-hello-world/ts/src/app/hello_world.ts b/aio/content/examples/homepage-hello-world/ts/src/app/hello_world.ts new file mode 100644 index 0000000000..7bdf994436 --- /dev/null +++ b/aio/content/examples/homepage-hello-world/ts/src/app/hello_world.ts @@ -0,0 +1,18 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + // Set the base for module-relative URLs + moduleId: module.id, + + // Declare the tag name in index.html to where the component attaches + selector: 'hello-world', + + // Location of the template for this component + templateUrl: './hello_world.html' +}) +export class HelloWorldComponent { + + // Declaring the variable for binding with initial value + yourName: string = ''; +} diff --git a/aio/content/examples/homepage-hello-world/ts/src/index.1.html b/aio/content/examples/homepage-hello-world/ts/src/index.1.html new file mode 100644 index 0000000000..a92d32fa9c --- /dev/null +++ b/aio/content/examples/homepage-hello-world/ts/src/index.1.html @@ -0,0 +1,32 @@ + + + + + Angular Hello World + + + + + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/homepage-hello-world/ts/src/index.html b/aio/content/examples/homepage-hello-world/ts/src/index.html new file mode 100644 index 0000000000..9dab660d10 --- /dev/null +++ b/aio/content/examples/homepage-hello-world/ts/src/index.html @@ -0,0 +1,28 @@ + + + + + Angular Hello World + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/homepage-hello-world/ts/src/main.ts b/aio/content/examples/homepage-hello-world/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/aio/content/examples/homepage-hello-world/ts/src/main.ts @@ -0,0 +1,5 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/homepage-tabs/e2e-spec.ts b/aio/content/examples/homepage-tabs/e2e-spec.ts new file mode 100644 index 0000000000..2131d75906 --- /dev/null +++ b/aio/content/examples/homepage-tabs/e2e-spec.ts @@ -0,0 +1,17 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('Homepage Tabs', function () { + + beforeAll(function () { + browser.get(''); + }); + + // Does it even launch? + let expectedAppTitle = 'Tabs Demo'; + it(`should display app title: ${expectedAppTitle}`, function () { + expect(element(by.css('h4')).getText()).toEqual(expectedAppTitle); + }); + +}); diff --git a/aio/content/examples/homepage-tabs/ts/example-config.json b/aio/content/examples/homepage-tabs/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/homepage-tabs/ts/plnkr.json b/aio/content/examples/homepage-tabs/ts/plnkr.json new file mode 100644 index 0000000000..c1a33dc8f0 --- /dev/null +++ b/aio/content/examples/homepage-tabs/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Tabs", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1].*" + ] +} diff --git a/aio/content/examples/homepage-tabs/ts/plnkr.no-link.html b/aio/content/examples/homepage-tabs/ts/plnkr.no-link.html new file mode 100644 index 0000000000..5bcce585ea --- /dev/null +++ b/aio/content/examples/homepage-tabs/ts/plnkr.no-link.html @@ -0,0 +1,292 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/homepage-tabs/ts/src/app/app.module.ts b/aio/content/examples/homepage-tabs/ts/src/app/app.module.ts new file mode 100644 index 0000000000..bd2193755c --- /dev/null +++ b/aio/content/examples/homepage-tabs/ts/src/app/app.module.ts @@ -0,0 +1,17 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { DiDemoComponent } from './di_demo'; +import { UiTabsComponent, UiPaneDirective } from './ui_tabs'; + +@NgModule({ + imports: [ BrowserModule ], + declarations: [ + DiDemoComponent, + UiTabsComponent, + UiPaneDirective + ], + bootstrap: [ DiDemoComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/homepage-tabs/ts/src/app/di_demo.ts b/aio/content/examples/homepage-tabs/ts/src/app/di_demo.ts new file mode 100644 index 0000000000..be72ae5c79 --- /dev/null +++ b/aio/content/examples/homepage-tabs/ts/src/app/di_demo.ts @@ -0,0 +1,45 @@ +// #docregion +import { Component } from '@angular/core'; + +class Detail { + title: string; + text: string; +} + +@Component({ + selector: 'di-demo', + template: ` +

    Tabs Demo

    + + + + + +
    + + ` +}) +export class DiDemoComponent { + details: Detail[] = []; + id: number = 0; + + addDetail() { + this.id++; + this.details.push({ + title: `Detail ${this.id}`, + text: `Some detail text for ${this.id}...` + }); + } + + removeDetail(detail: Detail) { + this.details = this.details.filter((d) => d !== detail); + } +} + diff --git a/aio/content/examples/homepage-tabs/ts/src/app/ui_tabs.ts b/aio/content/examples/homepage-tabs/ts/src/app/ui_tabs.ts new file mode 100644 index 0000000000..5e2f47b140 --- /dev/null +++ b/aio/content/examples/homepage-tabs/ts/src/app/ui_tabs.ts @@ -0,0 +1,51 @@ +// #docregion +import { Component, Directive, Input, QueryList, + ViewContainerRef, TemplateRef, ContentChildren } from '@angular/core'; + +@Directive({ + selector: '[uiPane]' +}) +export class UiPaneDirective { + @Input() title: string; + private _active: boolean = false; + + constructor(public viewContainer: ViewContainerRef, + public templateRef: TemplateRef) { } + + @Input() set active(active: boolean) { + if (active === this._active) { return; } + this._active = active; + if (active) { + this.viewContainer.createEmbeddedView(this.templateRef); + } else { + this.viewContainer.remove(0); + } + } + + get active(): boolean { + return this._active; + } +} + +@Component({ + selector: 'ui-tabs', + template: ` + + + `, + styles: ['a { cursor: pointer; cursor: hand; }'] +}) +export class UiTabsComponent { + @ContentChildren(UiPaneDirective) panes: QueryList; + + select(pane: UiPaneDirective) { + this.panes.toArray().forEach((p: UiPaneDirective) => p.active = p === pane); + } +} + diff --git a/aio/content/examples/homepage-tabs/ts/src/index.1.html b/aio/content/examples/homepage-tabs/ts/src/index.1.html new file mode 100644 index 0000000000..7d0a0131e6 --- /dev/null +++ b/aio/content/examples/homepage-tabs/ts/src/index.1.html @@ -0,0 +1,33 @@ + + + + + Angular Tabs + + + + + + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/homepage-tabs/ts/src/index.html b/aio/content/examples/homepage-tabs/ts/src/index.html new file mode 100644 index 0000000000..d38b36e033 --- /dev/null +++ b/aio/content/examples/homepage-tabs/ts/src/index.html @@ -0,0 +1,29 @@ + + + + + Angular Tabs + + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/homepage-tabs/ts/src/main.ts b/aio/content/examples/homepage-tabs/ts/src/main.ts new file mode 100644 index 0000000000..f332d1d245 --- /dev/null +++ b/aio/content/examples/homepage-tabs/ts/src/main.ts @@ -0,0 +1,6 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/homepage-todo/e2e-spec.ts b/aio/content/examples/homepage-todo/e2e-spec.ts new file mode 100644 index 0000000000..fb74e4e70f --- /dev/null +++ b/aio/content/examples/homepage-todo/e2e-spec.ts @@ -0,0 +1,17 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('Homepage Todo', function () { + + beforeAll(function () { + browser.get(''); + }); + + // Does it even launch? + let expectedAppTitle = 'Todo'; + it(`should display app title: ${expectedAppTitle}`, function () { + expect(element(by.css('h2')).getText()).toEqual(expectedAppTitle); + }); + +}); diff --git a/aio/content/examples/homepage-todo/ts/example-config.json b/aio/content/examples/homepage-todo/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/homepage-todo/ts/plnkr.json b/aio/content/examples/homepage-todo/ts/plnkr.json new file mode 100644 index 0000000000..8e69701221 --- /dev/null +++ b/aio/content/examples/homepage-todo/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Todo", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1].*" + ] +} diff --git a/aio/content/examples/homepage-todo/ts/plnkr.no-link.html b/aio/content/examples/homepage-todo/ts/plnkr.no-link.html new file mode 100644 index 0000000000..c067783ced --- /dev/null +++ b/aio/content/examples/homepage-todo/ts/plnkr.no-link.html @@ -0,0 +1,303 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/homepage-todo/ts/src/app/app.module.ts b/aio/content/examples/homepage-todo/ts/src/app/app.module.ts new file mode 100644 index 0000000000..a77bb640ab --- /dev/null +++ b/aio/content/examples/homepage-todo/ts/src/app/app.module.ts @@ -0,0 +1,22 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; + +import { TodoAppComponent } from './todo_app'; +import { TodoListComponent } from './todo_list'; +import { TodoFormComponent } from './todo_form'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule + ], + declarations: [ + TodoAppComponent, + TodoListComponent, + TodoFormComponent + ], + bootstrap: [ TodoAppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/homepage-todo/ts/src/app/todo.ts b/aio/content/examples/homepage-todo/ts/src/app/todo.ts new file mode 100644 index 0000000000..35faec9705 --- /dev/null +++ b/aio/content/examples/homepage-todo/ts/src/app/todo.ts @@ -0,0 +1,6 @@ +// #docregion +// Declare an interaface for type safety +export interface Todo { + text: string; + done: boolean; +} diff --git a/aio/content/examples/homepage-todo/ts/src/app/todo_app.ts b/aio/content/examples/homepage-todo/ts/src/app/todo_app.ts new file mode 100644 index 0000000000..e56bad95b6 --- /dev/null +++ b/aio/content/examples/homepage-todo/ts/src/app/todo_app.ts @@ -0,0 +1,38 @@ +// #docregion +import { Component } from '@angular/core'; + +import { Todo } from './todo'; + +@Component({ + selector: 'todo-app', + template: ` +

    Todo

    + {{remaining}} of {{todos.length}} remaining + [ archive ] + + + `, + styles: ['a { cursor: pointer; cursor: hand; }'] +}) +export class TodoAppComponent { + todos: Todo[] = [ + {text: 'learn angular', done: true}, + {text: 'build an angular app', done: false} + ]; + + get remaining() { + return this.todos.filter(todo => !todo.done).length; + } + + archive(): void { + let oldTodos = this.todos; + this.todos = []; + oldTodos.forEach(todo => { + if (!todo.done) { this.todos.push(todo); } + }); + } + + addTask(task: Todo) { + this.todos.push(task); + } +} diff --git a/aio/content/examples/homepage-todo/ts/src/app/todo_form.ts b/aio/content/examples/homepage-todo/ts/src/app/todo_form.ts new file mode 100644 index 0000000000..38e8d991a7 --- /dev/null +++ b/aio/content/examples/homepage-todo/ts/src/app/todo_form.ts @@ -0,0 +1,25 @@ +// #docregion +import { Component, Output, EventEmitter } from '@angular/core'; +import { Todo } from './todo'; + +@Component({ + selector: 'todo-form', + template: ` +
    + + +
    ` +}) +export class TodoFormComponent { + @Output() newTask = new EventEmitter(); + task: string = ''; + + addTodo() { + if (this.task) { + this.newTask.emit({text: this.task, done: false}); + } + this.task = ''; + } +} + diff --git a/aio/content/examples/homepage-todo/ts/src/app/todo_list.ts b/aio/content/examples/homepage-todo/ts/src/app/todo_list.ts new file mode 100644 index 0000000000..b6495f37c2 --- /dev/null +++ b/aio/content/examples/homepage-todo/ts/src/app/todo_list.ts @@ -0,0 +1,24 @@ +// #docregion +import { Component, Input } from '@angular/core'; + +import { Todo } from './todo'; + +@Component({ + selector: 'todo-list', + styles: [` + .done-true { + text-decoration: line-through; + color: grey; + }` + ], + template: ` +
      +
    • + + {{todo.text}} +
    • +
    ` +}) +export class TodoListComponent { + @Input() todos: Todo[]; +} diff --git a/aio/content/examples/homepage-todo/ts/src/index.1.html b/aio/content/examples/homepage-todo/ts/src/index.1.html new file mode 100644 index 0000000000..2fcc1e01e9 --- /dev/null +++ b/aio/content/examples/homepage-todo/ts/src/index.1.html @@ -0,0 +1,33 @@ + + + + + Angular Todos + + + + + + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/homepage-todo/ts/src/index.html b/aio/content/examples/homepage-todo/ts/src/index.html new file mode 100644 index 0000000000..e7a656a106 --- /dev/null +++ b/aio/content/examples/homepage-todo/ts/src/index.html @@ -0,0 +1,29 @@ + + + + + Angular Todos + + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/homepage-todo/ts/src/main.ts b/aio/content/examples/homepage-todo/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/aio/content/examples/homepage-todo/ts/src/main.ts @@ -0,0 +1,5 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/jsconfig.json b/aio/content/examples/jsconfig.json new file mode 100644 index 0000000000..82750484d1 --- /dev/null +++ b/aio/content/examples/jsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "target": "ES5", + "module": "commonjs" + } +} diff --git a/aio/content/examples/lifecycle-hooks/e2e-spec.ts b/aio/content/examples/lifecycle-hooks/e2e-spec.ts new file mode 100644 index 0000000000..8e9acb1c76 --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/e2e-spec.ts @@ -0,0 +1,178 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('Lifecycle hooks', function () { + + beforeAll(function () { + browser.get(''); + }); + + it('should open correctly', function () { + expect(element.all(by.css('h2')).get(0).getText()).toEqual('Peek-A-Boo'); + }); + + it('should support peek-a-boo', function () { + let pabComp = element(by.css('peek-a-boo-parent peek-a-boo')); + expect(pabComp.isPresent()).toBe(false, 'should not be able to find the "peek-a-boo" component'); + let pabButton = element.all(by.css('peek-a-boo-parent button')).get(0); + let updateHeroButton = element.all(by.css('peek-a-boo-parent button')).get(1); + expect(pabButton.getText()).toContain('Create Peek'); + pabButton.click().then(function () { + expect(pabButton.getText()).toContain('Destroy Peek'); + expect(pabComp.isDisplayed()).toBe(true, 'should be able to see the "peek-a-boo" component'); + expect(pabComp.getText()).toContain('Windstorm'); + expect(pabComp.getText()).not.toContain('Windstorm!'); + expect(updateHeroButton.isPresent()).toBe(true, 'should be able to see the update hero button'); + return updateHeroButton.click(); + }).then(function () { + expect(pabComp.getText()).toContain('Windstorm!'); + return pabButton.click(); + }).then(function () { + expect(pabComp.isPresent()).toBe(false, 'should no longer be able to find the "peek-a-boo" component'); + }); + }); + + it('should support OnChanges hook', function () { + let onChangesViewEle = element.all(by.css('on-changes div')).get(0); + let inputEles = element.all(by.css('on-changes-parent input')); + let heroNameInputEle = inputEles.get(1); + let powerInputEle = inputEles.get(0); + let titleEle = onChangesViewEle.element(by.css('p')); + let changeLogEles = onChangesViewEle.all(by.css('div')); + + expect(titleEle.getText()).toContain('Windstorm can sing'); + expect(changeLogEles.count()).toEqual(2, 'should start with 2 messages'); + heroNameInputEle.sendKeys('-foo-'); + expect(titleEle.getText()).toContain('Windstorm-foo- can sing'); + expect(changeLogEles.count()).toEqual(2, 'should still have 2 messages'); + powerInputEle.sendKeys('-bar-'); + expect(titleEle.getText()).toContain('Windstorm-foo- can sing-bar-'); + // 7 == 2 previously + length of '-bar-' + expect(changeLogEles.count()).toEqual(7, 'should have 7 messages now'); + }); + + it('should support DoCheck hook', function () { + let doCheckViewEle = element.all(by.css('do-check div')).get(0); + let inputEles = element.all(by.css('do-check-parent input')); + let heroNameInputEle = inputEles.get(1); + let powerInputEle = inputEles.get(0); + let titleEle = doCheckViewEle.element(by.css('p')); + let changeLogEles = doCheckViewEle.all(by.css('div')); + let logCount: number; + + expect(titleEle.getText()).toContain('Windstorm can sing'); + changeLogEles.count().then(function(count: number) { + // 3 messages to start + expect(count).toEqual(3, 'should start with 3 messages'); + logCount = count; + return heroNameInputEle.sendKeys('-foo-'); + }).then(function () { + expect(titleEle.getText()).toContain('Windstorm-foo- can sing'); + return changeLogEles.count(); + }).then(function(count: number) { + // one more for each keystroke + expect(count).toEqual(logCount + 5, 'should add 5 more messages'); + logCount = count; + return powerInputEle.sendKeys('-bar-'); + }).then(function () { + expect(titleEle.getText()).toContain('Windstorm-foo- can sing-bar-'); + expect(changeLogEles.count()).toEqual(logCount + 6, 'should add 6 more messages'); + }); + }); + + it('should support AfterView hooks', function () { + let parentEle = element(by.tagName('after-view-parent')); + let buttonEle = parentEle.element(by.tagName('button')); // Reset + let commentEle = parentEle.element(by.className('comment')); + let logEles = parentEle.all(by.css('h4 ~ div')); + let childViewInputEle = parentEle.element(by.css('my-child-view input')); + let logCount: number; + + expect(childViewInputEle.getAttribute('value')).toContain('Magneta'); + expect(commentEle.isPresent()).toBe(false, 'comment should not be in DOM'); + + logEles.count().then(function(count: number) { + logCount = count; + return childViewInputEle.sendKeys('-test-'); + }).then(function() { + expect(childViewInputEle.getAttribute('value')).toContain('-test-'); + expect(commentEle.isPresent()).toBe(true, 'should have comment because >10 chars'); + expect(commentEle.getText()).toContain('long name'); + return logEles.count(); + }).then(function(count: number) { + expect(logCount + 7).toEqual(count, '7 additional log messages should have been added'); + logCount = count; + return buttonEle.click(); + }).then(function() { + expect(logEles.count()).toBeLessThan(logCount, 'log should shrink after reset'); + }); + }); + + + it('should support AfterContent hooks', function () { + let parentEle = element(by.tagName('after-content-parent')); + let buttonEle = parentEle.element(by.tagName('button')); // Reset + let commentEle = parentEle.element(by.className('comment')); + let logEles = parentEle.all(by.css('h4 ~ div')); + let childViewInputEle = parentEle.element(by.css('my-child input')); + let logCount: number; + + expect(childViewInputEle.getAttribute('value')).toContain('Magneta'); + expect(commentEle.isPresent()).toBe(false, 'comment should not be in DOM'); + + logEles.count().then(function(count: number) { + logCount = count; + return childViewInputEle.sendKeys('-test-'); + }).then(function() { + expect(childViewInputEle.getAttribute('value')).toContain('-test-'); + expect(commentEle.isPresent()).toBe(true, 'should have comment because >10 chars'); + expect(commentEle.getText()).toContain('long name'); + return logEles.count(); + }).then(function(count: number) { + expect(logCount + 5).toEqual(count, '5 additional log messages should have been added'); + logCount = count; + return buttonEle.click(); + }).then(function() { + expect(logEles.count()).toBeLessThan(logCount, 'log should shrink after reset'); + }); + }); + + it('should support spy\'s OnInit & OnDestroy hooks', function () { + let inputEle = element(by.css('spy-parent input')); + let addHeroButtonEle = element(by.cssContainingText('spy-parent button', 'Add Hero')); + let resetHeroesButtonEle = element(by.cssContainingText('spy-parent button', 'Reset Heroes')); + let heroEles = element.all(by.css('spy-parent div[mySpy')); + let logEles = element.all(by.css('spy-parent h4 ~ div')); + expect(heroEles.count()).toBe(2, 'should have two heroes displayed'); + expect(logEles.count()).toBe(2, 'should have two log entries'); + inputEle.sendKeys('-test-').then(function() { + return addHeroButtonEle.click(); + }).then(function() { + expect(heroEles.count()).toBe(3, 'should have added one hero'); + expect(heroEles.get(2).getText()).toContain('-test-'); + expect(logEles.count()).toBe(3, 'should now have 3 log entries'); + return resetHeroesButtonEle.click(); + }).then(function() { + expect(heroEles.count()).toBe(0, 'should no longer have any heroes'); + expect(logEles.count()).toBe(7, 'should now have 7 log entries - 3 orig + 1 reset + 3 removeall'); + }); + }); + + it('should support "spy counter"', function () { + let updateCounterButtonEle = element(by.cssContainingText('counter-parent button', 'Update')); + let resetCounterButtonEle = element(by.cssContainingText('counter-parent button', 'Reset')); + let textEle = element(by.css('counter-parent my-counter > div')); + let logEles = element.all(by.css('counter-parent h4 ~ div')); + expect(textEle.getText()).toContain('Counter = 0'); + expect(logEles.count()).toBe(2, 'should start with two log entries'); + updateCounterButtonEle.click().then(function() { + expect(textEle.getText()).toContain('Counter = 1'); + expect(logEles.count()).toBe(3, 'should now have 3 log entries'); + return resetCounterButtonEle.click(); + }).then(function() { + expect(textEle.getText()).toContain('Counter = 0'); + expect(logEles.count()).toBe(7, 'should now have 7 log entries - 3 prev + 1 reset + 2 destroy + 1 init'); + }); + }); +}); diff --git a/aio/content/examples/lifecycle-hooks/ts/example-config.json b/aio/content/examples/lifecycle-hooks/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/lifecycle-hooks/ts/plnkr.json b/aio/content/examples/lifecycle-hooks/ts/plnkr.json new file mode 100644 index 0000000000..49290aa804 --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Lifecycle Hooks", + "basePath": "src/", + "files":["!**/*.d.ts", "!**/*.js"], + "tags": ["lifecycle", "hooks", + "OnInit", "OnDestroy", "OnChange", "DoCheck", + "AfterContentInit", "AfterContentChecked", + "AfterViewInit", "AfterViewChecked"] +} diff --git a/aio/content/examples/lifecycle-hooks/ts/plnkr.no-link.html b/aio/content/examples/lifecycle-hooks/ts/plnkr.no-link.html new file mode 100644 index 0000000000..5cf3712760 --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/plnkr.no-link.html @@ -0,0 +1,1097 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/lifecycle-hooks/ts/src/app/after-content.component.ts b/aio/content/examples/lifecycle-hooks/ts/src/app/after-content.component.ts new file mode 100644 index 0000000000..dfd2b80d24 --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/src/app/after-content.component.ts @@ -0,0 +1,116 @@ +// #docplaster +// #docregion +import { AfterContentChecked, AfterContentInit, Component, ContentChild } from '@angular/core'; + +import { LoggerService } from './logger.service'; + +////////////////// +@Component({ + selector: 'my-child', + template: '' +}) +export class ChildComponent { + hero = 'Magneta'; +} + +////////////////////// +@Component({ + selector: 'after-content', +// #docregion template + template: ` +
    -- projected content begins --
    + +
    -- projected content ends --
    ` +// #enddocregion template + + ` +

    + {{comment}} +

    + ` +}) +// #docregion hooks +export class AfterContentComponent implements AfterContentChecked, AfterContentInit { + private prevHero = ''; + comment = ''; + + // Query for a CONTENT child of type `ChildComponent` + @ContentChild(ChildComponent) contentChild: ChildComponent; + +// #enddocregion hooks + constructor(private logger: LoggerService) { + this.logIt('AfterContent constructor'); + } + +// #docregion hooks + ngAfterContentInit() { + // contentChild is set after the content has been initialized + this.logIt('AfterContentInit'); + this.doSomething(); + } + + ngAfterContentChecked() { + // contentChild is updated after the content has been checked + if (this.prevHero === this.contentChild.hero) { + this.logIt('AfterContentChecked (no change)'); + } else { + this.prevHero = this.contentChild.hero; + this.logIt('AfterContentChecked'); + this.doSomething(); + } + } +// #enddocregion hooks +// #docregion do-something + + // This surrogate for real business logic sets the `comment` + private doSomething() { + this.comment = this.contentChild.hero.length > 10 ? `That's a long name` : ''; + } + + private logIt(method: string) { + let child = this.contentChild; + let message = `${method}: ${child ? child.hero : 'no'} child content`; + this.logger.log(message); + } +// #docregion hooks + // ... +} +// #enddocregion hooks + +////////////// +@Component({ + selector: 'after-content-parent', + template: ` +
    +

    AfterContent

    + +
    ` + +// #docregion parent-template + ` + + ` +// #enddocregion parent-template ++ `
    + +

    -- AfterContent Logs --

    +

    +
    {{msg}}
    +
    + `, + styles: ['.parent {background: burlywood}'], + providers: [LoggerService] +}) +export class AfterContentParentComponent { + logs: string[]; + show = true; + + constructor(private logger: LoggerService) { + this.logs = logger.logs; + } + + reset() { + this.logs.length = 0; + // quickly remove and reload AfterContentComponent which recreates it + this.show = false; + this.logger.tick_then(() => this.show = true); + } +} diff --git a/aio/content/examples/lifecycle-hooks/ts/src/app/after-view.component.ts b/aio/content/examples/lifecycle-hooks/ts/src/app/after-view.component.ts new file mode 100644 index 0000000000..71310e4530 --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/src/app/after-view.component.ts @@ -0,0 +1,118 @@ +// #docplaster +// #docregion +import { AfterViewChecked, AfterViewInit, Component, ViewChild } from '@angular/core'; + +import { LoggerService } from './logger.service'; + +////////////////// +// #docregion child-view +@Component({ + selector: 'my-child-view', + template: '' +}) +export class ChildViewComponent { + hero = 'Magneta'; +} +// #enddocregion child-view + +////////////////////// +@Component({ + selector: 'after-view', +// #docregion template + template: ` +
    -- child view begins --
    + +
    -- child view ends --
    ` +// #enddocregion template + + ` +

    + {{comment}} +

    + ` +}) +// #docregion hooks +export class AfterViewComponent implements AfterViewChecked, AfterViewInit { + private prevHero = ''; + + // Query for a VIEW child of type `ChildViewComponent` + @ViewChild(ChildViewComponent) viewChild: ChildViewComponent; + +// #enddocregion hooks + constructor(private logger: LoggerService) { + this.logIt('AfterView constructor'); + } + +// #docregion hooks + ngAfterViewInit() { + // viewChild is set after the view has been initialized + this.logIt('AfterViewInit'); + this.doSomething(); + } + + ngAfterViewChecked() { + // viewChild is updated after the view has been checked + if (this.prevHero === this.viewChild.hero) { + this.logIt('AfterViewChecked (no change)'); + } else { + this.prevHero = this.viewChild.hero; + this.logIt('AfterViewChecked'); + this.doSomething(); + } + } +// #enddocregion hooks + + comment = ''; + +// #docregion do-something + // This surrogate for real business logic sets the `comment` + private doSomething() { + let c = this.viewChild.hero.length > 10 ? `That's a long name` : ''; + if (c !== this.comment) { + // Wait a tick because the component's view has already been checked + this.logger.tick_then(() => this.comment = c); + } + } +// #enddocregion do-something + + private logIt(method: string) { + let child = this.viewChild; + let message = `${method}: ${child ? child.hero : 'no'} child view`; + this.logger.log(message); + } +// #docregion hooks + // ... +} +// #enddocregion hooks + +////////////// +@Component({ + selector: 'after-view-parent', + template: ` +
    +

    AfterView

    + + + +

    -- AfterView Logs --

    +

    +
    {{msg}}
    +
    + `, + styles: ['.parent {background: burlywood}'], + providers: [LoggerService] +}) +export class AfterViewParentComponent { + logs: string[]; + show = true; + + constructor(private logger: LoggerService) { + this.logs = logger.logs; + } + + reset() { + this.logs.length = 0; + // quickly remove and reload AfterViewComponent which recreates it + this.show = false; + this.logger.tick_then(() => this.show = true); + } +} diff --git a/aio/content/examples/lifecycle-hooks/ts/src/app/app.component.html b/aio/content/examples/lifecycle-hooks/ts/src/app/app.component.html new file mode 100644 index 0000000000..d0692e28ac --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/src/app/app.component.html @@ -0,0 +1,37 @@ + +

    Component Lifecycle Hooks

    +Peek-a-boo: (most) lifecycle hooks
    +OnChanges
    +DoCheck
    +AfterViewInit & AfterViewChecked
    +AfterContentInit & AfterContentChecked
    +Spy: directive with OnInit & OnDestroy
    +Counter: OnChanges + Spy directive
    + + + +back to top + + + +back to top + + + +back to top + + + +back to top + + + +back to top + + + +back to top + + + +back to top diff --git a/aio/content/examples/lifecycle-hooks/ts/src/app/app.component.ts b/aio/content/examples/lifecycle-hooks/ts/src/app/app.component.ts new file mode 100644 index 0000000000..a917088410 --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/src/app/app.component.ts @@ -0,0 +1,8 @@ +// #docregion +import { Component } from '@angular/core'; +@Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: './app.component.html' +}) +export class AppComponent { } diff --git a/aio/content/examples/lifecycle-hooks/ts/src/app/app.module.ts b/aio/content/examples/lifecycle-hooks/ts/src/app/app.module.ts new file mode 100644 index 0000000000..4d9cabf218 --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/src/app/app.module.ts @@ -0,0 +1,67 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; + +import { AppComponent } from './app.component'; + +import { + AfterContentParentComponent, + AfterContentComponent, + ChildComponent +} from './after-content.component'; + +import { + AfterViewParentComponent, + AfterViewComponent, + ChildViewComponent +} from './after-view.component'; + +import { + CounterParentComponent, + MyCounterComponent +} from './counter.component'; + +import { + DoCheckParentComponent, + DoCheckComponent +} from './do-check.component'; + +import { + OnChangesParentComponent, + OnChangesComponent +} from './on-changes.component'; + +import { PeekABooParentComponent } from './peek-a-boo-parent.component'; +import { PeekABooComponent } from './peek-a-boo.component'; + +import { SpyParentComponent } from './spy.component'; +import { SpyDirective } from './spy.directive'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule + ], + declarations: [ + AppComponent, + AfterContentParentComponent, + AfterContentComponent, + ChildComponent, + AfterViewParentComponent, + AfterViewComponent, + ChildViewComponent, + CounterParentComponent, + MyCounterComponent, + DoCheckParentComponent, + DoCheckComponent, + OnChangesParentComponent, + OnChangesComponent, + PeekABooParentComponent, + PeekABooComponent, + SpyParentComponent, + SpyDirective + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/lifecycle-hooks/ts/src/app/counter.component.ts b/aio/content/examples/lifecycle-hooks/ts/src/app/counter.component.ts new file mode 100644 index 0000000000..8efd1fbd52 --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/src/app/counter.component.ts @@ -0,0 +1,84 @@ +// #docregion +import { + Component, Input, + OnChanges, SimpleChanges, +} from '@angular/core'; + +import { LoggerService } from './logger.service'; + +@Component({ + selector: 'my-counter', + template: ` +
    + Counter = {{counter}} + +
    -- Counter Change Log --
    +
    {{chg}}
    +
    + `, + styles: ['.counter {background: LightYellow; padding: 8px; margin-top: 8px}'] +}) +export class MyCounterComponent implements OnChanges { + @Input() counter: number; + changeLog: string[] = []; + + ngOnChanges(changes: SimpleChanges) { + + // Empty the changeLog whenever counter goes to zero + // hint: this is a way to respond programmatically to external value changes. + if (this.counter === 0) { + this.changeLog.length = 0; + } + + // A change to `counter` is the only change we care about + let chng = changes['counter']; + let cur = chng.currentValue; + let prev = JSON.stringify(chng.previousValue); // first time is {}; after is integer + this.changeLog.push(`counter: currentValue = ${cur}, previousValue = ${prev}`); + } +} + +/***************************************/ + +@Component({ + selector: 'counter-parent', + template: ` +
    +

    Counter Spy

    + + + + + + +

    -- Spy Lifecycle Hook Log --

    +
    {{msg}}
    +
    + `, + styles: ['.parent {background: gold;}'], + providers: [LoggerService] +}) +export class CounterParentComponent { + value: number; + spyLog: string[] = []; + + private logger: LoggerService; + + constructor(logger: LoggerService) { + this.logger = logger; + this.spyLog = logger.logs; + this.reset(); + } + + updateCounter() { + this.value += 1; + this.logger.tick(); + } + + reset() { + this.logger.log('-- reset --'); + this.value = 0; + this.logger.tick(); + } +} + diff --git a/aio/content/examples/lifecycle-hooks/ts/src/app/do-check-parent.component.html b/aio/content/examples/lifecycle-hooks/ts/src/app/do-check-parent.component.html new file mode 100644 index 0000000000..cf7c2b91ce --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/src/app/do-check-parent.component.html @@ -0,0 +1,13 @@ +
    +

    {{title}}

    + + + + +
    Power:
    Hero.name:
    +

    + + + + +
    diff --git a/aio/content/examples/lifecycle-hooks/ts/src/app/do-check.component.ts b/aio/content/examples/lifecycle-hooks/ts/src/app/do-check.component.ts new file mode 100644 index 0000000000..bdd96ed65b --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/src/app/do-check.component.ts @@ -0,0 +1,96 @@ +/* tslint:disable:forin */ +// #docregion +import { Component, DoCheck, Input, ViewChild } from '@angular/core'; + +class Hero { + constructor(public name: string) {} +} + +@Component({ + selector: 'do-check', + template: ` +
    +

    {{hero.name}} can {{power}}

    + +

    -- Change Log --

    +
    {{chg}}
    +
    + `, + styles: [ + '.hero {background: LightYellow; padding: 8px; margin-top: 8px}', + 'p {background: Yellow; padding: 8px; margin-top: 8px}' + ] +}) +export class DoCheckComponent implements DoCheck { + @Input() hero: Hero; + @Input() power: string; + + changeDetected = false; + changeLog: string[] = []; + oldHeroName = ''; + oldPower = ''; + oldLogLength = 0; + noChangeCount = 0; + + // #docregion ng-do-check + ngDoCheck() { + + if (this.hero.name !== this.oldHeroName) { + this.changeDetected = true; + this.changeLog.push(`DoCheck: Hero name changed to "${this.hero.name}" from "${this.oldHeroName}"`); + this.oldHeroName = this.hero.name; + } + + if (this.power !== this.oldPower) { + this.changeDetected = true; + this.changeLog.push(`DoCheck: Power changed to "${this.power}" from "${this.oldPower}"`); + this.oldPower = this.power; + } + + if (this.changeDetected) { + this.noChangeCount = 0; + } else { + // log that hook was called when there was no relevant change. + let count = this.noChangeCount += 1; + let noChangeMsg = `DoCheck called ${count}x when no change to hero or power`; + if (count === 1) { + // add new "no change" message + this.changeLog.push(noChangeMsg); + } else { + // update last "no change" message + this.changeLog[this.changeLog.length - 1] = noChangeMsg; + } + } + + this.changeDetected = false; + } + // #enddocregion ng-do-check + + reset() { + this.changeDetected = true; + this.changeLog.length = 0; + } +} + +/***************************************/ + +@Component({ + moduleId: module.id, + selector: 'do-check-parent', + templateUrl: './do-check-parent.component.html', + styles: ['.parent {background: Lavender}'] +}) +export class DoCheckParentComponent { + hero: Hero; + power: string; + title = 'DoCheck'; + @ViewChild(DoCheckComponent) childView: DoCheckComponent; + + constructor() { this.reset(); } + + reset() { + this.hero = new Hero('Windstorm'); + this.power = 'sing'; + if (this.childView) { this.childView.reset(); } + } +} diff --git a/aio/content/examples/lifecycle-hooks/ts/src/app/logger.service.ts b/aio/content/examples/lifecycle-hooks/ts/src/app/logger.service.ts new file mode 100644 index 0000000000..56ddaf9063 --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/src/app/logger.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class LoggerService { + logs: string[] = []; + prevMsg = ''; + prevMsgCount = 1; + + log(msg: string) { + if (msg === this.prevMsg) { + // Repeat message; update last log entry with count. + this.logs[this.logs.length - 1] = msg + ` (${this.prevMsgCount += 1}x)`; + } else { + // New message; log it. + this.prevMsg = msg; + this.prevMsgCount = 1; + this.logs.push(msg); + } + } + + clear() { this.logs.length = 0; } + + // schedules a view refresh to ensure display catches up + tick() { this.tick_then(() => { }); } + tick_then(fn: () => any) { setTimeout(fn, 0); } +} diff --git a/aio/content/examples/lifecycle-hooks/ts/src/app/on-changes-parent.component.html b/aio/content/examples/lifecycle-hooks/ts/src/app/on-changes-parent.component.html new file mode 100644 index 0000000000..a0fd404931 --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/src/app/on-changes-parent.component.html @@ -0,0 +1,13 @@ +
    +

    {{title}}

    + + + + +
    Power:
    Hero.name:
    +

    + + + + +
    diff --git a/aio/content/examples/lifecycle-hooks/ts/src/app/on-changes.component.ts b/aio/content/examples/lifecycle-hooks/ts/src/app/on-changes.component.ts new file mode 100644 index 0000000000..c9a243a5f0 --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/src/app/on-changes.component.ts @@ -0,0 +1,74 @@ +/* tslint:disable:forin */ +// #docregion +import { + Component, Input, OnChanges, + SimpleChanges, ViewChild +} from '@angular/core'; + +class Hero { + constructor(public name: string) {} +} + +@Component({ + selector: 'on-changes', + template: ` +
    +

    {{hero.name}} can {{power}}

    + +

    -- Change Log --

    +
    {{chg}}
    +
    + `, + styles: [ + '.hero {background: LightYellow; padding: 8px; margin-top: 8px}', + 'p {background: Yellow; padding: 8px; margin-top: 8px}' + ] +}) +export class OnChangesComponent implements OnChanges { +// #docregion inputs + @Input() hero: Hero; + @Input() power: string; +// #enddocregion inputs + + changeLog: string[] = []; + + // #docregion ng-on-changes + ngOnChanges(changes: SimpleChanges) { + for (let propName in changes) { + let chng = changes[propName]; + let cur = JSON.stringify(chng.currentValue); + let prev = JSON.stringify(chng.previousValue); + this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`); + } + } + // #enddocregion ng-on-changes + + reset() { this.changeLog.length = 0; } +} + +/***************************************/ + +@Component({ + moduleId: module.id, + selector: 'on-changes-parent', + templateUrl: './on-changes-parent.component.html', + styles: ['.parent {background: Lavender;}'] +}) +export class OnChangesParentComponent { + hero: Hero; + power: string; + title = 'OnChanges'; + @ViewChild(OnChangesComponent) childView: OnChangesComponent; + + constructor() { + this.reset(); + } + + reset() { + // new Hero object every time; triggers onChanges + this.hero = new Hero('Windstorm'); + // setting power only triggers onChanges if this value is different + this.power = 'sing'; + if (this.childView) { this.childView.reset(); } + } +} diff --git a/aio/content/examples/lifecycle-hooks/ts/src/app/peek-a-boo-parent.component.ts b/aio/content/examples/lifecycle-hooks/ts/src/app/peek-a-boo-parent.component.ts new file mode 100644 index 0000000000..3f2bd8585d --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/src/app/peek-a-boo-parent.component.ts @@ -0,0 +1,53 @@ +// #docregion +import { Component } from '@angular/core'; + +import { LoggerService } from './logger.service'; + +@Component({ + selector: 'peek-a-boo-parent', + template: ` +
    +

    Peek-A-Boo

    + + + + + + + +

    -- Lifecycle Hook Log --

    +
    {{msg}}
    +
    + `, + styles: ['.parent {background: moccasin}'], + providers: [ LoggerService ] +}) +export class PeekABooParentComponent { + + hasChild = false; + hookLog: string[]; + + heroName = 'Windstorm'; + private logger: LoggerService; + + constructor(logger: LoggerService) { + this.logger = logger; + this.hookLog = logger.logs; + } + + toggleChild() { + this.hasChild = !this.hasChild; + if (this.hasChild) { + this.heroName = 'Windstorm'; + this.logger.clear(); // clear log on create + } + this.logger.tick(); + } + + updateHero() { + this.heroName += '!'; + this.logger.tick(); + } +} diff --git a/aio/content/examples/lifecycle-hooks/ts/src/app/peek-a-boo.component.ts b/aio/content/examples/lifecycle-hooks/ts/src/app/peek-a-boo.component.ts new file mode 100644 index 0000000000..dcee428753 --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/src/app/peek-a-boo.component.ts @@ -0,0 +1,85 @@ +import { + AfterContentChecked, + AfterContentInit, + AfterViewChecked, + AfterViewInit, + DoCheck, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges +} from '@angular/core'; +import { Component, Input } from '@angular/core'; +import { LoggerService } from './logger.service'; + +let nextId = 1; + +// #docregion ngOnInit +export class PeekABoo implements OnInit { + constructor(private logger: LoggerService) { } + + // implement OnInit's `ngOnInit` method + ngOnInit() { this.logIt(`OnInit`); } + + logIt(msg: string) { + this.logger.log(`#${nextId++} ${msg}`); + } +} +// #enddocregion ngOnInit + +@Component({ + selector: 'peek-a-boo', + template: '

    Now you see my hero, {{name}}

    ', + styles: ['p {background: LightYellow; padding: 8px}'] +}) +// Don't HAVE to mention the Lifecycle Hook interfaces +// unless we want typing and tool support. +export class PeekABooComponent extends PeekABoo implements + OnChanges, OnInit, DoCheck, + AfterContentInit, AfterContentChecked, + AfterViewInit, AfterViewChecked, + OnDestroy { + @Input() name: string; + + private verb = 'initialized'; + + constructor(logger: LoggerService) { + super(logger); + + let is = this.name ? 'is' : 'is not'; + this.logIt(`name ${is} known at construction`); + } + + // only called for/if there is an @input variable set by parent. + ngOnChanges(changes: SimpleChanges) { + let changesMsgs: string[] = []; + for (let propName in changes) { + if (propName === 'name') { + let name = changes['name'].currentValue; + changesMsgs.push(`name ${this.verb} to "${name}"`); + } else { + changesMsgs.push(propName + ' ' + this.verb); + } + } + this.logIt(`OnChanges: ${changesMsgs.join('; ')}`); + this.verb = 'changed'; // next time it will be a change + } + + // Beware! Called frequently! + // Called in every change detection cycle anywhere on the page + ngDoCheck() { this.logIt(`DoCheck`); } + + ngAfterContentInit() { this.logIt(`AfterContentInit`); } + + // Beware! Called frequently! + // Called in every change detection cycle anywhere on the page + ngAfterContentChecked() { this.logIt(`AfterContentChecked`); } + + ngAfterViewInit() { this.logIt(`AfterViewInit`); } + + // Beware! Called frequently! + // Called in every change detection cycle anywhere on the page + ngAfterViewChecked() { this.logIt(`AfterViewChecked`); } + + ngOnDestroy() { this.logIt(`OnDestroy`); } +} diff --git a/aio/content/examples/lifecycle-hooks/ts/src/app/spy.component.html b/aio/content/examples/lifecycle-hooks/ts/src/app/spy.component.html new file mode 100644 index 0000000000..782435b961 --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/src/app/spy.component.html @@ -0,0 +1,16 @@ +
    +

    Spy Directive

    + + + + + +

    + +
    + {{hero}} +
    + +

    -- Spy Lifecycle Hook Log --

    +
    {{msg}}
    +
    diff --git a/aio/content/examples/lifecycle-hooks/ts/src/app/spy.component.ts b/aio/content/examples/lifecycle-hooks/ts/src/app/spy.component.ts new file mode 100644 index 0000000000..6cbed98d1d --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/src/app/spy.component.ts @@ -0,0 +1,41 @@ +// #docregion +import { Component } from '@angular/core'; + +import { LoggerService } from './logger.service'; + +@Component({ + moduleId: module.id, + selector: 'spy-parent', + templateUrl: './spy.component.html', + styles: [ + '.parent {background: khaki;}', + '.heroes {background: LightYellow; padding: 0 8px}' + ], + providers: [LoggerService] +}) +export class SpyParentComponent { + newName = 'Herbie'; + heroes: string[] = ['Windstorm', 'Magneta']; + spyLog: string[]; + + constructor(private logger: LoggerService) { + this.spyLog = logger.logs; + } + + addHero() { + if (this.newName.trim()) { + this.heroes.push(this.newName.trim()); + this.newName = ''; + this.logger.tick(); + } + } + removeHero(hero: string) { + this.heroes.splice(this.heroes.indexOf(hero), 1); + this.logger.tick(); + } + reset() { + this.logger.log('-- reset --'); + this.heroes.length = 0; + this.logger.tick(); + } +} diff --git a/aio/content/examples/lifecycle-hooks/ts/src/app/spy.directive.ts b/aio/content/examples/lifecycle-hooks/ts/src/app/spy.directive.ts new file mode 100644 index 0000000000..01f3f95880 --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/src/app/spy.directive.ts @@ -0,0 +1,24 @@ +// #docregion +import { Directive, OnInit, OnDestroy } from '@angular/core'; + +import { LoggerService } from './logger.service'; + +let nextId = 1; + +// #docregion spy-directive +// Spy on any element to which it is applied. +// Usage:
    ...
    +@Directive({selector: '[mySpy]'}) +export class SpyDirective implements OnInit, OnDestroy { + + constructor(private logger: LoggerService) { } + + ngOnInit() { this.logIt(`onInit`); } + + ngOnDestroy() { this.logIt(`onDestroy`); } + + private logIt(msg: string) { + this.logger.log(`Spy #${nextId++} ${msg}`); + } +} +// #enddocregion spy-directive diff --git a/aio/content/examples/lifecycle-hooks/ts/src/index.html b/aio/content/examples/lifecycle-hooks/ts/src/index.html new file mode 100644 index 0000000000..cf7059f780 --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/src/index.html @@ -0,0 +1,28 @@ + + + + + Angular Lifecycle Hooks + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/lifecycle-hooks/ts/src/main.ts b/aio/content/examples/lifecycle-hooks/ts/src/main.ts new file mode 100644 index 0000000000..f22933ba8e --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/src/main.ts @@ -0,0 +1,4 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/lifecycle-hooks/ts/src/sample.css b/aio/content/examples/lifecycle-hooks/ts/src/sample.css new file mode 100644 index 0000000000..df17c897c6 --- /dev/null +++ b/aio/content/examples/lifecycle-hooks/ts/src/sample.css @@ -0,0 +1,13 @@ +.parent { + color: #666; + margin: 14px 0; + padding: 8px; +} +input { + margin: 4px; + padding: 4px; +} +.comment { + color: red; + font-style: italic; +} diff --git a/aio/content/examples/ngcontainer/ts/example-config.json b/aio/content/examples/ngcontainer/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/ngcontainer/ts/plnkr.json b/aio/content/examples/ngcontainer/ts/plnkr.json new file mode 100644 index 0000000000..a6cdc4ba1a --- /dev/null +++ b/aio/content/examples/ngcontainer/ts/plnkr.json @@ -0,0 +1,11 @@ +{ + "description": "", + "basePath": "src/", + "files": [ + "!**/*.d.ts", + "!**/*.js" + ], + "tags": [ + "ngcontainer", "structural", "directives" + ] +} diff --git a/aio/content/examples/ngcontainer/ts/plnkr.no-link.html b/aio/content/examples/ngcontainer/ts/plnkr.no-link.html new file mode 100644 index 0000000000..2e1baa19e2 --- /dev/null +++ b/aio/content/examples/ngcontainer/ts/plnkr.no-link.html @@ -0,0 +1,586 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/ngcontainer/ts/src/app/app.component.css b/aio/content/examples/ngcontainer/ts/src/app/app.component.css new file mode 100644 index 0000000000..cdb6631713 --- /dev/null +++ b/aio/content/examples/ngcontainer/ts/src/app/app.component.css @@ -0,0 +1,34 @@ +/* #docregion */ +button { + min-width: 100px; + font-size: 100%; +} + +code, .code { + background-color: #eee; + color: black; + font-family: Courier, sans-serif; + font-size: 85%; +} + +div.code { + width: 400px; +} + +.heroic { + font-size: 150%; + font-weight: bold; +} + +hr { + margin: 40px 0 +} + +td, th { + text-align: left; + vertical-align: top; +} + +/* #docregion p-span */ +p span { color: red; font-size: 70%; } +/* #enddocregion p-span */ diff --git a/aio/content/examples/ngcontainer/ts/src/app/app.component.html b/aio/content/examples/ngcontainer/ts/src/app/app.component.html new file mode 100644 index 0000000000..afd0b00f36 --- /dev/null +++ b/aio/content/examples/ngcontainer/ts/src/app/app.component.html @@ -0,0 +1,279 @@ + + +

    <ng-container>

    + + +
    {{hero.name}}
    + + +
    + +

    <ng-container> and CSS

    +

    Examples demonstrating issues with rigid CSS styles.

    + + + +

    #1 <ng-container> and <p>

    + +

    + I turned the corner + + and saw {{hero.name}}. I waved + + and continued on my way. +

    + + +

    + I turned the corner + + and saw {{hero.name}}. I waved + + and continued on my way. +

    + + +

    #2 <ng-container> and <p>

    + +
    + +

    + {{hero.name}} is + + , + and + {{trait}} + . +

    + + + +

    + {{hero.name}} is + + , + and + {{trait}} + . +

    + + +
    + +

    #3 <ng-container> and <p>

    + +

    + +

    + + +
    + The hero.id in the <span> + is caught by the p-span CSS: + +

    + + Id: ({{hero.id}}) + + Name: {{hero.name}} +

    + +
    + +
    + The hero.id in the <ng-container> + is unaffected by the p-span CSS: +

    + + Id: ({{hero.id}}) + + Name: {{hero.name}} +

    +
    + +
    + The hero.id in the <template *ngIf> disappears: +

    + + Name: {{hero.name}} +

    +
    + +
    + The hero.id in the <template [ngIf]> + is unaffected by the p-span CSS: +

    + + Name: {{hero.name}} +

    +
    + +
    + +
    + +

    <ng-container> and layout-sensitive elements

    +

    + Examples demonstrating issues with layout-sensitive elements + such as <select> and <table>. +

    + +

    #1 <ng-container> and <options>

    + +

    <select> with <span>

    +
    + Pick your favorite hero + () +
    + + + + +

    <select> with <ng-container>

    +
    + Pick your favorite hero + () +
    + + + + +



    + +

    #2 <ng-container> and <options>

    +

    + +

    + +

    Options with <ng-container>

    + + + + +

    Options with <span>

    + + + + +
    + +

    <ng-container> and <table>

    +

    + + + +

    + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    DirectiveTypeDescription
    NgClassAAdd or remove multiple CSS classes.
    xxxSdiv with *ngIf formats crazy.
    yyySdiv with *ngIf formats crazy.
    NgForSRepeat the template for each item in a list.
    NgIfSAdd or remove DOM elements.
    NgStyleAAdd or remove multiple style attributes.
    NgSwitchSInclude in DOM if case matches the switch value.
    + +
    + +

    Do not confuse <ng-container> with <ng-content>

    + +

    <ng-container>Inside ng-container</ng-container>

    + +Inside ng-container + + +

    <ng-content>this is an Angular parse error</ng-content>

    + + + +
    Template parse errors:
    +<ng-content> element cannot have content.
    + +

    Demo of </ng-content>

    + + + Projected content + + diff --git a/aio/content/examples/ngcontainer/ts/src/app/app.component.ts b/aio/content/examples/ngcontainer/ts/src/app/app.component.ts new file mode 100644 index 0000000000..2d6d7f959c --- /dev/null +++ b/aio/content/examples/ngcontainer/ts/src/app/app.component.ts @@ -0,0 +1,25 @@ +// #docregion +import { Component } from '@angular/core'; + +import { heroes } from './hero'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: './app.component.html', + styleUrls: [ './app.component.css' ] +}) +export class AppComponent { + heroes = heroes; + hero = this.heroes[0]; + heroTraits = [ 'honest', 'brave', 'considerate' ]; + + // flags for the table + attrDirs = true; + strucDirs = true; + divNgIf = false; + + showId = true; + showDefaultTraits = true; + showSad = true; +} diff --git a/aio/content/examples/ngcontainer/ts/src/app/app.module.ts b/aio/content/examples/ngcontainer/ts/src/app/app.module.ts new file mode 100644 index 0000000000..57ac92f518 --- /dev/null +++ b/aio/content/examples/ngcontainer/ts/src/app/app.module.ts @@ -0,0 +1,19 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; +import { ContentComponent } from './content.component'; +import { heroComponents } from './hero.components'; + +@NgModule({ + imports: [ BrowserModule, FormsModule ], + declarations: [ + AppComponent, + ContentComponent, + heroComponents + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/ngcontainer/ts/src/app/content.component.ts b/aio/content/examples/ngcontainer/ts/src/app/content.component.ts new file mode 100644 index 0000000000..a2580b0dee --- /dev/null +++ b/aio/content/examples/ngcontainer/ts/src/app/content.component.ts @@ -0,0 +1,16 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'content-comp', + // #docregion template + template: + `
    + +
    `, + // #enddocregion template + styles: [ ` + div { border: medium dashed green; padding: 1em; width: 150px; text-align: center} + `] +}) +export class ContentComponent { } diff --git a/aio/content/examples/ngcontainer/ts/src/app/hero.components.ts b/aio/content/examples/ngcontainer/ts/src/app/hero.components.ts new file mode 100644 index 0000000000..77920ebdf9 --- /dev/null +++ b/aio/content/examples/ngcontainer/ts/src/app/hero.components.ts @@ -0,0 +1,43 @@ +// #docregion +import { Component, Input } from '@angular/core'; +import { Hero } from './hero'; + +@Component({ + selector: 'happy-hero', + template: `Wow. You like {{hero.name}}. What a happy hero ... just like you.` +}) +export class HappyHeroComponent { + @Input() hero: Hero; +} + +@Component({ + selector: 'sad-hero', + template: `You like {{hero.name}}? Such a sad hero. Are you sad too?` +}) +export class SadHeroComponent { + @Input() hero: Hero; +} + +@Component({ + selector: 'confused-hero', + template: `Are you as confused as {{hero.name}}?` +}) +export class ConfusedHeroComponent { + @Input() hero: Hero; +} + +@Component({ + selector: 'unknown-hero', + template: `{{message}}` +}) +export class UnknownHeroComponent { + @Input() hero: Hero; + get message() { + return this.hero && this.hero.name ? + `${this.hero.name} is strange and mysterious.` : + 'Are you feeling indecisive?'; + } +} + +export const heroComponents = + [ HappyHeroComponent, SadHeroComponent, ConfusedHeroComponent, UnknownHeroComponent ]; diff --git a/aio/content/examples/ngcontainer/ts/src/app/hero.ts b/aio/content/examples/ngcontainer/ts/src/app/hero.ts new file mode 100644 index 0000000000..89f0cbfdf8 --- /dev/null +++ b/aio/content/examples/ngcontainer/ts/src/app/hero.ts @@ -0,0 +1,13 @@ +// #docregion +export class Hero { + id: number; + name: string; + emotion?: string; +} + +export const heroes: Hero[] = [ + { id: 1, name: 'Mr. Nice', emotion: 'happy'}, + { id: 2, name: 'Narco', emotion: 'sad' }, + { id: 3, name: 'Windstorm', emotion: 'confused' }, + { id: 4, name: 'Magneta'} +]; diff --git a/aio/content/examples/ngcontainer/ts/src/index.html b/aio/content/examples/ngcontainer/ts/src/index.html new file mode 100644 index 0000000000..fc5ff417c3 --- /dev/null +++ b/aio/content/examples/ngcontainer/ts/src/index.html @@ -0,0 +1,26 @@ + + + + + Angular <ng-container> + + + + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/ngcontainer/ts/src/main.ts b/aio/content/examples/ngcontainer/ts/src/main.ts new file mode 100644 index 0000000000..105b06712d --- /dev/null +++ b/aio/content/examples/ngcontainer/ts/src/main.ts @@ -0,0 +1,6 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); + diff --git a/aio/content/examples/ngmodule/e2e-spec.ts b/aio/content/examples/ngmodule/e2e-spec.ts new file mode 100644 index 0000000000..0fbce1213d --- /dev/null +++ b/aio/content/examples/ngmodule/e2e-spec.ts @@ -0,0 +1,223 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('NgModule', function () { + + // helpers + const gold = 'rgba(255, 215, 0, 1)'; + const powderblue = 'rgba(176, 224, 230, 1)'; + const lightgray = 'rgba(211, 211, 211, 1)'; + const white = 'rgba(0, 0, 0, 0)'; + + function getCommonsSectionStruct() { + const buttons = element.all(by.css('nav a')); + + return { + title: element.all(by.tagName('h1')).get(0), + subtitle: element.all(by.css('app-title p i')).get(0), + contactButton: buttons.get(0), + crisisButton: buttons.get(1), + heroesButton: buttons.get(2) + }; + } + + function getContactSectionStruct() { + const buttons = element.all(by.css('app-contact form button')); + + return { + header: element.all(by.css('app-contact h2')).get(0), + popupMessage: element.all(by.css('app-contact div')).get(0), + contactNameHeader: element.all(by.css('app-contact form h3')).get(0), + input: element.all(by.css('app-contact form input')).get(0), + validationError: element.all(by.css('app-contact form .alert')).get(0), + saveButton: buttons.get(0), // can't be tested + nextContactButton: buttons.get(1), + newContactButton: buttons.get(2) + }; + } + + function getCrisisSectionStruct() { + return { + title: element.all(by.css('ng-component h3')).get(0), + items: element.all(by.css('ng-component a')), + itemId: element.all(by.css('ng-component div')).get(0), + listLink: element.all(by.css('ng-component a')).get(0), + }; + } + + function getHeroesSectionStruct() { + return { + header: element.all(by.css('ng-component h2')).get(0), + title: element.all(by.css('ng-component h3')).get(0), + items: element.all(by.css('ng-component a')), + itemId: element.all(by.css('ng-component ng-component div div')).get(0), + itemInput: element.all(by.css('ng-component ng-component input')).get(0), + listLink: element.all(by.css('ng-component ng-component a')).get(0), + }; + } + + // tests + function appTitleTests(color: string, name?: string) { + return function() { + it('should have a gray header', function() { + const commons = getCommonsSectionStruct(); + expect(commons.title.getCssValue('backgroundColor')).toBe(color); + }); + + it('should welcome us', function () { + const commons = getCommonsSectionStruct(); + expect(commons.subtitle.getText()).toBe('Welcome, ' + (name || 'Sherlock Holmes')); + }); + }; + } + + function contactTests(color: string, name?: string) { + return function() { + it('shows the contact\'s owner', function() { + const contacts = getContactSectionStruct(); + expect(contacts.header.getText()).toBe('Contact of ' + (name || 'Sherlock Holmes')); + }); + + it('can cycle between contacts', function () { + const contacts = getContactSectionStruct(); + const nextButton = contacts.nextContactButton; + expect(contacts.contactNameHeader.getText()).toBe('Awesome Sam Spade'); + expect(contacts.contactNameHeader.getCssValue('backgroundColor')).toBe(color); + nextButton.click().then(function () { + expect(contacts.contactNameHeader.getText()).toBe('Awesome Nick Danger'); + return nextButton.click(); + }).then(function () { + expect(contacts.contactNameHeader.getText()).toBe('Awesome Nancy Drew'); + }); + }); + + it('can change an existing contact', function () { + const contacts = getContactSectionStruct(); + contacts.input.sendKeys('a'); + expect(contacts.input.getCssValue('backgroundColor')).toBe(color); + expect(contacts.contactNameHeader.getText()).toBe('Awesome Sam Spadea'); + }); + + it('can create a new contact', function () { + const contacts = getContactSectionStruct(); + const newContactButton = contacts.newContactButton; + newContactButton.click().then(function () { + expect(contacts.validationError.getText()).toBe('Name is required'); + contacts.input.sendKeys('John Doe'); + expect(contacts.contactNameHeader.getText()).toBe('Awesome John Doe'); + expect(contacts.validationError.getText()).toBe(''); + }); + }); + }; + } + + describe('index.html', function () { + beforeEach(function () { + browser.get(''); + }); + + describe('app-title', appTitleTests(white, 'Miss Marple')); + + describe('contact', contactTests(lightgray, 'Miss Marple')); + + describe('crisis center', function () { + beforeEach(function () { + getCommonsSectionStruct().crisisButton.click(); + }); + + it('shows a list of crisis', function () { + const crisis = getCrisisSectionStruct(); + expect(crisis.title.getText()).toBe('Crisis List'); + expect(crisis.items.count()).toBe(4); + expect(crisis.items.get(0).getText()).toBe('1 - Dragon Burning Cities'); + }); + + it('can navigate to one crisis details', function () { + const crisis = getCrisisSectionStruct(); + crisis.items.get(0).click().then(function() { + expect(crisis.itemId.getText()).toBe('Crisis id: 1'); + return crisis.listLink.click(); + }).then(function () { + // We are back to the list + expect(crisis.items.count()).toBe(4); + }); + }); + }); + + describe('heroes', function () { + beforeEach(function () { + getCommonsSectionStruct().heroesButton.click(); + }); + + it('shows a list of heroes', function() { + const heroes = getHeroesSectionStruct(); + expect(heroes.header.getText()).toBe('Heroes of Miss Marple'); + expect(heroes.title.getText()).toBe('Hero List'); + expect(heroes.items.count()).toBe(6); + expect(heroes.items.get(0).getText()).toBe('11 - Mr. Nice'); + }); + + it('can navigate and edit one hero details', function () { + const heroes = getHeroesSectionStruct(); + heroes.items.get(0).click().then(function () { + expect(heroes.itemId.getText()).toBe('Id: 11'); + heroes.itemInput.sendKeys(' try'); + return heroes.listLink.click(); + }).then(function () { + // We are back to the list + expect(heroes.items.count()).toBe(6); + expect(heroes.items.get(0).getText()).toBe('11 - Mr. Nice try'); + }); + }); + }); + }); + + describe('index.0.html', function() { + beforeEach(function () { + browser.get('index.0.html'); + }); + + it('has a title', function () { + const title = element.all(by.tagName('h1')).get(0); + expect(title.getText()).toBe('Minimal NgModule'); + }); + }); + + describe('index.1.html', function () { + beforeEach(function () { + browser.get('index.1.html'); + }); + + describe('app-title', appTitleTests(powderblue)); + }); + + describe('index.1b.html', function () { + beforeEach(function () { + browser.get('index.1b.html'); + }); + + describe('app-title', appTitleTests(powderblue)); + + describe('contact', contactTests(powderblue)); + }); + + describe('index.2.html', function () { + beforeEach(function () { + browser.get('index.2.html'); + }); + + describe('app-title', appTitleTests(gold)); + + describe('contact', contactTests(powderblue)); + }); + + describe('index.3.html', function () { + beforeEach(function () { + browser.get('index.3.html'); + }); + + describe('app-title', appTitleTests(gold)); + }); + +}); diff --git a/aio/content/examples/ngmodule/ts/contact.1b.plnkr.json b/aio/content/examples/ngmodule/ts/contact.1b.plnkr.json new file mode 100644 index 0000000000..0f61c91f75 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/contact.1b.plnkr.json @@ -0,0 +1,25 @@ +{ + "description": "Contact NgModule v.1", + "basePath": "src/", + "files": [ + "app/app.component.1b.ts", + "app/app.module.1b.ts", + "app/highlight.directive.ts", + "app/title.component.html", + "app/title.component.ts", + "app/user.service.ts", + + "app/contact/awesome.pipe.ts", + "app/contact/contact.component.css", + "app/contact/contact.component.html", + "app/contact/contact.component.3.ts", + "app/contact/contact.service.ts", + "app/contact/highlight.directive.ts", + + "main.1b.ts", + "styles.css", + "index.1b.html" + ], + "main": "index.1b.html", + "tags": ["NgModule"] +} diff --git a/aio/content/examples/ngmodule/ts/contact.1b.plnkr.no-link.html b/aio/content/examples/ngmodule/ts/contact.1b.plnkr.no-link.html new file mode 100644 index 0000000000..ef8bffbbbe --- /dev/null +++ b/aio/content/examples/ngmodule/ts/contact.1b.plnkr.no-link.html @@ -0,0 +1,475 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/ngmodule/ts/contact.2.plnkr.json b/aio/content/examples/ngmodule/ts/contact.2.plnkr.json new file mode 100644 index 0000000000..6555b76dcd --- /dev/null +++ b/aio/content/examples/ngmodule/ts/contact.2.plnkr.json @@ -0,0 +1,27 @@ +{ + "description": "Contact NgModule v.2", + "basePath": "src/", + "files": [ + "app/app.component.2.ts", + "app/app.module.2.ts", + "app/highlight.directive.ts", + "app/title.component.html", + "app/title.component.ts", + "app/user.service.ts", + + "app/contact/contact.component.css", + "app/contact/contact.component.html", + "app/contact/contact.service.ts", + + "app/contact/awesome.pipe.ts", + "app/contact/contact.component.3.ts", + "app/contact/contact.module.2.ts", + "app/contact/highlight.directive.ts", + + "main.2.ts", + "styles.css", + "index.2.html" + ], + "main": "index.2.html", + "tags": ["NgModule"] +} diff --git a/aio/content/examples/ngmodule/ts/contact.2.plnkr.no-link.html b/aio/content/examples/ngmodule/ts/contact.2.plnkr.no-link.html new file mode 100644 index 0000000000..2fd5e40230 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/contact.2.plnkr.no-link.html @@ -0,0 +1,491 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/ngmodule/ts/example-config.json b/aio/content/examples/ngmodule/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/ngmodule/ts/minimal.0.plnkr.json b/aio/content/examples/ngmodule/ts/minimal.0.plnkr.json new file mode 100644 index 0000000000..e5e37ac4ee --- /dev/null +++ b/aio/content/examples/ngmodule/ts/minimal.0.plnkr.json @@ -0,0 +1,13 @@ +{ + "description": "Minimal NgModule", + "basePath": "src/", + "files": [ + "app/app.component.0.ts", + "app/app.module.0.ts", + "main.0.ts", + "styles.css", + "index.0.html" + ], + "main": "index.0.html", + "tags": ["NgModule"] +} diff --git a/aio/content/examples/ngmodule/ts/minimal.0.plnkr.no-link.html b/aio/content/examples/ngmodule/ts/minimal.0.plnkr.no-link.html new file mode 100644 index 0000000000..4c6469a512 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/minimal.0.plnkr.no-link.html @@ -0,0 +1,195 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/ngmodule/ts/plnkr.json b/aio/content/examples/ngmodule/ts/plnkr.json new file mode 100644 index 0000000000..4d9d2ec20b --- /dev/null +++ b/aio/content/examples/ngmodule/ts/plnkr.json @@ -0,0 +1,41 @@ +{ + "description": "NgModule Final", + "basePath": "src/", + "files": [ + "app/app.component.ts", + "app/app.module.ts", + "app/app-routing.module.ts", + + "app/contact/contact.component.css", + "app/contact/contact.component.html", + "app/contact/contact.service.ts", + + "app/contact/contact.component.ts", + "app/contact/contact.module.ts", + "app/contact/contact-routing.module.ts", + + "app/crisis/*.ts", + + "app/hero/hero-detail.component.ts", + "app/hero/hero-list.component.ts", + "app/hero/hero.service.ts", + + "app/hero/hero.component.ts", + "app/hero/hero.module.ts", + "app/hero/hero-routing.module.ts", + + "app/core/*.css", + "app/core/*.html", + "app/core/*.ts", + + "app/shared/*.css", + "app/shared/*.html", + "app/shared/*.ts", + + "main.ts", + "styles.css", + "index.html" + ], + "main": "index.html", + "tags": ["NgModule"] +} diff --git a/aio/content/examples/ngmodule/ts/plnkr.no-link.html b/aio/content/examples/ngmodule/ts/plnkr.no-link.html new file mode 100644 index 0000000000..4364d1e589 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/plnkr.no-link.html @@ -0,0 +1,909 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/ngmodule/ts/pre-shared.3.plnkr.json b/aio/content/examples/ngmodule/ts/pre-shared.3.plnkr.json new file mode 100644 index 0000000000..9747801604 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/pre-shared.3.plnkr.json @@ -0,0 +1,41 @@ +{ + "description": "NgModule v.3", + "basePath": "src/", + "files": [ + "app/app.component.3.ts", + "app/app.module.3.ts", + "app/app-routing.module.3.ts", + + "app/highlight.directive.ts", + "app/title.component.html", + "app/title.component.ts", + "app/user.service.ts", + + "app/contact/contact.component.css", + "app/contact/contact.component.html", + "app/contact/contact.service.ts", + + "app/contact/awesome.pipe.ts", + "app/contact/contact.component.3.ts", + "app/contact/contact.module.3.ts", + "app/contact/contact-routing.module.3.ts", + "app/contact/highlight.directive.ts", + + "app/crisis/*.ts", + + "app/hero/hero-detail.component.ts", + "app/hero/hero-list.component.ts", + "app/hero/hero.service.ts", + + "app/hero/hero.component.3.ts", + "app/hero/hero.module.3.ts", + "app/hero/hero-routing.module.3.ts", + "app/hero/highlight.directive.ts", + + "main.3.ts", + "styles.css", + "index.3.html" + ], + "main": "index.3.html", + "tags": ["NgModule"] +} diff --git a/aio/content/examples/ngmodule/ts/pre-shared.3.plnkr.no-link.html b/aio/content/examples/ngmodule/ts/pre-shared.3.plnkr.no-link.html new file mode 100644 index 0000000000..458da3fb33 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/pre-shared.3.plnkr.no-link.html @@ -0,0 +1,861 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/ngmodule/ts/src/app/app-routing.module.3.ts b/aio/content/examples/ngmodule/ts/src/app/app-routing.module.3.ts new file mode 100644 index 0000000000..1d53b708f8 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/app-routing.module.3.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +export const routes: Routes = [ + { path: '', redirectTo: 'contact', pathMatch: 'full'}, + { path: 'crisis', loadChildren: 'app/crisis/crisis.module#CrisisModule' }, + { path: 'heroes', loadChildren: 'app/hero/hero.module.3#HeroModule' } +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +export class AppRoutingModule {} diff --git a/aio/content/examples/ngmodule/ts/src/app/app-routing.module.ts b/aio/content/examples/ngmodule/ts/src/app/app-routing.module.ts new file mode 100644 index 0000000000..c753dcd488 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/app-routing.module.ts @@ -0,0 +1,19 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +export const routes: Routes = [ + { path: '', redirectTo: 'contact', pathMatch: 'full'}, +// #docregion lazy-routes + { path: 'crisis', loadChildren: 'app/crisis/crisis.module#CrisisModule' }, + { path: 'heroes', loadChildren: 'app/hero/hero.module#HeroModule' } +// #enddocregion lazy-routes +]; + +// #docregion forRoot +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +export class AppRoutingModule {} +// #enddocregion forRoot diff --git a/aio/content/examples/ngmodule/ts/src/app/app.component.0.ts b/aio/content/examples/ngmodule/ts/src/app/app.component.0.ts new file mode 100644 index 0000000000..4977890c3b --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/app.component.0.ts @@ -0,0 +1,10 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: '

    {{title}}

    ', +}) +export class AppComponent { + title = 'Minimal NgModule'; +} diff --git a/aio/content/examples/ngmodule/ts/src/app/app.component.1.ts b/aio/content/examples/ngmodule/ts/src/app/app.component.1.ts new file mode 100644 index 0000000000..ccf44d4416 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/app.component.1.ts @@ -0,0 +1,19 @@ +// #docplaster +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', +// #enddocregion + /* + // #docregion template + template: '

    {{title}}

    ' + // #enddocregion template + */ +// #docregion + template: '' +}) +export class AppComponent { + subtitle = '(v1)'; +} +// #enddocregion diff --git a/aio/content/examples/ngmodule/ts/src/app/app.component.1b.ts b/aio/content/examples/ngmodule/ts/src/app/app.component.1b.ts new file mode 100644 index 0000000000..291bf0ac6b --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/app.component.1b.ts @@ -0,0 +1,15 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + // #docregion template + template: ` + + + ` + // #enddocregion template +}) +export class AppComponent { + subtitle = '(v1)'; +} diff --git a/aio/content/examples/ngmodule/ts/src/app/app.component.2.ts b/aio/content/examples/ngmodule/ts/src/app/app.component.2.ts new file mode 100644 index 0000000000..a68b7d337d --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/app.component.2.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: ` + + + ` +}) +export class AppComponent { + subtitle = '(v2)'; +} diff --git a/aio/content/examples/ngmodule/ts/src/app/app.component.3.ts b/aio/content/examples/ngmodule/ts/src/app/app.component.3.ts new file mode 100644 index 0000000000..6d69a56f70 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/app.component.3.ts @@ -0,0 +1,19 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + // #docregion template + template: ` + + + + ` + // #enddocregion template +}) +export class AppComponent { + subtitle = '(v3)'; +} diff --git a/aio/content/examples/ngmodule/ts/src/app/app.component.ts b/aio/content/examples/ngmodule/ts/src/app/app.component.ts new file mode 100644 index 0000000000..67336c8b08 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/app.component.ts @@ -0,0 +1,19 @@ +// #docplaster +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: ` + + + + ` +}) +export class AppComponent { + subtitle = '(Final)'; +} diff --git a/aio/content/examples/ngmodule/ts/src/app/app.module.0.ts b/aio/content/examples/ngmodule/ts/src/app/app.module.0.ts new file mode 100644 index 0000000000..144ad7bb50 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/app.module.0.ts @@ -0,0 +1,23 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import +// #enddocregion + { AppComponent } from './app.component.0'; +/* +// #docregion + { AppComponent } from './app.component'; +// #enddocregion +*/ +// #docregion + +@NgModule({ +// #docregion imports + imports: [ BrowserModule ], +// #enddocregion imports + declarations: [ AppComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/ngmodule/ts/src/app/app.module.1.ts b/aio/content/examples/ngmodule/ts/src/app/app.module.1.ts new file mode 100644 index 0000000000..f9f50e18d0 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/app.module.1.ts @@ -0,0 +1,54 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import +// #enddocregion + { AppComponent } from './app.component.1'; +/* +// #docregion + { AppComponent } from './app.component'; +// #enddocregion +*/ +// #docregion +import { HighlightDirective } from './highlight.directive'; +import { TitleComponent } from './title.component'; +import { UserService } from './user.service'; + +/* Contact Related Imports */ +import { FormsModule } from '@angular/forms'; + +import { AwesomePipe } from './contact/awesome.pipe'; +import { ContactComponent } from './contact/contact.component.3'; + +// #docregion import-contact-directive +import { + HighlightDirective as ContactHighlightDirective +} from './contact/highlight.directive'; +// #enddocregion import-contact-directive + +@NgModule({ +// #docregion imports + imports: [ BrowserModule, FormsModule ], +// #enddocregion imports +// #docregion declarations, directive, component + declarations: [ + AppComponent, + HighlightDirective, +// #enddocregion directive + TitleComponent, +// #enddocregion component + + AwesomePipe, + ContactComponent, + ContactHighlightDirective +// #docregion directive, component + ], +// #enddocregion declarations, directive, component +// #docregion providers + providers: [ UserService ], +// #enddocregion providers + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/ngmodule/ts/src/app/app.module.1b.ts b/aio/content/examples/ngmodule/ts/src/app/app.module.1b.ts new file mode 100644 index 0000000000..ae04326239 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/app.module.1b.ts @@ -0,0 +1,53 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +/* App Root */ +import +// #enddocregion + { AppComponent } from './app.component.1b'; +/* +// #docregion + { AppComponent } from './app.component'; +// #enddocregion +*/ +// #docregion +import { HighlightDirective } from './highlight.directive'; +import { TitleComponent } from './title.component'; +import { UserService } from './user.service'; + +/* Contact Imports */ +import +// #enddocregion + { ContactComponent } from './contact/contact.component.3'; +/* +// #docregion + { ContactComponent } from './contact/contact.component'; +// #enddocregion +*/ +// #docregion +import { ContactService } from './contact/contact.service'; +import { AwesomePipe } from './contact/awesome.pipe'; + +// #docregion import-alias +import { + HighlightDirective as ContactHighlightDirective +} from './contact/highlight.directive'; +// #enddocregion import-alias + +import { FormsModule } from '@angular/forms'; + +@NgModule({ + imports: [ BrowserModule, FormsModule ], +// #docregion declarations + declarations: [ + AppComponent, HighlightDirective, TitleComponent, + AwesomePipe, ContactComponent, ContactHighlightDirective + ], +// #docregion providers + providers: [ ContactService, UserService ], +// #enddocregion providers + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/ngmodule/ts/src/app/app.module.2.ts b/aio/content/examples/ngmodule/ts/src/app/app.module.2.ts new file mode 100644 index 0000000000..f00e9b5d27 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/app.module.2.ts @@ -0,0 +1,37 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +/* App Root */ +import +// #enddocregion + { AppComponent } from './app.component.2'; +/* +// #docregion + { AppComponent } from './app.component'; +// #enddocregion +*/ +// #docregion +import { HighlightDirective } from './highlight.directive'; +import { TitleComponent } from './title.component'; +import { UserService } from './user.service'; + +/* Contact Imports */ +import +// #enddocregion + { ContactModule } from './contact/contact.module.2'; +/* +// #docregion + { ContactModule } from './contact/contact.module'; +// #enddocregion +*/ +// #docregion + +@NgModule({ + imports: [ BrowserModule, ContactModule ], + declarations: [ AppComponent, HighlightDirective, TitleComponent ], + providers: [ UserService ], + bootstrap: [ AppComponent ], +}) +export class AppModule { } diff --git a/aio/content/examples/ngmodule/ts/src/app/app.module.3.ts b/aio/content/examples/ngmodule/ts/src/app/app.module.3.ts new file mode 100644 index 0000000000..8ca0a46d9a --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/app.module.3.ts @@ -0,0 +1,30 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +/* App Root */ +import { AppComponent } from './app.component.3'; +import { HighlightDirective } from './highlight.directive'; +import { TitleComponent } from './title.component'; +import { UserService } from './user.service'; + +/* Feature Modules */ +import { ContactModule } from './contact/contact.module.3'; + +/* Routing Module */ +import { AppRoutingModule } from './app-routing.module.3'; + +@NgModule({ +// #docregion imports + imports: [ + BrowserModule, + ContactModule, + AppRoutingModule + ], +// #enddocregion imports + providers: [ UserService ], + declarations: [ AppComponent, HighlightDirective, TitleComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/ngmodule/ts/src/app/app.module.ts b/aio/content/examples/ngmodule/ts/src/app/app.module.ts new file mode 100644 index 0000000000..da03420804 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/app.module.ts @@ -0,0 +1,40 @@ +// #docplaster +// #docregion +// #docregion v4 +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +/* App Root */ +import { AppComponent } from './app.component'; + +/* Feature Modules */ +import { ContactModule } from './contact/contact.module'; +import { CoreModule } from './core/core.module'; + +/* Routing Module */ +import { AppRoutingModule } from './app-routing.module'; + +@NgModule({ + // #docregion import-for-root + imports: [ + BrowserModule, + ContactModule, +// #enddocregion v4 +// #enddocregion import-for-root +/* +// #docregion v4 + CoreModule, +// #enddocregion v4 +*/ +// #docregion import-for-root + CoreModule.forRoot({userName: 'Miss Marple'}), +// #docregion v4 + AppRoutingModule + ], + // #enddocregion import-for-root + declarations: [ AppComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } +// #enddocregion v4 +// #enddocregion diff --git a/aio/content/examples/ngmodule/ts/src/app/contact/awesome.pipe.ts b/aio/content/examples/ngmodule/ts/src/app/contact/awesome.pipe.ts new file mode 100644 index 0000000000..d6dce99901 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/contact/awesome.pipe.ts @@ -0,0 +1,10 @@ +// #docregion +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ name: 'awesome' }) +/** Precede the input string with the word "Awesome " */ +export class AwesomePipe implements PipeTransform { + transform(phrase: string) { + return phrase ? 'Awesome ' + phrase : ''; + } +} diff --git a/aio/content/examples/ngmodule/ts/src/app/contact/contact-routing.module.3.ts b/aio/content/examples/ngmodule/ts/src/app/contact/contact-routing.module.3.ts new file mode 100644 index 0000000000..27dfc232b7 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/contact/contact-routing.module.3.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { ContactComponent } from './contact.component.3'; + +@NgModule({ + imports: [RouterModule.forChild([ + { path: 'contact', component: ContactComponent} + ])], + exports: [RouterModule] +}) +export class ContactRoutingModule {} diff --git a/aio/content/examples/ngmodule/ts/src/app/contact/contact-routing.module.ts b/aio/content/examples/ngmodule/ts/src/app/contact/contact-routing.module.ts new file mode 100644 index 0000000000..2fa81af5a9 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/contact/contact-routing.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { ContactComponent } from './contact.component'; + +// #docregion routing +@NgModule({ + imports: [RouterModule.forChild([ + { path: 'contact', component: ContactComponent } + ])], + exports: [RouterModule] +}) +export class ContactRoutingModule {} +// #enddocregion diff --git a/aio/content/examples/ngmodule/ts/src/app/contact/contact.component.3.ts b/aio/content/examples/ngmodule/ts/src/app/contact/contact.component.3.ts new file mode 100644 index 0000000000..fcc409b9af --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/contact/contact.component.3.ts @@ -0,0 +1,54 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; + +import { Contact, ContactService } from './contact.service'; +import { UserService } from '../user.service'; + +@Component({ + moduleId: module.id, + selector: 'app-contact', + templateUrl: './contact.component.html', + styleUrls: [ './contact.component.css' ] +}) +export class ContactComponent implements OnInit { + contact: Contact; + contacts: Contact[]; + + msg = 'Loading contacts ...'; + userName = ''; + + constructor(private contactService: ContactService, userService: UserService) { + this.userName = userService.userName; + } + + ngOnInit() { + this.contactService.getContacts().then(contacts => { + this.msg = ''; + this.contacts = contacts; + this.contact = contacts[0]; + }); + } + + next() { + let ix = 1 + this.contacts.indexOf(this.contact); + if (ix >= this.contacts.length) { ix = 0; } + this.contact = this.contacts[ix]; + } + + onSubmit() { + // POST-DEMO TODO: do something like save it + this.displayMessage('Saved ' + this.contact.name); + } + + newContact() { + this.displayMessage('New contact'); + this.contact = {id: 42, name: ''}; + this.contacts.push(this.contact); + } + + /** Display a message briefly, then remove it. */ + displayMessage(msg: string) { + this.msg = msg; + setTimeout(() => this.msg = '', 1500); + } +} diff --git a/aio/content/examples/ngmodule/ts/src/app/contact/contact.component.css b/aio/content/examples/ngmodule/ts/src/app/contact/contact.component.css new file mode 100644 index 0000000000..45e8f6e76d --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/contact/contact.component.css @@ -0,0 +1,29 @@ +/* #docregion */ +.ng-valid[required] { + border-left: 5px solid #42A948; /* green */ +} + +.ng-invalid { + border-left: 5px solid #a94442; /* red */ +} + +.alert { + padding: 15px; + margin: 8px 0; + border: 1px solid transparent; + border-radius: 4px; +} +.alert-danger { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} + +.msg { + color: blue; + background-color: whitesmoke; + border: 1px solid transparent; + border-radius: 4px; + margin-bottom: 20px; +} + diff --git a/aio/content/examples/ngmodule/ts/src/app/contact/contact.component.html b/aio/content/examples/ngmodule/ts/src/app/contact/contact.component.html new file mode 100644 index 0000000000..483480571e --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/contact/contact.component.html @@ -0,0 +1,23 @@ + +

    Contact of {{userName}}

    +
    {{msg}}
    + +
    + +

    {{ contact.name | awesome }}

    + +
    + + +
    + Name is required +
    +
    +
    + + + +
    + diff --git a/aio/content/examples/ngmodule/ts/src/app/contact/contact.component.ts b/aio/content/examples/ngmodule/ts/src/app/contact/contact.component.ts new file mode 100644 index 0000000000..f9116a4213 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/contact/contact.component.ts @@ -0,0 +1,55 @@ +// Exact copy except import UserService from core +// #docregion +import { Component, OnInit } from '@angular/core'; + +import { Contact, ContactService } from './contact.service'; +import { UserService } from '../core/user.service'; + +@Component({ + moduleId: module.id, + selector: 'app-contact', + templateUrl: './contact.component.html', + styleUrls: [ './contact.component.css' ] +}) +export class ContactComponent implements OnInit { + contact: Contact; + contacts: Contact[]; + + msg = 'Loading contacts ...'; + userName = ''; + + constructor(private contactService: ContactService, userService: UserService) { + this.userName = userService.userName; + } + + ngOnInit() { + this.contactService.getContacts().then(contacts => { + this.msg = ''; + this.contacts = contacts; + this.contact = contacts[0]; + }); + } + + next() { + let ix = 1 + this.contacts.indexOf(this.contact); + if (ix >= this.contacts.length) { ix = 0; } + this.contact = this.contacts[ix]; + } + + onSubmit() { + // POST-DEMO TODO: do something like save it + this.displayMessage('Saved ' + this.contact.name); + } + + newContact() { + this.displayMessage('New contact'); + this.contact = {id: 42, name: ''}; + this.contacts.push(this.contact); + } + + /** Display a message briefly, then remove it. */ + displayMessage(msg: string) { + this.msg = msg; + setTimeout(() => this.msg = '', 1500); + } +} diff --git a/aio/content/examples/ngmodule/ts/src/app/contact/contact.module.2.ts b/aio/content/examples/ngmodule/ts/src/app/contact/contact.module.2.ts new file mode 100644 index 0000000000..f347bd3b51 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/contact/contact.module.2.ts @@ -0,0 +1,30 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { AwesomePipe } from './awesome.pipe'; + +import +// #enddocregion + { ContactComponent } from './contact.component.3'; +/* +// #docregion + { ContactComponent } from './contact.component'; +// #enddocregion +*/ +// #docregion +import { ContactService } from './contact.service'; +import { HighlightDirective } from './highlight.directive'; + +// #docregion class +@NgModule({ + imports: [ CommonModule, FormsModule ], + declarations: [ ContactComponent, HighlightDirective, AwesomePipe ], + exports: [ ContactComponent ], + providers: [ ContactService ] +}) +export class ContactModule { } +// #enddocregion class +// #enddocregion diff --git a/aio/content/examples/ngmodule/ts/src/app/contact/contact.module.3.ts b/aio/content/examples/ngmodule/ts/src/app/contact/contact.module.3.ts new file mode 100644 index 0000000000..ff70721b2f --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/contact/contact.module.3.ts @@ -0,0 +1,22 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { AwesomePipe } from './awesome.pipe'; + +import { ContactComponent } from './contact.component.3'; +import { ContactService } from './contact.service'; +import { HighlightDirective } from './highlight.directive'; + +import { ContactRoutingModule } from './contact-routing.module.3'; + +// #docregion class +@NgModule({ + imports: [ CommonModule, FormsModule, ContactRoutingModule ], + declarations: [ ContactComponent, HighlightDirective, AwesomePipe ], + providers: [ ContactService ] +}) +export class ContactModule { } +// #enddocregion class +// #enddocregion diff --git a/aio/content/examples/ngmodule/ts/src/app/contact/contact.module.ts b/aio/content/examples/ngmodule/ts/src/app/contact/contact.module.ts new file mode 100644 index 0000000000..9456de7654 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/contact/contact.module.ts @@ -0,0 +1,16 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { SharedModule } from '../shared/shared.module'; + +import { ContactComponent } from './contact.component'; +import { ContactService } from './contact.service'; +import { ContactRoutingModule } from './contact-routing.module'; + +// #docregion class +@NgModule({ + imports: [ SharedModule, ContactRoutingModule ], + declarations: [ ContactComponent ], + providers: [ ContactService ] +}) +export class ContactModule { } +// #enddocregion class diff --git a/aio/content/examples/ngmodule/ts/src/app/contact/contact.service.ts b/aio/content/examples/ngmodule/ts/src/app/contact/contact.service.ts new file mode 100644 index 0000000000..28b18bd84a --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/contact/contact.service.ts @@ -0,0 +1,29 @@ +// #docregion +import { Injectable } from '@angular/core'; + +export class Contact { + constructor(public id: number, public name: string) { } +} + +const CONTACTS: Contact[] = [ + new Contact(21, 'Sam Spade'), + new Contact(22, 'Nick Danger'), + new Contact(23, 'Nancy Drew') +]; + +const FETCH_LATENCY = 500; + +@Injectable() +export class ContactService { + + getContacts() { + return new Promise(resolve => { + setTimeout(() => { resolve(CONTACTS); }, FETCH_LATENCY); + }); + } + + getContact(id: number | string) { + return this.getContacts() + .then(heroes => heroes.find(hero => hero.id === +id)); + } +} diff --git a/aio/content/examples/ngmodule/ts/src/app/contact/highlight.directive.ts b/aio/content/examples/ngmodule/ts/src/app/contact/highlight.directive.ts new file mode 100644 index 0000000000..64338b1377 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/contact/highlight.directive.ts @@ -0,0 +1,18 @@ +/* tslint:disable */ +// Same directive name and selector as +// HighlightDirective in parent AppModule +// It selects for both input boxes and 'highlight' attr +// and it highlights in blue instead of gold + +// #docregion +import { Directive, ElementRef } from '@angular/core'; + +@Directive({ selector: '[highlight], input' }) +/** Highlight the attached element or an InputElement in blue */ +export class HighlightDirective { + constructor(el: ElementRef) { + el.nativeElement.style.backgroundColor = 'powderblue'; + console.log( + `* Contact highlight called for ${el.nativeElement.tagName}`); + } +} diff --git a/aio/content/examples/ngmodule/ts/src/app/core/core.module.ts b/aio/content/examples/ngmodule/ts/src/app/core/core.module.ts new file mode 100644 index 0000000000..27cb9a2193 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/core/core.module.ts @@ -0,0 +1,48 @@ +/* tslint:disable:member-ordering no-unused-variable */ +// #docplaster +// #docregion +// #docregion v4 +import { + ModuleWithProviders, NgModule, + Optional, SkipSelf } from '@angular/core'; + +import { CommonModule } from '@angular/common'; + +import { TitleComponent } from './title.component'; +import { UserService } from './user.service'; +// #enddocregion +import { UserServiceConfig } from './user.service'; + +// #docregion v4 +@NgModule({ + imports: [ CommonModule ], + declarations: [ TitleComponent ], + exports: [ TitleComponent ], + providers: [ UserService ] +}) +export class CoreModule { +// #enddocregion v4 + + // #docregion ctor + constructor (@Optional() @SkipSelf() parentModule: CoreModule) { + if (parentModule) { + throw new Error( + 'CoreModule is already loaded. Import it in the AppModule only'); + } + } + // #enddocregion ctor + + // #docregion for-root + static forRoot(config: UserServiceConfig): ModuleWithProviders { + return { + ngModule: CoreModule, + providers: [ + {provide: UserServiceConfig, useValue: config } + ] + }; + } + // #enddocregion for-root +// #docregion v4 +} +// #enddocregion v4 +// #enddocregion diff --git a/aio/content/examples/ngmodule/ts/src/app/core/title.component.html b/aio/content/examples/ngmodule/ts/src/app/core/title.component.html new file mode 100644 index 0000000000..8ebd08ae43 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/core/title.component.html @@ -0,0 +1,6 @@ + +

    {{title}} {{subtitle}}

    +

    + Welcome, {{user}} +

    + diff --git a/aio/content/examples/ngmodule/ts/src/app/core/title.component.ts b/aio/content/examples/ngmodule/ts/src/app/core/title.component.ts new file mode 100644 index 0000000000..5fbd64d5fd --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/core/title.component.ts @@ -0,0 +1,18 @@ +// Exact copy of app/title.component.ts except import UserService from shared +import { Component, Input } from '@angular/core'; +import { UserService } from '../core/user.service'; + +@Component({ + moduleId: module.id, + selector: 'app-title', + templateUrl: './title.component.html', +}) +export class TitleComponent { + @Input() subtitle = ''; + title = 'Angular Modules'; + user = ''; + + constructor(userService: UserService) { + this.user = userService.userName; + } +} diff --git a/aio/content/examples/ngmodule/ts/src/app/core/user.service.ts b/aio/content/examples/ngmodule/ts/src/app/core/user.service.ts new file mode 100644 index 0000000000..8fe839075e --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/core/user.service.ts @@ -0,0 +1,32 @@ +// Crazy copy of the app/user.service +// Proves that UserService is an app-wide singleton and only instantiated once +// IFF shared.module follows the `forRoot` pattern +// +// If it didn't, a new instance of UserService would be created +// after each lazy load and the userName would double up. + +import { Injectable, Optional } from '@angular/core'; + +let nextId = 1; + +export class UserServiceConfig { + userName = 'Philip Marlowe'; +} + +@Injectable() +export class UserService { + id = nextId++; + private _userName = 'Sherlock Holmes'; + + // #docregion ctor + constructor(@Optional() config: UserServiceConfig) { + if (config) { this._userName = config.userName; } + } + // #enddocregion ctor + + get userName() { + // Demo: add a suffix if this service has been created more than once + const suffix = this.id > 1 ? ` times ${this.id}` : ''; + return this._userName + suffix; + } +} diff --git a/aio/content/examples/ngmodule/ts/src/app/crisis/crisis-detail.component.ts b/aio/content/examples/ngmodule/ts/src/app/crisis/crisis-detail.component.ts new file mode 100644 index 0000000000..9749029d62 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/crisis/crisis-detail.component.ts @@ -0,0 +1,19 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +@Component({ + template: ` +

    Crisis Detail

    +
    Crisis id: {{id}}
    +
    + Crisis List + ` +}) +export class CrisisDetailComponent implements OnInit { + id: number; + constructor(private route: ActivatedRoute) { } + + ngOnInit() { + this.id = parseInt(this.route.snapshot.params['id'], 10); + } +} diff --git a/aio/content/examples/ngmodule/ts/src/app/crisis/crisis-list.component.ts b/aio/content/examples/ngmodule/ts/src/app/crisis/crisis-list.component.ts new file mode 100644 index 0000000000..ae459cdf1b --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/crisis/crisis-list.component.ts @@ -0,0 +1,22 @@ +import { Component, OnInit } from '@angular/core'; + +import { Crisis, + CrisisService } from './crisis.service'; + +@Component({ + template: ` +

    Crisis List

    + + ` +}) +export class CrisisListComponent implements OnInit { + crisises: Promise; + + constructor(private crisisService: CrisisService) { } + + ngOnInit() { + this.crisises = this.crisisService.getCrises(); + } +} diff --git a/aio/content/examples/ngmodule/ts/src/app/crisis/crisis-routing.module.ts b/aio/content/examples/ngmodule/ts/src/app/crisis/crisis-routing.module.ts new file mode 100644 index 0000000000..c60efa8cb4 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/crisis/crisis-routing.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { Routes, + RouterModule } from '@angular/router'; + +import { CrisisListComponent } from './crisis-list.component'; +import { CrisisDetailComponent } from './crisis-detail.component'; + +const routes: Routes = [ + { path: '', redirectTo: 'list', pathMatch: 'full'}, + { path: 'list', component: CrisisListComponent }, + { path: ':id', component: CrisisDetailComponent } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class CrisisRoutingModule {} diff --git a/aio/content/examples/ngmodule/ts/src/app/crisis/crisis.module.ts b/aio/content/examples/ngmodule/ts/src/app/crisis/crisis.module.ts new file mode 100644 index 0000000000..f557bd6423 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/crisis/crisis.module.ts @@ -0,0 +1,14 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { CrisisListComponent } from './crisis-list.component'; +import { CrisisDetailComponent } from './crisis-detail.component'; +import { CrisisService } from './crisis.service'; +import { CrisisRoutingModule } from './crisis-routing.module'; + +@NgModule({ + imports: [ CommonModule, CrisisRoutingModule ], + declarations: [ CrisisDetailComponent, CrisisListComponent ], + providers: [ CrisisService ] +}) +export class CrisisModule {} diff --git a/aio/content/examples/ngmodule/ts/src/app/crisis/crisis.service.ts b/aio/content/examples/ngmodule/ts/src/app/crisis/crisis.service.ts new file mode 100644 index 0000000000..419ee19b36 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/crisis/crisis.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; + +export class Crisis { + constructor(public id: number, public name: string) { } +} + +const CRISES: Crisis[] = [ + new Crisis(1, 'Dragon Burning Cities'), + new Crisis(2, 'Sky Rains Great White Sharks'), + new Crisis(3, 'Giant Asteroid Heading For Earth'), + new Crisis(4, 'Procrastinators Meeting Delayed Again'), +]; + +const FETCH_LATENCY = 500; + +@Injectable() +export class CrisisService { + + getCrises() { + return new Promise(resolve => { + setTimeout(() => { resolve(CRISES); }, FETCH_LATENCY); + }); + } + + getCrisis(id: number | string) { + return this.getCrises() + .then(heroes => heroes.find(hero => hero.id === +id)); + } + +} diff --git a/aio/content/examples/ngmodule/ts/src/app/hero/hero-detail.component.ts b/aio/content/examples/ngmodule/ts/src/app/hero/hero-detail.component.ts new file mode 100644 index 0000000000..1478ad350c --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/hero/hero-detail.component.ts @@ -0,0 +1,31 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +import { Hero, + HeroService } from './hero.service'; + +@Component({ + template: ` +

    Hero Detail

    +
    +
    Id: {{hero.id}}

    + +
    +
    + Hero List + ` +}) +export class HeroDetailComponent implements OnInit { + hero: Hero; + + constructor( + private route: ActivatedRoute, + private heroService: HeroService) { } + + ngOnInit() { + let id = parseInt(this.route.snapshot.params['id'], 10); + this.heroService.getHero(id).then(hero => this.hero = hero); + } +} diff --git a/aio/content/examples/ngmodule/ts/src/app/hero/hero-list.component.ts b/aio/content/examples/ngmodule/ts/src/app/hero/hero-list.component.ts new file mode 100644 index 0000000000..5a4e9ef0c4 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/hero/hero-list.component.ts @@ -0,0 +1,21 @@ +import { Component, OnInit } from '@angular/core'; + +import { Hero, + HeroService } from './hero.service'; + +@Component({ + template: ` +

    Hero List

    + + ` +}) +export class HeroListComponent implements OnInit { + heroes: Promise; + constructor(private heroService: HeroService) { } + + ngOnInit() { + this.heroes = this.heroService.getHeroes(); + } +} diff --git a/aio/content/examples/ngmodule/ts/src/app/hero/hero-routing.module.3.ts b/aio/content/examples/ngmodule/ts/src/app/hero/hero-routing.module.3.ts new file mode 100644 index 0000000000..588ffd94be --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/hero/hero-routing.module.3.ts @@ -0,0 +1,23 @@ +import { NgModule } from '@angular/core'; +import { Routes, + RouterModule } from '@angular/router'; + +import { HeroComponent } from './hero.component.3'; +import { HeroListComponent } from './hero-list.component'; +import { HeroDetailComponent } from './hero-detail.component'; + +const routes: Routes = [ + { path: '', + component: HeroComponent, + children: [ + { path: '', component: HeroListComponent }, + { path: ':id', component: HeroDetailComponent } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class HeroRoutingModule {} diff --git a/aio/content/examples/ngmodule/ts/src/app/hero/hero-routing.module.ts b/aio/content/examples/ngmodule/ts/src/app/hero/hero-routing.module.ts new file mode 100644 index 0000000000..d97aab3beb --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/hero/hero-routing.module.ts @@ -0,0 +1,23 @@ +import { NgModule } from '@angular/core'; +import { Routes, + RouterModule } from '@angular/router'; + +import { HeroComponent } from './hero.component'; +import { HeroListComponent } from './hero-list.component'; +import { HeroDetailComponent } from './hero-detail.component'; + +const routes: Routes = [ + { path: '', + component: HeroComponent, + children: [ + { path: '', component: HeroListComponent }, + { path: ':id', component: HeroDetailComponent } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class HeroRoutingModule {} diff --git a/aio/content/examples/ngmodule/ts/src/app/hero/hero.component.3.ts b/aio/content/examples/ngmodule/ts/src/app/hero/hero.component.3.ts new file mode 100644 index 0000000000..d52bc253df --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/hero/hero.component.3.ts @@ -0,0 +1,18 @@ +import { Component } from '@angular/core'; + +import { HeroService } from './hero.service'; +import { UserService } from '../user.service'; + +@Component({ + template: ` +

    Heroes of {{userName}}

    + + `, + providers: [ HeroService ] +}) +export class HeroComponent { + userName = ''; + constructor(userService: UserService) { + this.userName = userService.userName; + } +} diff --git a/aio/content/examples/ngmodule/ts/src/app/hero/hero.component.ts b/aio/content/examples/ngmodule/ts/src/app/hero/hero.component.ts new file mode 100644 index 0000000000..86338fb0ae --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/hero/hero.component.ts @@ -0,0 +1,19 @@ +// Exact copy except import UserService from core +import { Component } from '@angular/core'; + +import { HeroService } from './hero.service'; +import { UserService } from '../core/user.service'; + +@Component({ + template: ` +

    Heroes of {{userName}}

    + + `, + providers: [ HeroService ] +}) +export class HeroComponent { + userName = ''; + constructor(userService: UserService) { + this.userName = userService.userName; + } +} diff --git a/aio/content/examples/ngmodule/ts/src/app/hero/hero.module.3.ts b/aio/content/examples/ngmodule/ts/src/app/hero/hero.module.3.ts new file mode 100644 index 0000000000..c00f4eedd5 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/hero/hero.module.3.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { HeroComponent } from './hero.component.3'; +import { HeroDetailComponent } from './hero-detail.component'; +import { HeroListComponent } from './hero-list.component'; +import { HighlightDirective } from './highlight.directive'; +import { HeroRoutingModule } from './hero-routing.module.3'; + +// #docregion class +@NgModule({ + imports: [ CommonModule, FormsModule, HeroRoutingModule ], + declarations: [ + HeroComponent, HeroDetailComponent, HeroListComponent, + HighlightDirective + ] +}) +export class HeroModule { } +// #enddocregion class diff --git a/aio/content/examples/ngmodule/ts/src/app/hero/hero.module.ts b/aio/content/examples/ngmodule/ts/src/app/hero/hero.module.ts new file mode 100644 index 0000000000..98d7b76b00 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/hero/hero.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; + +import { SharedModule } from '../shared/shared.module'; + +import { HeroComponent } from './hero.component'; +import { HeroDetailComponent } from './hero-detail.component'; +import { HeroListComponent } from './hero-list.component'; +import { HeroRoutingModule } from './hero-routing.module'; + +@NgModule({ + imports: [ SharedModule, HeroRoutingModule ], + declarations: [ + HeroComponent, HeroDetailComponent, HeroListComponent, + ] +}) +export class HeroModule { } diff --git a/aio/content/examples/ngmodule/ts/src/app/hero/hero.service.ts b/aio/content/examples/ngmodule/ts/src/app/hero/hero.service.ts new file mode 100644 index 0000000000..bb7ff5fa5c --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/hero/hero.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; + +export class Hero { + constructor(public id: number, public name: string) { } +} + +const HEROES: Hero[] = [ + new Hero(11, 'Mr. Nice'), + new Hero(12, 'Narco'), + new Hero(13, 'Bombasto'), + new Hero(14, 'Celeritas'), + new Hero(15, 'Magneta'), + new Hero(16, 'RubberMan') +]; + +const FETCH_LATENCY = 500; + +@Injectable() +export class HeroService { + + getHeroes() { + return new Promise(resolve => { + setTimeout(() => { resolve(HEROES); }, FETCH_LATENCY); + }); + } + + getHero(id: number | string) { + return this.getHeroes() + .then(heroes => heroes.find(hero => hero.id === +id)); + } + +} diff --git a/aio/content/examples/ngmodule/ts/src/app/hero/highlight.directive.ts b/aio/content/examples/ngmodule/ts/src/app/hero/highlight.directive.ts new file mode 100644 index 0000000000..d7e39afd05 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/hero/highlight.directive.ts @@ -0,0 +1,14 @@ +// #docregion +import { Directive, ElementRef } from '@angular/core'; + +// Same directive name and selector as +// HighlightDirective in parent AppRootModule +// It selects for both input boxes and 'highlight' attr +// and it highlights in beige instead of yellow +@Directive({ selector: '[highlight]' }) +export class HighlightDirective { + constructor(el: ElementRef) { + el.nativeElement.style.backgroundColor = 'beige'; + console.log(`* Hero highlight called for ${el.nativeElement.tagName}`); + } +} diff --git a/aio/content/examples/ngmodule/ts/src/app/highlight.directive.ts b/aio/content/examples/ngmodule/ts/src/app/highlight.directive.ts new file mode 100644 index 0000000000..df67a3ae89 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/highlight.directive.ts @@ -0,0 +1,12 @@ +// #docregion +import { Directive, ElementRef } from '@angular/core'; + +@Directive({ selector: '[highlight]' }) +/** Highlight the attached element in gold */ +export class HighlightDirective { + constructor(el: ElementRef) { + el.nativeElement.style.backgroundColor = 'gold'; + console.log( + `* AppRoot highlight called for ${el.nativeElement.tagName}`); + } +} diff --git a/aio/content/examples/ngmodule/ts/src/app/shared/awesome.pipe.ts b/aio/content/examples/ngmodule/ts/src/app/shared/awesome.pipe.ts new file mode 100644 index 0000000000..a1a0001d24 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/shared/awesome.pipe.ts @@ -0,0 +1,10 @@ +// Exact copy of contact.awesome.pipe +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ name: 'awesome' }) +/** Precede the input string with the word "Awesome " */ +export class AwesomePipe implements PipeTransform { + transform(phrase: string) { + return phrase ? 'Awesome ' + phrase : ''; + } +} diff --git a/aio/content/examples/ngmodule/ts/src/app/shared/highlight.directive.ts b/aio/content/examples/ngmodule/ts/src/app/shared/highlight.directive.ts new file mode 100644 index 0000000000..63fbd4e488 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/shared/highlight.directive.ts @@ -0,0 +1,13 @@ +/* tslint:disable */ +// Exact copy of contact/highlight.directive except for color and message +import { Directive, ElementRef } from '@angular/core'; + +@Directive({ selector: '[highlight], input' }) +/** Highlight the attached element or an InputElement in gray */ +export class HighlightDirective { + constructor(el: ElementRef) { + el.nativeElement.style.backgroundColor = 'lightgray'; + console.log( + `* Shared highlight called for ${el.nativeElement.tagName}`); + } +} diff --git a/aio/content/examples/ngmodule/ts/src/app/shared/shared.module.ts b/aio/content/examples/ngmodule/ts/src/app/shared/shared.module.ts new file mode 100644 index 0000000000..2da7d7b2a5 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/shared/shared.module.ts @@ -0,0 +1,18 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { AwesomePipe } from './awesome.pipe'; +import { HighlightDirective } from './highlight.directive'; + +// #docregion module +@NgModule({ + imports: [ CommonModule ], + declarations: [ AwesomePipe, HighlightDirective ], + exports: [ AwesomePipe, HighlightDirective, + CommonModule, FormsModule ] +}) +export class SharedModule { } +// #enddocregion module +// #enddocregion diff --git a/aio/content/examples/ngmodule/ts/src/app/title.component.html b/aio/content/examples/ngmodule/ts/src/app/title.component.html new file mode 100644 index 0000000000..3db364cd4b --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/title.component.html @@ -0,0 +1,10 @@ + + +

    {{title}} {{subtitle}}

    + + +

    + Welcome, {{user}} +

    + + diff --git a/aio/content/examples/ngmodule/ts/src/app/title.component.ts b/aio/content/examples/ngmodule/ts/src/app/title.component.ts new file mode 100644 index 0000000000..c5815edcbd --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/title.component.ts @@ -0,0 +1,25 @@ +// #docplaster +// #docregion +// #docregion v1 +import { Component, Input } from '@angular/core'; +// #enddocregion v1 +import { UserService } from './user.service'; +// #docregion v1 + +@Component({ + moduleId: module.id, + selector: 'app-title', + templateUrl: './title.component.html', +}) +export class TitleComponent { + @Input() subtitle = ''; + title = 'Angular Modules'; +// #enddocregion v1 + user = ''; + + constructor(userService: UserService) { + this.user = userService.userName; + } +// #docregion v1 +} +// #enddocregion v1 diff --git a/aio/content/examples/ngmodule/ts/src/app/user.service.ts b/aio/content/examples/ngmodule/ts/src/app/user.service.ts new file mode 100644 index 0000000000..7d996b26fa --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/app/user.service.ts @@ -0,0 +1,8 @@ +// #docregion +import { Injectable } from '@angular/core'; + +@Injectable() +/** Dummy version of an authenticated user service */ +export class UserService { + userName = 'Sherlock Holmes'; +} diff --git a/aio/content/examples/ngmodule/ts/src/index.0.html b/aio/content/examples/ngmodule/ts/src/index.0.html new file mode 100644 index 0000000000..2281b9fecb --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/index.0.html @@ -0,0 +1,24 @@ + + + + + NgModule Minimal + + + + + + + + + + + + + + + Loading... + + diff --git a/aio/content/examples/ngmodule/ts/src/index.1.html b/aio/content/examples/ngmodule/ts/src/index.1.html new file mode 100644 index 0000000000..65f7991a26 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/index.1.html @@ -0,0 +1,24 @@ + + + + + NgModule - Contact + + + + + + + + + + + + + + + Loading... + + diff --git a/aio/content/examples/ngmodule/ts/src/index.1b.html b/aio/content/examples/ngmodule/ts/src/index.1b.html new file mode 100644 index 0000000000..b4a89d6549 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/index.1b.html @@ -0,0 +1,24 @@ + + + + + NgModule - Contact + + + + + + + + + + + + + + + Loading... + + diff --git a/aio/content/examples/ngmodule/ts/src/index.2.html b/aio/content/examples/ngmodule/ts/src/index.2.html new file mode 100644 index 0000000000..72c545f1f3 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/index.2.html @@ -0,0 +1,24 @@ + + + + + NgModule - Contact + + + + + + + + + + + + + + + Loading... + + diff --git a/aio/content/examples/ngmodule/ts/src/index.3.html b/aio/content/examples/ngmodule/ts/src/index.3.html new file mode 100644 index 0000000000..ec55dd984e --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/index.3.html @@ -0,0 +1,24 @@ + + + + + NgModule - Contact + + + + + + + + + + + + + + + Loading... + + diff --git a/aio/content/examples/ngmodule/ts/src/index.html b/aio/content/examples/ngmodule/ts/src/index.html new file mode 100644 index 0000000000..9fc50c02a5 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/index.html @@ -0,0 +1,25 @@ + + + + + NgModule Deluxe + + + + + + + + + + + + + + + + Loading... + + diff --git a/aio/content/examples/ngmodule/ts/src/main-static.ts b/aio/content/examples/ngmodule/ts/src/main-static.ts new file mode 100644 index 0000000000..a1bcf466d2 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/main-static.ts @@ -0,0 +1,13 @@ +// #docplaster +/* +// #docregion +// The browser platform without a compiler +import { platformBrowser } from '@angular/platform-browser'; + +// The app module factory produced by the static offline compiler +import { AppModuleNgFactory } from './app/app.module.ngfactory'; + +// Launch with the app module factory. +platformBrowser().bootstrapModuleFactory(AppModuleNgFactory); +// #enddocregion +*/ diff --git a/aio/content/examples/ngmodule/ts/src/main.0.ts b/aio/content/examples/ngmodule/ts/src/main.0.ts new file mode 100644 index 0000000000..6d1d712f07 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/main.0.ts @@ -0,0 +1,4 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module.0'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/ngmodule/ts/src/main.1.ts b/aio/content/examples/ngmodule/ts/src/main.1.ts new file mode 100644 index 0000000000..48129f4cc9 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/main.1.ts @@ -0,0 +1,4 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module.1'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/ngmodule/ts/src/main.1b.ts b/aio/content/examples/ngmodule/ts/src/main.1b.ts new file mode 100644 index 0000000000..7a7bfae22f --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/main.1b.ts @@ -0,0 +1,4 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module.1b'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/ngmodule/ts/src/main.2.ts b/aio/content/examples/ngmodule/ts/src/main.2.ts new file mode 100644 index 0000000000..c535e2e775 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/main.2.ts @@ -0,0 +1,4 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module.2'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/ngmodule/ts/src/main.3.ts b/aio/content/examples/ngmodule/ts/src/main.3.ts new file mode 100644 index 0000000000..72f21dd142 --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/main.3.ts @@ -0,0 +1,4 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module.3'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/ngmodule/ts/src/main.ts b/aio/content/examples/ngmodule/ts/src/main.ts new file mode 100644 index 0000000000..c8424d8c4c --- /dev/null +++ b/aio/content/examples/ngmodule/ts/src/main.ts @@ -0,0 +1,9 @@ +// #docregion +// The browser platform with a compiler +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +// The app module +import { AppModule } from './app/app.module'; + +// Compile and launch the module +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/package.json b/aio/content/examples/package.json new file mode 100644 index 0000000000..6b20c9fda1 --- /dev/null +++ b/aio/content/examples/package.json @@ -0,0 +1,88 @@ +{ + "name": "angular-examples-master", + "version": "1.0.0", + "private": true, + "description": "Master package.json, the superset of all dependencies for all of the _example package.json files. See _boilerplate/package.json for example npm scripts.", + "scripts": { + "http-server": "http-server", + "protractor": "protractor", + "webdriver:update": "webdriver-manager update --standalone false --gecko false" + }, + "keywords": [], + "author": "", + "license": "MIT", + "dependencies": { + "@angular/common": "2.4.5", + "@angular/compiler": "2.4.5", + "@angular/compiler-cli": "2.4.5", + "@angular/core": "2.4.5", + "@angular/forms": "2.4.5", + "@angular/http": "2.4.5", + "@angular/platform-browser": "2.4.5", + "@angular/platform-browser-dynamic": "2.4.5", + "@angular/platform-server": "2.4.5", + "@angular/router": "3.4.5", + "@angular/tsc-wrapped": "^0.5.0", + "@angular/upgrade": "2.4.5", + "angular-in-memory-web-api": "~0.2.4", + "core-js": "^2.4.1", + "rxjs": "5.0.1", + "systemjs": "0.19.39", + "zone.js": "^0.7.4" + }, + "devDependencies": { + "@types/angular": "^1.5.16", + "@types/angular-animate": "^1.5.5", + "@types/angular-cookies": "^1.4.2", + "@types/angular-mocks": "^1.5.5", + "@types/angular-resource": "^1.5.6", + "@types/angular-route": "^1.3.2", + "@types/angular-sanitize": "^1.3.3", + "@types/jasmine": "2.5.36", + "@types/node": "^6.0.45", + "angular-cli": "^1.0.0-beta.26", + "angular2-template-loader": "^0.6.0", + "awesome-typescript-loader": "^3.0.0-beta.18", + "babel-cli": "^6.16.0", + "babel-preset-angular2": "^0.0.2", + "babel-preset-es2015": "^6.16.0", + "canonical-path": "0.0.2", + "concurrently": "^3.0.0", + "css-loader": "^0.26.1", + "extract-text-webpack-plugin": "2.0.0-beta.5", + "file-loader": "^0.9.0", + "html-loader": "^0.4.3", + "html-webpack-plugin": "^2.16.1", + "http-server": "^0.9.0", + "jasmine": "~2.4.1", + "jasmine-core": "~2.4.1", + "karma": "^1.3.0", + "karma-chrome-launcher": "^2.0.0", + "karma-cli": "^1.0.1", + "karma-jasmine": "^1.0.2", + "karma-jasmine-html-reporter": "^0.2.2", + "karma-phantomjs-launcher": "^1.0.2", + "karma-sourcemap-loader": "^0.3.7", + "karma-webpack": "^2.0.1", + "lite-server": "^2.2.2", + "lodash": "^4.16.2", + "null-loader": "^0.1.1", + "phantomjs-prebuilt": "^2.1.7", + "protractor": "~4.0.14", + "raw-loader": "^0.5.1", + "rimraf": "^2.5.4", + "rollup": "^0.36.0", + "rollup-plugin-commonjs": "^4.1.0", + "rollup-plugin-node-resolve": "^2.0.0", + "rollup-plugin-uglify": "^1.0.1", + "source-map-explorer": "^1.3.2", + "style-loader": "^0.13.1", + "ts-node": "^1.3.0", + "tslint": "^3.15.1", + "typescript": "~2.0.10", + "webpack": "2.2.0", + "webpack-dev-server": "2.2.0-rc.0", + "webpack-merge": "^2.4.0" + }, + "repository": {} +} diff --git a/aio/content/examples/pipes/e2e-spec.ts b/aio/content/examples/pipes/e2e-spec.ts new file mode 100644 index 0000000000..a2c4062dfd --- /dev/null +++ b/aio/content/examples/pipes/e2e-spec.ts @@ -0,0 +1,111 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('Pipes', function () { + + beforeAll(function () { + browser.get(''); + }); + + it('should open correctly', function () { + expect(element.all(by.tagName('h1')).get(0).getText()).toEqual('Pipes'); + expect(element(by.css('hero-birthday p')).getText()).toEqual(`The hero's birthday is Apr 15, 1988`); + }); + + it('should show 4 heroes', function () { + expect(element.all(by.css('hero-list div')).count()).toEqual(4); + }); + + it('should show a familiar hero in json', function () { + expect(element(by.cssContainingText('hero-list p', 'Heroes as JSON')).getText()).toContain('Bombasto'); + }); + + it('should show alternate birthday formats', function () { + expect(element(by.cssContainingText('my-app > p', `The hero's birthday is Apr 15, 1988`)).isDisplayed()).toBe(true); + expect(element(by.cssContainingText('my-app > p', `The hero's birthday is 04/15/88`)).isDisplayed()).toBe(true); + }); + + it('should be able to toggle birthday formats', function () { + let birthDayEle = element(by.css('hero-birthday2 > p')); + expect(birthDayEle.getText()).toEqual(`The hero's birthday is 4/15/1988`); + let buttonEle = element(by.cssContainingText('hero-birthday2 > button', 'Toggle Format')); + expect(buttonEle.isDisplayed()).toBe(true); + buttonEle.click().then(function() { + expect(birthDayEle.getText()).toEqual(`The hero's birthday is Friday, April 15, 1988`); + }); + }); + + it('should be able to chain and compose pipes', function () { + let chainedPipeEles = element.all(by.cssContainingText('my-app p', `The chained hero's`)); + expect(chainedPipeEles.count()).toBe(3, 'should have 3 chained pipe examples'); + expect(chainedPipeEles.get(0).getText()).toContain('APR 15, 1988'); + expect(chainedPipeEles.get(1).getText()).toContain('FRIDAY, APRIL 15, 1988'); + expect(chainedPipeEles.get(2).getText()).toContain('FRIDAY, APRIL 15, 1988'); + }); + + it('should be able to use ExponentialStrengthPipe pipe', function () { + let ele = element(by.css('power-booster p')); + expect(ele.getText()).toContain('Super power boost: 1024'); + }); + + it('should be able to use the exponential calculator', function () { + let eles = element.all(by.css('power-boost-calculator input')); + let baseInputEle = eles.get(0); + let factorInputEle = eles.get(1); + let outputEle = element(by.css('power-boost-calculator p')); + baseInputEle.clear().then(function() { + baseInputEle.sendKeys('7'); + return factorInputEle.clear(); + }).then(function() { + factorInputEle.sendKeys('3'); + expect(outputEle.getText()).toContain('343'); + }); + }); + + + xit('should support flying heroes (pure) ', function () { + let nameEle = element(by.css('flying-heroes input[type="text"]')); + let canFlyCheckEle = element(by.css('flying-heroes #can-fly')); + let mutateCheckEle = element(by.css('flying-heroes #mutate')); + let resetEle = element(by.css('flying-heroes button')); + let flyingHeroesEle = element.all(by.css('flying-heroes #flyers div')); + + expect(canFlyCheckEle.getAttribute('checked')).toEqual('true', 'should default to "can fly"'); + expect(mutateCheckEle.getAttribute('checked')).toEqual('true', 'should default to mutating array'); + expect(flyingHeroesEle.count()).toEqual(2, 'only two of the original heroes can fly'); + + nameEle.sendKeys('test1\n'); + expect(flyingHeroesEle.count()).toEqual(2, 'no change while mutating array'); + mutateCheckEle.click().then(function() { + nameEle.sendKeys('test2\n'); + expect(flyingHeroesEle.count()).toEqual(4, 'not mutating; should see both adds'); + expect(flyingHeroesEle.get(2).getText()).toContain('test1'); + expect(flyingHeroesEle.get(3).getText()).toContain('test2'); + return resetEle.click(); + }) + .then(function() { + expect(flyingHeroesEle.count()).toEqual(2, 'reset should restore orginal flying heroes'); + }); + }); + + + xit('should support flying heroes (impure) ', function () { + let nameEle = element(by.css('flying-heroes-impure input[type="text"]')); + let canFlyCheckEle = element(by.css('flying-heroes-impure #can-fly')); + let mutateCheckEle = element(by.css('flying-heroes-impure #mutate')); + let flyingHeroesEle = element.all(by.css('flying-heroes-impure #flyers div')); + + expect(canFlyCheckEle.getAttribute('checked')).toEqual('true', 'should default to "can fly"'); + expect(mutateCheckEle.getAttribute('checked')).toEqual('true', 'should default to mutating array'); + expect(flyingHeroesEle.count()).toEqual(2, 'only two of the original heroes can fly'); + + nameEle.sendKeys('test1\n'); + expect(flyingHeroesEle.count()).toEqual(3, 'new flying hero should show in mutating array'); + }); + + it('should show an async hero message', function () { + expect(element.all(by.tagName('hero-message')).get(0).getText()).toContain('hero'); + }); + +}); diff --git a/aio/content/examples/pipes/ts/example-config.json b/aio/content/examples/pipes/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/pipes/ts/plnkr.json b/aio/content/examples/pipes/ts/plnkr.json new file mode 100644 index 0000000000..3c07266109 --- /dev/null +++ b/aio/content/examples/pipes/ts/plnkr.json @@ -0,0 +1,8 @@ +{ + "description": "Pipes", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js"], + "tags": ["pipe"] +} diff --git a/aio/content/examples/pipes/ts/plnkr.no-link.html b/aio/content/examples/pipes/ts/plnkr.no-link.html new file mode 100644 index 0000000000..009b0e1944 --- /dev/null +++ b/aio/content/examples/pipes/ts/plnkr.no-link.html @@ -0,0 +1,663 @@ +

    \ No newline at end of file diff --git a/aio/content/examples/pipes/ts/src/app/app.component.html b/aio/content/examples/pipes/ts/src/app/app.component.html new file mode 100644 index 0000000000..a27d587fcd --- /dev/null +++ b/aio/content/examples/pipes/ts/src/app/app.component.html @@ -0,0 +1,83 @@ + +

    Pipes

    +Happy Birthday v1
    +Birthday DatePipe
    +Happy Birthday v2
    +Birthday Pipe Chaining
    +Power Booster custom pipe
    +Power Boost Calculator custom pipe with params
    +Flying Heroes filter pipe (pure)
    +Flying Heroes filter pipe (impure)
    +Async Hero Message and AsyncPipe
    +Hero List with caching FetchJsonPipe
    + + +
    + +

    Hero Birthday v1

    + + +
    + +

    Birthday DatePipe

    + +

    The hero's birthday is {{ birthday | date }}

    + + + +

    The hero's birthday is {{ birthday | date:"MM/dd/yy" }}

    + + +
    + +

    Hero Birthday v2

    + + +
    + +

    Birthday Pipe Chaining

    +

    + + The chained hero's birthday is + {{ birthday | date | uppercase}} + +

    + +

    + + The chained hero's birthday is + {{ birthday | date:'fullDate' | uppercase}} + +

    +

    + + The chained hero's birthday is + {{ ( birthday | date:'fullDate' ) | uppercase}} + +

    +
    + + + +
    + +loading + +
    + + + +
    + + + +
    + + + + +
    + + + +
    diff --git a/aio/content/examples/pipes/ts/src/app/app.component.ts b/aio/content/examples/pipes/ts/src/app/app.component.ts new file mode 100644 index 0000000000..d21801ddcf --- /dev/null +++ b/aio/content/examples/pipes/ts/src/app/app.component.ts @@ -0,0 +1,11 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: './app.component.html' +}) +export class AppComponent { + birthday = new Date(1988, 3, 15); // April 15, 1988 +} diff --git a/aio/content/examples/pipes/ts/src/app/app.module.ts b/aio/content/examples/pipes/ts/src/app/app.module.ts new file mode 100644 index 0000000000..89a3a29505 --- /dev/null +++ b/aio/content/examples/pipes/ts/src/app/app.module.ts @@ -0,0 +1,48 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; +import { HttpModule } from '@angular/http'; + +import { AppComponent } from './app.component'; +import { + FlyingHeroesComponent, + FlyingHeroesImpureComponent +} from './flying-heroes.component'; +import { HeroAsyncMessageComponent } from './hero-async-message.component'; +import { HeroBirthdayComponent } from './hero-birthday1.component'; +import { HeroBirthday2Component } from './hero-birthday2.component'; +import { HeroListComponent } from './hero-list.component'; +import { PowerBoosterComponent } from './power-booster.component'; +import { PowerBoostCalculatorComponent } from './power-boost-calculator.component'; +import { + FlyingHeroesPipe, + FlyingHeroesImpurePipe +} from './flying-heroes.pipe'; +import { FetchJsonPipe } from './fetch-json.pipe'; +import { ExponentialStrengthPipe } from './exponential-strength.pipe'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule, + HttpModule + ], + declarations: [ + AppComponent, + FlyingHeroesComponent, + FlyingHeroesImpureComponent, + HeroAsyncMessageComponent, + HeroBirthdayComponent, + HeroBirthday2Component, + HeroListComponent, + PowerBoosterComponent, + PowerBoostCalculatorComponent, + FlyingHeroesPipe, + FlyingHeroesImpurePipe, + FetchJsonPipe, + ExponentialStrengthPipe + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/pipes/ts/src/app/exponential-strength.pipe.ts b/aio/content/examples/pipes/ts/src/app/exponential-strength.pipe.ts new file mode 100644 index 0000000000..0a703d7016 --- /dev/null +++ b/aio/content/examples/pipes/ts/src/app/exponential-strength.pipe.ts @@ -0,0 +1,18 @@ +// #docregion +import { Pipe, PipeTransform } from '@angular/core'; +/* + * Raise the value exponentially + * Takes an exponent argument that defaults to 1. + * Usage: + * value | exponentialStrength:exponent + * Example: + * {{ 2 | exponentialStrength:10}} + * formats to: 1024 +*/ +@Pipe({name: 'exponentialStrength'}) +export class ExponentialStrengthPipe implements PipeTransform { + transform(value: number, exponent: string): number { + let exp = parseFloat(exponent); + return Math.pow(value, isNaN(exp) ? 1 : exp); + } +} diff --git a/aio/content/examples/pipes/ts/src/app/fetch-json.pipe.ts b/aio/content/examples/pipes/ts/src/app/fetch-json.pipe.ts new file mode 100644 index 0000000000..4d56e865f3 --- /dev/null +++ b/aio/content/examples/pipes/ts/src/app/fetch-json.pipe.ts @@ -0,0 +1,30 @@ +// #docregion +import { Pipe, PipeTransform } from '@angular/core'; +import { Http } from '@angular/http'; + +import 'rxjs/add/operator/map'; + +// #docregion pipe-metadata +@Pipe({ + name: 'fetch', + pure: false +}) +// #enddocregion pipe-metadata +export class FetchJsonPipe implements PipeTransform { + private cachedData: any = null; + private cachedUrl = ''; + + constructor(private http: Http) { } + + transform(url: string): any { + if (url !== this.cachedUrl) { + this.cachedData = null; + this.cachedUrl = url; + this.http.get(url) + .map( result => result.json() ) + .subscribe( result => this.cachedData = result ); + } + + return this.cachedData; + } +} diff --git a/aio/content/examples/pipes/ts/src/app/flying-heroes-impure.component.html b/aio/content/examples/pipes/ts/src/app/flying-heroes-impure.component.html new file mode 100644 index 0000000000..66bd86f81c --- /dev/null +++ b/aio/content/examples/pipes/ts/src/app/flying-heroes-impure.component.html @@ -0,0 +1,38 @@ + + +

    {{title}}

    +

    + +New hero: + + + can fly +

    +

    + Mutate array + + + +

    + +

    Heroes who fly (piped)

    +
    + +
    + {{hero.name}} +
    + +
    + +

    All Heroes (no pipe)

    +
    + + +
    + {{hero.name}} +
    + + +
    diff --git a/aio/content/examples/pipes/ts/src/app/flying-heroes.component.html b/aio/content/examples/pipes/ts/src/app/flying-heroes.component.html new file mode 100644 index 0000000000..93e635b662 --- /dev/null +++ b/aio/content/examples/pipes/ts/src/app/flying-heroes.component.html @@ -0,0 +1,38 @@ + + +

    {{title}}

    +

    + +New hero: + + + can fly +

    +

    + Mutate array + + + +

    + +

    Heroes who fly (piped)

    +
    + +
    + {{hero.name}} +
    + +
    + +

    All Heroes (no pipe)

    +
    + + +
    + {{hero.name}} +
    + + +
    diff --git a/aio/content/examples/pipes/ts/src/app/flying-heroes.component.ts b/aio/content/examples/pipes/ts/src/app/flying-heroes.component.ts new file mode 100644 index 0000000000..295620310b --- /dev/null +++ b/aio/content/examples/pipes/ts/src/app/flying-heroes.component.ts @@ -0,0 +1,63 @@ +// #docplaster +// #docregion +import { Component } from '@angular/core'; + +import { HEROES } from './heroes'; + +@Component({ + moduleId: module.id, + selector: 'flying-heroes', + templateUrl: './flying-heroes.component.html', + styles: ['#flyers, #all {font-style: italic}'] +}) +// #docregion v1 +export class FlyingHeroesComponent { + heroes: any[] = []; + canFly = true; +// #enddocregion v1 + mutate = true; + title = 'Flying Heroes (pure pipe)'; + +// #docregion v1 + constructor() { this.reset(); } + + addHero(name: string) { + name = name.trim(); + if (!name) { return; } + let hero = {name, canFly: this.canFly}; +// #enddocregion v1 + if (this.mutate) { + // Pure pipe won't update display because heroes array reference is unchanged + // Impure pipe will display +// #docregion v1 +// #docregion push + this.heroes.push(hero); +// #enddocregion push +// #enddocregion v1 + } else { + // Pipe updates display because heroes array is a new object +// #docregion concat + this.heroes = this.heroes.concat(hero); +// #enddocregion concat + } +// #docregion v1 + } + + reset() { this.heroes = HEROES.slice(); } +} +// #enddocregion v1 + +////// Identical except for impure pipe ////// +// #docregion impure-component +@Component({ + moduleId: module.id, + selector: 'flying-heroes-impure', + templateUrl: './flying-heroes-impure.component.html', +// #enddocregion impure-component + styles: ['.flyers, .all {font-style: italic}'], +// #docregion impure-component +}) +export class FlyingHeroesImpureComponent extends FlyingHeroesComponent { + title = 'Flying Heroes (impure pipe)'; +} +// #enddocregion impure-component diff --git a/aio/content/examples/pipes/ts/src/app/flying-heroes.pipe.ts b/aio/content/examples/pipes/ts/src/app/flying-heroes.pipe.ts new file mode 100644 index 0000000000..87db9c277e --- /dev/null +++ b/aio/content/examples/pipes/ts/src/app/flying-heroes.pipe.ts @@ -0,0 +1,27 @@ +/* tslint:disable use-pipe-transform-interface */ +// #docregion +// #docregion pure +import { Pipe, PipeTransform } from '@angular/core'; + +import { Flyer } from './heroes'; + +@Pipe({ name: 'flyingHeroes' }) +export class FlyingHeroesPipe implements PipeTransform { + transform(allHeroes: Flyer[]) { + // #docregion filter + return allHeroes.filter(hero => hero.canFly); + // #enddocregion filter + } +} +// #enddocregion pure + +/////// Identical except for the pure flag +// #docregion impure +// #docregion pipe-decorator +@Pipe({ + name: 'flyingHeroesImpure', + pure: false +}) +// #enddocregion pipe-decorator +export class FlyingHeroesImpurePipe extends FlyingHeroesPipe {} +// #enddocregion impure diff --git a/aio/content/examples/pipes/ts/src/app/hero-async-message.component.ts b/aio/content/examples/pipes/ts/src/app/hero-async-message.component.ts new file mode 100644 index 0000000000..d5bbd9fb0e --- /dev/null +++ b/aio/content/examples/pipes/ts/src/app/hero-async-message.component.ts @@ -0,0 +1,38 @@ +// #docregion +import { Component } from '@angular/core'; + +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/observable/interval'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/take'; + +@Component({ + selector: 'hero-message', + template: ` +

    Async Hero Message and AsyncPipe

    +

    Message: {{ message$ | async }}

    + `, +}) +export class HeroAsyncMessageComponent { + message$: Observable; + + private messages = [ + 'You are my hero!', + 'You are the best hero!', + 'Will you be my hero?' + ]; + + constructor() { this.resend(); } + + resend() { + this.message$ = Observable.interval(500) + .map(i => this.messages[i]) + .take(this.messages.length); + } +} +// #enddocregion + +// Alternative message$ formula: +// this.message$ = Observable.fromArray(this.messages) +// .map(message => Observable.timer(500).map(() => message)) +// .concatAll(); diff --git a/aio/content/examples/pipes/ts/src/app/hero-birthday1.component.ts b/aio/content/examples/pipes/ts/src/app/hero-birthday1.component.ts new file mode 100644 index 0000000000..a670a807a8 --- /dev/null +++ b/aio/content/examples/pipes/ts/src/app/hero-birthday1.component.ts @@ -0,0 +1,12 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'hero-birthday', + // #docregion hero-birthday-template + template: `

    The hero's birthday is {{ birthday | date }}

    ` + // #enddocregion hero-birthday-template +}) +export class HeroBirthdayComponent { + birthday = new Date(1988, 3, 15); // April 15, 1988 +} diff --git a/aio/content/examples/pipes/ts/src/app/hero-birthday2.component.ts b/aio/content/examples/pipes/ts/src/app/hero-birthday2.component.ts new file mode 100644 index 0000000000..87481aa121 --- /dev/null +++ b/aio/content/examples/pipes/ts/src/app/hero-birthday2.component.ts @@ -0,0 +1,20 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'hero-birthday2', + // #docregion template + template: ` +

    The hero's birthday is {{ birthday | date:format }}

    + + ` + // #enddocregion template +}) +// #docregion class +export class HeroBirthday2Component { + birthday = new Date(1988, 3, 15); // April 15, 1988 + toggle = true; // start with true == shortDate + + get format() { return this.toggle ? 'shortDate' : 'fullDate'; } + toggleFormat() { this.toggle = !this.toggle; } +} diff --git a/aio/content/examples/pipes/ts/src/app/hero-list.component.ts b/aio/content/examples/pipes/ts/src/app/hero-list.component.ts new file mode 100644 index 0000000000..df231120ce --- /dev/null +++ b/aio/content/examples/pipes/ts/src/app/hero-list.component.ts @@ -0,0 +1,17 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'hero-list', + template: ` +

    Heroes from JSON File

    + +
    + {{hero.name}} +
    + +

    Heroes as JSON: + {{'heroes.json' | fetch | json}} +

    ` +}) +export class HeroListComponent { } diff --git a/aio/content/examples/pipes/ts/src/app/heroes.ts b/aio/content/examples/pipes/ts/src/app/heroes.ts new file mode 100644 index 0000000000..b2edabe0da --- /dev/null +++ b/aio/content/examples/pipes/ts/src/app/heroes.ts @@ -0,0 +1,7 @@ +export interface Flyer { canFly: boolean; } +export const HEROES = [ + {name: 'Windstorm', canFly: true}, + {name: 'Bombasto', canFly: false}, + {name: 'Magneto', canFly: false}, + {name: 'Tornado', canFly: true} +]; diff --git a/aio/content/examples/pipes/ts/src/app/power-boost-calculator.component.ts b/aio/content/examples/pipes/ts/src/app/power-boost-calculator.component.ts new file mode 100644 index 0000000000..e65e29ad4c --- /dev/null +++ b/aio/content/examples/pipes/ts/src/app/power-boost-calculator.component.ts @@ -0,0 +1,18 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'power-boost-calculator', + template: ` +

    Power Boost Calculator

    +
    Normal power:
    +
    Boost factor:
    +

    + Super Hero Power: {{power | exponentialStrength: factor}} +

    + ` +}) +export class PowerBoostCalculatorComponent { + power = 5; + factor = 1; +} diff --git a/aio/content/examples/pipes/ts/src/app/power-booster.component.ts b/aio/content/examples/pipes/ts/src/app/power-booster.component.ts new file mode 100644 index 0000000000..08e3e24c7b --- /dev/null +++ b/aio/content/examples/pipes/ts/src/app/power-booster.component.ts @@ -0,0 +1,11 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'power-booster', + template: ` +

    Power Booster

    +

    Super power boost: {{2 | exponentialStrength: 10}}

    + ` +}) +export class PowerBoosterComponent { } diff --git a/aio/content/examples/pipes/ts/src/heroes.json b/aio/content/examples/pipes/ts/src/heroes.json new file mode 100644 index 0000000000..e5f0b77262 --- /dev/null +++ b/aio/content/examples/pipes/ts/src/heroes.json @@ -0,0 +1,6 @@ +[ + {"name": "Windstorm", "canFly": true}, + {"name": "Bombasto", "canFly": false}, + {"name": "Magneto", "canFly": false}, + {"name": "Tornado", "canFly": true} +] diff --git a/aio/content/examples/pipes/ts/src/index.html b/aio/content/examples/pipes/ts/src/index.html new file mode 100644 index 0000000000..fedb5c2e5e --- /dev/null +++ b/aio/content/examples/pipes/ts/src/index.html @@ -0,0 +1,26 @@ + + + + Pipes + + + + + + + + + + + + + + + + + my-app loading ... + + + diff --git a/aio/content/examples/pipes/ts/src/main.ts b/aio/content/examples/pipes/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/aio/content/examples/pipes/ts/src/main.ts @@ -0,0 +1,5 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/protractor-helpers.ts b/aio/content/examples/protractor-helpers.ts new file mode 100644 index 0000000000..61af74ba30 --- /dev/null +++ b/aio/content/examples/protractor-helpers.ts @@ -0,0 +1,35 @@ +import { browser } from 'protractor'; + +export var appLang = { + appIsTs: false, + appIsJs: false, + appIsDart: false, + appIsUnknown: false +}; + +export function describeIf(cond: boolean, name: string, func: () => void): void { + if (cond) { + describe(name, func); + } else { + xdescribe(name, func); + } +} + +export function itIf(cond: boolean, name: string, func: (done: DoneFn) => void): void { + if (cond) { + it(name, func); + } else { + xit(name, func); + } +} + +// protractor.config.js is set to ng2 mode by default, so we must manually +// change it for upgradeAdapter tests +export function setProtractorToNg1Mode(): void { + browser.rootEl = 'body'; +} + +export function setProtractorToHybridMode() { + setProtractorToNg1Mode(); + browser.ng12Hybrid = true; +} diff --git a/aio/content/examples/protractor.config.js b/aio/content/examples/protractor.config.js new file mode 100644 index 0000000000..92b5e1fda5 --- /dev/null +++ b/aio/content/examples/protractor.config.js @@ -0,0 +1,175 @@ +// FIRST TIME ONLY- run: +// ./node_modules/.bin/webdriver-manager update +// +// Try: `npm run webdriver:update` +// +// AND THEN EVERYTIME ... +// 1. Compile with `tsc` +// 2. Make sure the test server (e.g., http-server: localhost:8080) is running. +// 3. ./node_modules/.bin/protractor protractor.config.js +// +// To do all steps, try: `npm run e2e` + +var fs = require('fs'); +var path = require('canonical-path'); +var _ = require('lodash'); + + +exports.config = { + directConnect: true, + + // Capabilities to be passed to the webdriver instance. + capabilities: { + 'browserName': 'chrome' + }, + + // Framework to use. Jasmine is recommended. + framework: 'jasmine', + + // For angular tests + useAllAngular2AppRoots: true, + + // Base URL for application server + baseUrl: 'http://localhost:8080', + + // doesn't seem to work. + // resultJsonOutputFile: "foo.json", + + onPrepare: function() { + //// SpecReporter + //var SpecReporter = require('jasmine-spec-reporter'); + //jasmine.getEnv().addReporter(new SpecReporter({displayStacktrace: 'none'})); + //// jasmine.getEnv().addReporter(new SpecReporter({displayStacktrace: 'all'})); + + // debugging + // console.log('browser.params:' + JSON.stringify(browser.params)); + var protractorHelpers = require('./protractor-helpers.ts'); + + var appDir = browser.params.appDir; + if (appDir) { + if (appDir.match('/ts') != null) { + protractorHelpers.appLang.appIsTs = true; + } else if (appDir.match('/js') != null) { + protractorHelpers.appLang.appIsJs = true; + } else if (appDir.match('/dart') != null) { + protractorHelpers.appLang.appIsDart = true; + } else { + protractorHelpers.appLang.appIsUnknown = true; + } + } else { + protractorHelpers.appLang.appIsUnknown = true; + } + jasmine.getEnv().addReporter(new Reporter( browser.params )); + }, + + jasmineNodeOpts: { + // defaultTimeoutInterval: 60000, + defaultTimeoutInterval: 10000, + showTiming: true, + print: function() {} + }, + + beforeLaunch: function() { + // add TS support for specs + require('ts-node').register({ + project: './tsconfig.json' + }); + } +}; + +// See http://jasmine.github.io/2.1/custom_reporter.html +function Reporter(options) { + var _defaultOutputFile = path.resolve(process.cwd(), "../../../", 'protractor-results.txt'); + options.outputFile = options.outputFile || _defaultOutputFile; + + var _root = { appDir: options.appDir, suites: [] }; + log('AppDir: ' + options.appDir, +1); + var _currentSuite; + + this.suiteStarted = function(suite) { + _currentSuite = { description: suite.description, status: null, specs: [] }; + _root.suites.push(_currentSuite); + log('Suite: ' + suite.description, +1); + }; + + this.suiteDone = function(suite) { + var statuses = _currentSuite.specs.map(function(spec) { + return spec.status; + }); + statuses = _.uniq(statuses); + var status = statuses.indexOf('failed') >= 0 ? 'failed' : statuses.join(', '); + _currentSuite.status = status; + log('Suite ' + _currentSuite.status + ': ' + suite.description, -1); + }; + + this.specStarted = function(spec) { + + }; + + this.specDone = function(spec) { + var currentSpec = { + description: spec.description, + status: spec.status + }; + if (spec.failedExpectations.length > 0) { + currentSpec.failedExpectations = spec.failedExpectations; + } + + _currentSuite.specs.push(currentSpec); + log(spec.status + ' - ' + spec.description); + if (spec.status === 'failed') { + spec.failedExpectations.forEach(function(err) { + log(err.message); + }); + } + }; + + this.jasmineDone = function() { + outputFile = options.outputFile; + //// Alternate approach - just stringify the _root - not as pretty + //// but might be more useful for automation. + // var output = JSON.stringify(_root, null, 2); + var output = formatOutput(_root); + fs.appendFileSync(outputFile, output); + }; + + // for output file output + function formatOutput(output) { + var indent = ' '; + var pad = ' '; + var results = []; + results.push('AppDir:' + output.appDir); + output.suites.forEach(function(suite) { + results.push(pad + 'Suite: ' + suite.description + ' -- ' + suite.status); + pad+=indent; + suite.specs.forEach(function(spec) { + results.push(pad + spec.status + ' - ' + spec.description); + if (spec.failedExpectations) { + pad+=indent; + spec.failedExpectations.forEach(function (fe) { + results.push(pad + 'message: ' + fe.message); + }); + pad=pad.substr(2); + } + }); + pad = pad.substr(2); + results.push(''); + }); + results.push(''); + return results.join('\n'); + } + + // for console output + var _pad; + function log(str, indent) { + _pad = _pad || ''; + if (indent == -1) { + _pad = _pad.substr(2); + } + console.log(_pad + str); + if (indent == 1) { + _pad = _pad + ' '; + } + } + +} diff --git a/aio/content/examples/quickstart/e2e-spec.ts b/aio/content/examples/quickstart/e2e-spec.ts new file mode 100644 index 0000000000..73921707ee --- /dev/null +++ b/aio/content/examples/quickstart/e2e-spec.ts @@ -0,0 +1,17 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('QuickStart E2E Tests', function () { + + let expectedMsg = 'Hello Angular'; + + beforeEach(function () { + browser.get(''); + }); + + it(`should display: ${expectedMsg}`, function () { + expect(element(by.css('h1')).getText()).toEqual(expectedMsg); + }); + +}); diff --git a/aio/content/examples/quickstart/js/bs-config.1.json b/aio/content/examples/quickstart/js/bs-config.1.json new file mode 100644 index 0000000000..4e58595267 --- /dev/null +++ b/aio/content/examples/quickstart/js/bs-config.1.json @@ -0,0 +1,8 @@ +{ + "server": { + "baseDir": "src", + "routes": { + "/node_modules": "node_modules" + } + } +} diff --git a/aio/content/examples/quickstart/js/example-config.json b/aio/content/examples/quickstart/js/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/quickstart/js/package.1.json b/aio/content/examples/quickstart/js/package.1.json new file mode 100644 index 0000000000..541a11373a --- /dev/null +++ b/aio/content/examples/quickstart/js/package.1.json @@ -0,0 +1,29 @@ +{ + "name": "angular-quickstart", + "version": "1.0.0", + "scripts": { + "start": "npm run lite", + "lite": "lite-server" + }, + "license": "MIT", + "dependencies": { + "@angular/common": "~2.4.0", + "@angular/compiler": "~2.4.0", + "@angular/core": "~2.4.0", + "@angular/forms": "~2.4.0", + "@angular/http": "~2.4.0", + "@angular/platform-browser": "~2.4.0", + "@angular/platform-browser-dynamic": "~2.4.0", + "@angular/router": "~3.4.0", + "@angular/upgrade": "~2.4.0", + + "angular-in-memory-web-api": "~0.2.4", + "core-js": "^2.4.1", + "rxjs": "5.0.1", + "zone.js": "^0.7.4" + }, + "devDependencies": { + "concurrently": "^3.0.0", + "lite-server": "^2.2.2" + } +} diff --git a/aio/content/examples/quickstart/js/plnkr.json b/aio/content/examples/quickstart/js/plnkr.json new file mode 100644 index 0000000000..5e24ad9790 --- /dev/null +++ b/aio/content/examples/quickstart/js/plnkr.json @@ -0,0 +1,8 @@ +{ + "description": "QuickStart", + "basePath": "src/", + "files": [ + "!**/*.[1].*" + ], + "tags": ["quickstart"] +} diff --git a/aio/content/examples/quickstart/js/plnkr.no-link.html b/aio/content/examples/quickstart/js/plnkr.no-link.html new file mode 100644 index 0000000000..152dbf3f07 --- /dev/null +++ b/aio/content/examples/quickstart/js/plnkr.no-link.html @@ -0,0 +1,204 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/quickstart/js/src/app/app.component.js b/aio/content/examples/quickstart/js/src/app/app.component.js new file mode 100644 index 0000000000..26ba116efe --- /dev/null +++ b/aio/content/examples/quickstart/js/src/app/app.component.js @@ -0,0 +1,27 @@ +// #docplaster +// #docregion +// #docregion iife +(function(app) { + // #enddocregion iife + // #docregion ng-namespace-funcs, export + app.AppComponent = + // #enddocregion export + // #docregion component + ng.core.Component({ + // #enddocregion ng-namespace-funcs + selector: 'my-app', + template: '

    Hello Angular

    ' + // #docregion ng-namespace-funcs + }) + // #enddocregion component + // #docregion class + .Class({ + // #enddocregion ng-namespace-funcs + constructor: function() {} + // #docregion ng-namespace-funcs + }); + // #enddocregion class + // #enddocregion ng-namespace-funcs +// #docregion iife +})(window.app || (window.app = {})); +// #enddocregion iife diff --git a/aio/content/examples/quickstart/js/src/app/app.module.js b/aio/content/examples/quickstart/js/src/app/app.module.js new file mode 100644 index 0000000000..0f3d5f82cf --- /dev/null +++ b/aio/content/examples/quickstart/js/src/app/app.module.js @@ -0,0 +1,15 @@ +// #docplaster +// #docregion +(function(app) { + app.AppModule = + ng.core.NgModule({ + imports: [ ng.platformBrowser.BrowserModule ], + // #docregion import + declarations: [ app.AppComponent ], + // #enddocregion import + bootstrap: [ app.AppComponent ] + }) + .Class({ + constructor: function() {} + }); +})(window.app || (window.app = {})); diff --git a/aio/content/examples/quickstart/js/src/index.html b/aio/content/examples/quickstart/js/src/index.html new file mode 100644 index 0000000000..43d63e9c83 --- /dev/null +++ b/aio/content/examples/quickstart/js/src/index.html @@ -0,0 +1,42 @@ + + + + + Angular QuickStart JS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Loading... + + + + diff --git a/aio/content/examples/quickstart/js/src/main.js b/aio/content/examples/quickstart/js/src/main.js new file mode 100644 index 0000000000..785823fa84 --- /dev/null +++ b/aio/content/examples/quickstart/js/src/main.js @@ -0,0 +1,8 @@ +// #docregion +(function(app) { + document.addEventListener('DOMContentLoaded', function() { + ng.platformBrowserDynamic + .platformBrowserDynamic() + .bootstrapModule(app.AppModule); + }); +})(window.app || (window.app = {})); diff --git a/aio/content/examples/quickstart/js/src/styles.1.css b/aio/content/examples/quickstart/js/src/styles.1.css new file mode 100644 index 0000000000..fbc30e2c9e --- /dev/null +++ b/aio/content/examples/quickstart/js/src/styles.1.css @@ -0,0 +1,14 @@ +/* #docregion */ +h1 { + color: #369; + font-family: Arial, Helvetica, sans-serif; + font-size: 250%; +} +body { + margin: 2em; +} + + /* + * See https://github.com/angular/angular.io/blob/master/public/docs/_examples/styles.css + * for the full set of master styles used by the documentation samples + */ diff --git a/aio/content/examples/quickstart/ts/bs-config.1.json b/aio/content/examples/quickstart/ts/bs-config.1.json new file mode 100644 index 0000000000..4e58595267 --- /dev/null +++ b/aio/content/examples/quickstart/ts/bs-config.1.json @@ -0,0 +1,8 @@ +{ + "server": { + "baseDir": "src", + "routes": { + "/node_modules": "node_modules" + } + } +} diff --git a/aio/content/examples/quickstart/ts/example-config.json b/aio/content/examples/quickstart/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/quickstart/ts/plnkr.json b/aio/content/examples/quickstart/ts/plnkr.json new file mode 100644 index 0000000000..8edf7c25ea --- /dev/null +++ b/aio/content/examples/quickstart/ts/plnkr.json @@ -0,0 +1,12 @@ +{ + "description": "QuickStart", + "basePath": "src/", + "files": [ + "app/app.component.ts", + "app/app.module.ts", + "main.ts", + "index.html" + ], + "open": "app/app.component.ts", + "tags": ["quickstart"] +} diff --git a/aio/content/examples/quickstart/ts/plnkr.no-link.html b/aio/content/examples/quickstart/ts/plnkr.no-link.html new file mode 100644 index 0000000000..63a97cf8d8 --- /dev/null +++ b/aio/content/examples/quickstart/ts/plnkr.no-link.html @@ -0,0 +1,74 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/quickstart/ts/src/app/app.component.ts b/aio/content/examples/quickstart/ts/src/app/app.component.ts new file mode 100644 index 0000000000..1ef28fc5c4 --- /dev/null +++ b/aio/content/examples/quickstart/ts/src/app/app.component.ts @@ -0,0 +1,8 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: `

    Hello {{name}}

    ` +}) +export class AppComponent { name = 'Angular'; } diff --git a/aio/content/examples/quickstart/ts/src/app/app.module.ts b/aio/content/examples/quickstart/ts/src/app/app.module.ts new file mode 100644 index 0000000000..50a0e6eb47 --- /dev/null +++ b/aio/content/examples/quickstart/ts/src/app/app.module.ts @@ -0,0 +1,11 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ BrowserModule ], + declarations: [ AppComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/quickstart/ts/src/index.html b/aio/content/examples/quickstart/ts/src/index.html new file mode 100644 index 0000000000..21fb56edb9 --- /dev/null +++ b/aio/content/examples/quickstart/ts/src/index.html @@ -0,0 +1,31 @@ + + + + Angular Quickstart + + + + + + + + + + + + + + + + + + + Loading AppComponent content here ... + + + + diff --git a/aio/content/examples/quickstart/ts/src/main.ts b/aio/content/examples/quickstart/ts/src/main.ts new file mode 100644 index 0000000000..311c44b76d --- /dev/null +++ b/aio/content/examples/quickstart/ts/src/main.ts @@ -0,0 +1,5 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/quickstart/ts/src/tsconfig.1.json b/aio/content/examples/quickstart/ts/src/tsconfig.1.json new file mode 100644 index 0000000000..2c7260d1bc --- /dev/null +++ b/aio/content/examples/quickstart/ts/src/tsconfig.1.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ "es2015", "dom" ], + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true + } +} diff --git a/aio/content/examples/reactive-forms/e2e-spec.ts b/aio/content/examples/reactive-forms/e2e-spec.ts new file mode 100644 index 0000000000..bb788397ef --- /dev/null +++ b/aio/content/examples/reactive-forms/e2e-spec.ts @@ -0,0 +1,1020 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +function finalDemoAddressForm(element: any, index: number) { + let form = { + street: element.all(by.css('input[formcontrolname=street]')).get(index).getAttribute('value'), + city: element.all(by.css('input[formcontrolname=city]')).get(index).getAttribute('value'), + state: element.all(by.css('select[formcontrolname=state]')).get(index).getAttribute('value'), + zip: element.all(by.css('input[formcontrolname=zip]')).get(index).getAttribute('value') + }; + return form; +} + +describe('Reactive forms', function() { + let select: any; + + beforeEach(function() { + browser.get(''); + select = element(by.css('.container > h4 > select')); + }); + + describe('navigation', function() { + it('should display the title', function() { + let title = element(by.css('.container > h1')); + expect(title.getText()).toBe('Reactive Forms'); + }); + + it('should contain a dropdown with each example', function() { + expect(select.isDisplayed()).toBe(true); + }); + + it('should have 9 options for different demos', function() { + let options = select.all(by.tagName('option')); + expect(options.count()).toBe(9); + }); + + it('should start with Final Demo', function() { + select.getAttribute('value').then(function(demo: string) { + expect(demo).toBe('Final Demo'); + }); + }); + }); + +// *************Begin Final Demo test******************************* + + describe('final demo', function() { + it('does not select any hero by default', function() { + let heroSection = element(by.css('hero-list > div')); + expect(heroSection.isPresent()).toBe(false); + }); + + it('refreshes the page upon button click', function() { + // We move to another page... + let whirlwindButton = element.all(by.css('nav a')).get(0); + whirlwindButton.click(); + let refresh = element(by.css('button')); + refresh.click(); + let heroSection = element(by.css('hero-list > div')); + expect(heroSection.isPresent()).toBe(false); + }); + + describe('Whirlwind form', function() { + beforeEach(function() { + let whirlwindButton = element.all(by.css('nav a')).get(0); + whirlwindButton.click(); + }); + + it('should show a hero information when the button is clicked', function() { + let editMessage = element(by.css('hero-list > div > h3')); + expect(editMessage.getText()).toBe('Editing: Whirlwind'); + }); + + it('should show a form with the selected hero information', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + expect(nameInput.getAttribute('value')).toBe('Whirlwind'); + let address1 = finalDemoAddressForm(element, 0); + expect(address1.street).toBe('123 Main'); + expect(address1.state).toBe('CA'); + expect(address1.zip).toBe('94801'); + expect(address1.city).toBe('Anywhere'); + let address2 = finalDemoAddressForm(element, 1); + expect(address2.street).toBe('456 Maple'); + expect(address2.state).toBe('VA'); + expect(address2.zip).toBe('23226'); + expect(address2.city).toBe('Somewhere'); + }); + + it('shows a json output from the form', function() { + let json = element(by.css('hero-detail > p')); + expect(json.getText()).toContain('Whirlwind'); + expect(json.getText()).toContain('Anywhere'); + expect(json.getText()).toContain('Somewhere'); + expect(json.getText()).toContain('VA'); + }); + + it('has two disabled buttons by default', function() { + let buttons = element.all(by.css('hero-detail > form > div > button')); + expect(buttons.get(0).getAttribute('disabled')).toBe('true'); + expect(buttons.get(1).getAttribute('disabled')).toBe('true'); + }); + + it('enables the buttons after we edit the form', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + nameInput.sendKeys('a'); + let buttons = element.all(by.css('hero-detail > form > div > button')); + expect(buttons.get(0).getAttribute('disabled')).toBeNull(); + expect(buttons.get(1).getAttribute('disabled')).toBeNull(); + }); + + it('saves the changes when the save button is clicked', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + nameInput.sendKeys('a'); + let save = element.all(by.css('hero-detail > form > div > button')).get(0); + save.click(); + let editMessage = element(by.css('hero-list > div > h3')); + expect(editMessage.getText()).toBe('Editing: Whirlwinda'); + }); + + it('reverts the changes when the revert button is clicked', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + nameInput.sendKeys('a'); + let revert = element.all(by.css('hero-detail > form > div > button')).get(1); + revert.click(); + let editMessage = element(by.css('hero-list > div > h3')); + expect(editMessage.getText()).toBe('Editing: Whirlwind'); + expect(nameInput.getAttribute('value')).toBe('Whirlwind'); + }); + + it('is able to add a new empty address', function() { + let newLairButton = element.all(by.css('button')).get(3); + newLairButton.click(); + let address3 = finalDemoAddressForm(element, 2); + expect(address3.street).toBe(''); + expect(address3.state).toBe(''); + expect(address3.zip).toBe(''); + expect(address3.city).toBe(''); + }); + + it('should show three radio buttons', function() { + let radioButtons = element.all(by.css('input[formcontrolname=power]')); + expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); + expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); + expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); + }); + + it('should show a checkbox', function() { + let checkbox = element(by.css('input[formcontrolname=sidekick]')); + expect(checkbox.getAttribute('checked')).toBe(null); + }); + }); + + describe('Bombastic form', function() { + beforeEach(function() { + let bombastaButton = element.all(by.css('nav a')).get(1); + bombastaButton.click(); + }); + + it('should show a hero information when the button is clicked', function() { + let editMessage = element(by.css('hero-list > div > h3')); + expect(editMessage.getText()).toBe('Editing: Bombastic'); + }); + + it('should show a form with the selected hero information', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + expect(nameInput.getAttribute('value')).toBe('Bombastic'); + let address1 = finalDemoAddressForm(element, 0); + expect(address1.street).toBe('789 Elm'); + // expect(address1.state).toBe('OH'); + expect(address1.zip).toBe('04501'); + expect(address1.city).toBe('Smallville'); + }); + + it('shows a json output from the form', function() { + let json = element(by.css('hero-detail > p')); + expect(json.getText()).toContain('Bombastic'); + expect(json.getText()).toContain('Smallville'); + expect(json.getText()).toContain('OH'); + expect(json.getText()).toContain('04501'); + }); + + it('has two disabled buttons by default', function() { + let buttons = element.all(by.css('hero-detail > form > div > button')); + expect(buttons.get(0).getAttribute('disabled')).toBe('true'); + expect(buttons.get(1).getAttribute('disabled')).toBe('true'); + }); + + it('enables the buttons after we edit the form', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + nameInput.sendKeys('a'); + let buttons = element.all(by.css('hero-detail > form > div > button')); + expect(buttons.get(0).getAttribute('disabled')).toBeNull(); + expect(buttons.get(1).getAttribute('disabled')).toBeNull(); + }); + + it('saves the changes when the save button is clicked', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + nameInput.sendKeys('a'); + let save = element.all(by.css('hero-detail > form > div > button')).get(0); + save.click(); + let editMessage = element(by.css('hero-list > div > h3')); + expect(editMessage.getText()).toBe('Editing: Bombastica'); + }); + + it('reverts the changes when the revert button is clicked', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + nameInput.sendKeys('a'); + let revert = element.all(by.css('hero-detail > form > div > button')).get(1); + revert.click(); + let editMessage = element(by.css('hero-list > div > h3')); + expect(editMessage.getText()).toBe('Editing: Bombastic'); + expect(nameInput.getAttribute('value')).toBe('Bombastic'); + }); + + it('is able to add a new empty address', function() { + let newLairButton = element.all(by.css('button')).get(3); + newLairButton.click(); + let address2 = finalDemoAddressForm(element, 1); + expect(address2.street).toBe(''); + expect(address2.state).toBe(''); + expect(address2.zip).toBe(''); + expect(address2.city).toBe(''); + }); + + it('should show three radio buttons', function() { + let radioButtons = element.all(by.css('input[formcontrolname=power]')); + expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); + expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); + expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); + }); + + it('should show a checbox', function() { + let checkbox = element(by.css('input[formcontrolname=sidekick]')); + expect(checkbox.getAttribute('checked')).toBe(null); + }); + }); + + describe('Magneta form', function() { + + beforeEach(function() { + let magnetaButton = element.all(by.css('nav a')).get(2); + magnetaButton.click(); + }); + + it('should show hero information when the button is clicked', function() { + let editMessage = element(by.css('hero-list > div > h3')); + expect(editMessage.getText()).toBe('Editing: Magneta'); + }); + + it('should show a form with the selected hero information', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + expect(nameInput.getAttribute('value')).toBe('Magneta'); + }); + + it('shows a json output from the form', function() { + let json = element(by.css('hero-detail > p')); + expect(json.getText()).toContain('Magneta'); + }); + + it('has two disabled buttons by default', function() { + let buttons = element.all(by.css('hero-detail > form > div > button')); + expect(buttons.get(0).getAttribute('disabled')).toBe('true'); + expect(buttons.get(1).getAttribute('disabled')).toBe('true'); + }); + + it('enables the buttons after we edit the form', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + nameInput.sendKeys('a'); + let buttons = element.all(by.css('hero-detail > form > div > button')); + expect(buttons.get(0).getAttribute('disabled')).toBeNull(); + expect(buttons.get(1).getAttribute('disabled')).toBeNull(); + }); + + it('saves the changes when the save button is clicked', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + nameInput.sendKeys('a'); + let save = element.all(by.css('hero-detail > form > div > button')).get(0); + save.click(); + let editMessage = element(by.css('hero-list > div > h3')); + expect(editMessage.getText()).toBe('Editing: Magnetaa'); + }); + + it('reverts the changes when the revert button is clicked', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + nameInput.sendKeys('a'); + let revert = element.all(by.css('hero-detail > form > div > button')).get(1); + revert.click(); + let editMessage = element(by.css('hero-list > div > h3')); + expect(editMessage.getText()).toBe('Editing: Magneta'); + expect(nameInput.getAttribute('value')).toBe('Magneta'); + }); + + it('is able to add a new empty address', function() { + let newLairButton = element.all(by.css('button')).get(3); + newLairButton.click(); + let address = finalDemoAddressForm(element, 0); + expect(address.street).toBe(''); + expect(address.state).toBe(''); + expect(address.zip).toBe(''); + expect(address.city).toBe(''); + }); + + it('should show three radio buttons', function() { + let radioButtons = element.all(by.css('input[formcontrolname=power]')); + expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); + expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); + expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); + }); + + it('should show a checkbox', function() { + let checkbox = element(by.css('input[formcontrolname=sidekick]')); + expect(checkbox.getAttribute('checked')).toBe(null); + }); + }); + }); // final demo + +// *************Begin FormArray Demo test******************************* + + + describe('formArray demo', function() { + beforeEach(function() { + let FormArrayOption = element.all(by.css('select option')).get(7); + FormArrayOption.click(); + }); + + it('should show FormArray Demo', function() { + select.getAttribute('value').then(function(demo: string) { + expect(demo).toBe('FormArray Demo'); + }); + }); + + it('does not select any hero by default', function() { + let heroSection = element(by.css('hero-list > div')); + expect(heroSection.isPresent()).toBe(false); + }); + + it('refreshes the page upon button click', function() { + // We move to another page... + let whirlwindButton = element.all(by.css('nav a')).get(0); + whirlwindButton.click(); + let refresh = element(by.css('button')); + refresh.click(); + let heroSection = element(by.css('hero-list > div')); + expect(heroSection.isPresent()).toBe(false); + }); + + describe('Whirlwind form', function() { + beforeEach(function() { + let whirlwindButton = element.all(by.css('nav a')).get(0); + whirlwindButton.click(); + }); + + it('should show hero information when the button is clicked', function() { + let editMessage = element(by.css('div.demo > div > div > h3')); + expect(editMessage.getText()).toBe('Editing: Whirlwind'); + }); + + it('should show a form with the selected hero information', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + expect(nameInput.getAttribute('value')).toBe('Whirlwind'); + let address1 = finalDemoAddressForm(element, 0); + expect(address1.street).toBe('123 Main'); + expect(address1.state).toBe('CA'); + expect(address1.zip).toBe('94801'); + expect(address1.city).toBe('Anywhere'); + let address2 = finalDemoAddressForm(element, 1); + expect(address2.street).toBe('456 Maple'); + expect(address2.state).toBe('VA'); + expect(address2.zip).toBe('23226'); + expect(address2.city).toBe('Somewhere'); + }); + it('shows a json output from the form', function() { + let json = element(by.css('hero-detail-8 > p')); + expect(json.getText()).toContain('Whirlwind'); + expect(json.getText()).toContain('Anywhere'); + expect(json.getText()).toContain('Somewhere'); + expect(json.getText()).toContain('VA'); + }); + + it('is able to add a new empty address', function() { + let newLairButton = element.all(by.css('button')).get(1); + newLairButton.click(); + let address2 = finalDemoAddressForm(element, 2); + expect(address2.street).toBe(''); + expect(address2.state).toBe(''); + expect(address2.zip).toBe(''); + expect(address2.city).toBe(''); + }); + + it('should show three radio buttons', function() { + let radioButtons = element.all(by.css('input[formcontrolname=power]')); + expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); + expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); + expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); + }); + + it('should show a checkbox', function() { + let checkbox = element(by.css('input[formcontrolname=sidekick]')); + expect(checkbox.getAttribute('checked')).toBe(null); + }); + + }); // Whirlwind form + + describe('Bombastic FormArray form', function() { + beforeEach(function() { + let bombasticButton = element.all(by.css('nav a')).get(1); + bombasticButton.click(); + }); + + it('should show a hero information when the button is clicked', function() { + let editMessage = element(by.css('div.demo > div > div > h3')); + expect(editMessage.getText()).toBe('Editing: Bombastic'); + }); + + it('should show a form with the selected hero information', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + // nameInput.getAttribute('value').then(function(name: string) { + // expect(name).toBe('Whirlwind'); + // }); + expect(nameInput.getAttribute('value')).toBe('Bombastic'); + let address1 = finalDemoAddressForm(element, 0); + expect(address1.street).toBe('789 Elm'); + // expect(address1.state).toBe('OH'); + // This select should be OH not CA, which it shows in the UI, the JSON shows OH. + expect(address1.zip).toBe('04501'); + expect(address1.city).toBe('Smallville'); + }); + + it('shows a json output from the form', function() { + let json = element(by.css('hero-detail-8 > p')); + expect(json.getText()).toContain('Bombastic'); + expect(json.getText()).toContain('Smallville'); + expect(json.getText()).toContain('04501'); + expect(json.getText()).toContain('789 Elm'); + }); + + it('is able to add a new empty address', function() { + let newLairButton = element.all(by.css('button')).get(1); + newLairButton.click(); + let address1 = finalDemoAddressForm(element, 1); + expect(address1.street).toBe(''); + expect(address1.state).toBe(''); + expect(address1.zip).toBe(''); + expect(address1.city).toBe(''); + }); + + it('should show three radio buttons', function() { + let radioButtons = element.all(by.css('input[formcontrolname=power]')); + expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); + expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); + expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); + }); + + it('should show a checkbox', function() { + let checkbox = element(by.css('input[formcontrolname=sidekick]')); + expect(checkbox.getAttribute('checked')).toBe(null); + }); + + }); // Bombastic FormArray form + + describe('Magneta FormArray form', function() { + beforeEach(function() { + let magnetaButton = element.all(by.css('nav a')).get(2); + magnetaButton.click(); + }); + + it('should show a hero information when the button is clicked', function() { + let editMessage = element(by.css('div.demo > div > div > h3')); + expect(editMessage.getText()).toBe('Editing: Magneta'); + }); + + it('should show a form with the selected hero information', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + expect(nameInput.getAttribute('value')).toBe('Magneta'); + }); + + it('shows a json output from the form', function() { + let json = element(by.css('hero-detail-8 > p')); + expect(json.getText()).toContain('Magneta'); + }); + + it('is able to add a new empty address', function() { + let newLairButton = element.all(by.css('button')).get(1); + newLairButton.click(); + let address1 = finalDemoAddressForm(element, 0); + expect(address1.street).toBe(''); + expect(address1.state).toBe(''); + expect(address1.zip).toBe(''); + expect(address1.city).toBe(''); + }); + + it('should show three radio buttons', function() { + let radioButtons = element.all(by.css('input[formcontrolname=power]')); + expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); + expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); + expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); + }); + + it('should show a checkbox', function() { + let checkbox = element(by.css('input[formcontrolname=sidekick]')); + expect(checkbox.getAttribute('checked')).toBe(null); + }); + + }); // Magneta FormArray form + + }); // formArray demo + + +// *************Begin SetValue Demo test******************************* + + describe('SetValue demo', function() { + beforeEach(function() { + let SetValueOption = element.all(by.css('select option')).get(6); + SetValueOption.click(); + }); + + it('should show SetValue Demo', function() { + select.getAttribute('value').then(function(demo: string) { + expect(demo).toBe('SetValue Demo'); + }); + }); + + it('does not select any hero by default', function() { + let heroSection = element(by.css('hero-list > div')); + expect(heroSection.isPresent()).toBe(false); + }); + + it('refreshes the page upon button click', function() { + // We move to another page... + let whirlwindButton = element.all(by.css('nav a')).get(0); + whirlwindButton.click(); + let refresh = element(by.css('button')); + refresh.click(); + let heroSection = element(by.css('hero-list > div')); + expect(heroSection.isPresent()).toBe(false); + }); + + describe('Whirlwind setValue form', function() { + beforeEach(function() { + let whirlwindButton = element.all(by.css('nav a')).get(0); + whirlwindButton.click(); + }); + + it('should show a hero information when the button is clicked', function() { + let editMessage = element(by.css('.demo > div > div > h3')); + expect(editMessage.getText()).toBe('Editing: Whirlwind'); + }); + + it('should show a form with the selected hero information', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + expect(nameInput.getAttribute('value')).toBe('Whirlwind'); + let address1 = finalDemoAddressForm(element, 0); + expect(address1.street).toBe('123 Main'); + expect(address1.state).toBe('CA'); + expect(address1.zip).toBe('94801'); + expect(address1.city).toBe('Anywhere'); + }); + + it('shows a json output from the form', function() { + let json = element(by.css('hero-detail-7 > p')); + expect(json.getText()).toContain('Whirlwind'); + expect(json.getText()).toContain('Anywhere'); + let nameOutput = element(by.css('hero-detail-7 > p ~ p')); + expect(nameOutput.getText()).toContain('Name value: Whirlwind'); + let streetOutput = element(by.css('hero-detail-7 > p ~ p ~ p')); + expect(streetOutput.getText()).toContain('Street value: 123 Main'); + }); + + it('should show three radio buttons', function() { + let radioButtons = element.all(by.css('input[formcontrolname=power]')); + expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); + expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); + expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); + }); + + it('should show a checkbox', function() { + let checkbox = element(by.css('input[formcontrolname=sidekick]')); + expect(checkbox.getAttribute('checked')).toBe(null); + }); + + }); // Whirlwind setValue form + + describe('Bombastic setValue form', function() { + beforeEach(function() { + let bombasticButton = element.all(by.css('nav a')).get(1); + bombasticButton.click(); + }); + + it('should show a hero information when the button is clicked', function() { + let editMessage = element(by.css('.demo > div > div > h3')); + expect(editMessage.getText()).toBe('Editing: Bombastic'); + }); + + it('should show a form with the selected hero information', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + expect(nameInput.getAttribute('value')).toBe('Bombastic'); + let address1 = finalDemoAddressForm(element, 0); + expect(address1.street).toBe('789 Elm'); + expect(address1.state).toBe('OH'); + expect(address1.zip).toBe('04501'); + expect(address1.city).toBe('Smallville'); + }); + + it('shows a json output from the form', function() { + let json = element(by.css('hero-detail-7 > p')); + expect(json.getText()).toContain('Bombastic'); + expect(json.getText()).toContain('Smallville'); + expect(json.getText()).toContain('04501'); + expect(json.getText()).toContain('789 Elm'); + let nameOutput = element(by.css('hero-detail-7 > p ~ p')); + expect(nameOutput.getText()).toContain('Name value: Bombastic'); + let streetOutput = element(by.css('hero-detail-7 > p ~ p ~ p')); + expect(streetOutput.getText()).toContain('Street value: 789 Elm'); + }); + + it('should show three radio buttons', function() { + let radioButtons = element.all(by.css('input[formcontrolname=power]')); + expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); + expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); + expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); + }); + + it('should show a checkbox', function() { + let checkbox = element(by.css('input[formcontrolname=sidekick]')); + expect(checkbox.getAttribute('checked')).toBe(null); + }); + + }); // Bombastic setValue form + + describe('Magneta setValue form', function() { + beforeEach(function() { + let magnetaButton = element.all(by.css('nav a')).get(2); + magnetaButton.click(); + }); + + it('should show a hero information when the button is clicked', function() { + let editMessage = element(by.css('.demo > div > div > h3')); + expect(editMessage.getText()).toBe('Editing: Magneta'); + }); + + it('should show a form with the selected hero information', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + expect(nameInput.getAttribute('value')).toBe('Magneta'); + }); + + it('should show three radio buttons', function() { + let radioButtons = element.all(by.css('input[formcontrolname=power]')); + expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); + expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); + expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); + }); + + it('should show a checkbox', function() { + let checkbox = element(by.css('input[formcontrolname=sidekick]')); + expect(checkbox.getAttribute('checked')).toBe(null); + }); + + it('shows a json output from the form', function() { + let json = element(by.css('hero-detail-7 > p')); + expect(json.getText()).toContain('Magneta'); + let nameOutput = element(by.css('hero-detail-7 > p ~ p')); + expect(nameOutput.getText()).toContain('Name value: Magneta'); + let streetOutput = element(by.css('hero-detail-7 > p ~ p ~ p')); + expect(streetOutput.getText()).toContain('Street value:'); + }); + + }); // Magneta setValue form + }); // SetValue demo + +// *************Begin patchValue Demo test******************************* + + describe('patchValue demo', function() { + beforeEach(function() { + let SetValueOption = element.all(by.css('select option')).get(5); + SetValueOption.click(); + }); + + it('should show patchValue Demo', function() { + select.getAttribute('value').then(function(demo: string) { + expect(demo).toBe('PatchValue Demo'); + }); + }); + + it('does not select any hero by default', function() { + let heroSection = element(by.css('.demo > div > div')); + expect(heroSection.isPresent()).toBe(false); + }); + + it('refreshes the page upon button click', function() { + // We move to another page... + let whirlwindButton = element.all(by.css('nav a')).get(0); + whirlwindButton.click(); + let refresh = element(by.css('button')); + refresh.click(); + let heroSection = element(by.css('.demo > div > div')); + expect(heroSection.isPresent()).toBe(false); + }); + + describe('Whirlwind patchValue form', function() { + beforeEach(function() { + let whirlwindButton = element.all(by.css('nav a')).get(0); + whirlwindButton.click(); + }); + + it('should show a hero information when the button is clicked', function() { + let editMessage = element(by.css('h2 ~ h3')); + expect(editMessage.getText()).toBe('Editing: Whirlwind'); + }); + + it('should show a form with the selected hero information', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + expect(nameInput.getAttribute('value')).toBe('Whirlwind'); + let address1 = finalDemoAddressForm(element, 0); + expect(address1.street).toBe(''); + expect(address1.state).toBe(''); + expect(address1.zip).toBe(''); + expect(address1.city).toBe(''); + }); + + it('should show three radio buttons', function() { + let radioButtons = element.all(by.css('input[formcontrolname=power]')); + expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); + expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); + expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); + }); + + it('should show a checkbox', function() { + let checkbox = element(by.css('input[formcontrolname=sidekick]')); + expect(checkbox.getAttribute('checked')).toBe(null); + }); + + it('shows a json output from the form', function() { + let json = element(by.css('hero-detail-6 > p')); + expect(json.getText()).toContain('Whirlwind'); + let nameOutput = element(by.css('hero-detail-6 > p ~ p')); + expect(nameOutput.getText()).toContain('Name value: Whirlwind'); + let streetOutput = element(by.css('hero-detail-6 > p ~ p ~ p')); + expect(streetOutput.getText()).toContain('Street value:'); + }); + + + }); // Bombastic patchValue form + describe('Bombastic patchValue form', function() { + beforeEach(function() { + let bombasticButton = element.all(by.css('nav a')).get(1); + bombasticButton.click(); + }); + + it('should show a hero information when the button is clicked', function() { + let editMessage = element(by.css('h2 ~ h3')); + expect(editMessage.getText()).toBe('Editing: Bombastic'); + }); + + it('should show a form with the selected hero information', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + expect(nameInput.getAttribute('value')).toBe('Bombastic'); + let address1 = finalDemoAddressForm(element, 0); + expect(address1.street).toBe(''); + expect(address1.state).toBe(''); + expect(address1.zip).toBe(''); + expect(address1.city).toBe(''); + }); + + it('should show three radio buttons', function() { + let radioButtons = element.all(by.css('input[formcontrolname=power]')); + expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); + expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); + expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); + }); + + it('should show a checkbox', function() { + let checkbox = element(by.css('input[formcontrolname=sidekick]')); + expect(checkbox.getAttribute('checked')).toBe(null); + }); + + it('shows a json output from the form', function() { + let json = element(by.css('hero-detail-6 > p')); + expect(json.getText()).toContain('Bombastic'); + let nameOutput = element(by.css('hero-detail-6 > p ~ p')); + expect(nameOutput.getText()).toContain('Name value: Bombastic'); + let streetOutput = element(by.css('hero-detail-6 > p ~ p ~ p')); + expect(streetOutput.getText()).toContain('Street value:'); + }); + }); // Bombastic patchValue form + + describe('Magneta patchValue form', function() { + beforeEach(function() { + let magnetaButton = element.all(by.css('nav a')).get(2); + magnetaButton.click(); + }); + + it('should show a hero information when the button is clicked', function() { + let editMessage = element(by.css('h2 ~ h3')); + expect(editMessage.getText()).toBe('Editing: Magneta'); + }); + + it('should show a form with the selected hero information', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + expect(nameInput.getAttribute('value')).toBe('Magneta'); + let address1 = finalDemoAddressForm(element, 0); + expect(address1.street).toBe(''); + expect(address1.state).toBe(''); + expect(address1.zip).toBe(''); + expect(address1.city).toBe(''); + }); + + it('should show three radio buttons', function() { + let radioButtons = element.all(by.css('input[formcontrolname=power]')); + expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); + expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); + expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); + }); + + it('should show a checkbox', function() { + let checkbox = element(by.css('input[formcontrolname=sidekick]')); + expect(checkbox.getAttribute('checked')).toBe(null); + }); + + it('shows a json output from the form', function() { + let json = element(by.css('hero-detail-6 > p')); + expect(json.getText()).toContain('Magneta'); + let nameOutput = element(by.css('hero-detail-6 > p ~ p')); + expect(nameOutput.getText()).toContain('Name value: Magneta'); + let streetOutput = element(by.css('hero-detail-6 > p ~ p ~ p')); + expect(streetOutput.getText()).toContain('Street value:'); + }); + + }); // Magneta patchValue form + }); // PatchValue demo + + + +// *************Begin Nested FormBuilder Demo test******************************* + + describe('Nested FormBuilder demo', function() { + beforeEach(function() { + let NestedFormBuilderOption = element.all(by.css('select option')).get(4); + NestedFormBuilderOption.click(); + }); + + it('should show Nested FormBuilder Demo', function() { + select.getAttribute('value').then(function(demo: string) { + expect(demo).toBe('Nested FormBuilder group Demo'); + }); + }); + + it('should show a form for hero information', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + expect(nameInput.getAttribute('value')).toBe(''); + let address1 = finalDemoAddressForm(element, 0); + expect(address1.street).toBe(''); + expect(address1.state).toBe(''); + expect(address1.zip).toBe(''); + expect(address1.city).toBe(''); + }); + + it('should show three radio buttons', function() { + let radioButtons = element.all(by.css('input[formcontrolname=power]')); + expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); + expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); + expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); + }); + + it('should show a checkbox', function() { + let checkbox = element(by.css('input[formcontrolname=sidekick]')); + expect(checkbox.getAttribute('checked')).toBe(null); + }); + + it('shows a json output from the form', function() { + let json = element(by.css('hero-detail-5 > p')); + expect(json.getText()).toContain('address'); + let nameOutput = element(by.css('hero-detail-5 > p ~ p')); + expect(nameOutput.getText()).toContain('Name value:'); + let streetOutput = element(by.css('hero-detail-5 > p ~ p ~ p')); + expect(streetOutput.getText()).toContain('Street value:'); + }); + + }); // Nested FormBuilder demo + +// *************Begin Group with multiple controls Demo test******************************* + + describe('Group with multiple controls demo', function() { + beforeEach(function() { + let NestedFormBuilderOption = element.all(by.css('select option')).get(3); + NestedFormBuilderOption.click(); + }); + + it('should show Group with multiple controls Demo', function() { + select.getAttribute('value').then(function(demo: string) { + expect(demo).toBe('Group with multiple controls Demo'); + }); + }); + + it('should show header', function() { + let header = element(by.css('hero-detail-4 > h3')); + expect(header.getText()).toBe('A FormGroup with multiple FormControls'); + }); + + it('should show a form for hero information', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + expect(nameInput.getAttribute('value')).toBe(''); + let address1 = finalDemoAddressForm(element, 0); + expect(address1.street).toBe(''); + expect(address1.state).toBe(''); + expect(address1.zip).toBe(''); + expect(address1.city).toBe(''); + }); + + it('should show three radio buttons', function() { + let radioButtons = element.all(by.css('input[formcontrolname=power]')); + expect(radioButtons.get(0).getAttribute('value')).toBe('flight'); + expect(radioButtons.get(1).getAttribute('value')).toBe('x-ray vision'); + expect(radioButtons.get(2).getAttribute('value')).toBe('strength'); + }); + it('should show a checkbox', function() { + let checkbox = element(by.css('input[formcontrolname=sidekick]')); + expect(checkbox.getAttribute('checked')).toBe(null); + }); + it('shows a json output from the form', function() { + let json = element(by.css('hero-detail-4 > p')); + expect(json.getText()).toContain('power'); + }); + +}); // Group with multiple controls demo + + + +// *************Begin Group with multiple controls Demo test******************************* + + describe('Simple FormBuilder Group demo', function() { + beforeEach(function() { + let SimpleFormBuilderOption = element.all(by.css('select option')).get(2); + SimpleFormBuilderOption.click(); + }); + + it('should show Simple FormBuilder group Demo', function() { + select.getAttribute('value').then(function(demo: string) { + expect(demo).toBe('Simple FormBuilder group Demo'); + }); + }); + + it('should show header', function() { + let header = element(by.css('hero-detail-3 > h3')); + expect(header.getText()).toBe('A FormGroup with a single FormControl using FormBuilder'); + }); + + it('should show a form for hero information', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + expect(nameInput.getAttribute('value')).toBe(''); + }); + + it('shows a json output from the form', function() { + let json = element(by.css('hero-detail-3 > p')); + expect(json.getText()).toContain('name'); + let validStatus = element(by.css('hero-detail-3 > p ~ p')); + expect(validStatus.getText()).toContain('INVALID'); + }); + +}); // Group with multiple controls demo + + +// *************Begin FormControl in a FormGroup Demo test******************************* + + describe('FormControl in a FormGroup demo', function() { + beforeEach(function() { + let SimpleFormBuilderOption = element.all(by.css('select option')).get(1); + SimpleFormBuilderOption.click(); + }); + + it('should show FormControl in a FormGroup Demo', function() { + select.getAttribute('value').then(function(demo: string) { + expect(demo).toBe('FormControl in a FormGroup Demo'); + }); + }); + + it('should show header', function() { + let header = element(by.css('hero-detail-2 > h3')); + expect(header.getText()).toBe('FormControl in a FormGroup'); + }); + + it('should show a form for hero information', function() { + let nameInput = element(by.css('input[formcontrolname=name]')); + expect(nameInput.getAttribute('value')).toBe(''); + }); + + it('shows a json output from the form', function() { + let json = element(by.css('hero-detail-2 > p')); + expect(json.getText()).toContain('name'); + }); + +}); // Group with multiple controls demo + +// *************Begin Just A FormControl Demo test******************************* + + describe('Just a FormControl demo', function() { + beforeEach(function() { + let FormControlOption = element.all(by.css('select option')).get(0); + FormControlOption.click(); + }); + + it('should show Just a FormControl demo', function() { + select.getAttribute('value').then(function(demo: string) { + expect(demo).toBe('Just a FormControl Demo'); + }); + }); + + it('should show header', function() { + let header = element(by.css('hero-detail-1 > h3')); + expect(header.getText()).toBe('Just a FormControl'); + }); + + it('should show a form for hero information', function() { + let nameInput = element(by.css('input')); + expect(nameInput.getAttribute('value')).toBe(''); + }); + + }); // Just a FormControl demo test + + +}); // reactive forms diff --git a/aio/content/examples/reactive-forms/ts/example-config.json b/aio/content/examples/reactive-forms/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/reactive-forms/ts/final.plnkr.json b/aio/content/examples/reactive-forms/ts/final.plnkr.json new file mode 100644 index 0000000000..41481acc99 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/final.plnkr.json @@ -0,0 +1,21 @@ +{ + "description": "Angular Reactive Forms (final)", + "basePath": "src/", + "files":[ + "styles.css", + + "app/app.component.ts", + "app/app.module.ts", + "app/data-model.ts", + "app/hero.service.ts", + "app/hero-detail.component.html", + "app/hero-detail.component.ts", + "app/hero-list.component.html", + "app/hero-list.component.ts", + + "main-final.ts", + "index-final.html" + ], + "main": "index-final.html", + "tags": ["reactive", "forms"] +} diff --git a/aio/content/examples/reactive-forms/ts/final.plnkr.no-link.html b/aio/content/examples/reactive-forms/ts/final.plnkr.no-link.html new file mode 100644 index 0000000000..758f006eaf --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/final.plnkr.no-link.html @@ -0,0 +1,515 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/reactive-forms/ts/plnkr.json b/aio/content/examples/reactive-forms/ts/plnkr.json new file mode 100644 index 0000000000..f0daeb4aad --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/plnkr.json @@ -0,0 +1,15 @@ +{ + "description": "Angular Reactive Forms (Demo runner)", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js", + + "!app/app.component.1.ts", + "!app/hero-list.component.1.html", + + "!app/main-final.ts", + "!index-final.html" + ], + "tags": ["reactive", "forms"] +} diff --git a/aio/content/examples/reactive-forms/ts/plnkr.no-link.html b/aio/content/examples/reactive-forms/ts/plnkr.no-link.html new file mode 100644 index 0000000000..fb7fd71c4d --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/plnkr.no-link.html @@ -0,0 +1,1325 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/reactive-forms/ts/src/app/app.component.1.ts b/aio/content/examples/reactive-forms/ts/src/app/app.component.1.ts new file mode 100644 index 0000000000..4ab3949863 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/app.component.1.ts @@ -0,0 +1,13 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + template: ` +
    +

    Reactive Forms

    + +
    ` +}) +export class AppComponent { } diff --git a/aio/content/examples/reactive-forms/ts/src/app/app.component.ts b/aio/content/examples/reactive-forms/ts/src/app/app.component.ts new file mode 100644 index 0000000000..e41b9f8b35 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/app.component.ts @@ -0,0 +1,13 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + template: ` +
    +

    Reactive Forms

    + +
    ` +}) +export class AppComponent { } diff --git a/aio/content/examples/reactive-forms/ts/src/app/app.module.ts b/aio/content/examples/reactive-forms/ts/src/app/app.module.ts new file mode 100644 index 0000000000..b9f2ea8f99 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/app.module.ts @@ -0,0 +1,39 @@ +// #docplaster +// #docregion +// #docregion v1 +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { ReactiveFormsModule } from '@angular/forms'; // <-- #1 import module + +import { AppComponent } from './app.component'; +import { HeroDetailComponent } from './hero-detail.component'; // <-- #1 import component +// #enddocregion v1 +import { HeroListComponent } from './hero-list.component'; + +import { HeroService } from './hero.service'; // <-- #1 import service +// #docregion v1 + +@NgModule({ + imports: [ + BrowserModule, + ReactiveFormsModule // <-- #2 add to Angular module imports + ], + declarations: [ + AppComponent, + HeroDetailComponent, // <-- #3 declare app component +// #enddocregion v1 + HeroListComponent +// #docregion v1 + ], +// #enddocregion v1 + exports: [ // export for the DemoModule + AppComponent, + HeroDetailComponent, + HeroListComponent + ], + providers: [ HeroService ], // <-- #4 provide HeroService +// #docregion v1 + bootstrap: [ AppComponent ] +}) +export class AppModule { } +// #enddocregion v1 diff --git a/aio/content/examples/reactive-forms/ts/src/app/data-model.ts b/aio/content/examples/reactive-forms/ts/src/app/data-model.ts new file mode 100644 index 0000000000..ad01ddee56 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/data-model.ts @@ -0,0 +1,40 @@ +// #docregion +// #docregion model-classes +export class Hero { + id = 0; + name = ''; + addresses: Address[]; +} + +export class Address { + street = ''; + city = ''; + state = ''; + zip = ''; +} +// #enddocregion model-classes + +export const heroes: Hero[] = [ + { + id: 1, + name: 'Whirlwind', + addresses: [ + {street: '123 Main', city: 'Anywhere', state: 'CA', zip: '94801'}, + {street: '456 Maple', city: 'Somewhere', state: 'VA', zip: '23226'}, + ] + }, + { + id: 2, + name: 'Bombastic', + addresses: [ + {street: '789 Elm', city: 'Smallville', state: 'OH', zip: '04501'}, + ] + }, + { + id: 3, + name: 'Magneta', + addresses: [ ] + }, +]; + +export const states = ['CA', 'MD', 'OH', 'VA']; diff --git a/aio/content/examples/reactive-forms/ts/src/app/demo.component.html b/aio/content/examples/reactive-forms/ts/src/app/demo.component.html new file mode 100644 index 0000000000..1caae127af --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/demo.component.html @@ -0,0 +1,40 @@ +
    +

    Reactive Forms

    +

    Pick a demo: + +

    + +
    + +
    + + + + + + + +
    + +

    Loading heroes ...

    +

    Select a hero:

    + + + +
    +
    +

    Hero Detail

    +

    Editing: {{selectedHero.name}}

    + + + + +
    +
    +
    +
    diff --git a/aio/content/examples/reactive-forms/ts/src/app/demo.component.ts b/aio/content/examples/reactive-forms/ts/src/app/demo.component.ts new file mode 100644 index 0000000000..5e3a95636d --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/demo.component.ts @@ -0,0 +1,48 @@ +/* tslint:disable:member-ordering */ +import { Component } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; + +import { Hero } from './data-model'; +import { HeroService } from './hero.service'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: 'demo.component.html' +}) +export class DemoComponent { + + demos: string[] = [ + 'Just a FormControl', + 'FormControl in a FormGroup', + 'Simple FormBuilder group', + 'Group with multiple controls', + 'Nested FormBuilder group', + 'PatchValue', + 'SetValue', + 'FormArray', + 'Final'].map(n => n + ' Demo'); + + final = this.demos.length; + demo = this.final; // current demo + + heroes: Observable; + isLoading = false; + selectedHero: Hero; + + constructor(private heroService: HeroService) { } + + getHeroes() { + this.isLoading = true; + this.heroes = this.heroService.getHeroes() + .finally(() => this.isLoading = false); + this.selectedHero = undefined; + } + + select(hero: Hero) { this.selectedHero = hero; } + + selectDemo(demo: number) { + this.demo = demo + 1; + this.getHeroes(); + } +} diff --git a/aio/content/examples/reactive-forms/ts/src/app/demo.module.ts b/aio/content/examples/reactive-forms/ts/src/app/demo.module.ts new file mode 100644 index 0000000000..dac9145ca9 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/demo.module.ts @@ -0,0 +1,33 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { ReactiveFormsModule } from '@angular/forms'; + +import { AppModule } from './app.module'; +import { DemoComponent } from './demo.component'; +import { HeroDetailComponent1 } from './hero-detail-1.component'; +import { HeroDetailComponent2 } from './hero-detail-2.component'; +import { HeroDetailComponent3 } from './hero-detail-3.component'; +import { HeroDetailComponent4 } from './hero-detail-4.component'; +import { HeroDetailComponent5 } from './hero-detail-5.component'; +import { HeroDetailComponent6 } from './hero-detail-6.component'; +import { HeroDetailComponent7 } from './hero-detail-7.component'; +import { HeroDetailComponent8 } from './hero-detail-8.component'; + +@NgModule({ + imports: [ + BrowserModule, + ReactiveFormsModule, + AppModule, + ], + declarations: [ DemoComponent, + HeroDetailComponent1, + HeroDetailComponent2, + HeroDetailComponent3, + HeroDetailComponent4, + HeroDetailComponent5, + HeroDetailComponent6, + HeroDetailComponent7, + HeroDetailComponent8], + bootstrap: [ DemoComponent ] +}) +export class DemoModule { } diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-detail-1.component.html b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-1.component.html new file mode 100644 index 0000000000..7217708d22 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-1.component.html @@ -0,0 +1,8 @@ + +

    Hero Detail

    +

    Just a FormControl

    + + + diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-detail-1.component.ts b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-1.component.ts new file mode 100644 index 0000000000..586ca36e76 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-1.component.ts @@ -0,0 +1,15 @@ +/* tslint:disable:component-class-suffix */ +// #docregion imports +import { Component } from '@angular/core'; +import { FormControl } from '@angular/forms'; +// #enddocregion + +@Component({ + moduleId: module.id, + selector: 'hero-detail-1', + templateUrl: './hero-detail-1.component.html' +}) +// #docregion v1 +export class HeroDetailComponent1 { + name = new FormControl(); +} diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-detail-2.component.html b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-2.component.html new file mode 100644 index 0000000000..1e98354842 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-2.component.html @@ -0,0 +1,18 @@ + +

    Hero Detail

    +

    FormControl in a FormGroup

    +
    +
    + +
    +
    + + + +

    Form value: {{ heroForm.value | json }}

    + + + + diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-detail-2.component.ts b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-2.component.ts new file mode 100644 index 0000000000..e3c0448a7f --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-2.component.ts @@ -0,0 +1,18 @@ +/* tslint:disable:component-class-suffix */ +// #docregion imports +import { Component } from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; +// #enddocregion imports + +@Component({ + moduleId: module.id, + selector: 'hero-detail-2', + templateUrl: './hero-detail-2.component.html' +}) +// #docregion v2 +export class HeroDetailComponent2 { + heroForm = new FormGroup ({ + name: new FormControl() + }); +} +// #enddocregion v2 diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-detail-3.component.html b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-3.component.html new file mode 100644 index 0000000000..8edc544dd4 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-3.component.html @@ -0,0 +1,16 @@ + +

    Hero Detail

    +

    A FormGroup with a single FormControl using FormBuilder

    +
    +
    + +
    +
    + + + +

    Form value: {{ heroForm.value | json }}

    +

    Form status: {{ heroForm.status | json }}

    + diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-detail-3.component.ts b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-3.component.ts new file mode 100644 index 0000000000..400c6911d6 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-3.component.ts @@ -0,0 +1,28 @@ +/* tslint:disable:component-class-suffix */ +// #docregion imports +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +// #enddocregion imports + +@Component({ + moduleId: module.id, + selector: 'hero-detail-3', + templateUrl: './hero-detail-3.component.html' +}) +// #docregion v3 +export class HeroDetailComponent3 { + heroForm: FormGroup; // <--- heroForm is of type FormGroup + + constructor(private fb: FormBuilder) { // <--- inject FormBuilder + this.createForm(); + } + + createForm() { + // #docregion required + this.heroForm = this.fb.group({ + name: ['', Validators.required ], + }); + // #enddocregion required + } +} +// #enddocregion v3 diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-detail-3a.component.ts b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-3a.component.ts new file mode 100644 index 0000000000..b76803d7ed --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-3a.component.ts @@ -0,0 +1,26 @@ +/* tslint:disable:component-class-suffix */ +// #docregion imports +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +// #enddocregion imports + +@Component({ + moduleId: module.id, + selector: 'hero-detail-3', + templateUrl: './hero-detail-3.component.html' +}) +// #docregion v3a +export class HeroDetailComponent3 { + heroForm: FormGroup; // <--- heroForm is of type FormGroup + + constructor(private fb: FormBuilder) { // <--- inject FormBuilder + this.createForm(); + } + + createForm() { + this.heroForm = this.fb.group({ + name: '', // <--- the FormControl called "name" + }); + } +} +// #enddocregion v3a diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-detail-4.component.html b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-4.component.html new file mode 100644 index 0000000000..30529868e9 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-4.component.html @@ -0,0 +1,46 @@ + +

    Hero Detail

    +

    A FormGroup with multiple FormControls

    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +

    Super power:

    + + + +
    +
    + +
    +
    + + +

    Form value: {{ heroForm.value | json }}

    diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-detail-4.component.ts b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-4.component.ts new file mode 100644 index 0000000000..8705765a56 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-4.component.ts @@ -0,0 +1,35 @@ +/* tslint:disable:component-class-suffix */ +// #docregion imports +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; + +import { states } from './data-model'; +// #enddocregion imports + +@Component({ + moduleId: module.id, + selector: 'hero-detail-4', + templateUrl: './hero-detail-4.component.html' +}) +// #docregion v4 +export class HeroDetailComponent4 { + heroForm: FormGroup; + states = states; + + constructor(private fb: FormBuilder) { + this.createForm(); + } + + createForm() { + this.heroForm = this.fb.group({ + name: ['', Validators.required ], + street: '', + city: '', + state: '', + zip: '', + power: '', + sidekick: '' + }); + } +} +// #enddocregion v4 diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-detail-5.component.html b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-5.component.html new file mode 100644 index 0000000000..2a41b4f2f9 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-5.component.html @@ -0,0 +1,56 @@ + +
    +
    + +
    + +
    +

    Secret Lair

    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +

    Super power:

    + + + +
    +
    + +
    +
    + +

    heroForm value: {{ heroForm.value | json}}

    +

    Extra info for the curious:

    + +

    Name value: {{ heroForm.get('name').value }}

    + + + +

    Street value: {{ heroForm.get('address.street').value}}

    + diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-detail-5.component.ts b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-5.component.ts new file mode 100644 index 0000000000..2bafd0709e --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-5.component.ts @@ -0,0 +1,36 @@ +/* tslint:disable:component-class-suffix */ +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; + +import { states } from './data-model'; + +@Component({ + moduleId: module.id, + selector: 'hero-detail-5', + templateUrl: './hero-detail-5.component.html' +}) +// #docregion v5 +export class HeroDetailComponent5 { + heroForm: FormGroup; + states = states; + + constructor(private fb: FormBuilder) { + this.createForm(); + } + + createForm() { + this.heroForm = this.fb.group({ // <-- the parent FormGroup + name: ['', Validators.required ], + address: this.fb.group({ // <-- the child FormGroup + street: '', + city: '', + state: '', + zip: '' + }), + power: '', + sidekick: '' + }); + } +} +// #enddocregion v5 + diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-detail-6.component.html b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-6.component.html new file mode 100644 index 0000000000..4c8b5c9e05 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-6.component.html @@ -0,0 +1,46 @@ + +

    Hero Detail

    +

    PatchValue to initialize a value

    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +

    Super power:

    + + + +
    +
    + +
    +
    + + +

    Form value: {{ heroForm.value | json }}

    diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-detail-6.component.ts b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-6.component.ts new file mode 100644 index 0000000000..9c592a953e --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-6.component.ts @@ -0,0 +1,59 @@ +/* tslint:disable:component-class-suffix */ +// #docregion import-input +import { Component, Input, OnChanges } from '@angular/core'; +// #enddocregion import-input +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; + +// #docregion import-hero +import { Hero, states } from './data-model'; +// #enddocregion import-hero + +////////// 6 //////////////////// + +@Component({ + moduleId: module.id, + selector: 'hero-detail-6', + templateUrl: './hero-detail-5.component.html' +}) +// #docregion v6 +export class HeroDetailComponent6 implements OnChanges { + // #docregion hero + @Input() hero: Hero; + // #enddocregion hero + + heroForm: FormGroup; + states = states; + + constructor(private fb: FormBuilder) { + this.createForm(); + } + + createForm() { + // #docregion hero-form-model + this.heroForm = this.fb.group({ + name: ['', Validators.required ], + address: this.fb.group({ + street: '', + city: '', + state: '', + zip: '' + }), + power: '', + sidekick: '' + }); + // #enddocregion hero-form-model + } + + // #docregion patch-value-on-changes + ngOnChanges() { // <-- wrap patchValue in ngOnChanges + this.heroForm.reset(); + // #docregion patch-value + this.heroForm.patchValue({ + name: this.hero.name + }); + // #enddocregion patch-value + } + // #enddocregion patch-value-on-changes +} + +// #enddocregion v6 diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-detail-7.component.html b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-7.component.html new file mode 100644 index 0000000000..6d68b49b4d --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-7.component.html @@ -0,0 +1,46 @@ + +

    Hero Detail

    +

    A FormGroup with multiple FormControls

    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +

    Super power:

    + + + +
    +
    + +
    +
    + + +

    Form value: {{ heroForm.value | json }}

    diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-detail-7.component.ts b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-7.component.ts new file mode 100644 index 0000000000..60a220b6bd --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-7.component.ts @@ -0,0 +1,67 @@ +/* tslint:disable:component-class-suffix */ +// #docplaster +// #docregion imports +import { Component, Input, OnChanges } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; + +import { Address, Hero, states } from './data-model'; +// #enddocregion imports + +@Component({ + moduleId: module.id, + selector: 'hero-detail-7', + templateUrl: './hero-detail-5.component.html' +}) +// #docregion v7 +export class HeroDetailComponent7 implements OnChanges { + @Input() hero: Hero; + + heroForm: FormGroup; + states = states; + + constructor(private fb: FormBuilder) { + this.createForm(); + } + + createForm() { + // #docregion address-form-group + this.heroForm = this.fb.group({ + name: ['', Validators.required ], + address: this.fb.group(new Address()), // <-- a FormGroup with a new address + power: '', + sidekick: '' + }); + // #enddocregion address-form-group + } + + // #docregion ngOnChanges + ngOnChanges() { + this.heroForm.reset({ + name: this.hero.name, + address: this.hero.addresses[0] || new Address() + }); + } + // #enddocregion ngOnChanges + + /* First version of ngOnChanges + // #docregion ngOnChanges-1 + ngOnChanges() + // #enddocregion ngOnChanges-1 + */ + ngOnChanges1() { + // #docregion reset + this.heroForm.reset(); + // #enddocregion reset + // #docregion ngOnChanges-1 + // #docregion set-value + this.heroForm.setValue({ + name: this.hero.name, + // #docregion set-value-address + address: this.hero.addresses[0] || new Address() + // #enddocregion set-value-address + }); + // #enddocregion set-value + } + // #enddocregion ngOnChanges-1 +} + diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-detail-8.component.html b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-8.component.html new file mode 100644 index 0000000000..d8e47ae798 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-8.component.html @@ -0,0 +1,70 @@ + +

    Using FormArray to add groups

    + +
    +

    Form Changed: {{ heroForm.dirty }}

    + +
    + +
    + + +
    +
    + + +

    Address #{{i + 1}}

    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + + +
    + + + + + + + +
    + + +
    +

    Super power:

    + + + +
    +
    + +
    +
    + +

    heroForm value: {{ heroForm.value | json}}

    diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-detail-8.component.ts b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-8.component.ts new file mode 100644 index 0000000000..99cd6f822a --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-detail-8.component.ts @@ -0,0 +1,69 @@ +/* tslint:disable:component-class-suffix */ +// #docregion imports +import { Component, Input, OnChanges } from '@angular/core'; +import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; + +import { Address, Hero, states } from './data-model'; +// #enddocregion imports + +@Component({ + moduleId: module.id, + selector: 'hero-detail-8', + templateUrl: './hero-detail-8.component.html' +}) +// #docregion v8 +export class HeroDetailComponent8 implements OnChanges { + @Input() hero: Hero; + + heroForm: FormGroup; + states = states; + + // #docregion ctor + constructor(private fb: FormBuilder) { + this.createForm(); + this.logNameChange(); + } + // #enddocregion ctor + + createForm() { + // #docregion secretLairs-form-array + this.heroForm = this.fb.group({ + name: ['', Validators.required ], + secretLairs: this.fb.array([]), // <-- secretLairs as an empty FormArray + power: '', + sidekick: '' + }); + // #enddocregion secretLairs-form-array + } + + logNameChange() {/* Coming soon */} + + // #docregion onchanges + ngOnChanges() { + this.heroForm.reset({ + name: this.hero.name + }); + this.setAddresses(this.hero.addresses); + } + // #enddocregion onchanges + + // #docregion get-secret-lairs + get secretLairs(): FormArray { + return this.heroForm.get('secretLairs') as FormArray; + }; + // #enddocregion get-secret-lairs + + // #docregion set-addresses + setAddresses(addresses: Address[]) { + const addressFGs = addresses.map(address => this.fb.group(address)); + const addressFormArray = this.fb.array(addressFGs); + this.heroForm.setControl('secretLairs', addressFormArray); + } + // #enddocregion set-addresses + + // #docregion add-lair + addLair() { + this.secretLairs.push(this.fb.group(new Address())); + } + // #enddocregion add-lair +} diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-detail.component.html b/aio/content/examples/reactive-forms/ts/src/app/hero-detail.component.html new file mode 100644 index 0000000000..8f5b8bf2c8 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-detail.component.html @@ -0,0 +1,73 @@ + + + +
    +
    +   + +
    + + + +
    + +
    + +
    +
    + +

    Address #{{i + 1}}

    +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + +
    + +
    + +
    +

    Super power:

    + + + +
    +
    + +
    +
    + + +

    heroForm value: {{ heroForm.value | json}}

    + + +

    Name change log

    +
    {{name}}
    + diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-detail.component.ts b/aio/content/examples/reactive-forms/ts/src/app/hero-detail.component.ts new file mode 100644 index 0000000000..a644e54ffb --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-detail.component.ts @@ -0,0 +1,108 @@ +// #docplaster +// #docregion +import { Component, Input, OnChanges } from '@angular/core'; +import { FormArray, FormBuilder, FormGroup } from '@angular/forms'; + +import { Address, Hero, states } from './data-model'; +// #docregion import-service +import { HeroService } from './hero.service'; +// #enddocregion import-service + +// #docregion metadata +@Component({ + moduleId: module.id, + selector: 'hero-detail', + templateUrl: './hero-detail.component.html' +}) +// #enddocregion metadata +export class HeroDetailComponent implements OnChanges { + @Input() hero: Hero; + + heroForm: FormGroup; + // #docregion log-name-change + nameChangeLog: string[] = []; + // #enddocregion log-name-change + states = states; + + // #docregion ctor + constructor( + private fb: FormBuilder, + private heroService: HeroService) { + + this.createForm(); + this.logNameChange(); + } + // #enddocregion ctor + + createForm() { + this.heroForm = this.fb.group({ + name: '', + secretLairs: this.fb.array([]), + power: '', + sidekick: '' + }); + } + + ngOnChanges() { + this.heroForm.reset({ + name: this.hero.name + }); + this.setAddresses(this.hero.addresses); + } + + get secretLairs(): FormArray { + return this.heroForm.get('secretLairs') as FormArray; + }; + + setAddresses(addresses: Address[]) { + const addressFGs = addresses.map(address => this.fb.group(address)); + const addressFormArray = this.fb.array(addressFGs); + this.heroForm.setControl('secretLairs', addressFormArray); + } + + addLair() { + this.secretLairs.push(this.fb.group(new Address())); + } + + // #docregion on-submit + onSubmit() { + this.hero = this.prepareSaveHero(); + this.heroService.updateHero(this.hero).subscribe(/* error handling */); + this.ngOnChanges(); + } + // #enddocregion on-submit + + // #docregion prepare-save-hero + prepareSaveHero(): Hero { + const formModel = this.heroForm.value; + + // deep copy of form model lairs + const secretLairsDeepCopy: Address[] = formModel.secretLairs.map( + (address: Address) => Object.assign({}, address) + ); + + // return new `Hero` object containing a combination of original hero value(s) + // and deep copies of changed form model values + const saveHero: Hero = { + id: this.hero.id, + name: formModel.name as string, + // addresses: formModel.secretLairs // <-- bad! + addresses: secretLairsDeepCopy + }; + return saveHero; + } + // #enddocregion prepare-save-hero + + // #docregion revert + revert() { this.ngOnChanges(); } + // #enddocregion revert + + // #docregion log-name-change + logNameChange() { + const nameControl = this.heroForm.get('name'); + nameControl.valueChanges.forEach( + (value: string) => this.nameChangeLog.push(value) + ); + } + // #enddocregion log-name-change +} diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-list.component.1.html b/aio/content/examples/reactive-forms/ts/src/app/hero-list.component.1.html new file mode 100644 index 0000000000..fa76c84f54 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-list.component.1.html @@ -0,0 +1,8 @@ + + + +
    + +
    diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-list.component.html b/aio/content/examples/reactive-forms/ts/src/app/hero-list.component.html new file mode 100644 index 0000000000..d0fb2ee920 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-list.component.html @@ -0,0 +1,17 @@ + +

    Loading heroes ...

    +

    Select a hero:

    + + + +
    +
    +

    Hero Detail

    +

    Editing: {{selectedHero.name}}

    + + + +
    diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero-list.component.ts b/aio/content/examples/reactive-forms/ts/src/app/hero-list.component.ts new file mode 100644 index 0000000000..58fb2e7991 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero-list.component.ts @@ -0,0 +1,32 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/operator/finally'; + +import { Hero } from './data-model'; +import { HeroService } from './hero.service'; + +@Component({ + moduleId: module.id, + selector: 'hero-list', + templateUrl: 'hero-list.component.html' +}) +export class HeroListComponent implements OnInit { + heroes: Observable; + isLoading = false; + selectedHero: Hero; + + constructor(private heroService: HeroService) { } + + ngOnInit() { this.getHeroes(); } + + getHeroes() { + this.isLoading = true; + this.heroes = this.heroService.getHeroes() + // Todo: error handling + .finally(() => this.isLoading = false); + this.selectedHero = undefined; + } + + select(hero: Hero) { this.selectedHero = hero; } +} diff --git a/aio/content/examples/reactive-forms/ts/src/app/hero.service.ts b/aio/content/examples/reactive-forms/ts/src/app/hero.service.ts new file mode 100644 index 0000000000..6600586de7 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/app/hero.service.ts @@ -0,0 +1,26 @@ +// #docregion +import { Injectable } from '@angular/core'; + +import { Observable } from 'rxjs/Observable'; +import { of } from 'rxjs/observable/of'; +import 'rxjs/add/operator/delay'; + +import { Hero, heroes } from './data-model'; + +@Injectable() +export class HeroService { + + delayMs = 500; + + // Fake server get; assume nothing can go wrong + getHeroes(): Observable { + return of(heroes).delay(this.delayMs); // simulate latency with delay + } + + // Fake server update; assume nothing can go wrong + updateHero(hero: Hero): Observable { + const oldHero = heroes.find(h => h.id === hero.id); + const newHero = Object.assign(oldHero, hero); // Demo: mutate cached hero + return of(newHero).delay(this.delayMs); // simulate latency with delay + } +} diff --git a/aio/content/examples/reactive-forms/ts/src/index-final.html b/aio/content/examples/reactive-forms/ts/src/index-final.html new file mode 100644 index 0000000000..9ef0a379e0 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/index-final.html @@ -0,0 +1,31 @@ + + + + + Hero Form + + + + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/reactive-forms/ts/src/index.html b/aio/content/examples/reactive-forms/ts/src/index.html new file mode 100644 index 0000000000..802d12f211 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/index.html @@ -0,0 +1,31 @@ + + + + + Hero Form + + + + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/reactive-forms/ts/src/main-final.ts b/aio/content/examples/reactive-forms/ts/src/main-final.ts new file mode 100644 index 0000000000..7572d1f1d3 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/main-final.ts @@ -0,0 +1,5 @@ +// tslint:disable:no-unused-variable +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/reactive-forms/ts/src/main.ts b/aio/content/examples/reactive-forms/ts/src/main.ts new file mode 100644 index 0000000000..f415a71708 --- /dev/null +++ b/aio/content/examples/reactive-forms/ts/src/main.ts @@ -0,0 +1,6 @@ +// tslint:disable:no-unused-variable +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; // just the final version +import { DemoModule } from './app/demo.module'; // demo picker + +platformBrowserDynamic().bootstrapModule(DemoModule); // (AppModule); diff --git a/aio/content/examples/router/e2e-spec.ts b/aio/content/examples/router/e2e-spec.ts new file mode 100644 index 0000000000..ed93fa20d7 --- /dev/null +++ b/aio/content/examples/router/e2e-spec.ts @@ -0,0 +1,166 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by, ElementFinder } from 'protractor'; + +describe('Router', function () { + + beforeAll(function () { + browser.get(''); + }); + + function getPageStruct() { + let hrefEles = element.all(by.css('my-app a')); + + return { + hrefs: hrefEles, + routerParent: element(by.css('my-app > ng-component')), + routerTitle: element(by.css('my-app > ng-component > h2')), + + crisisHref: hrefEles.get(0), + crisisList: element.all(by.css('my-app > ng-component > ng-component li')), + crisisDetail: element(by.css('my-app > ng-component > ng-component > ng-component > div')), + crisisDetailTitle: element(by.css('my-app > ng-component > ng-component > ng-component > div > h3')), + + heroesHref: hrefEles.get(1), + heroesList: element.all(by.css('my-app > ng-component li')), + heroDetail: element(by.css('my-app > ng-component > div')), + heroDetailTitle: element(by.css('my-app > ng-component > div > h3')), + + adminHref: hrefEles.get(2), + adminPreloadList: element.all(by.css('my-app > ng-component > ng-component > ul > li')), + + loginHref: hrefEles.get(3), + loginButton: element.all(by.css('my-app > ng-component > p > button')), + + contactHref: hrefEles.get(4), + contactCancelButton: element.all(by.buttonText('Cancel')), + + outletComponents: element.all(by.css('my-app > ng-component')) + }; + } + + it('should be able to see the start screen', function () { + let page = getPageStruct(); + expect(page.hrefs.count()).toEqual(5, 'should be 5 dashboard choices'); + expect(page.crisisHref.getText()).toEqual('Crisis Center'); + expect(page.heroesHref.getText()).toEqual('Heroes'); + expect(page.adminHref.getText()).toEqual('Admin'); + expect(page.loginHref.getText()).toEqual('Login'); + expect(page.contactHref.getText()).toEqual('Contact'); + }); + + it('should be able to see crises center items', function () { + let page = getPageStruct(); + page.crisisHref.click().then(function() { + expect(page.crisisList.count()).toBe(4, 'should be 4 crisis center entries at start'); + }); + }); + + it('should be able to see hero items', function () { + let page = getPageStruct(); + page.heroesHref.click().then(function() { + expect(page.routerTitle.getText()).toContain('HEROES'); + expect(page.heroesList.count()).toBe(6, 'should be 6 heroes'); + }); + }); + + it('should be able to toggle the views', function () { + let page = getPageStruct(); + page.crisisHref.click().then(function() { + expect(page.crisisList.count()).toBe(4, 'should be 4 crisis center entries'); + return page.heroesHref.click(); + }).then(function() { + expect(page.heroesList.count()).toBe(6, 'should be 6 heroes'); + }); + }); + + it('should be able to edit and save details from the crisis center view', function () { + let page = getPageStruct(); + page.crisisHref.click().then(function() { + crisisCenterEdit(2, true); + }); + }); + + xit('should be able to edit and cancel details from the crisis center view', function () { + let page = getPageStruct(); + page.crisisHref.click().then(function() { + crisisCenterEdit(3, false); + }); + }); + + it('should be able to edit and save details from the heroes view', function () { + let page = getPageStruct(); + let heroEle: ElementFinder; + let heroText: string; + page.heroesHref.click().then(function() { + heroEle = page.heroesList.get(4); + return heroEle.getText(); + }).then(function(text: string) { + expect(text.length).toBeGreaterThan(0, 'should have some text'); + // remove leading id from text + heroText = text.substr(text.indexOf(' ')).trim(); + return heroEle.click(); + }).then(function() { + expect(page.heroesList.count()).toBe(0, 'should no longer see crisis center entries'); + expect(page.heroDetail.isPresent()).toBe(true, 'should be able to see crisis detail'); + expect(page.heroDetailTitle.getText()).toContain(heroText); + let inputEle = page.heroDetail.element(by.css('input')); + inputEle.sendKeys('-foo'); + expect(page.heroDetailTitle.getText()).toContain(heroText + '-foo'); + let buttonEle = page.heroDetail.element(by.css('button')); + return buttonEle.click(); + }).then(function() { + expect(heroEle.getText()).toContain(heroText + '-foo'); + }); + }); + + it('should be able to see the preloaded modules', function () { + let page = getPageStruct(); + page.loginHref.click().then(function() { + return page.loginButton.click(); + }).then(function() { + expect(page.adminPreloadList.count()).toBe(1, 'should be 1 preloaded module'); + expect(page.adminPreloadList.first().getText()).toBe('crisis-center', 'first preload should be crisis center'); + }); + }); + + it('should be able to see the secondary route', function () { + let page = getPageStruct(); + page.heroesHref.click().then(function() { + return page.contactHref.click(); + }).then(function() { + expect(page.outletComponents.count()).toBe(2, 'should be 2 displayed routes'); + }); + }); + + function crisisCenterEdit(index: number, shouldSave: boolean) { + let page = getPageStruct(); + let crisisEle: ElementFinder; + let crisisText: string; + page.crisisHref.click() + .then(function () { + crisisEle = page.crisisList.get(index); + return crisisEle.getText(); + }).then(function(text: string) { + expect(text.length).toBeGreaterThan(0, 'should have some text'); + // remove leading id from text + crisisText = text.substr(text.indexOf(' ')).trim(); + return crisisEle.click(); + }).then(function () { + expect(page.crisisDetail.isPresent()).toBe(true, 'should be able to see crisis detail'); + expect(page.crisisDetailTitle.getText()).toContain(crisisText); + let inputEle = page.crisisDetail.element(by.css('input')); + inputEle.sendKeys('-foo'); + expect(page.crisisDetailTitle.getText()).toContain(crisisText + '-foo'); + let buttonEle = page.crisisDetail.element(by.cssContainingText('button', shouldSave ? 'Save' : 'Cancel')); + return buttonEle.click(); + }).then(function () { + if (shouldSave) { + expect(crisisEle.getText()).toContain(crisisText + '-foo'); + } else { + expect(crisisEle.getText()).not.toContain(crisisText + '-foo'); + } + }); + } + +}); diff --git a/aio/content/examples/router/ts/example-config.json b/aio/content/examples/router/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/router/ts/plnkr.json b/aio/content/examples/router/ts/plnkr.json new file mode 100644 index 0000000000..23ed1a1606 --- /dev/null +++ b/aio/content/examples/router/ts/plnkr.json @@ -0,0 +1,12 @@ +{ + "description": "Router", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[0-9].*", + "!app/crisis-list.component.ts", + "!app/hero-list.component.ts" + ], + "tags": ["router"] +} diff --git a/aio/content/examples/router/ts/plnkr.no-link.html b/aio/content/examples/router/ts/plnkr.no-link.html new file mode 100644 index 0000000000..c275a01f14 --- /dev/null +++ b/aio/content/examples/router/ts/plnkr.no-link.html @@ -0,0 +1,1353 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/router/ts/src/app/admin/admin-dashboard.component.1.ts b/aio/content/examples/router/ts/src/app/admin/admin-dashboard.component.1.ts new file mode 100644 index 0000000000..ffa3e3cb8f --- /dev/null +++ b/aio/content/examples/router/ts/src/app/admin/admin-dashboard.component.1.ts @@ -0,0 +1,9 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + template: ` +

    Dashboard

    + ` +}) +export class AdminDashboardComponent { } diff --git a/aio/content/examples/router/ts/src/app/admin/admin-dashboard.component.2.ts b/aio/content/examples/router/ts/src/app/admin/admin-dashboard.component.2.ts new file mode 100644 index 0000000000..8c8e481643 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/admin/admin-dashboard.component.2.ts @@ -0,0 +1,33 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/operator/map'; + +@Component({ + template: ` +

    Dashboard

    + +

    Session ID: {{ sessionId | async }}

    + +

    Token: {{ token | async }}

    + ` +}) +export class AdminDashboardComponent implements OnInit { + sessionId: Observable; + token: Observable; + + constructor(private route: ActivatedRoute) {} + + ngOnInit() { + // Capture the session ID if available + this.sessionId = this.route + .queryParams + .map(params => params['session_id'] || 'None'); + + // Capture the fragment if available + this.token = this.route + .fragment + .map(fragment => fragment || 'None'); + } +} diff --git a/aio/content/examples/router/ts/src/app/admin/admin-dashboard.component.ts b/aio/content/examples/router/ts/src/app/admin/admin-dashboard.component.ts new file mode 100644 index 0000000000..b3fc839616 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/admin/admin-dashboard.component.ts @@ -0,0 +1,47 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs/Observable'; + +import { SelectivePreloadingStrategy } from '../selective-preloading-strategy'; + +import 'rxjs/add/operator/map'; + +@Component({ + template: ` +

    Dashboard

    + +

    Session ID: {{ sessionId | async }}

    + +

    Token: {{ token | async }}

    + + Preloaded Modules +
      +
    • {{ module }}
    • +
    + ` +}) +export class AdminDashboardComponent implements OnInit { + sessionId: Observable; + token: Observable; + modules: string[]; + + constructor( + private route: ActivatedRoute, + private preloadStrategy: SelectivePreloadingStrategy + ) { + this.modules = preloadStrategy.preloadedModules; + } + + ngOnInit() { + // Capture the session ID if available + this.sessionId = this.route + .queryParams + .map(params => params['session_id'] || 'None'); + + // Capture the fragment if available + this.token = this.route + .fragment + .map(fragment => fragment || 'None'); + } +} diff --git a/aio/content/examples/router/ts/src/app/admin/admin-routing.module.1.ts b/aio/content/examples/router/ts/src/app/admin/admin-routing.module.1.ts new file mode 100644 index 0000000000..e7d83f113f --- /dev/null +++ b/aio/content/examples/router/ts/src/app/admin/admin-routing.module.1.ts @@ -0,0 +1,39 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { AdminComponent } from './admin.component'; +import { AdminDashboardComponent } from './admin-dashboard.component'; +import { ManageCrisesComponent } from './manage-crises.component'; +import { ManageHeroesComponent } from './manage-heroes.component'; + +// #docregion admin-routes +const adminRoutes: Routes = [ + { + path: 'admin', + component: AdminComponent, + children: [ + { + path: '', + children: [ + { path: 'crises', component: ManageCrisesComponent }, + { path: 'heroes', component: ManageHeroesComponent }, + { path: '', component: AdminDashboardComponent } + ] + } + ] + } +]; + +@NgModule({ + imports: [ + RouterModule.forChild(adminRoutes) + ], + exports: [ + RouterModule + ] +}) +export class AdminRoutingModule {} +// #enddocregion admin-routes +// #enddocregion diff --git a/aio/content/examples/router/ts/src/app/admin/admin-routing.module.2.ts b/aio/content/examples/router/ts/src/app/admin/admin-routing.module.2.ts new file mode 100644 index 0000000000..d945201afe --- /dev/null +++ b/aio/content/examples/router/ts/src/app/admin/admin-routing.module.2.ts @@ -0,0 +1,44 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { AdminComponent } from './admin.component'; +import { AdminDashboardComponent } from './admin-dashboard.component'; +import { ManageCrisesComponent } from './manage-crises.component'; +import { ManageHeroesComponent } from './manage-heroes.component'; + +// #docregion admin-route +import { AuthGuard } from '../auth-guard.service'; + +const adminRoutes: Routes = [ + { + path: 'admin', + component: AdminComponent, + canActivate: [AuthGuard], + children: [ + { + path: '', + children: [ + { path: 'crises', component: ManageCrisesComponent }, + { path: 'heroes', component: ManageHeroesComponent }, + { path: '', component: AdminDashboardComponent } + ], + // #enddocregion admin-route + canActivateChild: [AuthGuard] + // #docregion admin-route + } + ] + } +]; + +@NgModule({ + imports: [ + RouterModule.forChild(adminRoutes) + ], + exports: [ + RouterModule + ] +}) +export class AdminRoutingModule {} +// #enddocregion diff --git a/aio/content/examples/router/ts/src/app/admin/admin-routing.module.3.ts b/aio/content/examples/router/ts/src/app/admin/admin-routing.module.3.ts new file mode 100644 index 0000000000..63f1c9aaf4 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/admin/admin-routing.module.3.ts @@ -0,0 +1,43 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { AdminComponent } from './admin.component'; +import { AdminDashboardComponent } from './admin-dashboard.component'; +import { ManageCrisesComponent } from './manage-crises.component'; +import { ManageHeroesComponent } from './manage-heroes.component'; + +// #docregion admin-route +import { AuthGuard } from '../auth-guard.service'; + +// #docregion can-activate-child +const adminRoutes: Routes = [ + { + path: 'admin', + component: AdminComponent, + canActivate: [AuthGuard], + children: [ + { + path: '', + canActivateChild: [AuthGuard], + children: [ + { path: 'crises', component: ManageCrisesComponent }, + { path: 'heroes', component: ManageHeroesComponent }, + { path: '', component: AdminDashboardComponent } + ] + } + ] + } +]; + +@NgModule({ + imports: [ + RouterModule.forChild(adminRoutes) + ], + exports: [ + RouterModule + ] +}) +export class AdminRoutingModule {} +// #enddocregion diff --git a/aio/content/examples/router/ts/src/app/admin/admin-routing.module.ts b/aio/content/examples/router/ts/src/app/admin/admin-routing.module.ts new file mode 100644 index 0000000000..2b1048d110 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/admin/admin-routing.module.ts @@ -0,0 +1,41 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { AdminComponent } from './admin.component'; +import { AdminDashboardComponent } from './admin-dashboard.component'; +import { ManageCrisesComponent } from './manage-crises.component'; +import { ManageHeroesComponent } from './manage-heroes.component'; + +import { AuthGuard } from '../auth-guard.service'; + +const adminRoutes: Routes = [ + { + path: '', + component: AdminComponent, + canActivate: [AuthGuard], + children: [ + { + path: '', + canActivateChild: [AuthGuard], + children: [ + { path: 'crises', component: ManageCrisesComponent }, + { path: 'heroes', component: ManageHeroesComponent }, + { path: '', component: AdminDashboardComponent } + ] + } + ] + } +]; + +@NgModule({ + imports: [ + RouterModule.forChild(adminRoutes) + ], + exports: [ + RouterModule + ] +}) +export class AdminRoutingModule {} +// #enddocregion diff --git a/aio/content/examples/router/ts/src/app/admin/admin.component.ts b/aio/content/examples/router/ts/src/app/admin/admin.component.ts new file mode 100644 index 0000000000..30abfa4524 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/admin/admin.component.ts @@ -0,0 +1,17 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + template: ` +

    ADMIN

    + + + ` +}) +export class AdminComponent { +} diff --git a/aio/content/examples/router/ts/src/app/admin/admin.module.ts b/aio/content/examples/router/ts/src/app/admin/admin.module.ts new file mode 100644 index 0000000000..2736f00e1d --- /dev/null +++ b/aio/content/examples/router/ts/src/app/admin/admin.module.ts @@ -0,0 +1,24 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { AdminComponent } from './admin.component'; +import { AdminDashboardComponent } from './admin-dashboard.component'; +import { ManageCrisesComponent } from './manage-crises.component'; +import { ManageHeroesComponent } from './manage-heroes.component'; + +import { AdminRoutingModule } from './admin-routing.module'; + +@NgModule({ + imports: [ + CommonModule, + AdminRoutingModule + ], + declarations: [ + AdminComponent, + AdminDashboardComponent, + ManageCrisesComponent, + ManageHeroesComponent + ] +}) +export class AdminModule {} diff --git a/aio/content/examples/router/ts/src/app/admin/manage-crises.component.ts b/aio/content/examples/router/ts/src/app/admin/manage-crises.component.ts new file mode 100644 index 0000000000..d3176563eb --- /dev/null +++ b/aio/content/examples/router/ts/src/app/admin/manage-crises.component.ts @@ -0,0 +1,9 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + template: ` +

    Manage your crises here

    + ` +}) +export class ManageCrisesComponent { } diff --git a/aio/content/examples/router/ts/src/app/admin/manage-heroes.component.ts b/aio/content/examples/router/ts/src/app/admin/manage-heroes.component.ts new file mode 100644 index 0000000000..7f3a39893d --- /dev/null +++ b/aio/content/examples/router/ts/src/app/admin/manage-heroes.component.ts @@ -0,0 +1,9 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + template: ` +

    Manage your heroes here

    + ` +}) +export class ManageHeroesComponent { } diff --git a/aio/content/examples/router/ts/src/app/animations.ts b/aio/content/examples/router/ts/src/app/animations.ts new file mode 100644 index 0000000000..d7a55d721f --- /dev/null +++ b/aio/content/examples/router/ts/src/app/animations.ts @@ -0,0 +1,26 @@ +// #docregion +import { animate, AnimationEntryMetadata, state, style, transition, trigger } from '@angular/core'; + +// Component transition animations +export const slideInDownAnimation: AnimationEntryMetadata = + trigger('routeAnimation', [ + state('*', + style({ + opacity: 1, + transform: 'translateX(0)' + }) + ), + transition(':enter', [ + style({ + opacity: 0, + transform: 'translateX(-100%)' + }), + animate('0.2s ease-in') + ]), + transition(':leave', [ + animate('0.5s ease-out', style({ + opacity: 0, + transform: 'translateY(100%)' + })) + ]) + ]); diff --git a/aio/content/examples/router/ts/src/app/app-routing.module.1.ts b/aio/content/examples/router/ts/src/app/app-routing.module.1.ts new file mode 100644 index 0000000000..8146e54671 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app-routing.module.1.ts @@ -0,0 +1,26 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { CrisisListComponent } from './crisis-list.component'; +import { HeroListComponent } from './hero-list.component'; +import { PageNotFoundComponent } from './not-found.component'; + +// #docregion appRoutes +const appRoutes: Routes = [ + { path: 'crisis-center', component: CrisisListComponent }, + { path: 'heroes', component: HeroListComponent }, + { path: '', redirectTo: '/heroes', pathMatch: 'full' }, + { path: '**', component: PageNotFoundComponent } +]; +// #enddocregion appRoutes + +@NgModule({ + imports: [ + RouterModule.forRoot(appRoutes) + ], + exports: [ + RouterModule + ] +}) +export class AppRoutingModule {} diff --git a/aio/content/examples/router/ts/src/app/app-routing.module.2.ts b/aio/content/examples/router/ts/src/app/app-routing.module.2.ts new file mode 100644 index 0000000000..42ac84a481 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app-routing.module.2.ts @@ -0,0 +1,24 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { CrisisListComponent } from './crisis-list.component'; +// import { HeroListComponent } from './hero-list.component'; // <-- delete this line +import { PageNotFoundComponent } from './not-found.component'; + +const appRoutes: Routes = [ + { path: 'crisis-center', component: CrisisListComponent }, + // { path: 'heroes', component: HeroListComponent }, // <-- delete this line + { path: '', redirectTo: '/heroes', pathMatch: 'full' }, + { path: '**', component: PageNotFoundComponent } +]; + +@NgModule({ + imports: [ + RouterModule.forRoot(appRoutes) + ], + exports: [ + RouterModule + ] +}) +export class AppRoutingModule {} diff --git a/aio/content/examples/router/ts/src/app/app-routing.module.3.ts b/aio/content/examples/router/ts/src/app/app-routing.module.3.ts new file mode 100644 index 0000000000..538ff9aafc --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app-routing.module.3.ts @@ -0,0 +1,31 @@ +// #docplaster +// #docregion , v3 +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { ComposeMessageComponent } from './compose-message.component'; +import { PageNotFoundComponent } from './not-found.component'; + +const appRoutes: Routes = [ +// #enddocregion v3 +// #docregion compose + { + path: 'compose', + component: ComposeMessageComponent, + outlet: 'popup' + }, +// #enddocregion compose +// #docregion v3 + { path: '', redirectTo: '/heroes', pathMatch: 'full' }, + { path: '**', component: PageNotFoundComponent } +]; + +@NgModule({ + imports: [ + RouterModule.forRoot(appRoutes) + ], + exports: [ + RouterModule + ] +}) +export class AppRoutingModule {} diff --git a/aio/content/examples/router/ts/src/app/app-routing.module.4.ts b/aio/content/examples/router/ts/src/app/app-routing.module.4.ts new file mode 100644 index 0000000000..6835d24a85 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app-routing.module.4.ts @@ -0,0 +1,30 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { ComposeMessageComponent } from './compose-message.component'; +import { CanDeactivateGuard } from './can-deactivate-guard.service'; +import { PageNotFoundComponent } from './not-found.component'; + +const appRoutes: Routes = [ + { + path: 'compose', + component: ComposeMessageComponent, + outlet: 'popup' + }, + { path: '', redirectTo: '/heroes', pathMatch: 'full' }, + { path: '**', component: PageNotFoundComponent } +]; + +@NgModule({ + imports: [ + RouterModule.forRoot(appRoutes) + ], + exports: [ + RouterModule + ], + providers: [ + CanDeactivateGuard + ] +}) +export class AppRoutingModule {} diff --git a/aio/content/examples/router/ts/src/app/app-routing.module.5.ts b/aio/content/examples/router/ts/src/app/app-routing.module.5.ts new file mode 100644 index 0000000000..2badf7f593 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app-routing.module.5.ts @@ -0,0 +1,45 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +// #docregion import-router +import { RouterModule, Routes } from '@angular/router'; +// #enddocregion import-router + +import { ComposeMessageComponent } from './compose-message.component'; +import { PageNotFoundComponent } from './not-found.component'; + +import { CanDeactivateGuard } from './can-deactivate-guard.service'; +import { AuthGuard } from './auth-guard.service'; + + +const appRoutes: Routes = [ + { + path: 'compose', + component: ComposeMessageComponent, + outlet: 'popup' + }, +// #docregion admin, admin-1 + { + path: 'admin', + loadChildren: 'app/admin/admin.module#AdminModule', +// #enddocregion admin-1 + canLoad: [AuthGuard] +// #docregion admin-1 + }, +// #enddocregion admin, admin-1 + { path: '', redirectTo: '/heroes', pathMatch: 'full' }, + { path: '**', component: PageNotFoundComponent } +]; + +@NgModule({ + imports: [ + RouterModule.forRoot(appRoutes) + ], + exports: [ + RouterModule + ], + providers: [ + CanDeactivateGuard + ] +}) +export class AppRoutingModule {} diff --git a/aio/content/examples/router/ts/src/app/app-routing.module.6.ts b/aio/content/examples/router/ts/src/app/app-routing.module.6.ts new file mode 100644 index 0000000000..df2c8c097d --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app-routing.module.6.ts @@ -0,0 +1,54 @@ +// #docplaster +// #docregion, preload-v1 +import { NgModule } from '@angular/core'; +import { + RouterModule, Routes, +// #enddocregion preload-v1 + PreloadAllModules +// #docregion preload-v1 +} from '@angular/router'; + +import { ComposeMessageComponent } from './compose-message.component'; +import { PageNotFoundComponent } from './not-found.component'; + +import { CanDeactivateGuard } from './can-deactivate-guard.service'; +import { AuthGuard } from './auth-guard.service'; + +const appRoutes: Routes = [ + { + path: 'compose', + component: ComposeMessageComponent, + outlet: 'popup' + }, + { + path: 'admin', + loadChildren: 'app/admin/admin.module#AdminModule', + canLoad: [AuthGuard] + }, + { + path: 'crisis-center', + loadChildren: 'app/crisis-center/crisis-center.module#CrisisCenterModule' + }, + { path: '', redirectTo: '/heroes', pathMatch: 'full' }, + { path: '**', component: PageNotFoundComponent } +]; + +@NgModule({ + imports: [ + // #docregion forRoot + RouterModule.forRoot( + appRoutes + // #enddocregion preload-v1 + , { preloadingStrategy: PreloadAllModules } + // #docregion preload-v1 + ) + // #enddocregion forRoot + ], + exports: [ + RouterModule + ], + providers: [ + CanDeactivateGuard + ] +}) +export class AppRoutingModule {} diff --git a/aio/content/examples/router/ts/src/app/app-routing.module.ts b/aio/content/examples/router/ts/src/app/app-routing.module.ts new file mode 100644 index 0000000000..cc01ced890 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app-routing.module.ts @@ -0,0 +1,50 @@ +// #docplaster +// #docregion, preload-v1 +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { ComposeMessageComponent } from './compose-message.component'; +import { PageNotFoundComponent } from './not-found.component'; + +import { CanDeactivateGuard } from './can-deactivate-guard.service'; +import { AuthGuard } from './auth-guard.service'; +import { SelectivePreloadingStrategy } from './selective-preloading-strategy'; + +const appRoutes: Routes = [ + { + path: 'compose', + component: ComposeMessageComponent, + outlet: 'popup' + }, + { + path: 'admin', + loadChildren: 'app/admin/admin.module#AdminModule', + canLoad: [AuthGuard] + }, + // #docregion preload-v2 + { + path: 'crisis-center', + loadChildren: 'app/crisis-center/crisis-center.module#CrisisCenterModule', + data: { preload: true } + }, + // #enddocregion preload-v2 + { path: '', redirectTo: '/heroes', pathMatch: 'full' }, + { path: '**', component: PageNotFoundComponent } +]; + +@NgModule({ + imports: [ + RouterModule.forRoot( + appRoutes, + { preloadingStrategy: SelectivePreloadingStrategy } + ) + ], + exports: [ + RouterModule + ], + providers: [ + CanDeactivateGuard, + SelectivePreloadingStrategy + ] +}) +export class AppRoutingModule { } diff --git a/aio/content/examples/router/ts/src/app/app.component.1.ts b/aio/content/examples/router/ts/src/app/app.component.1.ts new file mode 100644 index 0000000000..21e9aa417d --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app.component.1.ts @@ -0,0 +1,18 @@ +/* First version */ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + // #docregion template + template: ` +

    Angular Router

    + + + ` + // #enddocregion template +}) +export class AppComponent { } diff --git a/aio/content/examples/router/ts/src/app/app.component.2.ts b/aio/content/examples/router/ts/src/app/app.component.2.ts new file mode 100644 index 0000000000..ffd4d8dfae --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app.component.2.ts @@ -0,0 +1,16 @@ +/* Second Heroes version */ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: ` +

    Angular Router

    + + + ` +}) +export class AppComponent { } diff --git a/aio/content/examples/router/ts/src/app/app.component.3.ts b/aio/content/examples/router/ts/src/app/app.component.3.ts new file mode 100644 index 0000000000..6013df6321 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app.component.3.ts @@ -0,0 +1,48 @@ +/* tslint:disable:no-unused-variable */ +// #docplaster +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'my-app', + /* Typical link + // #docregion h-anchor + Heroes + // #enddocregion h-anchor + */ + /* Incomplete Crisis Center link when CC lacks a default + // #docregion cc-anchor-fail + // The link now fails with a "non-terminal link" error + // #docregion cc-anchor-w-default + Crisis Center + // #enddocregion cc-anchor-w-default + // #enddocregion cc-anchor-fail + */ + /* Crisis Center link when CC lacks a default + // #docregion cc-anchor-no-default + Crisis Center + // #enddocregion cc-anchor-no-default + */ + /* Crisis Center Detail link + // #docregion Dragon-anchor + Dragon Crisis + // #enddocregion Dragon-anchor + */ + /* Crisis Center link with optional query params + // #docregion cc-query-params + Crisis Center + // #enddocregion cc-query-params + */ +// #docregion template + template: ` +

    Angular Router

    + + + ` +// #enddocregion template +}) +export class AppComponent { } diff --git a/aio/content/examples/router/ts/src/app/app.component.4.ts b/aio/content/examples/router/ts/src/app/app.component.4.ts new file mode 100644 index 0000000000..a630703c28 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app.component.4.ts @@ -0,0 +1,23 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + // #docregion template + template: ` +

    Angular Router

    + + // #docregion outlets + + + // #enddocregion outlets + ` + // #enddocregion template +}) +export class AppComponent { } diff --git a/aio/content/examples/router/ts/src/app/app.component.5.ts b/aio/content/examples/router/ts/src/app/app.component.5.ts new file mode 100644 index 0000000000..24162c6136 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app.component.5.ts @@ -0,0 +1,20 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + // #docregion template + template: ` +

    Angular Router

    + + + + ` + // #enddocregion template +}) +export class AppComponent { } diff --git a/aio/content/examples/router/ts/src/app/app.component.ts b/aio/content/examples/router/ts/src/app/app.component.ts new file mode 100644 index 0000000000..a479680cbe --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app.component.ts @@ -0,0 +1,23 @@ +// #docplaster +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + // #docregion template + template: ` +

    Angular Router

    + + + + ` + // #enddocregion template +}) +export class AppComponent { +} diff --git a/aio/content/examples/router/ts/src/app/app.module.0.ts b/aio/content/examples/router/ts/src/app/app.module.0.ts new file mode 100644 index 0000000000..a195dbdd7a --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app.module.0.ts @@ -0,0 +1,41 @@ +// NEVER USED. For docs only. Should compile though +// #docplaster +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { HeroListComponent } from './hero-list.component'; +import { CrisisListComponent } from './crisis-list.component'; +import { PageNotFoundComponent } from './not-found.component'; +import { PageNotFoundComponent as HeroDetailComponent } from './not-found.component'; + +// #docregion +const appRoutes: Routes = [ + { path: 'crisis-center', component: CrisisListComponent }, + { path: 'hero/:id', component: HeroDetailComponent }, + { + path: 'heroes', + component: HeroListComponent, + data: { title: 'Heroes List' } + }, + { path: '', + redirectTo: '/heroes', + pathMatch: 'full' + }, + { path: '**', component: PageNotFoundComponent } +]; + +@NgModule({ + imports: [ + RouterModule.forRoot(appRoutes) + // other imports here + ], +// #enddocregion +/* +// #docregion + ... +}) +export class AppModule { } +// #enddocregion +*/ +}) +export class AppModule0 { } diff --git a/aio/content/examples/router/ts/src/app/app.module.1.ts b/aio/content/examples/router/ts/src/app/app.module.1.ts new file mode 100644 index 0000000000..32f93b8f79 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app.module.1.ts @@ -0,0 +1,49 @@ +// #docplaster +// #docregion +// #docregion first-config +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; +// #docregion import-router +import { RouterModule, Routes } from '@angular/router'; +// #enddocregion import-router + +import { AppComponent } from './app.component'; +import { CrisisListComponent } from './crisis-list.component'; +import { HeroListComponent } from './hero-list.component'; +// #enddocregion first-config +import { PageNotFoundComponent } from './not-found.component'; +// #docregion first-config + +// #docregion appRoutes +const appRoutes: Routes = [ + { path: 'crisis-center', component: CrisisListComponent }, + { path: 'heroes', component: HeroListComponent }, +// #enddocregion first-config + + { path: '', redirectTo: '/heroes', pathMatch: 'full' }, +// #docregion wildcard + { path: '**', component: PageNotFoundComponent } +// #enddocregion wildcard +// #docregion first-config +]; +// #enddocregion appRoutes + +@NgModule({ + imports: [ + BrowserModule, + FormsModule, + RouterModule.forRoot(appRoutes) + ], + declarations: [ + AppComponent, + HeroListComponent, + CrisisListComponent, +// #enddocregion first-config + PageNotFoundComponent +// #docregion first-config + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } +// #enddocregion diff --git a/aio/content/examples/router/ts/src/app/app.module.2.ts b/aio/content/examples/router/ts/src/app/app.module.2.ts new file mode 100644 index 0000000000..2ba739168c --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app.module.2.ts @@ -0,0 +1,31 @@ +// #docplaster +// #docregion +// #docregion hero-import +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; + +import { AppComponent } from './app.component'; +import { AppRoutingModule } from './app-routing.module'; + +import { CrisisListComponent } from './crisis-list.component'; +import { HeroListComponent } from './hero-list.component'; +import { PageNotFoundComponent } from './not-found.component'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule, + AppRoutingModule + ], + declarations: [ + AppComponent, + HeroListComponent, + CrisisListComponent, + PageNotFoundComponent + ], + bootstrap: [ AppComponent ] +}) +// #enddocregion hero-import +export class AppModule { } +// #enddocregion diff --git a/aio/content/examples/router/ts/src/app/app.module.3.ts b/aio/content/examples/router/ts/src/app/app.module.3.ts new file mode 100644 index 0000000000..862faf1c51 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app.module.3.ts @@ -0,0 +1,29 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; + +import { AppComponent } from './app.component'; +import { AppRoutingModule } from './app-routing.module'; +import { HeroesModule } from './heroes/heroes.module'; + +import { CrisisListComponent } from './crisis-list.component'; +import { PageNotFoundComponent } from './not-found.component'; + +@NgModule({ +// #docregion module-imports + imports: [ + BrowserModule, + FormsModule, + HeroesModule, + AppRoutingModule + ], +// #enddocregion module-imports + declarations: [ + AppComponent, + CrisisListComponent, + PageNotFoundComponent + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/router/ts/src/app/app.module.4.ts b/aio/content/examples/router/ts/src/app/app.module.4.ts new file mode 100644 index 0000000000..4825572361 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app.module.4.ts @@ -0,0 +1,46 @@ +// #docplaster +// #docregion +// #docregion crisis-center-module, admin-module +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { AppComponent } from './app.component'; +import { PageNotFoundComponent } from './not-found.component'; + +import { AppRoutingModule } from './app-routing.module'; +import { HeroesModule } from './heroes/heroes.module'; +import { CrisisCenterModule } from './crisis-center/crisis-center.module'; +// #enddocregion crisis-center-module, admin-module +import { ComposeMessageComponent } from './compose-message.component'; +// #docregion admin-module +import { AdminModule } from './admin/admin.module'; +// #docregion crisis-center-module + +import { DialogService } from './dialog.service'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + HeroesModule, + CrisisCenterModule, +// #enddocregion crisis-center-module + AdminModule, +// #docregion crisis-center-module + AppRoutingModule + ], + declarations: [ + AppComponent, +// #enddocregion admin-module, crisis-center-module + ComposeMessageComponent, +// #docregion admin-module, crisis-center-module + PageNotFoundComponent + ], + providers: [ + DialogService + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } +// #enddocregion diff --git a/aio/content/examples/router/ts/src/app/app.module.5.ts b/aio/content/examples/router/ts/src/app/app.module.5.ts new file mode 100644 index 0000000000..ad34668cea --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app.module.5.ts @@ -0,0 +1,38 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { AppComponent } from './app.component'; +import { AppRoutingModule } from './app-routing.module'; + +import { HeroesModule } from './heroes/heroes.module'; +import { CrisisCenterModule } from './crisis-center/crisis-center.module'; + +import { ComposeMessageComponent } from './compose-message.component'; +import { PageNotFoundComponent } from './not-found.component'; + +import { AdminModule } from './admin/admin.module'; +import { DialogService } from './dialog.service'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + HeroesModule, + CrisisCenterModule, + AdminModule, + AppRoutingModule + ], + declarations: [ + AppComponent, + ComposeMessageComponent, + PageNotFoundComponent + ], + providers: [ + DialogService + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/router/ts/src/app/app.module.6.ts b/aio/content/examples/router/ts/src/app/app.module.6.ts new file mode 100644 index 0000000000..4cb0b1fdd5 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app.module.6.ts @@ -0,0 +1,29 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; +import { Routes, RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { PageNotFoundComponent } from './not-found.component'; + +const routes: Routes = [ + +]; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule, + RouterModule.forRoot(routes, { useHash: true }) // .../#/crisis-center/ + ], + declarations: [ + AppComponent, + PageNotFoundComponent + ], + providers: [ + + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/router/ts/src/app/app.module.7.ts b/aio/content/examples/router/ts/src/app/app.module.7.ts new file mode 100644 index 0000000000..b6ca81ddea --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app.module.7.ts @@ -0,0 +1,38 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; + +import { AppComponent } from './app.component'; +import { AppRoutingModule } from './app-routing.module'; + +import { HeroesModule } from './heroes/heroes.module'; +import { CrisisCenterModule } from './crisis-center/crisis-center.module'; +import { ComposeMessageComponent } from './compose-message.component'; +import { LoginRoutingModule } from './login-routing.module'; +import { LoginComponent } from './login.component'; +import { PageNotFoundComponent } from './not-found.component'; + +import { DialogService } from './dialog.service'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule, + HeroesModule, + CrisisCenterModule, + LoginRoutingModule, + AppRoutingModule + ], + declarations: [ + AppComponent, + ComposeMessageComponent, + LoginComponent, + PageNotFoundComponent + ], + providers: [ + DialogService + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/router/ts/src/app/app.module.ts b/aio/content/examples/router/ts/src/app/app.module.ts new file mode 100644 index 0000000000..d580964e52 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/app.module.ts @@ -0,0 +1,47 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; +// #docregion inspect-config +import { Router } from '@angular/router'; + +// #enddocregion inspect-config +import { AppComponent } from './app.component'; +import { AppRoutingModule } from './app-routing.module'; + +import { HeroesModule } from './heroes/heroes.module'; +import { ComposeMessageComponent } from './compose-message.component'; +import { LoginRoutingModule } from './login-routing.module'; +import { LoginComponent } from './login.component'; +import { PageNotFoundComponent } from './not-found.component'; + +import { DialogService } from './dialog.service'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule, + HeroesModule, + LoginRoutingModule, + AppRoutingModule + ], + declarations: [ + AppComponent, + ComposeMessageComponent, + LoginComponent, + PageNotFoundComponent + ], + providers: [ + DialogService + ], + bootstrap: [ AppComponent ] +}) +// #docregion inspect-config +export class AppModule { + // Diagnostic only: inspect router configuration + constructor(router: Router) { + console.log('Routes: ', JSON.stringify(router.config, undefined, 2)); + } +} +// #enddocregion inspect-config diff --git a/aio/content/examples/router/ts/src/app/auth-guard.service.1.ts b/aio/content/examples/router/ts/src/app/auth-guard.service.1.ts new file mode 100644 index 0000000000..c824bcb208 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/auth-guard.service.1.ts @@ -0,0 +1,11 @@ +// #docregion +import { Injectable } from '@angular/core'; +import { CanActivate } from '@angular/router'; + +@Injectable() +export class AuthGuard implements CanActivate { + canActivate() { + console.log('AuthGuard#canActivate called'); + return true; + } +} diff --git a/aio/content/examples/router/ts/src/app/auth-guard.service.2.ts b/aio/content/examples/router/ts/src/app/auth-guard.service.2.ts new file mode 100644 index 0000000000..8fd00e151a --- /dev/null +++ b/aio/content/examples/router/ts/src/app/auth-guard.service.2.ts @@ -0,0 +1,37 @@ +// #docregion +import { Injectable } from '@angular/core'; +import { + CanActivate, Router, + ActivatedRouteSnapshot, + RouterStateSnapshot +} from '@angular/router'; +import { AuthService } from './auth.service'; + +@Injectable() +export class AuthGuard implements CanActivate { + constructor(private authService: AuthService, private router: Router) {} + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + let url: string = state.url; + + return this.checkLogin(url); + } + + checkLogin(url: string): boolean { + if (this.authService.isLoggedIn) { return true; } + + // Store the attempted URL for redirecting + this.authService.redirectUrl = url; + + // Navigate to the login page with extras + this.router.navigate(['/login']); + return false; + } +} +// #enddocregion + +/* +// #docregion can-load-interface +export class AuthGuard implements CanActivate, CanLoad { +// #enddocregion can-load-interface +*/ diff --git a/aio/content/examples/router/ts/src/app/auth-guard.service.3.ts b/aio/content/examples/router/ts/src/app/auth-guard.service.3.ts new file mode 100644 index 0000000000..dd89006411 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/auth-guard.service.3.ts @@ -0,0 +1,39 @@ +// #docregion +// #docregion can-activate-child +import { Injectable } from '@angular/core'; +import { + CanActivate, Router, + ActivatedRouteSnapshot, + RouterStateSnapshot, + CanActivateChild +} from '@angular/router'; +import { AuthService } from './auth.service'; + +@Injectable() +export class AuthGuard implements CanActivate, CanActivateChild { + constructor(private authService: AuthService, private router: Router) {} + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + let url: string = state.url; + + return this.checkLogin(url); + } + + canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + return this.canActivate(route, state); + } + +// #enddocregion can-activate-child + checkLogin(url: string): boolean { + if (this.authService.isLoggedIn) { return true; } + + // Store the attempted URL for redirecting + this.authService.redirectUrl = url; + + // Navigate to the login page + this.router.navigate(['/login']); + return false; + } +// #docregion can-activate-child +} +// #enddocregion diff --git a/aio/content/examples/router/ts/src/app/auth-guard.service.4.ts b/aio/content/examples/router/ts/src/app/auth-guard.service.4.ts new file mode 100644 index 0000000000..5d239a8432 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/auth-guard.service.4.ts @@ -0,0 +1,47 @@ +// #docplaster +// #docregion +import { Injectable } from '@angular/core'; +import { + CanActivate, Router, + ActivatedRouteSnapshot, + RouterStateSnapshot, + CanActivateChild, + NavigationExtras +} from '@angular/router'; +import { AuthService } from './auth.service'; + +@Injectable() +export class AuthGuard implements CanActivate, CanActivateChild { + constructor(private authService: AuthService, private router: Router) {} + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + let url: string = state.url; + + return this.checkLogin(url); + } + + canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + return this.canActivate(route, state); + } + + checkLogin(url: string): boolean { + if (this.authService.isLoggedIn) { return true; } + + // Store the attempted URL for redirecting + this.authService.redirectUrl = url; + + // Create a dummy session id + let sessionId = 123456789; + + // Set our navigation extras object + // that contains our global query params and fragment + let navigationExtras: NavigationExtras = { + queryParams: { 'session_id': sessionId }, + fragment: 'anchor' + }; + + // Navigate to the login page with extras + this.router.navigate(['/login'], navigationExtras); + return false; + } +} diff --git a/aio/content/examples/router/ts/src/app/auth-guard.service.ts b/aio/content/examples/router/ts/src/app/auth-guard.service.ts new file mode 100644 index 0000000000..a32b5cc2b8 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/auth-guard.service.ts @@ -0,0 +1,56 @@ +// #docplaster +import { Injectable } from '@angular/core'; +import { + CanActivate, Router, + ActivatedRouteSnapshot, + RouterStateSnapshot, + CanActivateChild, + NavigationExtras, + CanLoad, Route +} from '@angular/router'; +import { AuthService } from './auth.service'; + +@Injectable() +export class AuthGuard implements CanActivate, CanActivateChild, CanLoad { + constructor(private authService: AuthService, private router: Router) {} + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + let url: string = state.url; + + return this.checkLogin(url); + } + + canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + return this.canActivate(route, state); + } + +// #docregion, canLoad + canLoad(route: Route): boolean { + let url = `/${route.path}`; + + return this.checkLogin(url); + } +// #enddocregion canLoad + + checkLogin(url: string): boolean { + if (this.authService.isLoggedIn) { return true; } + + // Store the attempted URL for redirecting + this.authService.redirectUrl = url; + + // Create a dummy session id + let sessionId = 123456789; + + // Set our navigation extras object + // that contains our global query params and fragment + let navigationExtras: NavigationExtras = { + queryParams: { 'session_id': sessionId }, + fragment: 'anchor' + }; + + // Navigate to the login page with extras + this.router.navigate(['/login'], navigationExtras); + return false; + } +// #docregion admin-can-load +} diff --git a/aio/content/examples/router/ts/src/app/auth.service.ts b/aio/content/examples/router/ts/src/app/auth.service.ts new file mode 100644 index 0000000000..f86a80ebfe --- /dev/null +++ b/aio/content/examples/router/ts/src/app/auth.service.ts @@ -0,0 +1,23 @@ +// #docregion +import { Injectable } from '@angular/core'; + +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/observable/of'; +import 'rxjs/add/operator/do'; +import 'rxjs/add/operator/delay'; + +@Injectable() +export class AuthService { + isLoggedIn: boolean = false; + + // store the URL so we can redirect after logging in + redirectUrl: string; + + login(): Observable { + return Observable.of(true).delay(1000).do(val => this.isLoggedIn = true); + } + + logout(): void { + this.isLoggedIn = false; + } +} diff --git a/aio/content/examples/router/ts/src/app/can-deactivate-guard.service.1.ts b/aio/content/examples/router/ts/src/app/can-deactivate-guard.service.1.ts new file mode 100644 index 0000000000..0b7c8247cf --- /dev/null +++ b/aio/content/examples/router/ts/src/app/can-deactivate-guard.service.1.ts @@ -0,0 +1,31 @@ +// #docregion +import { Injectable } from '@angular/core'; +import { CanDeactivate, + ActivatedRouteSnapshot, + RouterStateSnapshot } from '@angular/router'; + +import { CrisisDetailComponent } from './crisis-center/crisis-detail.component'; + +@Injectable() +export class CanDeactivateGuard implements CanDeactivate { + + canDeactivate( + component: CrisisDetailComponent, + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Promise | boolean { + // Get the Crisis Center ID + console.log(route.params['id']); + + // Get the current URL + console.log(state.url); + + // Allow synchronous navigation (`true`) if no crisis or the crisis is unchanged + if (!component.crisis || component.crisis.name === component.editName) { + return true; + } + // Otherwise ask the user with the dialog service and return its + // promise which resolves to true or false when the user decides + return component.dialogService.confirm('Discard changes?'); + } +} diff --git a/aio/content/examples/router/ts/src/app/can-deactivate-guard.service.ts b/aio/content/examples/router/ts/src/app/can-deactivate-guard.service.ts new file mode 100644 index 0000000000..44da69f9c7 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/can-deactivate-guard.service.ts @@ -0,0 +1,15 @@ +// #docregion +import { Injectable } from '@angular/core'; +import { CanDeactivate } from '@angular/router'; +import { Observable } from 'rxjs/Observable'; + +export interface CanComponentDeactivate { + canDeactivate: () => Observable | Promise | boolean; +} + +@Injectable() +export class CanDeactivateGuard implements CanDeactivate { + canDeactivate(component: CanComponentDeactivate) { + return component.canDeactivate ? component.canDeactivate() : true; + } +} diff --git a/aio/content/examples/router/ts/src/app/compose-message.component.html b/aio/content/examples/router/ts/src/app/compose-message.component.html new file mode 100644 index 0000000000..f0b964e6ac --- /dev/null +++ b/aio/content/examples/router/ts/src/app/compose-message.component.html @@ -0,0 +1,17 @@ + +

    Contact Crisis Center

    +
    + {{ details }} +
    +
    +
    + +
    +
    + +
    +
    +

    + + +

    diff --git a/aio/content/examples/router/ts/src/app/compose-message.component.ts b/aio/content/examples/router/ts/src/app/compose-message.component.ts new file mode 100644 index 0000000000..38fd47fded --- /dev/null +++ b/aio/content/examples/router/ts/src/app/compose-message.component.ts @@ -0,0 +1,44 @@ +// #docregion +import { Component, HostBinding } from '@angular/core'; +import { Router } from '@angular/router'; + +import { slideInDownAnimation } from './animations'; + +@Component({ + moduleId: module.id, + templateUrl: './compose-message.component.html', + styles: [ ':host { position: relative; bottom: 10%; }' ], + animations: [ slideInDownAnimation ] +}) +export class ComposeMessageComponent { + @HostBinding('@routeAnimation') routeAnimation = true; + @HostBinding('style.display') display = 'block'; + @HostBinding('style.position') position = 'absolute'; + + details: string; + sending: boolean = false; + + constructor(private router: Router) {} + + send() { + this.sending = true; + this.details = 'Sending Message...'; + + setTimeout(() => { + this.sending = false; + this.closePopup(); + }, 1000); + } + + cancel() { + this.closePopup(); + } + + // #docregion closePopup + closePopup() { + // Providing a `null` value to the named outlet + // clears the contents of the named outlet + this.router.navigate([{ outlets: { popup: null }}]); + } + // #enddocregion closePopup +} diff --git a/aio/content/examples/router/ts/src/app/crisis-center/crisis-center-home.component.ts b/aio/content/examples/router/ts/src/app/crisis-center/crisis-center-home.component.ts new file mode 100644 index 0000000000..a71d485c02 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/crisis-center/crisis-center-home.component.ts @@ -0,0 +1,13 @@ +// #docregion +// #docplaster +import { Component } from '@angular/core'; + +// #docregion minus-imports +@Component({ + template: ` +

    Welcome to the Crisis Center

    + ` +}) +export class CrisisCenterHomeComponent { } +// #enddocregion minus-imports +// #enddocregion diff --git a/aio/content/examples/router/ts/src/app/crisis-center/crisis-center-routing.module.1.ts b/aio/content/examples/router/ts/src/app/crisis-center/crisis-center-routing.module.1.ts new file mode 100644 index 0000000000..e646f467d1 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/crisis-center/crisis-center-routing.module.1.ts @@ -0,0 +1,44 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { CrisisCenterHomeComponent } from './crisis-center-home.component'; +import { CrisisListComponent } from './crisis-list.component'; +import { CrisisCenterComponent } from './crisis-center.component'; +import { CrisisDetailComponent } from './crisis-detail.component'; + +// #docregion routes +const crisisCenterRoutes: Routes = [ + { + path: 'crisis-center', + component: CrisisCenterComponent, + children: [ + { + path: '', + component: CrisisListComponent, + children: [ + { + path: ':id', + component: CrisisDetailComponent + }, + { + path: '', + component: CrisisCenterHomeComponent + } + ] + } + ] + } +]; + +@NgModule({ + imports: [ + RouterModule.forChild(crisisCenterRoutes) + ], + exports: [ + RouterModule + ] +}) +export class CrisisCenterRoutingModule { } +// #enddocregion diff --git a/aio/content/examples/router/ts/src/app/crisis-center/crisis-center-routing.module.2.ts b/aio/content/examples/router/ts/src/app/crisis-center/crisis-center-routing.module.2.ts new file mode 100644 index 0000000000..9e9b514968 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/crisis-center/crisis-center-routing.module.2.ts @@ -0,0 +1,72 @@ +// #docplaster +// #docregion routes +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { CrisisCenterHomeComponent } from './crisis-center-home.component'; +import { CrisisListComponent } from './crisis-list.component'; +import { CrisisCenterComponent } from './crisis-center.component'; +import { CrisisDetailComponent } from './crisis-detail.component'; +// #enddocregion routes + +// #docregion can-deactivate-guard +import { CanDeactivateGuard } from '../can-deactivate-guard.service'; +// #enddocregion can-deactivate-guard +// #docregion crisis-detail-resolver +import { CrisisDetailResolver } from './crisis-detail-resolver.service'; + +// #enddocregion crisis-detail-resolver +// #docregion routes + +const crisisCenterRoutes: Routes = [ +// #enddocregion routes + // #docregion redirect, routes + { + path: '', + redirectTo: '/crisis-center', + pathMatch: 'full' + }, + // #enddocregion redirect, routes + // #docregion routes + { + path: 'crisis-center', + component: CrisisCenterComponent, + children: [ + { + path: '', + component: CrisisListComponent, + children: [ + { + path: ':id', + component: CrisisDetailComponent, + // #enddocregion routes + // #docregion can-deactivate-guard + canDeactivate: [CanDeactivateGuard], + // #enddocregion can-deactivate-guard + // #docregion crisis-detail-resolver + resolve: { + crisis: CrisisDetailResolver + } + // #enddocregion crisis-detail-resolver + // #docregion routes + }, + { + path: '', + component: CrisisCenterHomeComponent + } + ] + } + ] + } + // #enddocregion routes +]; + +@NgModule({ + imports: [ + RouterModule.forChild(crisisCenterRoutes) + ], + exports: [ + RouterModule + ] +}) +export class CrisisCenterRoutingModule { } diff --git a/aio/content/examples/router/ts/src/app/crisis-center/crisis-center-routing.module.3.ts b/aio/content/examples/router/ts/src/app/crisis-center/crisis-center-routing.module.3.ts new file mode 100644 index 0000000000..6d605dbe84 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/crisis-center/crisis-center-routing.module.3.ts @@ -0,0 +1,52 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { CrisisCenterHomeComponent } from './crisis-center-home.component'; +import { CrisisListComponent } from './crisis-list.component'; +import { CrisisCenterComponent } from './crisis-center.component'; +import { CrisisDetailComponent } from './crisis-detail.component'; + +// #docregion can-deactivate-guard +import { CanDeactivateGuard } from '../can-deactivate-guard.service'; + +const crisisCenterRoutes: Routes = [ + { + path: '', + redirectTo: '/crisis-center', + pathMatch: 'full' + }, + { + path: 'crisis-center', + component: CrisisCenterComponent, + children: [ + { + path: '', + component: CrisisListComponent, + children: [ + { + path: ':id', + component: CrisisDetailComponent, + canDeactivate: [CanDeactivateGuard] + }, + { + path: '', + component: CrisisCenterHomeComponent + } + ] + } + ] + } +]; + +@NgModule({ + imports: [ + RouterModule.forChild(crisisCenterRoutes) + ], + exports: [ + RouterModule + ] +}) +export class CrisisCenterRoutingModule { } +// #enddocregion diff --git a/aio/content/examples/router/ts/src/app/crisis-center/crisis-center-routing.module.4.ts b/aio/content/examples/router/ts/src/app/crisis-center/crisis-center-routing.module.4.ts new file mode 100644 index 0000000000..b7ac88e852 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/crisis-center/crisis-center-routing.module.4.ts @@ -0,0 +1,65 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { CrisisCenterHomeComponent } from './crisis-center-home.component'; +import { CrisisListComponent } from './crisis-list.component'; +import { CrisisCenterComponent } from './crisis-center.component'; +import { CrisisDetailComponent } from './crisis-detail.component'; + +import { CanDeactivateGuard } from '../can-deactivate-guard.service'; + +// #docregion crisis-detail-resolver +import { CrisisDetailResolver } from './crisis-detail-resolver.service'; + +// #enddocregion crisis-detail-resolver +const crisisCenterRoutes: Routes = [ + // #docregion redirect + { + path: '', + redirectTo: '/crisis-center', + pathMatch: 'full' + }, + // #enddocregion redirect + { + path: 'crisis-center', + component: CrisisCenterComponent, + children: [ + { + path: '', + component: CrisisListComponent, + children: [ + { + path: ':id', + component: CrisisDetailComponent, + canDeactivate: [CanDeactivateGuard], + resolve: { + crisis: CrisisDetailResolver + } + }, + { + path: '', + component: CrisisCenterHomeComponent + } + ] + } + ] + } +]; + +// #docregion crisis-detail-resolver +@NgModule({ + imports: [ + RouterModule.forChild(crisisCenterRoutes) + ], + exports: [ + RouterModule + ], + providers: [ + CrisisDetailResolver + ] +}) +export class CrisisCenterRoutingModule { } +// #enddocregion crisis-detail-resolver +// #enddocregion diff --git a/aio/content/examples/router/ts/src/app/crisis-center/crisis-center-routing.module.ts b/aio/content/examples/router/ts/src/app/crisis-center/crisis-center-routing.module.ts new file mode 100644 index 0000000000..c01d592455 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/crisis-center/crisis-center-routing.module.ts @@ -0,0 +1,53 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { CrisisCenterHomeComponent } from './crisis-center-home.component'; +import { CrisisListComponent } from './crisis-list.component'; +import { CrisisCenterComponent } from './crisis-center.component'; +import { CrisisDetailComponent } from './crisis-detail.component'; + +import { CanDeactivateGuard } from '../can-deactivate-guard.service'; +import { CrisisDetailResolver } from './crisis-detail-resolver.service'; + +const crisisCenterRoutes: Routes = [ + { + path: '', + component: CrisisCenterComponent, + children: [ + { + path: '', + component: CrisisListComponent, + children: [ + { + path: ':id', + component: CrisisDetailComponent, + canDeactivate: [CanDeactivateGuard], + resolve: { + crisis: CrisisDetailResolver + } + }, + { + path: '', + component: CrisisCenterHomeComponent + } + ] + } + ] + } +]; + +@NgModule({ + imports: [ + RouterModule.forChild(crisisCenterRoutes) + ], + exports: [ + RouterModule + ], + providers: [ + CrisisDetailResolver + ] +}) +export class CrisisCenterRoutingModule { } +// #enddocregion diff --git a/aio/content/examples/router/ts/src/app/crisis-center/crisis-center.component.ts b/aio/content/examples/router/ts/src/app/crisis-center/crisis-center.component.ts new file mode 100644 index 0000000000..31d1790f45 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/crisis-center/crisis-center.component.ts @@ -0,0 +1,14 @@ +// #docregion +// #docplaster +import { Component } from '@angular/core'; + +// #docregion minus-imports +@Component({ + template: ` +

    CRISIS CENTER

    + + ` +}) +export class CrisisCenterComponent { } +// #enddocregion minus-imports +// #enddocregion diff --git a/aio/content/examples/router/ts/src/app/crisis-center/crisis-center.module.1.ts b/aio/content/examples/router/ts/src/app/crisis-center/crisis-center.module.1.ts new file mode 100644 index 0000000000..5a3e45f58f --- /dev/null +++ b/aio/content/examples/router/ts/src/app/crisis-center/crisis-center.module.1.ts @@ -0,0 +1,36 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { CommonModule } from '@angular/common'; + +import { CrisisService } from './crisis.service'; + +import { CrisisCenterComponent } from './crisis-center.component'; +import { CrisisListComponent } from './crisis-list.component'; +import { CrisisCenterHomeComponent } from './crisis-center-home.component'; +import { CrisisDetailComponent } from './crisis-detail.component'; + +import { CrisisCenterRoutingModule } from './crisis-center-routing.module'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + CrisisCenterRoutingModule + ], + declarations: [ + CrisisCenterComponent, + CrisisListComponent, + CrisisCenterHomeComponent, + CrisisDetailComponent + ], + + // #docregion providers + providers: [ + CrisisService + ] + // #enddocregion providers +}) +export class CrisisCenterModule {} +// #enddocregion diff --git a/aio/content/examples/router/ts/src/app/crisis-center/crisis-center.module.ts b/aio/content/examples/router/ts/src/app/crisis-center/crisis-center.module.ts new file mode 100644 index 0000000000..4061ceac60 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/crisis-center/crisis-center.module.ts @@ -0,0 +1,35 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { CommonModule } from '@angular/common'; + +import { CrisisService } from './crisis.service'; + +import { CrisisCenterComponent } from './crisis-center.component'; +import { CrisisListComponent } from './crisis-list.component'; +import { CrisisCenterHomeComponent } from './crisis-center-home.component'; +import { CrisisDetailComponent } from './crisis-detail.component'; + +import { CrisisCenterRoutingModule } from './crisis-center-routing.module'; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + CrisisCenterRoutingModule + ], + declarations: [ + CrisisCenterComponent, + CrisisListComponent, + CrisisCenterHomeComponent, + CrisisDetailComponent + ], + providers: [ + CrisisService + ] +}) +// #docregion crisis-center-module-export +export class CrisisCenterModule {} +// #enddocregion crisis-center-module-export +// #enddocregion diff --git a/aio/content/examples/router/ts/src/app/crisis-center/crisis-detail-resolver.service.ts b/aio/content/examples/router/ts/src/app/crisis-center/crisis-detail-resolver.service.ts new file mode 100644 index 0000000000..94b4cd33e7 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/crisis-center/crisis-detail-resolver.service.ts @@ -0,0 +1,24 @@ +// #docregion +import { Injectable } from '@angular/core'; +import { Router, Resolve, RouterStateSnapshot, + ActivatedRouteSnapshot } from '@angular/router'; + +import { Crisis, CrisisService } from './crisis.service'; + +@Injectable() +export class CrisisDetailResolver implements Resolve { + constructor(private cs: CrisisService, private router: Router) {} + + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { + let id = route.params['id']; + + return this.cs.getCrisis(id).then(crisis => { + if (crisis) { + return crisis; + } else { // id not found + this.router.navigate(['/crisis-center']); + return null; + } + }); + } +} diff --git a/aio/content/examples/router/ts/src/app/crisis-center/crisis-detail.component.1.ts b/aio/content/examples/router/ts/src/app/crisis-center/crisis-detail.component.1.ts new file mode 100644 index 0000000000..d6fa27f629 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/crisis-center/crisis-detail.component.1.ts @@ -0,0 +1,87 @@ +// #docplaster +// #docregion +import 'rxjs/add/operator/switchMap'; +import { Component, OnInit, HostBinding } from '@angular/core'; +import { ActivatedRoute, Router, Params } from '@angular/router'; + +import { slideInDownAnimation } from '../animations'; +import { Crisis, CrisisService } from './crisis.service'; +import { DialogService } from '../dialog.service'; + +@Component({ + template: ` +
    +

    "{{ editName }}"

    +
    + {{ crisis.id }}
    +
    + + +
    +

    + + +

    +
    + `, + styles: ['input {width: 20em}'], + animations: [ slideInDownAnimation ] +}) +export class CrisisDetailComponent implements OnInit { + @HostBinding('@routeAnimation') routeAnimation = true; + @HostBinding('style.display') display = 'block'; + @HostBinding('style.position') position = 'absolute'; + + crisis: Crisis; + editName: string; + + constructor( + private service: CrisisService, + private router: Router, + private route: ActivatedRoute, + public dialogService: DialogService + ) {} + + // #docregion ngOnInit + ngOnInit() { + this.route.params + .switchMap((params: Params) => this.service.getCrisis(params['id'])) + .subscribe((crisis: Crisis) => { + if (crisis) { + this.editName = crisis.name; + this.crisis = crisis; + } else { // id not found + this.gotoCrises(); + } + }); + } + // #enddocregion ngOnInit + + cancel() { + this.gotoCrises(); + } + + save() { + this.crisis.name = this.editName; + this.gotoCrises(); + } + + canDeactivate(): Promise | boolean { + // Allow synchronous navigation (`true`) if no crisis or the crisis is unchanged + if (!this.crisis || this.crisis.name === this.editName) { + return true; + } + // Otherwise ask the user with the dialog service and return its + // promise which resolves to true or false when the user decides + return this.dialogService.confirm('Discard changes?'); + } + + gotoCrises() { + let crisisId = this.crisis ? this.crisis.id : null; + // Pass along the crisis id if available + // so that the CrisisListComponent can select that crisis. + // Add a totally useless `foo` parameter for kicks. + // Relative navigation back to the crises + this.router.navigate(['../', { id: crisisId, foo: 'foo' }], { relativeTo: this.route }); + } +} diff --git a/aio/content/examples/router/ts/src/app/crisis-center/crisis-detail.component.ts b/aio/content/examples/router/ts/src/app/crisis-center/crisis-detail.component.ts new file mode 100644 index 0000000000..f0939b47a8 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/crisis-center/crisis-detail.component.ts @@ -0,0 +1,86 @@ +// #docplaster +// #docregion +import { Component, OnInit, HostBinding } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; + +import { slideInDownAnimation } from '../animations'; +import { Crisis } from './crisis.service'; +import { DialogService } from '../dialog.service'; + +@Component({ + template: ` +
    +

    "{{ editName }}"

    +
    + {{ crisis.id }}
    +
    + + +
    +

    + + +

    +
    + `, + styles: ['input {width: 20em}'], + animations: [ slideInDownAnimation ] +}) +export class CrisisDetailComponent implements OnInit { + @HostBinding('@routeAnimation') routeAnimation = true; + @HostBinding('style.display') display = 'block'; + @HostBinding('style.position') position = 'absolute'; + + crisis: Crisis; + editName: string; + + constructor( + private route: ActivatedRoute, + private router: Router, + public dialogService: DialogService + ) {} + +// #docregion ngOnInit + ngOnInit() { + this.route.data + .subscribe((data: { crisis: Crisis }) => { + this.editName = data.crisis.name; + this.crisis = data.crisis; + }); + } +// #enddocregion ngOnInit + + // #docregion cancel-save + cancel() { + this.gotoCrises(); + } + + save() { + this.crisis.name = this.editName; + this.gotoCrises(); + } + // #enddocregion cancel-save + + // #docregion canDeactivate + canDeactivate(): Promise | boolean { + // Allow synchronous navigation (`true`) if no crisis or the crisis is unchanged + if (!this.crisis || this.crisis.name === this.editName) { + return true; + } + // Otherwise ask the user with the dialog service and return its + // promise which resolves to true or false when the user decides + return this.dialogService.confirm('Discard changes?'); + } + // #enddocregion canDeactivate + + gotoCrises() { + let crisisId = this.crisis ? this.crisis.id : null; + // Pass along the crisis id if available + // so that the CrisisListComponent can select that crisis. + // Add a totally useless `foo` parameter for kicks. + // #docregion gotoCrises-navigate + // Relative navigation back to the crises + this.router.navigate(['../', { id: crisisId, foo: 'foo' }], { relativeTo: this.route }); + // #enddocregion gotoCrises-navigate + } +} diff --git a/aio/content/examples/router/ts/src/app/crisis-center/crisis-list.component.1.ts b/aio/content/examples/router/ts/src/app/crisis-center/crisis-list.component.1.ts new file mode 100644 index 0000000000..0000dde082 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/crisis-center/crisis-list.component.1.ts @@ -0,0 +1,44 @@ +import 'rxjs/add/operator/do'; +import 'rxjs/add/operator/switchMap'; +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router, Params } from '@angular/router'; + +import { Crisis, CrisisService } from './crisis.service'; +import { Observable } from 'rxjs/Observable'; + +@Component({ + // #docregion relative-navigation-router-link + template: ` + ` + // #enddocregion relative-navigation-router-link +}) +export class CrisisListComponent implements OnInit { + crises: Observable; + selectedId: number; + + constructor( + private service: CrisisService, + private route: ActivatedRoute, + private router: Router + ) {} + + ngOnInit() { + this.crises = this.route.params + .switchMap((params: Params) => { + this.selectedId = +params['id']; + return this.service.getCrises(); + }); + } + + isSelected(crisis: Crisis) { + return crisis.id === this.selectedId; + } +} diff --git a/aio/content/examples/router/ts/src/app/crisis-center/crisis-list.component.ts b/aio/content/examples/router/ts/src/app/crisis-center/crisis-list.component.ts new file mode 100644 index 0000000000..4498a55c0f --- /dev/null +++ b/aio/content/examples/router/ts/src/app/crisis-center/crisis-list.component.ts @@ -0,0 +1,56 @@ +// #docregion +import 'rxjs/add/operator/switchMap'; +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router, Params } from '@angular/router'; + +import { Observable } from 'rxjs/Observable'; + +import { Crisis, CrisisService } from './crisis.service'; + +@Component({ + template: ` +
      +
    • + {{ crisis.id }} + {{ crisis.name }} +
    • +
    + + + ` +}) +export class CrisisListComponent implements OnInit { + crises: Observable; + selectedId: number; + + // #docregion ctor + constructor( + private service: CrisisService, + private route: ActivatedRoute, + private router: Router + ) {} + // #enddocregion ctor + + isSelected(crisis: Crisis) { + return crisis.id === this.selectedId; + } + + ngOnInit() { + this.crises = this.route.params + .switchMap((params: Params) => { + this.selectedId = +params['id']; + return this.service.getCrises(); + }); + } + + // #docregion onSelect + onSelect(crisis: Crisis) { + this.selectedId = crisis.id; + + // Navigate with relative link + this.router.navigate([crisis.id], { relativeTo: this.route }); + } + // #enddocregion onSelect +} diff --git a/aio/content/examples/router/ts/src/app/crisis-center/crisis.service.ts b/aio/content/examples/router/ts/src/app/crisis-center/crisis.service.ts new file mode 100644 index 0000000000..e7fd34d387 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/crisis-center/crisis.service.ts @@ -0,0 +1,40 @@ +// #docplaster +// #docregion , mock-crises +export class Crisis { + constructor(public id: number, public name: string) { } +} + +const CRISES = [ + new Crisis(1, 'Dragon Burning Cities'), + new Crisis(2, 'Sky Rains Great White Sharks'), + new Crisis(3, 'Giant Asteroid Heading For Earth'), + new Crisis(4, 'Procrastinators Meeting Delayed Again'), +]; +// #enddocregion mock-crises + +let crisesPromise = Promise.resolve(CRISES); + +import { Injectable } from '@angular/core'; + +@Injectable() +export class CrisisService { + + static nextCrisisId = 100; + + getCrises() { return crisesPromise; } + + getCrisis(id: number | string) { + return crisesPromise + .then(crises => crises.find(crisis => crisis.id === +id)); + } + + // #enddocregion + addCrisis(name: string) { + name = name.trim(); + if (name) { + let crisis = new Crisis(CrisisService.nextCrisisId++, name); + crisesPromise.then(crises => crises.push(crisis)); + } + } + // #docregion +} diff --git a/aio/content/examples/router/ts/src/app/crisis-list.component.ts b/aio/content/examples/router/ts/src/app/crisis-list.component.ts new file mode 100644 index 0000000000..6caa3653b5 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/crisis-list.component.ts @@ -0,0 +1,10 @@ +// Initial empty version +// #docregion +import { Component } from '@angular/core'; + +@Component({ + template: ` +

    CRISIS CENTER

    +

    Get your crisis here

    ` +}) +export class CrisisListComponent { } diff --git a/aio/content/examples/router/ts/src/app/dialog.service.ts b/aio/content/examples/router/ts/src/app/dialog.service.ts new file mode 100644 index 0000000000..0f09e4936d --- /dev/null +++ b/aio/content/examples/router/ts/src/app/dialog.service.ts @@ -0,0 +1,19 @@ +// #docregion +import { Injectable } from '@angular/core'; +/** + * Async modal dialog service + * DialogService makes this app easier to test by faking this service. + * TODO: better modal implementation that doesn't use window.confirm + */ +@Injectable() +export class DialogService { + /** + * Ask user to confirm an action. `message` explains the action and choices. + * Returns promise resolving to `true`=confirm or `false`=cancel + */ + confirm(message?: string) { + return new Promise(resolve => { + return resolve(window.confirm(message || 'Is it OK?')); + }); + }; +} diff --git a/aio/content/examples/router/ts/src/app/hero-list.component.ts b/aio/content/examples/router/ts/src/app/hero-list.component.ts new file mode 100644 index 0000000000..7a8f97ca1e --- /dev/null +++ b/aio/content/examples/router/ts/src/app/hero-list.component.ts @@ -0,0 +1,13 @@ +/// Initial empty version +// #docregion +import { Component } from '@angular/core'; + +@Component({ + template: ` +

    HEROES

    +

    Get your heroes here

    + + + ` +}) +export class HeroListComponent { } diff --git a/aio/content/examples/router/ts/src/app/heroes/hero-detail.component.1.ts b/aio/content/examples/router/ts/src/app/heroes/hero-detail.component.1.ts new file mode 100644 index 0000000000..93f0efaf0b --- /dev/null +++ b/aio/content/examples/router/ts/src/app/heroes/hero-detail.component.1.ts @@ -0,0 +1,55 @@ +// #docplaster +// #docregion +// #docregion rxjs-operator-import +import 'rxjs/add/operator/switchMap'; +// #enddocregion rxjs-operator-import +import { Component, OnInit } from '@angular/core'; +// #docregion imports +import { Router, ActivatedRoute, Params } from '@angular/router'; +// #enddocregion imports + +import { Hero, HeroService } from './hero.service'; + +@Component({ + template: ` +

    HEROES

    +
    +

    "{{ hero.name }}"

    +
    + {{ hero.id }}
    +
    + + +
    +

    + +

    +
    + ` +}) +export class HeroDetailComponent implements OnInit { + hero: Hero; + + // #docregion ctor + constructor( + private route: ActivatedRoute, + private router: Router, + private service: HeroService + ) {} + // #enddocregion ctor + + // #docregion ngOnInit + ngOnInit() { + this.route.params + // (+) converts string 'id' to a number + .switchMap((params: Params) => this.service.getHero(+params['id'])) + .subscribe((hero: Hero) => this.hero = hero); + } + // #enddocregion ngOnInit + + // #docregion gotoHeroes + gotoHeroes() { + this.router.navigate(['/heroes']); + } + // #enddocregion gotoHeroes +} diff --git a/aio/content/examples/router/ts/src/app/heroes/hero-detail.component.2.ts b/aio/content/examples/router/ts/src/app/heroes/hero-detail.component.2.ts new file mode 100644 index 0000000000..c3b69be965 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/heroes/hero-detail.component.2.ts @@ -0,0 +1,47 @@ +// Snapshot version +// #docregion +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; + +import { Hero, HeroService } from './hero.service'; + +@Component({ + template: ` +

    HEROES

    +
    +

    "{{ hero.name }}"

    +
    + {{ hero.id }}
    +
    + + +
    +

    + +

    +
    + ` +}) +export class HeroDetailComponent implements OnInit { + hero: Hero; + + constructor( + private route: ActivatedRoute, + private router: Router, + private service: HeroService + ) {} + + // #docregion snapshot + ngOnInit() { + // (+) converts string 'id' to a number + let id = +this.route.snapshot.params['id']; + + this.service.getHero(id) + .then((hero: Hero) => this.hero = hero); + } + // #enddocregion snapshot + + gotoHeroes() { + this.router.navigate(['/heroes']); + } +} diff --git a/aio/content/examples/router/ts/src/app/heroes/hero-detail.component.ts b/aio/content/examples/router/ts/src/app/heroes/hero-detail.component.ts new file mode 100644 index 0000000000..8135d37d32 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/heroes/hero-detail.component.ts @@ -0,0 +1,66 @@ +// #docplaster +// #docregion +// #docregion rxjs-operator-import +import 'rxjs/add/operator/switchMap'; +// #enddocregion rxjs-operator-import +import { Component, OnInit, HostBinding } from '@angular/core'; +import { Router, ActivatedRoute, Params } from '@angular/router'; + +import { slideInDownAnimation } from '../animations'; + +import { Hero, HeroService } from './hero.service'; + +@Component({ + template: ` +

    HEROES

    +
    +

    "{{ hero.name }}"

    +
    + {{ hero.id }}
    +
    + + +
    +

    + +

    +
    + `, + animations: [ slideInDownAnimation ] +}) +export class HeroDetailComponent implements OnInit { +// #docregion host-bindings + @HostBinding('@routeAnimation') routeAnimation = true; + @HostBinding('style.display') display = 'block'; + @HostBinding('style.position') position = 'absolute'; +// #enddocregion host-bindings + + hero: Hero; + + // #docregion ctor + constructor( + private route: ActivatedRoute, + private router: Router, + private service: HeroService + ) {} + // #enddocregion ctor + + // #docregion ngOnInit + ngOnInit() { + this.route.params + // (+) converts string 'id' to a number + .switchMap((params: Params) => this.service.getHero(+params['id'])) + .subscribe((hero: Hero) => this.hero = hero); + } + // #enddocregion ngOnInit + + // #docregion gotoHeroes + gotoHeroes() { + let heroId = this.hero ? this.hero.id : null; + // Pass along the hero id if available + // so that the HeroList component can select that hero. + // Include a junk 'foo' property for fun. + this.router.navigate(['/heroes', { id: heroId, foo: 'foo' }]); + } + // #enddocregion gotoHeroes +} diff --git a/aio/content/examples/router/ts/src/app/heroes/hero-list.component.1.ts b/aio/content/examples/router/ts/src/app/heroes/hero-list.component.1.ts new file mode 100644 index 0000000000..59552830a4 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/heroes/hero-list.component.1.ts @@ -0,0 +1,52 @@ +// #docplaster +// #docregion +// TODO SOMEDAY: Feature Componetized like HeroCenter +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +import { Hero, HeroService } from './hero.service'; + +@Component({ + // #docregion template + template: ` +

    HEROES

    +
      +
    • + {{ hero.id }} {{ hero.name }} +
    • +
    + + + ` + // #enddocregion template +}) +export class HeroListComponent implements OnInit { + heroes: Promise; + + // #docregion ctor + constructor( + private router: Router, + private service: HeroService + ) {} + // #enddocregion ctor + + ngOnInit() { + this.heroes = this.service.getHeroes(); + } + + // #docregion select + onSelect(hero: Hero) { + // #docregion nav-to-detail + this.router.navigate(['/hero', hero.id]); + // #enddocregion nav-to-detail + } + // #enddocregion select +} +// #enddocregion + +/* A link parameters array +// #docregion link-parameters-array +['/hero', hero.id] // { 15 } +// #enddocregion link-parameters-array +*/ diff --git a/aio/content/examples/router/ts/src/app/heroes/hero-list.component.ts b/aio/content/examples/router/ts/src/app/heroes/hero-list.component.ts new file mode 100644 index 0000000000..c7dcc3877e --- /dev/null +++ b/aio/content/examples/router/ts/src/app/heroes/hero-list.component.ts @@ -0,0 +1,63 @@ +// #docplaster +// #docregion +// TODO SOMEDAY: Feature Componetized like CrisisCenter +// #docregion rxjs-imports +import 'rxjs/add/operator/switchMap'; +import { Observable } from 'rxjs/Observable'; +// #enddocregion rxjs-imports +import { Component, OnInit } from '@angular/core'; +// #docregion import-router +import { Router, ActivatedRoute, Params } from '@angular/router'; +// #enddocregion import-router + +import { Hero, HeroService } from './hero.service'; + +@Component({ + // #docregion template + template: ` +

    HEROES

    +
      +
    • + {{ hero.id }} {{ hero.name }} +
    • +
    + + + ` + // #enddocregion template +}) +// #docregion ctor +export class HeroListComponent implements OnInit { + heroes: Observable; + + private selectedId: number; + + constructor( + private service: HeroService, + private route: ActivatedRoute, + private router: Router + ) {} + + ngOnInit() { + this.heroes = this.route.params + .switchMap((params: Params) => { + this.selectedId = +params['id']; + return this.service.getHeroes(); + }); + } + // #enddocregion ctor + + // #docregion isSelected + isSelected(hero: Hero) { return hero.id === this.selectedId; } + // #enddocregion isSelected + + // #docregion select + onSelect(hero: Hero) { + this.router.navigate(['/hero', hero.id]); + } + // #enddocregion select +// #docregion ctor +} +// #enddocregion diff --git a/aio/content/examples/router/ts/src/app/heroes/hero.service.ts b/aio/content/examples/router/ts/src/app/heroes/hero.service.ts new file mode 100644 index 0000000000..6e4e7bee60 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/heroes/hero.service.ts @@ -0,0 +1,27 @@ +// #docregion +import { Injectable } from '@angular/core'; + +export class Hero { + constructor(public id: number, public name: string) { } +} + +let HEROES = [ + new Hero(11, 'Mr. Nice'), + new Hero(12, 'Narco'), + new Hero(13, 'Bombasto'), + new Hero(14, 'Celeritas'), + new Hero(15, 'Magneta'), + new Hero(16, 'RubberMan') +]; + +let heroesPromise = Promise.resolve(HEROES); + +@Injectable() +export class HeroService { + getHeroes() { return heroesPromise; } + + getHero(id: number | string) { + return heroesPromise + .then(heroes => heroes.find(hero => hero.id === +id)); + } +} diff --git a/aio/content/examples/router/ts/src/app/heroes/heroes-routing.module.ts b/aio/content/examples/router/ts/src/app/heroes/heroes-routing.module.ts new file mode 100644 index 0000000000..dbee521793 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/heroes/heroes-routing.module.ts @@ -0,0 +1,24 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { HeroListComponent } from './hero-list.component'; +import { HeroDetailComponent } from './hero-detail.component'; + +const heroesRoutes: Routes = [ + { path: 'heroes', component: HeroListComponent }, +// #docregion hero-detail-route + { path: 'hero/:id', component: HeroDetailComponent } +// #enddocregion hero-detail-route +]; + +@NgModule({ + imports: [ + RouterModule.forChild(heroesRoutes) + ], + exports: [ + RouterModule + ] +}) +export class HeroRoutingModule { } +// #enddocregion diff --git a/aio/content/examples/router/ts/src/app/heroes/heroes.module.ts b/aio/content/examples/router/ts/src/app/heroes/heroes.module.ts new file mode 100644 index 0000000000..95ee64a182 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/heroes/heroes.module.ts @@ -0,0 +1,33 @@ +// #docplaster +// #docregion +// #docregion v1 +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { HeroListComponent } from './hero-list.component'; +import { HeroDetailComponent } from './hero-detail.component'; + +import { HeroService } from './hero.service'; + +// #enddocregion v1 +import { HeroRoutingModule } from './heroes-routing.module'; + +// #docregion v1 +@NgModule({ + imports: [ + CommonModule, + FormsModule, +// #enddocregion v1 + HeroRoutingModule +// #docregion v1 + ], + declarations: [ + HeroListComponent, + HeroDetailComponent + ], + providers: [ HeroService ] +}) +export class HeroesModule {} +// #enddocregion v1 +// #enddocregion diff --git a/aio/content/examples/router/ts/src/app/login-routing.module.ts b/aio/content/examples/router/ts/src/app/login-routing.module.ts new file mode 100644 index 0000000000..96d05e7972 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/login-routing.module.ts @@ -0,0 +1,24 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { AuthGuard } from './auth-guard.service'; +import { AuthService } from './auth.service'; +import { LoginComponent } from './login.component'; + +const loginRoutes: Routes = [ + { path: 'login', component: LoginComponent } +]; + +@NgModule({ + imports: [ + RouterModule.forChild(loginRoutes) + ], + exports: [ + RouterModule + ], + providers: [ + AuthGuard, + AuthService + ] +}) +export class LoginRoutingModule {} diff --git a/aio/content/examples/router/ts/src/app/login.component.1.ts b/aio/content/examples/router/ts/src/app/login.component.1.ts new file mode 100644 index 0000000000..ddee339011 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/login.component.1.ts @@ -0,0 +1,46 @@ +// #docregion +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; +import { AuthService } from './auth.service'; + +@Component({ + template: ` +

    LOGIN

    +

    {{message}}

    +

    + + +

    ` +}) +export class LoginComponent { + message: string; + + constructor(public authService: AuthService, public router: Router) { + this.setMessage(); + } + + setMessage() { + this.message = 'Logged ' + (this.authService.isLoggedIn ? 'in' : 'out'); + } + + login() { + this.message = 'Trying to log in ...'; + + this.authService.login().subscribe(() => { + this.setMessage(); + if (this.authService.isLoggedIn) { + // Get the redirect URL from our auth service + // If no redirect has been set, use the default + let redirect = this.authService.redirectUrl ? this.authService.redirectUrl : '/crisis-center/admin'; + + // Redirect the user + this.router.navigate([redirect]); + } + }); + } + + logout() { + this.authService.logout(); + this.setMessage(); + } +} diff --git a/aio/content/examples/router/ts/src/app/login.component.ts b/aio/content/examples/router/ts/src/app/login.component.ts new file mode 100644 index 0000000000..41c88f4068 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/login.component.ts @@ -0,0 +1,56 @@ +// #docregion +import { Component } from '@angular/core'; +import { Router, + NavigationExtras } from '@angular/router'; +import { AuthService } from './auth.service'; + +@Component({ + template: ` +

    LOGIN

    +

    {{message}}

    +

    + + +

    ` +}) +export class LoginComponent { + message: string; + + constructor(public authService: AuthService, public router: Router) { + this.setMessage(); + } + + setMessage() { + this.message = 'Logged ' + (this.authService.isLoggedIn ? 'in' : 'out'); + } + + login() { + this.message = 'Trying to log in ...'; + + this.authService.login().subscribe(() => { + this.setMessage(); + if (this.authService.isLoggedIn) { + // Get the redirect URL from our auth service + // If no redirect has been set, use the default + let redirect = this.authService.redirectUrl ? this.authService.redirectUrl : '/admin'; + + // #docregion preserve + // Set our navigation extras object + // that passes on our global query params and fragment + let navigationExtras: NavigationExtras = { + preserveQueryParams: true, + preserveFragment: true + }; + + // Redirect the user + this.router.navigate([redirect], navigationExtras); + // #enddocregion preserve + } + }); + } + + logout() { + this.authService.logout(); + this.setMessage(); + } +} diff --git a/aio/content/examples/router/ts/src/app/not-found.component.ts b/aio/content/examples/router/ts/src/app/not-found.component.ts new file mode 100644 index 0000000000..2e74544e17 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/not-found.component.ts @@ -0,0 +1,7 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + template: '

    Page not found

    ' +}) +export class PageNotFoundComponent {} diff --git a/aio/content/examples/router/ts/src/app/selective-preloading-strategy.ts b/aio/content/examples/router/ts/src/app/selective-preloading-strategy.ts new file mode 100644 index 0000000000..0e06cd8a38 --- /dev/null +++ b/aio/content/examples/router/ts/src/app/selective-preloading-strategy.ts @@ -0,0 +1,24 @@ +// #docregion +import 'rxjs/add/observable/of'; +import { Injectable } from '@angular/core'; +import { PreloadingStrategy, Route } from '@angular/router'; +import { Observable } from 'rxjs/Observable'; + +@Injectable() +export class SelectivePreloadingStrategy implements PreloadingStrategy { + preloadedModules: string[] = []; + + preload(route: Route, load: () => Observable): Observable { + if (route.data && route.data['preload']) { + // add the route path to our preloaded module array + this.preloadedModules.push(route.path); + + // log the route path to the console + console.log('Preloaded: ' + route.path); + + return load(); + } else { + return Observable.of(null); + } + } +} diff --git a/aio/content/examples/router/ts/src/index.html b/aio/content/examples/router/ts/src/index.html new file mode 100644 index 0000000000..0fa6453c36 --- /dev/null +++ b/aio/content/examples/router/ts/src/index.html @@ -0,0 +1,32 @@ + + + + + + + + + Angular Router + + + + + + + + + + + + + + + + loading... + + + + diff --git a/aio/content/examples/router/ts/src/main.ts b/aio/content/examples/router/ts/src/main.ts new file mode 100644 index 0000000000..f332d1d245 --- /dev/null +++ b/aio/content/examples/router/ts/src/main.ts @@ -0,0 +1,6 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/security/e2e-spec.ts b/aio/content/examples/security/e2e-spec.ts new file mode 100644 index 0000000000..23d11cd12b --- /dev/null +++ b/aio/content/examples/security/e2e-spec.ts @@ -0,0 +1,37 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, By } from 'protractor'; + +describe('Security E2E Tests', () => { + beforeAll(() => browser.get('')); + + it('sanitizes innerHTML', () => { + let interpolated = element(By.className('e2e-inner-html-interpolated')); + expect(interpolated.getText()) + .toContain('Template Syntax'); + let bound = element(By.className('e2e-inner-html-bound')); + expect(bound.getText()).toContain('Template alert("0wned") Syntax'); + let bold = element(By.css('.e2e-inner-html-bound b')); + expect(bold.getText()).toContain('Syntax'); + }); + + it('escapes untrusted URLs', () => { + let untrustedUrl = element(By.className('e2e-dangerous-url')); + expect(untrustedUrl.getAttribute('href')).toMatch(/^unsafe:javascript/); + }); + + it('binds trusted URLs', () => { + let trustedUrl = element(By.className('e2e-trusted-url')); + expect(trustedUrl.getAttribute('href')).toMatch(/^javascript:alert/); + }); + + it('escapes untrusted resource URLs', () => { + let iframe = element(By.className('e2e-iframe-untrusted-src')); + expect(iframe.getAttribute('src')).toBe(''); + }); + + it('binds trusted resource URLs', () => { + let iframe = element(By.className('e2e-iframe-trusted-src')); + expect(iframe.getAttribute('src')).toMatch(/^https:\/\/www.youtube.com\//); + }); +}); diff --git a/aio/content/examples/security/ts/example-config.json b/aio/content/examples/security/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/security/ts/plnkr.json b/aio/content/examples/security/ts/plnkr.json new file mode 100644 index 0000000000..4c9e85ce1e --- /dev/null +++ b/aio/content/examples/security/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Content Security", + "basePath": "src/", + "files": [ + "!**/*.d.ts", + "!**/*.js" + ], + "tags": ["security"] +} diff --git a/aio/content/examples/security/ts/plnkr.no-link.html b/aio/content/examples/security/ts/plnkr.no-link.html new file mode 100644 index 0000000000..1e348b3601 --- /dev/null +++ b/aio/content/examples/security/ts/plnkr.no-link.html @@ -0,0 +1,288 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/security/ts/src/app/app.component.ts b/aio/content/examples/security/ts/src/app/app.component.ts new file mode 100644 index 0000000000..c30235e8e7 --- /dev/null +++ b/aio/content/examples/security/ts/src/app/app.component.ts @@ -0,0 +1,13 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: ` +

    Security

    + + + ` +}) +export class AppComponent { +} diff --git a/aio/content/examples/security/ts/src/app/app.module.ts b/aio/content/examples/security/ts/src/app/app.module.ts new file mode 100644 index 0000000000..21d880be3b --- /dev/null +++ b/aio/content/examples/security/ts/src/app/app.module.ts @@ -0,0 +1,18 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; +import { BypassSecurityComponent } from './bypass-security.component'; +import { InnerHtmlBindingComponent } from './inner-html-binding.component'; + +@NgModule({ + imports: [ BrowserModule ], + declarations: [ + AppComponent, + BypassSecurityComponent, + InnerHtmlBindingComponent + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/security/ts/src/app/bypass-security.component.html b/aio/content/examples/security/ts/src/app/bypass-security.component.html new file mode 100644 index 0000000000..96adf058e2 --- /dev/null +++ b/aio/content/examples/security/ts/src/app/bypass-security.component.html @@ -0,0 +1,17 @@ + +

    Bypass Security Component

    + + +

    An untrusted URL:

    +

    Click me

    +

    A trusted URL:

    +

    Click me

    + + + +

    Resource URL:

    +

    Showing: {{dangerousVideoUrl}}

    +

    Trusted:

    + +

    Untrusted:

    + diff --git a/aio/content/examples/security/ts/src/app/bypass-security.component.ts b/aio/content/examples/security/ts/src/app/bypass-security.component.ts new file mode 100644 index 0000000000..769c0ba95a --- /dev/null +++ b/aio/content/examples/security/ts/src/app/bypass-security.component.ts @@ -0,0 +1,39 @@ +// #docplaster +// #docregion +import { Component } from '@angular/core'; +import { DomSanitizer, SafeResourceUrl, SafeUrl } from '@angular/platform-browser'; + +@Component({ + selector: 'bypass-security', + moduleId: module.id, + templateUrl: './bypass-security.component.html', +}) +export class BypassSecurityComponent { + dangerousUrl: string; + trustedUrl: SafeUrl; + dangerousVideoUrl: string; + videoUrl: SafeResourceUrl; + + // #docregion trust-url + constructor(private sanitizer: DomSanitizer) { + // javascript: URLs are dangerous if attacker controlled. + // Angular sanitizes them in data binding, but you can + // explicitly tell Angular to trust this value: + this.dangerousUrl = 'javascript:alert("Hi there")'; + this.trustedUrl = sanitizer.bypassSecurityTrustUrl(this.dangerousUrl); + // #enddocregion trust-url + this.updateVideoUrl('PUBnlbjZFAI'); + } + + // #docregion trust-video-url + updateVideoUrl(id: string) { + // Appending an ID to a YouTube URL is safe. + // Always make sure to construct SafeValue objects as + // close as possible to the input data so + // that it's easier to check if the value is safe. + this.dangerousVideoUrl = 'https://www.youtube.com/embed/' + id; + this.videoUrl = + this.sanitizer.bypassSecurityTrustResourceUrl(this.dangerousVideoUrl); + } + // #enddocregion trust-video-url +} diff --git a/aio/content/examples/security/ts/src/app/inner-html-binding.component.html b/aio/content/examples/security/ts/src/app/inner-html-binding.component.html new file mode 100644 index 0000000000..fe540d25fe --- /dev/null +++ b/aio/content/examples/security/ts/src/app/inner-html-binding.component.html @@ -0,0 +1,6 @@ + +

    Binding innerHTML

    +

    Bound value:

    +

    {{htmlSnippet}}

    +

    Result of binding to innerHTML:

    +

    diff --git a/aio/content/examples/security/ts/src/app/inner-html-binding.component.ts b/aio/content/examples/security/ts/src/app/inner-html-binding.component.ts new file mode 100644 index 0000000000..23c921e979 --- /dev/null +++ b/aio/content/examples/security/ts/src/app/inner-html-binding.component.ts @@ -0,0 +1,13 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'inner-html-binding', + templateUrl: './inner-html-binding.component.html', +}) +// #docregion class +export class InnerHtmlBindingComponent { + // For example, a user/attacker-controlled value from a URL. + htmlSnippet = 'Template Syntax'; +} diff --git a/aio/content/examples/security/ts/src/index.html b/aio/content/examples/security/ts/src/index.html new file mode 100644 index 0000000000..d5dd11c038 --- /dev/null +++ b/aio/content/examples/security/ts/src/index.html @@ -0,0 +1,26 @@ + + + + + Angular Content Security + + + + + + + + + + + + + + + + + Loading... + + diff --git a/aio/content/examples/security/ts/src/main.ts b/aio/content/examples/security/ts/src/main.ts new file mode 100644 index 0000000000..105b06712d --- /dev/null +++ b/aio/content/examples/security/ts/src/main.ts @@ -0,0 +1,6 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); + diff --git a/aio/content/examples/server-communication/e2e-spec.ts b/aio/content/examples/server-communication/e2e-spec.ts new file mode 100644 index 0000000000..2b6571b039 --- /dev/null +++ b/aio/content/examples/server-communication/e2e-spec.ts @@ -0,0 +1,138 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('Server Communication', function () { + + beforeAll(function () { + browser.get(''); + }); + + describe('Tour of Heroes (Observable)', function () { + + let initialHeroCount = 4; + let newHeroName = 'Mr. IQ'; + let heroCountAfterAdd = 5; + + let heroListComp = element(by.tagName('hero-list')); + let addButton = heroListComp.element(by.tagName('button')); + let heroTags = heroListComp.all(by.tagName('li')); + let heroNameInput = heroListComp.element(by.tagName('input')); + + it('should exist', function() { + expect(heroListComp).toBeDefined(' must exist'); + }); + + it('should display ' + initialHeroCount + ' heroes after init', function () { + expect(heroTags.count()).toBe(initialHeroCount); + }); + + it('should not add hero with empty name', function () { + expect(addButton).toBeDefined('"Add Hero" button must be defined'); + addButton.click().then(function() { + expect(heroTags.count()).toBe(initialHeroCount, 'No new hero should be added'); + }); + }); + + it('should add a new hero to the list', function () { + expect(heroNameInput).toBeDefined(' for hero name must exist'); + expect(addButton).toBeDefined('"Add Hero" button must be defined'); + heroNameInput.sendKeys(newHeroName); + addButton.click().then(function() { + expect(heroTags.count()).toBe(heroCountAfterAdd, 'A new hero should be added'); + let newHeroInList = heroTags.get(heroCountAfterAdd - 1).getText(); + expect(newHeroInList).toBe(newHeroName, 'The hero should be added to the end of the list'); + }); + }); + }); + + describe('Wikipedia Demo', function () { + + it('should initialize the demo with empty result list', function () { + let myWikiComp = element(by.tagName('my-wiki')); + expect(myWikiComp).toBeDefined(' must exist'); + let resultList = myWikiComp.all(by.tagName('li')); + expect(resultList.count()).toBe(0, 'result list must be empty'); + }); + + describe('Fetches after each keystroke', function () { + it('should fetch results after "B"', function(done) { + testForRefreshedResult('B', done); + }); + + it('should fetch results after "Ba"', function(done) { + testForRefreshedResult('a', done); + }); + + it('should fetch results after "Bas"', function(done) { + testForRefreshedResult('s', done); + }); + + it('should fetch results after "Basic"', function(done) { + testForRefreshedResult('ic', done); + }); + }); + + function testForRefreshedResult(keyPressed: string, done: () => void) { + testForResult('my-wiki', keyPressed, false, done); + } + }); + + describe('Smarter Wikipedia Demo', function () { + + it('should initialize the demo with empty result list', function () { + let myWikiSmartComp = element(by.tagName('my-wiki-smart')); + expect(myWikiSmartComp).toBeDefined(' must exist'); + let resultList = myWikiSmartComp.all(by.tagName('li')); + expect(resultList.count()).toBe(0, 'result list must be empty'); + }); + + it('should fetch results after "Java"', function(done) { + testForNewResult('Java', done); + }); + + it('should fetch results after "JavaS"', function(done) { + testForStaleResult('S', done); + }); + + it('should fetch results after "JavaSc"', function(done) { + testForStaleResult('c', done); + }); + + it('should fetch results after "JavaScript"', function(done) { + testForStaleResult('ript', done); + }); + + + function testForNewResult(keyPressed: string, done: () => void) { + testForResult('my-wiki-smart', keyPressed, false, done); + } + + function testForStaleResult(keyPressed: string, done: () => void) { + testForResult('my-wiki-smart', keyPressed, true, done); + } + + }); + + function testForResult(componentTagName: string, keyPressed: string, hasListBeforeSearch: boolean, done: () => void) { + let searchWait = 1000; // Wait for wikipedia but not so long that tests timeout + let wikiComponent = element(by.tagName(componentTagName)); + expect(wikiComponent).toBeDefined('<' + componentTagName + '> must exist'); + let searchBox = wikiComponent.element(by.tagName('input')); + expect(searchBox).toBeDefined(' for search must exist'); + + searchBox.sendKeys(keyPressed).then(function () { + let resultList = wikiComponent.all(by.tagName('li')); + + if (hasListBeforeSearch) { + expect(resultList.count()).toBeGreaterThan(0, 'result list should not be empty before search'); + } + + setTimeout(function() { + expect(resultList.count()).toBeGreaterThan(0, 'result list should not be empty after search'); + done(); + }, searchWait); + }); + } + +}); diff --git a/aio/content/examples/server-communication/ts/example-config.json b/aio/content/examples/server-communication/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/server-communication/ts/plnkr.json b/aio/content/examples/server-communication/ts/plnkr.json new file mode 100644 index 0000000000..fe966be012 --- /dev/null +++ b/aio/content/examples/server-communication/ts/plnkr.json @@ -0,0 +1,10 @@ +{ + "description": "Http", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1].*" + ], + "tags": ["http", "jsonp"] +} diff --git a/aio/content/examples/server-communication/ts/plnkr.no-link.html b/aio/content/examples/server-communication/ts/plnkr.no-link.html new file mode 100644 index 0000000000..c54e18ab92 --- /dev/null +++ b/aio/content/examples/server-communication/ts/plnkr.no-link.html @@ -0,0 +1,610 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/server-communication/ts/src/app/app.component.ts b/aio/content/examples/server-communication/ts/src/app/app.component.ts new file mode 100644 index 0000000000..780d044cab --- /dev/null +++ b/aio/content/examples/server-communication/ts/src/app/app.component.ts @@ -0,0 +1,13 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: ` + + + + + ` +}) +export class AppComponent { } diff --git a/aio/content/examples/server-communication/ts/src/app/app.module.1.ts b/aio/content/examples/server-communication/ts/src/app/app.module.1.ts new file mode 100644 index 0000000000..fb7012aa02 --- /dev/null +++ b/aio/content/examples/server-communication/ts/src/app/app.module.1.ts @@ -0,0 +1,23 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; +import { HttpModule, JsonpModule } from '@angular/http'; + +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule, + HttpModule, + JsonpModule + ], + declarations: [ AppComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule { +} + + + diff --git a/aio/content/examples/server-communication/ts/src/app/app.module.ts b/aio/content/examples/server-communication/ts/src/app/app.module.ts new file mode 100644 index 0000000000..fd0c720c3c --- /dev/null +++ b/aio/content/examples/server-communication/ts/src/app/app.module.ts @@ -0,0 +1,46 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; +import { HttpModule, JsonpModule } from '@angular/http'; + + +import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; +import { HeroData } from './hero-data'; +import { requestOptionsProvider } from './default-request-options.service'; + +import { AppComponent } from './app.component'; + +import { HeroListComponent } from './toh/hero-list.component'; +import { HeroListPromiseComponent } from './toh/hero-list.component.promise'; + +import { WikiComponent } from './wiki/wiki.component'; +import { WikiSmartComponent } from './wiki/wiki-smart.component'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule, + HttpModule, + JsonpModule, + // #docregion in-mem-web-api + InMemoryWebApiModule.forRoot(HeroData) + // #enddocregion in-mem-web-api + ], + declarations: [ + AppComponent, + HeroListComponent, + HeroListPromiseComponent, + WikiComponent, + WikiSmartComponent + ], +// #docregion provide-default-request-options + providers: [ requestOptionsProvider ], +// #enddocregion provide-default-request-options + bootstrap: [ AppComponent ] +}) +export class AppModule {} + + + diff --git a/aio/content/examples/server-communication/ts/src/app/default-request-options.service.ts b/aio/content/examples/server-communication/ts/src/app/default-request-options.service.ts new file mode 100644 index 0000000000..9ec52daa80 --- /dev/null +++ b/aio/content/examples/server-communication/ts/src/app/default-request-options.service.ts @@ -0,0 +1,16 @@ +// #docregion +import { Injectable } from '@angular/core'; +import { BaseRequestOptions, RequestOptions } from '@angular/http'; + +@Injectable() +export class DefaultRequestOptions extends BaseRequestOptions { + + constructor() { + super(); + + // Set the default 'Content-Type' header + this.headers.set('Content-Type', 'application/json'); + } +} + +export const requestOptionsProvider = { provide: RequestOptions, useClass: DefaultRequestOptions }; diff --git a/aio/content/examples/server-communication/ts/src/app/hero-data.ts b/aio/content/examples/server-communication/ts/src/app/hero-data.ts new file mode 100644 index 0000000000..4db6aca115 --- /dev/null +++ b/aio/content/examples/server-communication/ts/src/app/hero-data.ts @@ -0,0 +1,13 @@ +// #docregion +import { InMemoryDbService } from 'angular-in-memory-web-api'; +export class HeroData implements InMemoryDbService { + createDb() { + let heroes = [ + { id: 1, name: 'Windstorm' }, + { id: 2, name: 'Bombasto' }, + { id: 3, name: 'Magneta' }, + { id: 4, name: 'Tornado' } + ]; + return {heroes}; + } +} diff --git a/aio/content/examples/server-communication/ts/src/app/heroes.json b/aio/content/examples/server-communication/ts/src/app/heroes.json new file mode 100644 index 0000000000..dfb589066b --- /dev/null +++ b/aio/content/examples/server-communication/ts/src/app/heroes.json @@ -0,0 +1,8 @@ +{ + "data": [ + { "id": 1, "name": "Windstorm" }, + { "id": 2, "name": "Bombasto" }, + { "id": 3, "name": "Magneta" }, + { "id": 4, "name": "Tornado" } + ] +} diff --git a/aio/content/examples/server-communication/ts/src/app/toh/hero-list.component.html b/aio/content/examples/server-communication/ts/src/app/toh/hero-list.component.html new file mode 100644 index 0000000000..65ca9cfbb7 --- /dev/null +++ b/aio/content/examples/server-communication/ts/src/app/toh/hero-list.component.html @@ -0,0 +1,11 @@ + +

    Tour of Heroes ({{mode}})

    +

    Heroes:

    +
      +
    • {{hero.name}}
    • +
    + + + + +

    {{errorMessage}}

    diff --git a/aio/content/examples/server-communication/ts/src/app/toh/hero-list.component.promise.ts b/aio/content/examples/server-communication/ts/src/app/toh/hero-list.component.promise.ts new file mode 100644 index 0000000000..abe6a554e9 --- /dev/null +++ b/aio/content/examples/server-communication/ts/src/app/toh/hero-list.component.promise.ts @@ -0,0 +1,41 @@ +// #docregion +// Promise Version +import { Component, OnInit } from '@angular/core'; +import { Hero } from './hero'; +import { HeroService } from './hero.service.promise'; + +@Component({ + selector: 'hero-list-promise', + moduleId: module.id, + templateUrl: './hero-list.component.html', + providers: [ HeroService ], + styles: ['.error {color:red;}'] +}) +// #docregion component +export class HeroListPromiseComponent implements OnInit { + errorMessage: string; + heroes: Hero[]; + mode = 'Promise'; + + constructor (private heroService: HeroService) {} + + ngOnInit() { this.getHeroes(); } + + // #docregion methods + getHeroes() { + this.heroService.getHeroes() + .then( + heroes => this.heroes = heroes, + error => this.errorMessage = error); + } + + addHero (name: string) { + if (!name) { return; } + this.heroService.addHero(name) + .then( + hero => this.heroes.push(hero), + error => this.errorMessage = error); + } + // #enddocregion methods +} +// #enddocregion component diff --git a/aio/content/examples/server-communication/ts/src/app/toh/hero-list.component.ts b/aio/content/examples/server-communication/ts/src/app/toh/hero-list.component.ts new file mode 100644 index 0000000000..e28faacc0e --- /dev/null +++ b/aio/content/examples/server-communication/ts/src/app/toh/hero-list.component.ts @@ -0,0 +1,45 @@ +// #docregion +// Observable Version +import { Component, OnInit } from '@angular/core'; +import { Hero } from './hero'; +import { HeroService } from './hero.service'; + +@Component({ + moduleId: module.id, + selector: 'hero-list', + templateUrl: './hero-list.component.html', + providers: [ HeroService ], + styles: ['.error {color:red;}'] +}) +// #docregion component +export class HeroListComponent implements OnInit { + errorMessage: string; + heroes: Hero[]; + mode = 'Observable'; + + constructor (private heroService: HeroService) {} + + ngOnInit() { this.getHeroes(); } + + // #docregion methods + // #docregion getHeroes + getHeroes() { + this.heroService.getHeroes() + .subscribe( + heroes => this.heroes = heroes, + error => this.errorMessage = error); + } + // #enddocregion getHeroes + + // #docregion addHero + addHero (name: string) { + if (!name) { return; } + this.heroService.addHero(name) + .subscribe( + hero => this.heroes.push(hero), + error => this.errorMessage = error); + } + // #enddocregion addHero + // #enddocregion methods +} +// #enddocregion component diff --git a/aio/content/examples/server-communication/ts/src/app/toh/hero.service.promise.ts b/aio/content/examples/server-communication/ts/src/app/toh/hero.service.promise.ts new file mode 100644 index 0000000000..e38bd4bebf --- /dev/null +++ b/aio/content/examples/server-communication/ts/src/app/toh/hero.service.promise.ts @@ -0,0 +1,60 @@ +// #docplaster +// #docregion +// Promise Version +import { Injectable } from '@angular/core'; +import { Http, Response } from '@angular/http'; +import { Headers, RequestOptions } from '@angular/http'; + +// #docregion rxjs-imports +import 'rxjs/add/operator/toPromise'; +// #enddocregion rxjs-imports + +import { Hero } from './hero'; + +@Injectable() +export class HeroService { + // URL to web api + private heroesUrl = 'app/heroes'; + + constructor (private http: Http) {} + + // #docregion methods + getHeroes (): Promise { + return this.http.get(this.heroesUrl) + .toPromise() + .then(this.extractData) + .catch(this.handleError); + } + + addHero (name: string): Promise { + let headers = new Headers({ 'Content-Type': 'application/json' }); + let options = new RequestOptions({ headers: headers }); + + return this.http.post(this.heroesUrl, { name }, options) + .toPromise() + .then(this.extractData) + .catch(this.handleError); + } + + private extractData(res: Response) { + let body = res.json(); + return body.data || { }; + } + + private handleError (error: Response | any) { + // In a real world app, we might use a remote logging infrastructure + let errMsg: string; + if (error instanceof Response) { + const body = error.json() || ''; + const err = body.error || JSON.stringify(body); + errMsg = `${error.status} - ${error.statusText || ''} ${err}`; + } else { + errMsg = error.message ? error.message : error.toString(); + } + console.error(errMsg); + return Promise.reject(errMsg); + } + +// #enddocregion methods +} +// #enddocregion diff --git a/aio/content/examples/server-communication/ts/src/app/toh/hero.service.ts b/aio/content/examples/server-communication/ts/src/app/toh/hero.service.ts new file mode 100644 index 0000000000..804883d0e8 --- /dev/null +++ b/aio/content/examples/server-communication/ts/src/app/toh/hero.service.ts @@ -0,0 +1,80 @@ +// #docplaster +// #docregion +// Observable Version +// #docregion v1 +import { Injectable } from '@angular/core'; +import { Http, Response } from '@angular/http'; +// #enddocregion v1 +// #docregion import-request-options +import { Headers, RequestOptions } from '@angular/http'; +// #enddocregion import-request-options +// #docregion v1 + +// #docregion rxjs-imports +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/operator/catch'; +import 'rxjs/add/operator/map'; +// #enddocregion rxjs-imports + +import { Hero } from './hero'; + +@Injectable() +export class HeroService { + // #docregion endpoint + private heroesUrl = 'app/heroes'; // URL to web API + // #enddocregion endpoint + + // #docregion ctor + constructor (private http: Http) {} + // #enddocregion ctor + + // #docregion methods, error-handling, http-get + getHeroes (): Observable { + return this.http.get(this.heroesUrl) + .map(this.extractData) + .catch(this.handleError); + } + // #enddocregion error-handling, http-get, v1 + + // #docregion addhero, addhero-sig + addHero (name: string): Observable { + // #enddocregion addhero-sig + let headers = new Headers({ 'Content-Type': 'application/json' }); + let options = new RequestOptions({ headers: headers }); + + return this.http.post(this.heroesUrl, { name }, options) + .map(this.extractData) + .catch(this.handleError); + } + // #enddocregion addhero + + // #docregion v1, extract-data + private extractData(res: Response) { + let body = res.json(); + return body.data || { }; + } + // #enddocregion extract-data + // #docregion error-handling + + private handleError (error: Response | any) { + // In a real world app, we might use a remote logging infrastructure + let errMsg: string; + if (error instanceof Response) { + const body = error.json() || ''; + const err = body.error || JSON.stringify(body); + errMsg = `${error.status} - ${error.statusText || ''} ${err}`; + } else { + errMsg = error.message ? error.message : error.toString(); + } + console.error(errMsg); + return Observable.throw(errMsg); + } + // #enddocregion error-handling, methods +} +// #enddocregion + +/* + // #docregion endpoint-json + private heroesUrl = 'app/heroes.json'; // URL to JSON file + // #enddocregion endpoint-json +*/ diff --git a/aio/content/examples/server-communication/ts/src/app/toh/hero.ts b/aio/content/examples/server-communication/ts/src/app/toh/hero.ts new file mode 100644 index 0000000000..09b8d295ce --- /dev/null +++ b/aio/content/examples/server-communication/ts/src/app/toh/hero.ts @@ -0,0 +1,6 @@ +// #docregion +export class Hero { + constructor( + public id: number, + public name: string) { } +} diff --git a/aio/content/examples/server-communication/ts/src/app/wiki/wiki-smart.component.ts b/aio/content/examples/server-communication/ts/src/app/wiki/wiki-smart.component.ts new file mode 100644 index 0000000000..ec95472b98 --- /dev/null +++ b/aio/content/examples/server-communication/ts/src/app/wiki/wiki-smart.component.ts @@ -0,0 +1,48 @@ +/* tslint:disable: member-ordering forin */ +// #docplaster +// #docregion +import { Component, OnInit } from '@angular/core'; + +// #docregion rxjs-imports +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/operator/debounceTime'; +import 'rxjs/add/operator/distinctUntilChanged'; +import 'rxjs/add/operator/switchMap'; + +// #docregion import-subject +import { Subject } from 'rxjs/Subject'; +// #enddocregion import-subject + +import { WikipediaService } from './wikipedia.service'; + +@Component({ + moduleId: module.id, + selector: 'my-wiki-smart', + template: ` +

    Smarter Wikipedia Demo

    +

    Search when typing stops

    + +
      +
    • {{item}}
    • +
    `, + providers: [ WikipediaService ] +}) +export class WikiSmartComponent implements OnInit { + items: Observable; + + constructor (private wikipediaService: WikipediaService) {} + + // #docregion subject + private searchTermStream = new Subject(); + search(term: string) { this.searchTermStream.next(term); } + // #enddocregion subject + + ngOnInit() { + // #docregion observable-operators + this.items = this.searchTermStream + .debounceTime(300) + .distinctUntilChanged() + .switchMap((term: string) => this.wikipediaService.search(term)); + // #enddocregion observable-operators + } +} diff --git a/aio/content/examples/server-communication/ts/src/app/wiki/wiki.component.ts b/aio/content/examples/server-communication/ts/src/app/wiki/wiki.component.ts new file mode 100644 index 0000000000..4230df12a1 --- /dev/null +++ b/aio/content/examples/server-communication/ts/src/app/wiki/wiki.component.ts @@ -0,0 +1,26 @@ +// #docregion +import { Component } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; + +import { WikipediaService } from './wikipedia.service'; + +@Component({ + selector: 'my-wiki', + template: ` +

    Wikipedia Demo

    +

    Search after each keystroke

    + +
      +
    • {{item}}
    • +
    `, + providers: [ WikipediaService ] +}) +export class WikiComponent { + items: Observable; + + constructor (private wikipediaService: WikipediaService) { } + + search (term: string) { + this.items = this.wikipediaService.search(term); + } +} diff --git a/aio/content/examples/server-communication/ts/src/app/wiki/wikipedia.service.1.ts b/aio/content/examples/server-communication/ts/src/app/wiki/wikipedia.service.1.ts new file mode 100644 index 0000000000..5cbcb7d707 --- /dev/null +++ b/aio/content/examples/server-communication/ts/src/app/wiki/wikipedia.service.1.ts @@ -0,0 +1,26 @@ +// Create the query string by hand +// #docregion +import { Injectable } from '@angular/core'; +import { Jsonp } from '@angular/http'; + +import 'rxjs/add/operator/map'; + +@Injectable() +export class WikipediaService { + constructor(private jsonp: Jsonp) { } + + // TODO: Add error handling + search(term: string) { + + let wikiUrl = 'http://en.wikipedia.org/w/api.php'; + + // #docregion query-string + let queryString = + `?search=${term}&action=opensearch&format=json&callback=JSONP_CALLBACK`; + + return this.jsonp + .get(wikiUrl + queryString) + .map(response => response.json()[1]); + // #enddocregion query-string + } +} diff --git a/aio/content/examples/server-communication/ts/src/app/wiki/wikipedia.service.ts b/aio/content/examples/server-communication/ts/src/app/wiki/wikipedia.service.ts new file mode 100644 index 0000000000..a38167d1c6 --- /dev/null +++ b/aio/content/examples/server-communication/ts/src/app/wiki/wikipedia.service.ts @@ -0,0 +1,30 @@ +// #docregion +import { Injectable } from '@angular/core'; +import { Jsonp, URLSearchParams } from '@angular/http'; + +import 'rxjs/add/operator/map'; + +@Injectable() +export class WikipediaService { + constructor(private jsonp: Jsonp) {} + + search (term: string) { + + let wikiUrl = 'http://en.wikipedia.org/w/api.php'; + + // #docregion search-parameters + let params = new URLSearchParams(); + params.set('search', term); // the user's search value + params.set('action', 'opensearch'); + params.set('format', 'json'); + params.set('callback', 'JSONP_CALLBACK'); + // #enddocregion search-parameters + + // #docregion call-jsonp + // TODO: Add error handling + return this.jsonp + .get(wikiUrl, { search: params }) + .map(response => response.json()[1]); + // #enddocregion call-jsonp + } +} diff --git a/aio/content/examples/server-communication/ts/src/index.html b/aio/content/examples/server-communication/ts/src/index.html new file mode 100644 index 0000000000..5c51f21c91 --- /dev/null +++ b/aio/content/examples/server-communication/ts/src/index.html @@ -0,0 +1,27 @@ + + + + + Angular Http Demo + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/server-communication/ts/src/main.ts b/aio/content/examples/server-communication/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/aio/content/examples/server-communication/ts/src/main.ts @@ -0,0 +1,5 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/setup/e2e-spec.ts b/aio/content/examples/setup/e2e-spec.ts new file mode 100644 index 0000000000..73921707ee --- /dev/null +++ b/aio/content/examples/setup/e2e-spec.ts @@ -0,0 +1,17 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('QuickStart E2E Tests', function () { + + let expectedMsg = 'Hello Angular'; + + beforeEach(function () { + browser.get(''); + }); + + it(`should display: ${expectedMsg}`, function () { + expect(element(by.css('h1')).getText()).toEqual(expectedMsg); + }); + +}); diff --git a/aio/content/examples/setup/ts/example-config.json b/aio/content/examples/setup/ts/example-config.json new file mode 100644 index 0000000000..6af3884d8d --- /dev/null +++ b/aio/content/examples/setup/ts/example-config.json @@ -0,0 +1,3 @@ +{ + "unittesting": true +} diff --git a/aio/content/examples/setup/ts/non-essential-files.txt b/aio/content/examples/setup/ts/non-essential-files.txt new file mode 100644 index 0000000000..6389af193c --- /dev/null +++ b/aio/content/examples/setup/ts/non-essential-files.txt @@ -0,0 +1,13 @@ +.git +.gitignore +.travis.yml +*.spec*.ts +CHANGELOG.md +e2e +favicon.ico +karma.conf.js +karma-test-shim.js +LICENSE +non-essential-files.txt +protractor.config.js +README.md diff --git a/aio/content/examples/setup/ts/plnkr.json b/aio/content/examples/setup/ts/plnkr.json new file mode 100644 index 0000000000..22b528eec5 --- /dev/null +++ b/aio/content/examples/setup/ts/plnkr.json @@ -0,0 +1,13 @@ +{ + "description": "QuickStart Setup", + "basePath": "src/", + "files": [ + "app/app.component.ts", + "app/app.module.ts", + "index.html", + "main.ts", + "styles.css" + ], + "open": "app/app.component.ts", + "tags": ["quickstart", "setup", "seed"] +} diff --git a/aio/content/examples/setup/ts/plnkr.no-link.html b/aio/content/examples/setup/ts/plnkr.no-link.html new file mode 100644 index 0000000000..0d03a82e7a --- /dev/null +++ b/aio/content/examples/setup/ts/plnkr.no-link.html @@ -0,0 +1,189 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/setup/ts/quickstart-specs.plnkr.json b/aio/content/examples/setup/ts/quickstart-specs.plnkr.json new file mode 100644 index 0000000000..a7dfedb595 --- /dev/null +++ b/aio/content/examples/setup/ts/quickstart-specs.plnkr.json @@ -0,0 +1,13 @@ +{ + "description": "Quickstart AppComponent Testing", + "basePath": "src/", + "files":[ + "browser-test-shim.js", + "app/app.component.ts", + "app/app.component.spec.ts", + "quickstart-specs.html" + ], + "main": "quickstart-specs.html", + "open": "app/app.component.spec.ts", + "tags": ["quickstart", "setup", "testing"] +} diff --git a/aio/content/examples/setup/ts/quickstart-specs.plnkr.no-link.html b/aio/content/examples/setup/ts/quickstart-specs.plnkr.no-link.html new file mode 100644 index 0000000000..d1dbb02d71 --- /dev/null +++ b/aio/content/examples/setup/ts/quickstart-specs.plnkr.no-link.html @@ -0,0 +1,264 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/setup/ts/src/app/app.component.spec.ts b/aio/content/examples/setup/ts/src/app/app.component.spec.ts new file mode 100644 index 0000000000..7769024464 --- /dev/null +++ b/aio/content/examples/setup/ts/src/app/app.component.spec.ts @@ -0,0 +1,33 @@ +import { AppComponent } from './app.component'; + +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +describe('AppComponent', function () { + let de: DebugElement; + let comp: AppComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ AppComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AppComponent); + comp = fixture.componentInstance; + de = fixture.debugElement.query(By.css('h1')); + }); + + it('should create component', () => expect(comp).toBeDefined() ); + + it('should have expected

    text', () => { + fixture.detectChanges(); + const h1 = de.nativeElement; + expect(h1.innerText).toMatch(/angular/i, + '

    should say something about "Angular"'); + }); +}); diff --git a/aio/content/examples/setup/ts/src/app/app.component.ts b/aio/content/examples/setup/ts/src/app/app.component.ts new file mode 100644 index 0000000000..1ef28fc5c4 --- /dev/null +++ b/aio/content/examples/setup/ts/src/app/app.component.ts @@ -0,0 +1,8 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: `

    Hello {{name}}

    ` +}) +export class AppComponent { name = 'Angular'; } diff --git a/aio/content/examples/setup/ts/src/app/app.module.ts b/aio/content/examples/setup/ts/src/app/app.module.ts new file mode 100644 index 0000000000..50a0e6eb47 --- /dev/null +++ b/aio/content/examples/setup/ts/src/app/app.module.ts @@ -0,0 +1,11 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ BrowserModule ], + declarations: [ AppComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/setup/ts/src/index.html b/aio/content/examples/setup/ts/src/index.html new file mode 100644 index 0000000000..58e5112308 --- /dev/null +++ b/aio/content/examples/setup/ts/src/index.html @@ -0,0 +1,28 @@ + + + + + Angular Quickstart Seed + + + + + + + + + + + + + + + + + + + + + diff --git a/aio/content/examples/setup/ts/src/main.ts b/aio/content/examples/setup/ts/src/main.ts new file mode 100644 index 0000000000..02d58dceac --- /dev/null +++ b/aio/content/examples/setup/ts/src/main.ts @@ -0,0 +1,5 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/setup/ts/src/quickstart-specs.html b/aio/content/examples/setup/ts/src/quickstart-specs.html new file mode 100644 index 0000000000..9bc81ccf2e --- /dev/null +++ b/aio/content/examples/setup/ts/src/quickstart-specs.html @@ -0,0 +1,41 @@ + + + + + + + 1st Specs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aio/content/examples/structural-directives/e2e-spec.ts b/aio/content/examples/structural-directives/e2e-spec.ts new file mode 100644 index 0000000000..51f30bf0bb --- /dev/null +++ b/aio/content/examples/structural-directives/e2e-spec.ts @@ -0,0 +1,58 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('Structural Directives', function () { + + beforeAll(function () { + browser.get(''); + }); + + it('first div should show hero name with *ngIf', function () { + const allDivs = element.all(by.tagName('div')); + expect(allDivs.get(0).getText()).toEqual('Mr. Nice'); + }); + + it('first li should show hero name with *ngFor', function () { + const allLis = element.all(by.tagName('li')); + expect(allLis.get(0).getText()).toEqual('Mr. Nice'); + }); + + it('ngSwitch have three instances', function () { + const happyHeroEls = element.all(by.tagName('happy-hero')); + expect(happyHeroEls.count()).toEqual(3); + }); + + it('should toggle *ngIf="hero" with a button', function () { + const toggleHeroButton = element.all(by.cssContainingText('button', 'Toggle hero')).get(0); + const paragraph = element.all(by.cssContainingText('p', 'I turned the corner')); + expect(paragraph.get(0).getText()).toContain('I waved'); + toggleHeroButton.click().then(() => { + expect(paragraph.get(0).getText()).not.toContain('I waved'); + }); + }); + + it('should have only one "Hip!" (the other is erased)', function () { + const paragraph = element.all(by.cssContainingText('p', 'Hip!')); + expect(paragraph.count()).toEqual(1); + }); + + it('myUnless should show 3 paragraph (A)s and (B)s at the start', function () { + const paragraph = element.all(by.css('p.unless')); + expect(paragraph.count()).toEqual(3); + for (let i = 0; i < 3; i++) { + expect(paragraph.get(i).getText()).toContain('(A)'); + } + }); + + it('myUnless should show 1 paragraph (B) after toggling condition', function () { + const toggleConditionButton = element.all(by.cssContainingText('button', 'Toggle condition')).get(0); + const paragraph = element.all(by.css('p.unless')); + + toggleConditionButton.click().then(() => { + expect(paragraph.count()).toEqual(1); + expect(paragraph.get(0).getText()).toContain('(B)'); + }); + }); +}); + diff --git a/aio/content/examples/structural-directives/ts/example-config.json b/aio/content/examples/structural-directives/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/structural-directives/ts/plnkr.json b/aio/content/examples/structural-directives/ts/plnkr.json new file mode 100644 index 0000000000..58a26d7b4f --- /dev/null +++ b/aio/content/examples/structural-directives/ts/plnkr.json @@ -0,0 +1,13 @@ +{ + "description": "Structural directives", + "basePath": "src/", + "files": [ + "!**/*.d.ts", + "!**/*.js", + "!app/scrap.txt" + ], + "tags": [ + "structural", "directives", "template", "ngIf", + "ngSwitch", "ngFor" + ] +} diff --git a/aio/content/examples/structural-directives/ts/plnkr.no-link.html b/aio/content/examples/structural-directives/ts/plnkr.no-link.html new file mode 100644 index 0000000000..1f7888664f --- /dev/null +++ b/aio/content/examples/structural-directives/ts/plnkr.no-link.html @@ -0,0 +1,612 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/structural-directives/ts/src/app/app.component.css b/aio/content/examples/structural-directives/ts/src/app/app.component.css new file mode 100644 index 0000000000..a3ed305907 --- /dev/null +++ b/aio/content/examples/structural-directives/ts/src/app/app.component.css @@ -0,0 +1,70 @@ +/* #docregion */ +button { + min-width: 100px; + font-size: 100%; +} + +.box { + border: 1px solid gray; + max-width: 600px; + padding: 4px; +} +.choices { + font-style: italic; +} + +code, .code { + background-color: #eee; + color: black; + font-family: Courier, sans-serif; + font-size: 85%; +} + +div.code { + width: 400px; +} + +.heroic { + font-size: 150%; + font-weight: bold; +} + +hr { + margin: 40px 0 +} + +.odd { + background-color: palegoldenrod; +} + +td, th { + text-align: left; + vertical-align: top; +} + +/* #docregion p-span */ +p span { color: red; font-size: 70%; } +/* #enddocregion p-span */ + +.unless { + border: 2px solid; + padding: 6px; +} + +p.unless { + width: 500px; +} + +button.a, span.a, .unless.a { + color: red; + border-color: gold; + background-color: yellow; + font-size: 100%; +} + +button.b, span.b, .unless.b { + color: black; + border-color: green; + background-color: lightgreen; + font-size: 100%; +} diff --git a/aio/content/examples/structural-directives/ts/src/app/app.component.html b/aio/content/examples/structural-directives/ts/src/app/app.component.html new file mode 100644 index 0000000000..2758553e39 --- /dev/null +++ b/aio/content/examples/structural-directives/ts/src/app/app.component.html @@ -0,0 +1,252 @@ + + +

    Structural Directives

    + +

    Conditional display of hero

    + +
    + +
    {{hero.name}}
    + +
    + +

    List of heroes

    + + +
      + +
    • {{hero.name}}
    • + +
    + + + +
    + +

    NgIf

    + + +

    + Expression is true and ngIf is true. + This paragraph is in the DOM. +

    +

    + Expression is false and ngIf is false. + This paragraph is not in the DOM. +

    + + + +

    + Expression sets display to "block"" . + This paragraph is visible. +

    +

    + Expression sets display to "none" . + This paragraph is hidden but still in the DOM. +

    + + +

    NgIf with template

    +

    <template> element

    + + + + +

    template attribute

    + +
    {{hero.name}}
    + + +
    + +

    <ng-container>

    + +

    *ngIf with a <ng-container>

    + + + + +

    + I turned the corner + + and saw {{hero.name}}. I waved + + and continued on my way. +

    + + +

    + I turned the corner + + and saw {{hero.name}}. I waved + + and continued on my way. +

    + + +

    <select> with <span>

    + +
    + Pick your favorite hero + () +
    + + + +

    <select> with <ng-container>

    + +
    + Pick your favorite hero + () +
    + + +

    + +
    + +

    NgFor

    + +
    + +

    <div *ngFor="let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd">

    + +
    + ({{i}}) {{hero.name}} +
    + + +

    <div template="ngFor let hero of heroes; let i=index; let odd=odd; trackBy: trackById" [class.odd]="odd">

    + +
    + ({{i}}) {{hero.name}} +
    + + +

    <template ngFor let-hero [ngForOf]="heroes" let-i="index" let-odd="odd" [ngForTrackBy]="trackById">

    + + + + +
    +
    + +

    NgSwitch

    + +
    Pick your favorite hero
    +

    + + + + +

    + +

    NgSwitch

    + + +
    + + + + +
    + + +

    NgSwitch with template attribute

    + +
    + + + + +
    + + +

    NgSwitch with <template>

    + +
    + + + + +
    + + +
    + +

    <template>

    + +

    Hip!

    + +

    Hooray!

    + + +
    + +

    UnlessDirective

    +

    + The condition is currently + {{condition}}. + +

    + +

    + (A) This paragraph is displayed because the condition is false. +

    + +

    + (B) Although the condition is true, + this paragraph is displayed because myUnless is set to false. +

    + + + +

    UnlessDirective with template

    + + +

    Show this sentence unless the condition is true.

    + + +

    + (A) <p template="myUnless condition" class="code unless"> +

    + + + diff --git a/aio/content/examples/structural-directives/ts/src/app/app.component.ts b/aio/content/examples/structural-directives/ts/src/app/app.component.ts new file mode 100644 index 0000000000..5fd9dc417f --- /dev/null +++ b/aio/content/examples/structural-directives/ts/src/app/app.component.ts @@ -0,0 +1,24 @@ +// #docregion +import { Component } from '@angular/core'; + +import { Hero, heroes } from './hero'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: './app.component.html', + styleUrls: [ './app.component.css' ] +}) +export class AppComponent { + heroes = heroes; + hero = this.heroes[0]; + + condition = false; + logs: string[] = []; + showSad = true; + status = 'ready'; + + // #docregion trackByHero + trackById(index: number, hero: Hero): number { return hero.id; } + // #enddocregion trackByHero +} diff --git a/aio/content/examples/structural-directives/ts/src/app/app.module.ts b/aio/content/examples/structural-directives/ts/src/app/app.module.ts new file mode 100644 index 0000000000..b6ffb456c9 --- /dev/null +++ b/aio/content/examples/structural-directives/ts/src/app/app.module.ts @@ -0,0 +1,19 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; +import { heroSwitchComponents } from './hero-switch.components'; +import { UnlessDirective } from './unless.directive'; + +@NgModule({ + imports: [ BrowserModule, FormsModule ], + declarations: [ + AppComponent, + heroSwitchComponents, + UnlessDirective + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/structural-directives/ts/src/app/hero-switch.components.ts b/aio/content/examples/structural-directives/ts/src/app/hero-switch.components.ts new file mode 100644 index 0000000000..bb31563611 --- /dev/null +++ b/aio/content/examples/structural-directives/ts/src/app/hero-switch.components.ts @@ -0,0 +1,43 @@ +// #docregion +import { Component, Input } from '@angular/core'; +import { Hero } from './hero'; + +@Component({ + selector: 'happy-hero', + template: `Wow. You like {{hero.name}}. What a happy hero ... just like you.` +}) +export class HappyHeroComponent { + @Input() hero: Hero; +} + +@Component({ + selector: 'sad-hero', + template: `You like {{hero.name}}? Such a sad hero. Are you sad too?` +}) +export class SadHeroComponent { + @Input() hero: Hero; +} + +@Component({ + selector: 'confused-hero', + template: `Are you as confused as {{hero.name}}?` +}) +export class ConfusedHeroComponent { + @Input() hero: Hero; +} + +@Component({ + selector: 'unknown-hero', + template: `{{message}}` +}) +export class UnknownHeroComponent { + @Input() hero: Hero; + get message() { + return this.hero && this.hero.name ? + `${this.hero.name} is strange and mysterious.` : + 'Are you feeling indecisive?'; + } +} + +export const heroSwitchComponents = + [ HappyHeroComponent, SadHeroComponent, ConfusedHeroComponent, UnknownHeroComponent ]; diff --git a/aio/content/examples/structural-directives/ts/src/app/hero.ts b/aio/content/examples/structural-directives/ts/src/app/hero.ts new file mode 100644 index 0000000000..89f0cbfdf8 --- /dev/null +++ b/aio/content/examples/structural-directives/ts/src/app/hero.ts @@ -0,0 +1,13 @@ +// #docregion +export class Hero { + id: number; + name: string; + emotion?: string; +} + +export const heroes: Hero[] = [ + { id: 1, name: 'Mr. Nice', emotion: 'happy'}, + { id: 2, name: 'Narco', emotion: 'sad' }, + { id: 3, name: 'Windstorm', emotion: 'confused' }, + { id: 4, name: 'Magneta'} +]; diff --git a/aio/content/examples/structural-directives/ts/src/app/scrap.txt b/aio/content/examples/structural-directives/ts/src/app/scrap.txt new file mode 100644 index 0000000000..f2e4766bdb --- /dev/null +++ b/aio/content/examples/structural-directives/ts/src/app/scrap.txt @@ -0,0 +1,21 @@ +// interesting but unused code + heroChooser(picker: HTMLFieldSetElement) { + let choices = picker.children; + this.favoriteHero = undefined; + for (let i = 0; i < choices.length; i++) { + let choice = choices[i].children[0] as HTMLInputElement; + if (choice.checked) { this.favoriteHero = this.heroes[i]; } + } + } + + +

    Switch with *ngFor repeated switchCases using <ng-container>

    + +
    + Your favorite hero is ... + + {{hero.name}} + + None of the above +
    + diff --git a/aio/content/examples/structural-directives/ts/src/app/unless.directive.ts b/aio/content/examples/structural-directives/ts/src/app/unless.directive.ts new file mode 100644 index 0000000000..19d48b4453 --- /dev/null +++ b/aio/content/examples/structural-directives/ts/src/app/unless.directive.ts @@ -0,0 +1,55 @@ +// #docplaster +// #docregion +// #docregion no-docs +// #docregion skeleton +import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core'; + +// #enddocregion skeleton +/** + * Add the template content to the DOM unless the condition is true. +// #enddocregion no-docs + * + * If the expression assigned to `myUnless` evaluates to a truthy value + * then the templated elements are removed removed from the DOM, + * the templated elements are (re)inserted into the DOM. + * + *
    + * Congrats! Everything is great! + *
    + * + * ### Syntax + * * + * - `
    ...
    ` + * - `
    ...
    ` + * - `` + * +// #docregion no-docs + */ +// #docregion skeleton +@Directive({ selector: '[myUnless]'}) +export class UnlessDirective { + // #enddocregion skeleton + private hasView = false; + + // #docregion ctor + constructor( + private templateRef: TemplateRef, + private viewContainer: ViewContainerRef) { } + // #enddocregion ctor + + // #docregion set + @Input() set myUnless(condition: boolean) { + if (!condition && !this.hasView) { + this.viewContainer.createEmbeddedView(this.templateRef); + this.hasView = true; + } else if (condition && this.hasView) { + this.viewContainer.clear(); + this.hasView = false; + } + } + // #enddocregion set + // #docregion skeleton +} +// #enddocregion skeleton +// #enddocregion no-docs +// #enddocregion diff --git a/aio/content/examples/structural-directives/ts/src/index.html b/aio/content/examples/structural-directives/ts/src/index.html new file mode 100644 index 0000000000..ce7a33266d --- /dev/null +++ b/aio/content/examples/structural-directives/ts/src/index.html @@ -0,0 +1,27 @@ + + + + + Angular Structural Directives + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/structural-directives/ts/src/main.ts b/aio/content/examples/structural-directives/ts/src/main.ts new file mode 100644 index 0000000000..105b06712d --- /dev/null +++ b/aio/content/examples/structural-directives/ts/src/main.ts @@ -0,0 +1,6 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); + diff --git a/aio/content/examples/style-guide/e2e-spec.ts b/aio/content/examples/style-guide/e2e-spec.ts new file mode 100644 index 0000000000..d143ae5573 --- /dev/null +++ b/aio/content/examples/style-guide/e2e-spec.ts @@ -0,0 +1,191 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('Style Guide', function () { + it('01-01', function () { + browser.get('#/01-01'); + + let pre = element(by.tagName('toh-heroes > pre')); + expect(pre.getText()).toContain('Bombasto'); + expect(pre.getText()).toContain('Tornado'); + expect(pre.getText()).toContain('Magneta'); + }); + + it('02-07', function () { + browser.get('#/02-07'); + + let hero = element(by.tagName('toh-hero > div')); + let users = element(by.tagName('admin-users > div')); + + expect(hero.getText()).toBe('hero component'); + expect(users.getText()).toBe('users component'); + }); + + it('02-08', function () { + browser.get('#/02-08'); + + let input = element(by.tagName('input[tohvalidate]')); + expect(input.isPresent()).toBe(true); + }); + + it('03-01', function () { + browser.get('#/03-01'); + + let div = element(by.tagName('sg-app > div')); + expect(div.getText()).toBe('The expected error is 42'); + }); + + it('03-02', function () { + browser.get('#/03-02'); + + let divs = element.all(by.tagName('sg-app > div')); + expect(divs.get(0).getText()).toBe('Heroes url: api/heroes'); + expect(divs.get(1).getText()).toBe('Villains url: api/villains'); + }); + + it('03-03', function () { + browser.get('#/03-03'); + + let div = element(by.tagName('sg-app > div')); + expect(div.getText()).toBe('Our hero is RubberMan and He is so elastic'); + }); + + it('03-04', function () { + browser.get('#/03-04'); + + let buttons = element.all(by.tagName('sg-app > button')); + expect(buttons.get(0).getText()).toBe('Show toast'); + expect(buttons.get(1).getText()).toBe('Hide toast'); + }); + + it('03-06', function () { + browser.get('#/03-06'); + + let div = element(by.tagName('sg-app > div')); + expect(div.getText()).toBe('Actual favorite: Windstorm'); + + let lis = element.all(by.tagName('sg-app > ul > li')); + expect(lis.get(0).getText()).toBe('Windstorm'); + expect(lis.get(1).getText()).toBe('Bombasto'); + expect(lis.get(2).getText()).toBe('Magneta'); + expect(lis.get(3).getText()).toBe('Tornado'); + }); + + it('04-10', function () { + browser.get('#/04-10'); + + let div = element(by.tagName('sg-app > toh-heroes > div')); + expect(div.getText()).toBe('This is heroes component'); + }); + + it('05-02', function () { + browser.get('#/05-02'); + + let button = element(by.tagName('sg-app > toh-hero-button > button')); + expect(button.getText()).toBe('Hero button'); + }); + + it('05-03', function () { + browser.get('#/05-03'); + + let button = element(by.tagName('sg-app > toh-hero-button > button')); + expect(button.getText()).toBe('Hero button'); + }); + + it('05-04', function () { + browser.get('#/05-04'); + + let h2 = element(by.tagName('sg-app > toh-heroes > div > h2')); + expect(h2.getText()).toBe('My Heroes'); + }); + + it('05-12', function () { + browser.get('#/05-12'); + + let button = element(by.tagName('sg-app > toh-hero-button > button')); + expect(button.getText()).toBe('OK'); + }); + + it('05-13', function () { + browser.get('#/05-13'); + + let button = element(by.tagName('sg-app > toh-hero-button > button')); + expect(button.getText()).toBe('OK'); + }); + + it('05-14', function () { + browser.get('#/05-14'); + + let toast = element(by.tagName('sg-app > toh-toast')); + expect(toast.getText()).toBe('...'); + }); + + it('05-15', function () { + browser.get('#/05-15'); + + let heroList = element(by.tagName('sg-app > toh-hero-list')); + expect(heroList.getText()).toBe('...'); + }); + + it('05-16', function () { + browser.get('#/05-16'); + + let hero = element(by.tagName('sg-app > toh-hero')); + expect(hero.getText()).toBe('...'); + }); + + it('05-17', function () { + browser.get('#/05-17'); + + let section = element(by.tagName('sg-app > toh-hero-list > section')); + expect(section.getText()).toContain('Our list of heroes'); + expect(section.getText()).toContain('Total powers'); + expect(section.getText()).toContain('Average power'); + }); + + it('06-01', function () { + browser.get('#/06-01'); + + let div = element(by.tagName('sg-app > div[tohhighlight]')); + expect(div.getText()).toBe('Bombasta'); + }); + + it('06-03', function () { + browser.get('#/06-03'); + + let input = element(by.tagName('input[tohvalidator]')); + expect(input.isPresent()).toBe(true); + }); + + it('07-01', function () { + browser.get('#/07-01'); + + let lis = element.all(by.tagName('sg-app > ul > li')); + expect(lis.get(0).getText()).toBe('Windstorm'); + expect(lis.get(1).getText()).toBe('Bombasto'); + expect(lis.get(2).getText()).toBe('Magneta'); + expect(lis.get(3).getText()).toBe('Tornado'); + }); + + it('07-03', function () { + browser.get('#/07-03'); + + let pre = element(by.tagName('toh-heroes > pre')); + expect(pre.getText()).toContain('[]'); + }); + + it('07-04', function () { + browser.get('#/07-04'); + + let pre = element(by.tagName('toh-app > pre')); + expect(pre.getText()).toContain('[]'); + }); + + it('09-01', function () { + browser.get('#/09-01'); + + let button = element(by.tagName('sg-app > toh-hero-button > button')); + expect(button.getText()).toBe('OK'); + }); +}); diff --git a/aio/content/examples/style-guide/ts/.gitignore b/aio/content/examples/style-guide/ts/.gitignore new file mode 100644 index 0000000000..bd6423cecb --- /dev/null +++ b/aio/content/examples/style-guide/ts/.gitignore @@ -0,0 +1,2 @@ +*.js +!systemjs.custom.js diff --git a/aio/content/examples/style-guide/ts/example-config.json b/aio/content/examples/style-guide/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/style-guide/ts/plnkr.json b/aio/content/examples/style-guide/ts/plnkr.json new file mode 100644 index 0000000000..bf9e9681c5 --- /dev/null +++ b/aio/content/examples/style-guide/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Style Guide", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js" + ], + "tags": ["style guide, styleguide"] +} diff --git a/aio/content/examples/style-guide/ts/plnkr.no-link.html b/aio/content/examples/style-guide/ts/plnkr.no-link.html new file mode 100644 index 0000000000..f1edc0fe49 --- /dev/null +++ b/aio/content/examples/style-guide/ts/plnkr.no-link.html @@ -0,0 +1,4350 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/style-guide/ts/src/01-01/app/app.component.css b/aio/content/examples/style-guide/ts/src/01-01/app/app.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/style-guide/ts/src/01-01/app/app.component.ts b/aio/content/examples/style-guide/ts/src/01-01/app/app.component.ts new file mode 100644 index 0000000000..7997c4433c --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/01-01/app/app.component.ts @@ -0,0 +1,15 @@ +// #docregion +import { Component } from '@angular/core'; + +import { HeroService } from './heroes'; + +@Component({ + moduleId: module.id, + selector: 'toh-app', + template: ` + + `, + styleUrls: ['./app.component.css'], + providers: [ HeroService ] +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/01-01/app/app.module.ts b/aio/content/examples/style-guide/ts/src/01-01/app/app.module.ts new file mode 100644 index 0000000000..53f29ea8cb --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/01-01/app/app.module.ts @@ -0,0 +1,27 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { HeroesComponent } from './heroes/heroes.component'; + +@NgModule({ + imports: [ + BrowserModule, + // #enddocregion + RouterModule.forChild([{ path: '01-01', component: AppComponent }]) + // #docregion + ], + declarations: [ + AppComponent, + HeroesComponent + ], + exports: [ AppComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } +// #enddocregion + + diff --git a/aio/content/examples/style-guide/ts/src/01-01/app/heroes/hero.component.avoid.ts b/aio/content/examples/style-guide/ts/src/01-01/app/heroes/hero.component.avoid.ts new file mode 100644 index 0000000000..853e6ab64e --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/01-01/app/heroes/hero.component.avoid.ts @@ -0,0 +1,49 @@ +// #docregion +/* avoid */ + +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { BrowserModule } from '@angular/platform-browser'; +import { NgModule, Component, OnInit } from '@angular/core'; + +class Hero { + id: number; + name: string; +} + +@Component({ + selector: 'my-app', + template: ` +

    {{title}}

    +
    {{heroes | json}}
    + `, + styleUrls: ['app/app.component.css'] +}) +class AppComponent implements OnInit { + title = 'Tour of Heroes'; + + heroes: Hero[] = []; + + ngOnInit() { + getHeroes().then(heroes => this.heroes = heroes); + } +} + +@NgModule({ + imports: [ BrowserModule ], + declarations: [ AppComponent ], + exports: [ AppComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } + +platformBrowserDynamic().bootstrapModule(AppModule); + +const HEROES: Hero[] = [ + {id: 1, name: 'Bombasto'}, + {id: 2, name: 'Tornado'}, + {id: 3, name: 'Magneta'}, +]; + +function getHeroes(): Promise { + return Promise.resolve(HEROES); // TODO: get hero data from the server; +} diff --git a/aio/content/examples/style-guide/ts/src/01-01/app/heroes/heroes.component.ts b/aio/content/examples/style-guide/ts/src/01-01/app/heroes/heroes.component.ts new file mode 100644 index 0000000000..9e3546db90 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/01-01/app/heroes/heroes.component.ts @@ -0,0 +1,21 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; + +import { Hero, HeroService } from './shared'; + +@Component({ + selector: 'toh-heroes', + template: ` +
    {{heroes | json}}
    + ` +}) +export class HeroesComponent implements OnInit { + heroes: Hero[] = []; + + constructor(private heroService: HeroService) {} + + ngOnInit() { + this.heroService.getHeroes() + .then(heroes => this.heroes = heroes); + } +} diff --git a/aio/content/examples/style-guide/ts/src/01-01/app/heroes/index.ts b/aio/content/examples/style-guide/ts/src/01-01/app/heroes/index.ts new file mode 100644 index 0000000000..a8d7f1d422 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/01-01/app/heroes/index.ts @@ -0,0 +1,2 @@ +export * from './shared'; +export * from './heroes.component'; diff --git a/aio/content/examples/style-guide/ts/src/01-01/app/heroes/shared/hero.model.ts b/aio/content/examples/style-guide/ts/src/01-01/app/heroes/shared/hero.model.ts new file mode 100644 index 0000000000..8f7cc205c8 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/01-01/app/heroes/shared/hero.model.ts @@ -0,0 +1,5 @@ +// #docregion +export class Hero { + id: number; + name: string; +} diff --git a/aio/content/examples/style-guide/ts/src/01-01/app/heroes/shared/hero.service.ts b/aio/content/examples/style-guide/ts/src/01-01/app/heroes/shared/hero.service.ts new file mode 100644 index 0000000000..d94e5bacac --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/01-01/app/heroes/shared/hero.service.ts @@ -0,0 +1,11 @@ +// #docregion +import { Injectable } from '@angular/core'; + +import { HEROES } from './mock-heroes'; + +@Injectable() +export class HeroService { + getHeroes() { + return Promise.resolve(HEROES); + } +} diff --git a/aio/content/examples/style-guide/ts/src/01-01/app/heroes/shared/index.ts b/aio/content/examples/style-guide/ts/src/01-01/app/heroes/shared/index.ts new file mode 100644 index 0000000000..c0c1a87eb2 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/01-01/app/heroes/shared/index.ts @@ -0,0 +1,3 @@ +export * from './hero.model'; +export * from './hero.service'; +export * from './mock-heroes'; diff --git a/aio/content/examples/style-guide/ts/src/01-01/app/heroes/shared/mock-heroes.ts b/aio/content/examples/style-guide/ts/src/01-01/app/heroes/shared/mock-heroes.ts new file mode 100644 index 0000000000..2e9a69f59d --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/01-01/app/heroes/shared/mock-heroes.ts @@ -0,0 +1,8 @@ +// #docregion +import { Hero } from './hero.model'; + +export const HEROES: Hero[] = [ + {id: 1, name: 'Bombasto'}, + {id: 2, name: 'Tornado'}, + {id: 3, name: 'Magneta'}, +]; diff --git a/aio/content/examples/style-guide/ts/src/01-01/app/index.ts b/aio/content/examples/style-guide/ts/src/01-01/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/01-01/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/01-01/main.ts b/aio/content/examples/style-guide/ts/src/01-01/main.ts new file mode 100644 index 0000000000..7e8269bd65 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/01-01/main.ts @@ -0,0 +1,6 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/style-guide/ts/src/02-05/app/app.component.ts b/aio/content/examples/style-guide/ts/src/02-05/app/app.component.ts new file mode 100644 index 0000000000..268a2f940b --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/02-05/app/app.component.ts @@ -0,0 +1,11 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'toh-app', + template: ` + Tour of Heroes + ` +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/02-05/app/app.module.ts b/aio/content/examples/style-guide/ts/src/02-05/app/app.module.ts new file mode 100644 index 0000000000..306ec125e0 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/02-05/app/app.module.ts @@ -0,0 +1,23 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ + BrowserModule, + // #enddocregion + RouterModule.forChild([{ path: '02-05', component: AppComponent }]) + // #docregion + ], + declarations: [ + AppComponent + ], + exports: [ AppComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } +// #enddocregion diff --git a/aio/content/examples/style-guide/ts/src/02-05/main.ts b/aio/content/examples/style-guide/ts/src/02-05/main.ts new file mode 100644 index 0000000000..6c32161f84 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/02-05/main.ts @@ -0,0 +1,8 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule) + .then(success => console.log(`Bootstrap success`)) + .catch(err => console.error(err)); diff --git a/aio/content/examples/style-guide/ts/src/02-07/app/app.component.ts b/aio/content/examples/style-guide/ts/src/02-07/app/app.component.ts new file mode 100644 index 0000000000..c82e12624d --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/02-07/app/app.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'sg-app', + template: ` + + + ` +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/02-07/app/app.module.ts b/aio/content/examples/style-guide/ts/src/02-07/app/app.module.ts new file mode 100644 index 0000000000..4320fe67d5 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/02-07/app/app.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { HeroComponent } from './heroes'; +import { UsersComponent } from './users'; + +@NgModule({ + imports: [ + RouterModule.forChild([{ path: '02-07', component: AppComponent }]) + ], + declarations: [ + AppComponent, + HeroComponent, + UsersComponent + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/02-07/app/heroes/hero.component.avoid.ts b/aio/content/examples/style-guide/ts/src/02-07/app/heroes/hero.component.avoid.ts new file mode 100644 index 0000000000..976fcb6cdf --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/02-07/app/heroes/hero.component.avoid.ts @@ -0,0 +1,11 @@ +// #docregion +import { Component } from '@angular/core'; +// #docregion example +/* avoid */ + +// HeroComponent is in the Tour of Heroes feature +@Component({ + selector: 'hero' +}) +export class HeroComponent {} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/02-07/app/heroes/hero.component.ts b/aio/content/examples/style-guide/ts/src/02-07/app/heroes/hero.component.ts new file mode 100644 index 0000000000..44c04dd855 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/02-07/app/heroes/hero.component.ts @@ -0,0 +1,13 @@ +// #docplaster +// #docregion +import { Component } from '@angular/core'; + +// #docregion example +@Component({ + // #enddocregion example + template: '
    hero component
    ', + // #docregion example + selector: 'toh-hero' +}) +export class HeroComponent {} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/02-07/app/heroes/index.ts b/aio/content/examples/style-guide/ts/src/02-07/app/heroes/index.ts new file mode 100644 index 0000000000..084f36d703 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/02-07/app/heroes/index.ts @@ -0,0 +1 @@ +export * from './hero.component'; diff --git a/aio/content/examples/style-guide/ts/src/02-07/app/index.ts b/aio/content/examples/style-guide/ts/src/02-07/app/index.ts new file mode 100644 index 0000000000..fc87f976b3 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/02-07/app/index.ts @@ -0,0 +1,3 @@ +export * from './heroes'; +export * from './users'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/02-07/app/users/index.ts b/aio/content/examples/style-guide/ts/src/02-07/app/users/index.ts new file mode 100644 index 0000000000..475ba6d2a7 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/02-07/app/users/index.ts @@ -0,0 +1 @@ +export * from './users.component'; diff --git a/aio/content/examples/style-guide/ts/src/02-07/app/users/users.component.avoid.ts b/aio/content/examples/style-guide/ts/src/02-07/app/users/users.component.avoid.ts new file mode 100644 index 0000000000..9a5bd936e9 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/02-07/app/users/users.component.avoid.ts @@ -0,0 +1,11 @@ +// #docregion +import { Component } from '@angular/core'; +// #docregion example +/* avoid */ + +// UsersComponent is in an Admin feature +@Component({ + selector: 'users' +}) +export class UsersComponent {} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/02-07/app/users/users.component.ts b/aio/content/examples/style-guide/ts/src/02-07/app/users/users.component.ts new file mode 100644 index 0000000000..2fb6d54ebe --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/02-07/app/users/users.component.ts @@ -0,0 +1,13 @@ +// #docplaster +// #docregion +import { Component } from '@angular/core'; + +// #docregion example +@Component({ + // #enddocregion example + template: '
    users component
    ', + // #docregion example + selector: 'admin-users' +}) +export class UsersComponent {} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/02-08/app/app.component.ts b/aio/content/examples/style-guide/ts/src/02-08/app/app.component.ts new file mode 100644 index 0000000000..bf27aeaf8a --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/02-08/app/app.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'sg-app', + template: '' +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/02-08/app/app.module.ts b/aio/content/examples/style-guide/ts/src/02-08/app/app.module.ts new file mode 100644 index 0000000000..e840cc50a5 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/02-08/app/app.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { InputHighlightDirective, + ValidateDirective } from './shared'; + +@NgModule({ + imports: [ + RouterModule.forChild([{ path: '02-08', component: AppComponent }]) + ], + declarations: [ + AppComponent, + InputHighlightDirective, + ValidateDirective + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/02-08/app/index.ts b/aio/content/examples/style-guide/ts/src/02-08/app/index.ts new file mode 100644 index 0000000000..ebe5c92f03 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/02-08/app/index.ts @@ -0,0 +1,2 @@ +export * from './shared'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/02-08/app/shared/index.ts b/aio/content/examples/style-guide/ts/src/02-08/app/shared/index.ts new file mode 100644 index 0000000000..b844b75492 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/02-08/app/shared/index.ts @@ -0,0 +1,2 @@ +export * from './input-highlight.directive'; +export * from './validate.directive'; diff --git a/aio/content/examples/style-guide/ts/src/02-08/app/shared/input-highlight.directive.ts b/aio/content/examples/style-guide/ts/src/02-08/app/shared/input-highlight.directive.ts new file mode 100644 index 0000000000..fd74771981 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/02-08/app/shared/input-highlight.directive.ts @@ -0,0 +1,10 @@ +// #docregion +import { Directive, ElementRef } from '@angular/core'; + +@Directive({ selector: 'input'}) +/** Highlight the attached input text element in blue */ +export class InputHighlightDirective { + constructor(el: ElementRef) { + el.nativeElement.style.backgroundColor = 'powderblue'; + } +} diff --git a/aio/content/examples/style-guide/ts/src/02-08/app/shared/validate.directive.avoid.ts b/aio/content/examples/style-guide/ts/src/02-08/app/shared/validate.directive.avoid.ts new file mode 100644 index 0000000000..b038f37075 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/02-08/app/shared/validate.directive.avoid.ts @@ -0,0 +1,10 @@ +// #docregion +import { Directive } from '@angular/core'; +// #docregion example +/* avoid */ + +@Directive({ + selector: '[validate]' +}) +export class ValidateDirective {} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/02-08/app/shared/validate.directive.ts b/aio/content/examples/style-guide/ts/src/02-08/app/shared/validate.directive.ts new file mode 100644 index 0000000000..039afb846f --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/02-08/app/shared/validate.directive.ts @@ -0,0 +1,9 @@ +// #docregion +import { Directive } from '@angular/core'; + +// #docregion example +@Directive({ + selector: '[tohValidate]' +}) +export class ValidateDirective {} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/03-01/app/app.component.ts b/aio/content/examples/style-guide/ts/src/03-01/app/app.component.ts new file mode 100644 index 0000000000..cb9479d2d9 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-01/app/app.component.ts @@ -0,0 +1,18 @@ +import { Component, OnInit } from '@angular/core'; + +import { ExceptionService } from './core'; + +@Component({ + selector: 'sg-app', + template: '
    The expected error is {{errorCode}}
    ', + providers: [ExceptionService] +}) +export class AppComponent implements OnInit { + errorCode: number; + + constructor(private exceptionService: ExceptionService) { } + + ngOnInit() { + this.errorCode = this.exceptionService.getException(); + } +} diff --git a/aio/content/examples/style-guide/ts/src/03-01/app/app.module.ts b/aio/content/examples/style-guide/ts/src/03-01/app/app.module.ts new file mode 100644 index 0000000000..48079f21c7 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-01/app/app.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ + RouterModule.forChild([{ path: '03-01', component: AppComponent }]) + ], + declarations: [ + AppComponent + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/03-01/app/core/exception.service.avoid.ts b/aio/content/examples/style-guide/ts/src/03-01/app/core/exception.service.avoid.ts new file mode 100644 index 0000000000..0a22811fe3 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-01/app/core/exception.service.avoid.ts @@ -0,0 +1,11 @@ +// #docregion +import { Injectable } from '@angular/core'; + +@Injectable() +// #docregion example +/* avoid */ + +export class exceptionService { + constructor() { } +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/03-01/app/core/exception.service.ts b/aio/content/examples/style-guide/ts/src/03-01/app/core/exception.service.ts new file mode 100644 index 0000000000..dd77b4f7dc --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-01/app/core/exception.service.ts @@ -0,0 +1,14 @@ +// #docplaster +// #docregion +import { Injectable } from '@angular/core'; + +@Injectable() +// #docregion example +export class ExceptionService { + constructor() { } + // #enddocregion example + // testing harness + getException() { return 42; } + // #docregion example +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/03-01/app/core/index.ts b/aio/content/examples/style-guide/ts/src/03-01/app/core/index.ts new file mode 100644 index 0000000000..8acaa4bcf9 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-01/app/core/index.ts @@ -0,0 +1 @@ +export * from './exception.service'; diff --git a/aio/content/examples/style-guide/ts/src/03-01/app/index.ts b/aio/content/examples/style-guide/ts/src/03-01/app/index.ts new file mode 100644 index 0000000000..e120e2dbfd --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-01/app/index.ts @@ -0,0 +1,2 @@ +export * from './core'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/03-02/app/app.component.ts b/aio/content/examples/style-guide/ts/src/03-02/app/app.component.ts new file mode 100644 index 0000000000..132ea54c85 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-02/app/app.component.ts @@ -0,0 +1,19 @@ +import { Component } from '@angular/core'; + +import { heroesUrl, mockHeroes, VILLAINS_URL } from './core'; + +@Component({ + selector: 'sg-app', + template: ` +
    Heroes url: {{heroesUrl}}
    +
    Villains url: {{villainsUrl}}
    + +

    Mock Heroes

    +
    {{hero}}
    + ` +}) +export class AppComponent { + heroes = mockHeroes; // prefer + heroesUrl = heroesUrl; // prefer + villainsUrl = VILLAINS_URL; // tolerate +} diff --git a/aio/content/examples/style-guide/ts/src/03-02/app/app.module.ts b/aio/content/examples/style-guide/ts/src/03-02/app/app.module.ts new file mode 100644 index 0000000000..2db4012ebf --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-02/app/app.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ + BrowserModule, + RouterModule.forChild([{ path: '03-02', component: AppComponent }]) + ], + declarations: [ + AppComponent + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/03-02/app/core/data.service.ts b/aio/content/examples/style-guide/ts/src/03-02/app/core/data.service.ts new file mode 100644 index 0000000000..5c26478c7b --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-02/app/core/data.service.ts @@ -0,0 +1,4 @@ +// #docregion +export const mockHeroes = ['Sam', 'Jill']; // prefer +export const heroesUrl = 'api/heroes'; // prefer +export const VILLAINS_URL = 'api/villains'; // tolerate diff --git a/aio/content/examples/style-guide/ts/src/03-02/app/core/index.ts b/aio/content/examples/style-guide/ts/src/03-02/app/core/index.ts new file mode 100644 index 0000000000..2ba773ede8 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-02/app/core/index.ts @@ -0,0 +1 @@ +export * from './data.service'; diff --git a/aio/content/examples/style-guide/ts/src/03-02/app/index.ts b/aio/content/examples/style-guide/ts/src/03-02/app/index.ts new file mode 100644 index 0000000000..e120e2dbfd --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-02/app/index.ts @@ -0,0 +1,2 @@ +export * from './core'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/03-03/app/app.component.ts b/aio/content/examples/style-guide/ts/src/03-03/app/app.component.ts new file mode 100644 index 0000000000..3ca522bc45 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-03/app/app.component.ts @@ -0,0 +1,18 @@ +import { Component, OnInit } from '@angular/core'; + +import { Hero, HeroCollectorService } from './core'; + +@Component({ + selector: 'sg-app', + template: '
    Our hero is {{hero.name}} and {{hero.power}}
    ', + providers: [HeroCollectorService] +}) +export class AppComponent implements OnInit { + hero: Hero; + + constructor(private heroCollectorService: HeroCollectorService) { } + + ngOnInit() { + this.hero = this.heroCollectorService.getHero(); + } +} diff --git a/aio/content/examples/style-guide/ts/src/03-03/app/app.module.ts b/aio/content/examples/style-guide/ts/src/03-03/app/app.module.ts new file mode 100644 index 0000000000..29b3d2e765 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-03/app/app.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ + RouterModule.forChild([{ path: '03-03', component: AppComponent }]) + ], + declarations: [ + AppComponent + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/03-03/app/core/hero-collector.service.avoid.ts b/aio/content/examples/style-guide/ts/src/03-03/app/core/hero-collector.service.avoid.ts new file mode 100644 index 0000000000..f481af18b6 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-03/app/core/hero-collector.service.avoid.ts @@ -0,0 +1,15 @@ +// #docregion +// #docregion example +/* avoid */ + +import { Injectable } from '@angular/core'; + +import { IHero } from './hero.model.avoid'; + +@Injectable() +export class HeroCollectorService { + hero: IHero; + + constructor() { } +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/03-03/app/core/hero-collector.service.ts b/aio/content/examples/style-guide/ts/src/03-03/app/core/hero-collector.service.ts new file mode 100644 index 0000000000..1df5c0deb0 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-03/app/core/hero-collector.service.ts @@ -0,0 +1,25 @@ +// #docplaster +// #docregion +// #docregion example +import { Injectable } from '@angular/core'; + +import { Hero } from './hero.model'; + +@Injectable() +export class HeroCollectorService { + hero: Hero; + + constructor() { } + // #enddocregion example + // testing harness + getHero() { + this.hero = { + name: 'RubberMan', + power: 'He is so elastic' + }; + + return this.hero; + } + // #docregion example +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/03-03/app/core/hero.model.avoid.ts b/aio/content/examples/style-guide/ts/src/03-03/app/core/hero.model.avoid.ts new file mode 100644 index 0000000000..ce93b2c59a --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-03/app/core/hero.model.avoid.ts @@ -0,0 +1,14 @@ +// #docregion +// #docregion example +/* avoid */ + +export interface IHero { + name: string; + power: string; +} + +export class Hero implements IHero { + name: string; + power: string; +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/03-03/app/core/hero.model.ts b/aio/content/examples/style-guide/ts/src/03-03/app/core/hero.model.ts new file mode 100644 index 0000000000..c3277621cb --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-03/app/core/hero.model.ts @@ -0,0 +1,7 @@ +// #docregion +// #docregion example +export class Hero { + name: string; + power: string; +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/03-03/app/core/index.ts b/aio/content/examples/style-guide/ts/src/03-03/app/core/index.ts new file mode 100644 index 0000000000..17ad67b0b1 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-03/app/core/index.ts @@ -0,0 +1,2 @@ +export * from './hero-collector.service'; +export * from './hero.model'; diff --git a/aio/content/examples/style-guide/ts/src/03-03/app/index.ts b/aio/content/examples/style-guide/ts/src/03-03/app/index.ts new file mode 100644 index 0000000000..e120e2dbfd --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-03/app/index.ts @@ -0,0 +1,2 @@ +export * from './core'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/03-04/app/app.component.ts b/aio/content/examples/style-guide/ts/src/03-04/app/app.component.ts new file mode 100644 index 0000000000..555c9e9441 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-04/app/app.component.ts @@ -0,0 +1,27 @@ +import { Component, OnInit } from '@angular/core'; + +import { ToastService } from './core'; + +@Component({ + selector: 'sg-app', + template: ` + + + `, + providers: [ToastService] +}) +export class AppComponent implements OnInit { + constructor(private toastService: ToastService) { } + + hide() { + this.toastService.hide(); + } + + show() { + this.toastService.show(); + } + + ngOnInit() { + this.toastService.activate('Hello style-guide!'); + } +} diff --git a/aio/content/examples/style-guide/ts/src/03-04/app/app.module.ts b/aio/content/examples/style-guide/ts/src/03-04/app/app.module.ts new file mode 100644 index 0000000000..a5a8d5bb4e --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-04/app/app.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ + BrowserModule, + RouterModule.forChild([{ path: '03-04', component: AppComponent }]) + ], + declarations: [ + AppComponent + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/03-04/app/core/index.ts b/aio/content/examples/style-guide/ts/src/03-04/app/core/index.ts new file mode 100644 index 0000000000..e78b628f9c --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-04/app/core/index.ts @@ -0,0 +1 @@ +export * from './toast.service'; diff --git a/aio/content/examples/style-guide/ts/src/03-04/app/core/toast.service.avoid.ts b/aio/content/examples/style-guide/ts/src/03-04/app/core/toast.service.avoid.ts new file mode 100644 index 0000000000..0f3a7c25ea --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-04/app/core/toast.service.avoid.ts @@ -0,0 +1,27 @@ +// #docregion +// #docregion example +/* avoid */ + +import { Injectable } from '@angular/core'; + +@Injectable() +export class ToastService { + message: string; + + private _toastCount: number; + + hide() { + this._toastCount--; + this._log(); + } + + show() { + this._toastCount++; + this._log(); + } + + private _log() { + console.log(this.message); + } +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/03-04/app/core/toast.service.ts b/aio/content/examples/style-guide/ts/src/03-04/app/core/toast.service.ts new file mode 100644 index 0000000000..ab148a1732 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-04/app/core/toast.service.ts @@ -0,0 +1,32 @@ +// #docplaster +// #docregion +// #docregion example +import { Injectable } from '@angular/core'; + +@Injectable() +export class ToastService { + message: string; + + private toastCount: number; + + hide() { + this.toastCount--; + this.log(); + } + + show() { + this.toastCount++; + this.log(); + } + + private log() { + console.log(this.message); + } + // #enddocregion example + // testing harness + activate(message: string) { + this.message = message; + } + // #docregion example +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/03-04/app/index.ts b/aio/content/examples/style-guide/ts/src/03-04/app/index.ts new file mode 100644 index 0000000000..e120e2dbfd --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-04/app/index.ts @@ -0,0 +1,2 @@ +export * from './core'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/03-06/app/app.component.html b/aio/content/examples/style-guide/ts/src/03-06/app/app.component.html new file mode 100644 index 0000000000..67fb0d5964 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-06/app/app.component.html @@ -0,0 +1,6 @@ +
    Actual favorite: {{favorite?.name}}
    +
      +
    • + {{hero.name}} +
    • +
    diff --git a/aio/content/examples/style-guide/ts/src/03-06/app/app.component.ts b/aio/content/examples/style-guide/ts/src/03-06/app/app.component.ts new file mode 100644 index 0000000000..b7c47f72d0 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-06/app/app.component.ts @@ -0,0 +1,22 @@ +import { Component, OnInit } from '@angular/core'; + +import { Hero, HeroService } from './heroes'; +import { ExceptionService, SpinnerService, ToastService } from './core'; + +@Component({ + moduleId: module.id, + selector: 'sg-app', + templateUrl: './app.component.html', + providers: [HeroService, ExceptionService, SpinnerService, ToastService] +}) +export class AppComponent implements OnInit { + favorite: Hero; + heroes: Hero[]; + + constructor(private heroService: HeroService) { } + + ngOnInit() { + this.heroService.getHero(1).subscribe(hero => this.favorite = hero); + this.heroService.getHeroes().subscribe(heroes => this.heroes = heroes); + } +} diff --git a/aio/content/examples/style-guide/ts/src/03-06/app/app.module.ts b/aio/content/examples/style-guide/ts/src/03-06/app/app.module.ts new file mode 100644 index 0000000000..f259ce23a2 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-06/app/app.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ + BrowserModule, + RouterModule.forChild([{ path: '03-06', component: AppComponent }]) + ], + declarations: [ + AppComponent + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/03-06/app/core/exception.service.ts b/aio/content/examples/style-guide/ts/src/03-06/app/core/exception.service.ts new file mode 100644 index 0000000000..7180c88e6b --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-06/app/core/exception.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class ExceptionService { } diff --git a/aio/content/examples/style-guide/ts/src/03-06/app/core/index.ts b/aio/content/examples/style-guide/ts/src/03-06/app/core/index.ts new file mode 100644 index 0000000000..e4e6723f91 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-06/app/core/index.ts @@ -0,0 +1,6 @@ +// #docregion +// #docregion example +export * from './exception.service'; +export * from './spinner'; +export * from './toast'; +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/03-06/app/core/spinner/index.ts b/aio/content/examples/style-guide/ts/src/03-06/app/core/spinner/index.ts new file mode 100644 index 0000000000..1d619300c0 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-06/app/core/spinner/index.ts @@ -0,0 +1,3 @@ +// #docregion +export * from './spinner.component'; +export * from './spinner.service'; diff --git a/aio/content/examples/style-guide/ts/src/03-06/app/core/spinner/spinner.component.ts b/aio/content/examples/style-guide/ts/src/03-06/app/core/spinner/spinner.component.ts new file mode 100644 index 0000000000..1fd2a01500 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-06/app/core/spinner/spinner.component.ts @@ -0,0 +1,16 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; + +import { SpinnerService } from './spinner.service'; + +@Component({ + selector: 'toh-spinner', + template: '
    spinner
    ' +}) + +export class SpinnerComponent implements OnDestroy, OnInit { + constructor(private spinnerService: SpinnerService) { } + + ngOnInit() { } + + ngOnDestroy() { } +} diff --git a/aio/content/examples/style-guide/ts/src/03-06/app/core/spinner/spinner.service.ts b/aio/content/examples/style-guide/ts/src/03-06/app/core/spinner/spinner.service.ts new file mode 100644 index 0000000000..ad5d2ed6e0 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-06/app/core/spinner/spinner.service.ts @@ -0,0 +1,12 @@ +import { Injectable } from '@angular/core'; + +export interface ISpinnerState { } + +@Injectable() +export class SpinnerService { + spinnerState: any; + + show() { } + + hide() { } +} diff --git a/aio/content/examples/style-guide/ts/src/03-06/app/core/toast/index.ts b/aio/content/examples/style-guide/ts/src/03-06/app/core/toast/index.ts new file mode 100644 index 0000000000..01b41aff98 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-06/app/core/toast/index.ts @@ -0,0 +1,3 @@ +// #docregion +export * from './toast.component'; +export * from './toast.service'; diff --git a/aio/content/examples/style-guide/ts/src/03-06/app/core/toast/toast.component.ts b/aio/content/examples/style-guide/ts/src/03-06/app/core/toast/toast.component.ts new file mode 100644 index 0000000000..dd0bba5eba --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-06/app/core/toast/toast.component.ts @@ -0,0 +1,13 @@ +import { Component, OnInit } from '@angular/core'; + +import { ToastService } from './toast.service'; + +@Component({ + selector: 'toh-toast', + template: '
    toast
    ' +}) +export class ToastComponent implements OnInit { + constructor(toastService: ToastService) { } + + ngOnInit() { } +} diff --git a/aio/content/examples/style-guide/ts/src/03-06/app/core/toast/toast.service.ts b/aio/content/examples/style-guide/ts/src/03-06/app/core/toast/toast.service.ts new file mode 100644 index 0000000000..e92e75ee45 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-06/app/core/toast/toast.service.ts @@ -0,0 +1,6 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class ToastService { + activate: (message?: string, title?: string) => void; +} diff --git a/aio/content/examples/style-guide/ts/src/03-06/app/heroes/index.ts b/aio/content/examples/style-guide/ts/src/03-06/app/heroes/index.ts new file mode 100644 index 0000000000..c3da79f741 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-06/app/heroes/index.ts @@ -0,0 +1 @@ +export * from './shared'; diff --git a/aio/content/examples/style-guide/ts/src/03-06/app/heroes/shared/hero.model.ts b/aio/content/examples/style-guide/ts/src/03-06/app/heroes/shared/hero.model.ts new file mode 100644 index 0000000000..c3277621cb --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-06/app/heroes/shared/hero.model.ts @@ -0,0 +1,7 @@ +// #docregion +// #docregion example +export class Hero { + name: string; + power: string; +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/03-06/app/heroes/shared/hero.service.avoid.ts b/aio/content/examples/style-guide/ts/src/03-06/app/heroes/shared/hero.service.avoid.ts new file mode 100644 index 0000000000..8287c567a7 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-06/app/heroes/shared/hero.service.avoid.ts @@ -0,0 +1,32 @@ +// #docregion +// #docregion example +/* avoid */ + +import { ExceptionService, SpinnerService, ToastService } from '../../core'; +import { Http } from '@angular/http'; +import { Injectable } from '@angular/core'; +import { Hero } from './hero.model'; +// #enddocregion example + +@Injectable() +export class HeroService { + + constructor( + private exceptionService: ExceptionService, + private spinnerService: SpinnerService, + private toastService: ToastService, + private http: Http + ) { } + + getHero(id: number) { + return this.http.get(`api/heroes/${id}`) + .map(response => response.json().data as Hero); + } + + getHeroes() { + return this.http.get(`api/heroes`) + .map(response => response.json().data as Hero[]); + } + +} + diff --git a/aio/content/examples/style-guide/ts/src/03-06/app/heroes/shared/hero.service.ts b/aio/content/examples/style-guide/ts/src/03-06/app/heroes/shared/hero.service.ts new file mode 100644 index 0000000000..5792cd1ab2 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-06/app/heroes/shared/hero.service.ts @@ -0,0 +1,32 @@ +// #docregion +// #docregion example +import { Injectable } from '@angular/core'; +import { Http } from '@angular/http'; + +import { Hero } from './hero.model'; +import { ExceptionService, SpinnerService, ToastService } from '../../core'; +// #enddocregion example + +@Injectable() +export class HeroService { + cachedHeroes: Hero[]; + + constructor( + private exceptionService: ExceptionService, + private spinnerService: SpinnerService, + private toastService: ToastService, + private http: Http + ) { } + + getHero(id: number) { + return this.http.get(`api/heroes/${id}`) + .map(response => response.json().data as Hero); + } + + getHeroes() { + return this.http.get(`api/heroes`) + .map(response => response.json().data as Hero[]); + } + +} + diff --git a/aio/content/examples/style-guide/ts/src/03-06/app/heroes/shared/index.ts b/aio/content/examples/style-guide/ts/src/03-06/app/heroes/shared/index.ts new file mode 100644 index 0000000000..dbb150d3f8 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-06/app/heroes/shared/index.ts @@ -0,0 +1,2 @@ +export * from './hero.model'; +export * from './hero.service'; diff --git a/aio/content/examples/style-guide/ts/src/03-06/app/index.ts b/aio/content/examples/style-guide/ts/src/03-06/app/index.ts new file mode 100644 index 0000000000..cf861e261a --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-06/app/index.ts @@ -0,0 +1,3 @@ +export * from './heroes'; +export * from './core'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/03-06/app/shared/toast/toast.component.ts b/aio/content/examples/style-guide/ts/src/03-06/app/shared/toast/toast.component.ts new file mode 100644 index 0000000000..e1c1ae6665 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/03-06/app/shared/toast/toast.component.ts @@ -0,0 +1,13 @@ +import { Component, OnInit } from '@angular/core'; + +import { ToastService } from '../../core'; + +@Component({ + selector: 'toh-toast', + template: '
    toast
    ' +}) +export class ToastComponent implements OnInit { + constructor(toastService: ToastService) { } + + ngOnInit() { } +} diff --git a/aio/content/examples/style-guide/ts/src/04-08/app/app.component.ts b/aio/content/examples/style-guide/ts/src/04-08/app/app.component.ts new file mode 100644 index 0000000000..fdd8e6ef56 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-08/app/app.component.ts @@ -0,0 +1,8 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'sg-app', + template: '' +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/04-08/app/app.module.ts b/aio/content/examples/style-guide/ts/src/04-08/app/app.module.ts new file mode 100644 index 0000000000..25568b7fb4 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-08/app/app.module.ts @@ -0,0 +1,28 @@ +// #docplaster +// #docregion +// #docregion example +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +// #enddocregion example +import { RouterModule } from '@angular/router'; +// #docregion example + +import { AppComponent } from './app.component'; +import { HeroesComponent } from './heroes/heroes.component'; + +@NgModule({ + imports: [ + BrowserModule, +// #enddocregion example + RouterModule.forChild([{ path: '04-08', component: AppComponent }]) +// #docregion example + ], + declarations: [ + AppComponent, + HeroesComponent + ], + exports: [ AppComponent ], + entryComponents: [ AppComponent ] +}) +export class AppModule {} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/04-08/app/heroes/heroes.component.html b/aio/content/examples/style-guide/ts/src/04-08/app/heroes/heroes.component.html new file mode 100644 index 0000000000..1244e68a4a --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-08/app/heroes/heroes.component.html @@ -0,0 +1 @@ +
    This is heroes component
    diff --git a/aio/content/examples/style-guide/ts/src/04-08/app/heroes/heroes.component.ts b/aio/content/examples/style-guide/ts/src/04-08/app/heroes/heroes.component.ts new file mode 100644 index 0000000000..f9e67391ec --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-08/app/heroes/heroes.component.ts @@ -0,0 +1,12 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'toh-heroes', + templateUrl: './heroes.component.html' +}) +export class HeroesComponent implements OnInit { + constructor() { /* ... */ } + + ngOnInit() { /* ... */ } +} diff --git a/aio/content/examples/style-guide/ts/src/04-10/app/app.component.ts b/aio/content/examples/style-guide/ts/src/04-10/app/app.component.ts new file mode 100644 index 0000000000..fdd8e6ef56 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-10/app/app.component.ts @@ -0,0 +1,8 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'sg-app', + template: '' +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/04-10/app/app.module.ts b/aio/content/examples/style-guide/ts/src/04-10/app/app.module.ts new file mode 100644 index 0000000000..78106657ad --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-10/app/app.module.ts @@ -0,0 +1,30 @@ +// #docplaster +// #docregion +// #docregion example +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +// #enddocregion example +import { RouterModule } from '@angular/router'; +// #docregion example + +import { AppComponent } from './app.component'; +import { HeroesComponent } from './heroes/heroes.component'; +import { SharedModule } from './shared/shared.module'; + +@NgModule({ + imports: [ + BrowserModule, + SharedModule, +// #enddocregion example + RouterModule.forChild([{ path: '04-10', component: AppComponent }]) +// #docregion example + ], + declarations: [ + AppComponent, + HeroesComponent + ], + exports: [ AppComponent ], + entryComponents: [ AppComponent ] +}) +export class AppModule {} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/04-10/app/heroes/heroes.component.html b/aio/content/examples/style-guide/ts/src/04-10/app/heroes/heroes.component.html new file mode 100644 index 0000000000..170f76aee1 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-10/app/heroes/heroes.component.html @@ -0,0 +1,8 @@ + +
    This is heroes component
    +
      +
    • + {{hero.name}} +
    • +
    + diff --git a/aio/content/examples/style-guide/ts/src/04-10/app/heroes/heroes.component.ts b/aio/content/examples/style-guide/ts/src/04-10/app/heroes/heroes.component.ts new file mode 100644 index 0000000000..eb496ccde5 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-10/app/heroes/heroes.component.ts @@ -0,0 +1,28 @@ +// #docregion +import { Component } from '@angular/core'; + +import { FilterTextService } from '../shared/filter-text/filter-text.service'; + +@Component({ + moduleId: module.id, + selector: 'toh-heroes', + templateUrl: './heroes.component.html' +}) +export class HeroesComponent { + + heroes = [ + { id: 1, name: 'Windstorm' }, + { id: 2, name: 'Bombasto' }, + { id: 3, name: 'Magneta' }, + { id: 4, name: 'Tornado' } + ]; + + filteredHeroes = this.heroes; + + constructor(private filterService: FilterTextService) { } + + filterChanged(searchText: string) { + this.filteredHeroes = this.filterService.filter(searchText, ['id', 'name'], this.heroes); + } +} + diff --git a/aio/content/examples/style-guide/ts/src/04-10/app/shared/filter-text/filter-text.component.ts b/aio/content/examples/style-guide/ts/src/04-10/app/shared/filter-text/filter-text.component.ts new file mode 100644 index 0000000000..e23987d2f0 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-10/app/shared/filter-text/filter-text.component.ts @@ -0,0 +1,27 @@ +// #docregion +import { Component, EventEmitter, Output } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'toh-filter-text', + template: '' +}) +export class FilterTextComponent { + @Output() changed: EventEmitter; + + filter: string; + + constructor() { + this.changed = new EventEmitter(); + } + + clear() { + this.filter = ''; + } + + filterChanged(event: any) { + event.preventDefault(); + console.log(`Filter Changed: ${this.filter}`); + this.changed.emit(this.filter); + } +} diff --git a/aio/content/examples/style-guide/ts/src/04-10/app/shared/filter-text/filter-text.service.ts b/aio/content/examples/style-guide/ts/src/04-10/app/shared/filter-text/filter-text.service.ts new file mode 100644 index 0000000000..87978e10e5 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-10/app/shared/filter-text/filter-text.service.ts @@ -0,0 +1,30 @@ +// #docregion +import { Injectable } from '@angular/core'; + +@Injectable() +export class FilterTextService { + constructor() { + console.log('Created an instance of FilterTextService'); + } + + filter(data: string, props: Array, originalList: Array) { + let filteredList: any[]; + if (data && props && originalList) { + data = data.toLowerCase(); + let filtered = originalList.filter(item => { + let match = false; + for (let prop of props) { + if (item[prop].toString().toLowerCase().indexOf(data) > -1) { + match = true; + break; + } + }; + return match; + }); + filteredList = filtered; + } else { + filteredList = originalList; + } + return filteredList; + } +} diff --git a/aio/content/examples/style-guide/ts/src/04-10/app/shared/init-caps.pipe.ts b/aio/content/examples/style-guide/ts/src/04-10/app/shared/init-caps.pipe.ts new file mode 100644 index 0000000000..5019bcb234 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-10/app/shared/init-caps.pipe.ts @@ -0,0 +1,7 @@ +// #docregion +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ name: 'initCaps' }) +export class InitCapsPipe implements PipeTransform { + transform = (value: string) => value; +} diff --git a/aio/content/examples/style-guide/ts/src/04-10/app/shared/shared.module.ts b/aio/content/examples/style-guide/ts/src/04-10/app/shared/shared.module.ts new file mode 100644 index 0000000000..6160abcc84 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-10/app/shared/shared.module.ts @@ -0,0 +1,24 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { FilterTextComponent } from './filter-text/filter-text.component'; +import { FilterTextService } from './filter-text/filter-text.service'; +import { InitCapsPipe } from './init-caps.pipe'; + +@NgModule({ + imports: [CommonModule, FormsModule], + declarations: [ + FilterTextComponent, + InitCapsPipe + ], + providers: [FilterTextService], + exports: [ + CommonModule, + FormsModule, + FilterTextComponent, + InitCapsPipe + ] +}) +export class SharedModule { } diff --git a/aio/content/examples/style-guide/ts/src/04-11/app/app.component.ts b/aio/content/examples/style-guide/ts/src/04-11/app/app.component.ts new file mode 100644 index 0000000000..693619a982 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-11/app/app.component.ts @@ -0,0 +1,12 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'toh-app', + template: ` + + + + ` +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/04-11/app/app.module.ts b/aio/content/examples/style-guide/ts/src/04-11/app/app.module.ts new file mode 100644 index 0000000000..ef3c156bcf --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-11/app/app.module.ts @@ -0,0 +1,30 @@ +// #docplaster +// #docregion +// #docregion example +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +// #enddocregion example +import { RouterModule } from '@angular/router'; +// #docregion example + +import { AppComponent } from './app.component'; +import { HeroesComponent } from './heroes/heroes.component'; +import { CoreModule } from './core/core.module'; + +@NgModule({ + imports: [ + BrowserModule, + CoreModule, +// #enddocregion example + RouterModule.forChild([{ path: '04-11', component: AppComponent }]) +// #docregion example + ], + declarations: [ + AppComponent, + HeroesComponent + ], + exports: [ AppComponent ], + entryComponents: [ AppComponent ] +}) +export class AppModule {} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/04-11/app/core/core.module.ts b/aio/content/examples/style-guide/ts/src/04-11/app/core/core.module.ts new file mode 100644 index 0000000000..4ba5c79a14 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-11/app/core/core.module.ts @@ -0,0 +1,19 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { LoggerService } from './logger.service'; +import { NavComponent } from './nav/nav.component'; +import { SpinnerComponent } from './spinner/spinner.component'; +import { SpinnerService } from './spinner/spinner.service'; + +@NgModule({ + imports: [ + CommonModule // we use ngFor + ], +  exports: [NavComponent, SpinnerComponent], +  declarations: [NavComponent, SpinnerComponent], + providers: [LoggerService, SpinnerService] +}) +export class CoreModule { } + diff --git a/aio/content/examples/style-guide/ts/src/04-11/app/core/index.ts b/aio/content/examples/style-guide/ts/src/04-11/app/core/index.ts new file mode 100644 index 0000000000..098f40c7d5 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-11/app/core/index.ts @@ -0,0 +1,4 @@ +// #docregion +export * from './logger.service'; +export * from './spinner/spinner.service'; +export * from './nav/nav.component'; diff --git a/aio/content/examples/style-guide/ts/src/04-11/app/core/logger.service.ts b/aio/content/examples/style-guide/ts/src/04-11/app/core/logger.service.ts new file mode 100644 index 0000000000..9c7080f07a --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-11/app/core/logger.service.ts @@ -0,0 +1,13 @@ +// #docregion +import { Injectable } from '@angular/core'; + +@Injectable() +export class LoggerService { + log(msg: string) { + console.log(msg); + } + + error(msg: string) { + console.error(msg); + } +} diff --git a/aio/content/examples/style-guide/ts/src/04-11/app/core/nav/nav.component.css b/aio/content/examples/style-guide/ts/src/04-11/app/core/nav/nav.component.css new file mode 100644 index 0000000000..c7903fd25c --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-11/app/core/nav/nav.component.css @@ -0,0 +1,63 @@ +/*#docregion*/ +.mdl-layout__header { + display: flex; + position: fixed; + background-color: #222; +} + +.nav-link { + padding: 0 1em; + width: 100px; + color: rgba(255,255,255,.6); + text-align: center; + text-decoration: none; +} + +.nav-link.router-link-active { + color: rgba(255,255,255, 1); +} + +.nav-link.router-link-active::after { + height: 3px; + width: 100%; + display: block; + content: " "; + bottom: 0; + left: 0; + position: inherit; + background: rgb(83,109,254); +} + +.md-title-icon > i { + background-image: url("assets/ng.png"); + background-repeat: no-repeat; + background-position: center center; + padding: 1em 2em; +} + +.mdl-layout__header-row { + height: 56px; + padding: 0 16px 0 72px; + padding-left: 8px; + background-color: #673AB7; + background: #0033FF; + background-color: #222; +} + +#reset-button { + position: fixed; + right: 2em; + top: 1em; +} + +@media (max-width: 480px) { + #reset-button { + display: none + } +} + +@media (max-width: 320px) { + a.nav-link { + font-size: 12px; + } +} diff --git a/aio/content/examples/style-guide/ts/src/04-11/app/core/nav/nav.component.html b/aio/content/examples/style-guide/ts/src/04-11/app/core/nav/nav.component.html new file mode 100644 index 0000000000..b3c7e441e0 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-11/app/core/nav/nav.component.html @@ -0,0 +1,14 @@ + +
    +
    +

    Tour of Heroes

    +
    + +
    +
    diff --git a/aio/content/examples/style-guide/ts/src/04-11/app/core/nav/nav.component.ts b/aio/content/examples/style-guide/ts/src/04-11/app/core/nav/nav.component.ts new file mode 100644 index 0000000000..571040cce5 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-11/app/core/nav/nav.component.ts @@ -0,0 +1,20 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'toh-nav', + templateUrl: './nav.component.html', + styleUrls: ['./nav.component.css'], +}) +export class NavComponent implements OnInit { + menuItems = [ + 'Heroes', + 'Villains', + 'Other' + ]; + + ngOnInit() { } + + constructor() { } +} diff --git a/aio/content/examples/style-guide/ts/src/04-11/app/core/spinner/spinner.component.css b/aio/content/examples/style-guide/ts/src/04-11/app/core/spinner/spinner.component.css new file mode 100644 index 0000000000..afad0fe8e3 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-11/app/core/spinner/spinner.component.css @@ -0,0 +1,21 @@ +/*#docregion*/ +.spinner { + position: absolute; + left: 7em; + top: 20em; + position: absolute; + background-color: blue; + height: .3em; + width: 6em; + margin:-60px 0 0 -60px; + -webkit-animation:spin 4s linear infinite; + -moz-animation:spin 4s linear infinite; + animation:spin 4s linear infinite; +} +@-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } } +@-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } } +@keyframes spin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } } + +.spinner-hidden { + display:none; +} diff --git a/aio/content/examples/style-guide/ts/src/04-11/app/core/spinner/spinner.component.html b/aio/content/examples/style-guide/ts/src/04-11/app/core/spinner/spinner.component.html new file mode 100644 index 0000000000..a07e3378c0 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-11/app/core/spinner/spinner.component.html @@ -0,0 +1,2 @@ + +
    diff --git a/aio/content/examples/style-guide/ts/src/04-11/app/core/spinner/spinner.component.ts b/aio/content/examples/style-guide/ts/src/04-11/app/core/spinner/spinner.component.ts new file mode 100644 index 0000000000..ccd6ece7fb --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-11/app/core/spinner/spinner.component.ts @@ -0,0 +1,36 @@ +// #docregion +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Subscription } from 'rxjs/Subscription'; + +import { LoggerService } from '../logger.service'; +import { SpinnerState, SpinnerService } from './spinner.service'; + +@Component({ + moduleId: module.id, + selector: 'toh-spinner', + templateUrl: './spinner.component.html', + styleUrls: ['./spinner.component.css'] +}) +export class SpinnerComponent implements OnDestroy, OnInit { + visible = false; + + private spinnerStateChanged: Subscription; + + constructor( + private loggerService: LoggerService, + private spinnerService: SpinnerService + ) { } + + ngOnInit() { + console.log(this.visible); + this.spinnerStateChanged = this.spinnerService.spinnerState + .subscribe((state: SpinnerState) => { + this.visible = state.show; + this.loggerService.log(`visible=${this.visible}`); + }); + } + + ngOnDestroy() { + this.spinnerStateChanged.unsubscribe(); + } +} diff --git a/aio/content/examples/style-guide/ts/src/04-11/app/core/spinner/spinner.service.ts b/aio/content/examples/style-guide/ts/src/04-11/app/core/spinner/spinner.service.ts new file mode 100644 index 0000000000..85e366c43e --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-11/app/core/spinner/spinner.service.ts @@ -0,0 +1,24 @@ +// #docregion +import { Injectable } from '@angular/core'; +import { Subject } from 'rxjs/Subject'; + +export interface SpinnerState { + show: boolean; +} + +@Injectable() +export class SpinnerService { + private spinnerSubject = new Subject(); + + spinnerState = this.spinnerSubject.asObservable(); + + constructor() { } + + show() { + this.spinnerSubject.next({ show: true }); + } + + hide() { + this.spinnerSubject.next({ show: false }); + } +} diff --git a/aio/content/examples/style-guide/ts/src/04-11/app/heroes/heroes.component.html b/aio/content/examples/style-guide/ts/src/04-11/app/heroes/heroes.component.html new file mode 100644 index 0000000000..9035a4b8ff --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-11/app/heroes/heroes.component.html @@ -0,0 +1,12 @@ + +
    + + + +
      +
    • + {{hero.name}} +
    • +
    + +
    diff --git a/aio/content/examples/style-guide/ts/src/04-11/app/heroes/heroes.component.ts b/aio/content/examples/style-guide/ts/src/04-11/app/heroes/heroes.component.ts new file mode 100644 index 0000000000..cdbf1135f2 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-11/app/heroes/heroes.component.ts @@ -0,0 +1,33 @@ +import { Component } from '@angular/core'; + +import { LoggerService } from '../core/logger.service'; +import { SpinnerService } from '../core/spinner/spinner.service'; + +@Component({ + moduleId: module.id, + selector: 'toh-heroes', + templateUrl: './heroes.component.html' +}) +export class HeroesComponent { + heroes: any[]; + + constructor( + private loggerService: LoggerService, + private spinnerService: SpinnerService + ) { } + + getHeroes() { + this.loggerService.log(`Getting heroes`); + this.spinnerService.show(); + setTimeout(() => { + this.heroes = [ + { id: 1, name: 'Windstorm' }, + { id: 2, name: 'Bombasto' }, + { id: 3, name: 'Magneta' }, + { id: 4, name: 'Tornado' } + ]; + this.loggerService.log(`We have ${HeroesComponent.length} heroes`); + this.spinnerService.hide(); + }, 2000); + } +} diff --git a/aio/content/examples/style-guide/ts/src/04-12/app/app.component.ts b/aio/content/examples/style-guide/ts/src/04-12/app/app.component.ts new file mode 100644 index 0000000000..dc85b06e9f --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-12/app/app.component.ts @@ -0,0 +1,11 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'toh-app', + template: ` + + + ` +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/04-12/app/app.module.ts b/aio/content/examples/style-guide/ts/src/04-12/app/app.module.ts new file mode 100644 index 0000000000..7c9fb44182 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-12/app/app.module.ts @@ -0,0 +1,30 @@ +// #docplaster +// #docregion +// #docregion example +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +// #enddocregion example +import { RouterModule } from '@angular/router'; +// #docregion example + +import { AppComponent } from './app.component'; +import { HeroesComponent } from './heroes/heroes.component'; +import { CoreModule } from './core/core.module'; + +@NgModule({ + imports: [ + BrowserModule, + CoreModule, +// #enddocregion example + RouterModule.forChild([{ path: '04-12', component: AppComponent }]) +// #docregion example + ], + declarations: [ + AppComponent, + HeroesComponent + ], + exports: [ AppComponent ], + entryComponents: [ AppComponent ] +}) +export class AppModule {} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/04-12/app/core/core.module.ts b/aio/content/examples/style-guide/ts/src/04-12/app/core/core.module.ts new file mode 100644 index 0000000000..069141bf2a --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-12/app/core/core.module.ts @@ -0,0 +1,21 @@ +// #docregion +import { NgModule, Optional, SkipSelf } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { LoggerService } from './logger.service'; +import { NavComponent } from './nav/nav.component'; +import { throwIfAlreadyLoaded } from './module-import-guard'; + +@NgModule({ + imports: [ + CommonModule // we use ngFor + ], +  exports: [NavComponent], +  declarations: [NavComponent], + providers: [LoggerService] +}) +export class CoreModule { + constructor( @Optional() @SkipSelf() parentModule: CoreModule) { + throwIfAlreadyLoaded(parentModule, 'CoreModule'); + } +} diff --git a/aio/content/examples/style-guide/ts/src/04-12/app/core/index.ts b/aio/content/examples/style-guide/ts/src/04-12/app/core/index.ts new file mode 100644 index 0000000000..8768b77f41 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-12/app/core/index.ts @@ -0,0 +1,3 @@ +// #docregion +export * from './logger.service'; +export * from './nav/nav.component'; diff --git a/aio/content/examples/style-guide/ts/src/04-12/app/core/logger.service.ts b/aio/content/examples/style-guide/ts/src/04-12/app/core/logger.service.ts new file mode 100644 index 0000000000..9c7080f07a --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-12/app/core/logger.service.ts @@ -0,0 +1,13 @@ +// #docregion +import { Injectable } from '@angular/core'; + +@Injectable() +export class LoggerService { + log(msg: string) { + console.log(msg); + } + + error(msg: string) { + console.error(msg); + } +} diff --git a/aio/content/examples/style-guide/ts/src/04-12/app/core/module-import-guard.ts b/aio/content/examples/style-guide/ts/src/04-12/app/core/module-import-guard.ts new file mode 100644 index 0000000000..5248b15b2e --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-12/app/core/module-import-guard.ts @@ -0,0 +1,6 @@ +// #docregion +export function throwIfAlreadyLoaded(parentModule: any, moduleName: string) { + if (parentModule) { + throw new Error(`${moduleName} has already been loaded. Import Core modules in the AppModule only.`); + } +} diff --git a/aio/content/examples/style-guide/ts/src/04-12/app/core/nav/nav.component.css b/aio/content/examples/style-guide/ts/src/04-12/app/core/nav/nav.component.css new file mode 100644 index 0000000000..c7903fd25c --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-12/app/core/nav/nav.component.css @@ -0,0 +1,63 @@ +/*#docregion*/ +.mdl-layout__header { + display: flex; + position: fixed; + background-color: #222; +} + +.nav-link { + padding: 0 1em; + width: 100px; + color: rgba(255,255,255,.6); + text-align: center; + text-decoration: none; +} + +.nav-link.router-link-active { + color: rgba(255,255,255, 1); +} + +.nav-link.router-link-active::after { + height: 3px; + width: 100%; + display: block; + content: " "; + bottom: 0; + left: 0; + position: inherit; + background: rgb(83,109,254); +} + +.md-title-icon > i { + background-image: url("assets/ng.png"); + background-repeat: no-repeat; + background-position: center center; + padding: 1em 2em; +} + +.mdl-layout__header-row { + height: 56px; + padding: 0 16px 0 72px; + padding-left: 8px; + background-color: #673AB7; + background: #0033FF; + background-color: #222; +} + +#reset-button { + position: fixed; + right: 2em; + top: 1em; +} + +@media (max-width: 480px) { + #reset-button { + display: none + } +} + +@media (max-width: 320px) { + a.nav-link { + font-size: 12px; + } +} diff --git a/aio/content/examples/style-guide/ts/src/04-12/app/core/nav/nav.component.html b/aio/content/examples/style-guide/ts/src/04-12/app/core/nav/nav.component.html new file mode 100644 index 0000000000..b3c7e441e0 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-12/app/core/nav/nav.component.html @@ -0,0 +1,14 @@ + +
    +
    +

    Tour of Heroes

    +
    + +
    +
    diff --git a/aio/content/examples/style-guide/ts/src/04-12/app/core/nav/nav.component.ts b/aio/content/examples/style-guide/ts/src/04-12/app/core/nav/nav.component.ts new file mode 100644 index 0000000000..571040cce5 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-12/app/core/nav/nav.component.ts @@ -0,0 +1,20 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'toh-nav', + templateUrl: './nav.component.html', + styleUrls: ['./nav.component.css'], +}) +export class NavComponent implements OnInit { + menuItems = [ + 'Heroes', + 'Villains', + 'Other' + ]; + + ngOnInit() { } + + constructor() { } +} diff --git a/aio/content/examples/style-guide/ts/src/04-12/app/heroes/heroes.component.html b/aio/content/examples/style-guide/ts/src/04-12/app/heroes/heroes.component.html new file mode 100644 index 0000000000..9035a4b8ff --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-12/app/heroes/heroes.component.html @@ -0,0 +1,12 @@ + +
    + + + +
      +
    • + {{hero.name}} +
    • +
    + +
    diff --git a/aio/content/examples/style-guide/ts/src/04-12/app/heroes/heroes.component.ts b/aio/content/examples/style-guide/ts/src/04-12/app/heroes/heroes.component.ts new file mode 100644 index 0000000000..3b3e169c0c --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/04-12/app/heroes/heroes.component.ts @@ -0,0 +1,25 @@ +import { Component } from '@angular/core'; + +import { LoggerService } from '../core/logger.service'; + +@Component({ + moduleId: module.id, + selector: 'toh-heroes', + templateUrl: './heroes.component.html' +}) +export class HeroesComponent { + heroes: any[]; + + constructor(private loggerService: LoggerService) { } + + getHeroes() { + this.loggerService.log(`Getting heroes`); + this.heroes = [ + { id: 1, name: 'Windstorm' }, + { id: 2, name: 'Bombasto' }, + { id: 3, name: 'Magneta' }, + { id: 4, name: 'Tornado' } + ]; + this.loggerService.log(`We have ${HeroesComponent.length} heroes`); + } +} diff --git a/aio/content/examples/style-guide/ts/src/05-02/app/app.component.html b/aio/content/examples/style-guide/ts/src/05-02/app/app.component.html new file mode 100644 index 0000000000..607d068557 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-02/app/app.component.html @@ -0,0 +1,2 @@ + + diff --git a/aio/content/examples/style-guide/ts/src/05-02/app/app.component.ts b/aio/content/examples/style-guide/ts/src/05-02/app/app.component.ts new file mode 100644 index 0000000000..d57163f06f --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-02/app/app.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'sg-app', + templateUrl: './app.component.html' +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/05-02/app/app.module.ts b/aio/content/examples/style-guide/ts/src/05-02/app/app.module.ts new file mode 100644 index 0000000000..1c458e2ca1 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-02/app/app.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { HeroButtonComponent } from './heroes'; + +@NgModule({ + imports: [ + RouterModule.forChild([{ path: '05-02', component: AppComponent }]) + ], + declarations: [ + AppComponent, + HeroButtonComponent + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/05-02/app/heroes/index.ts b/aio/content/examples/style-guide/ts/src/05-02/app/heroes/index.ts new file mode 100644 index 0000000000..c3da79f741 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-02/app/heroes/index.ts @@ -0,0 +1 @@ +export * from './shared'; diff --git a/aio/content/examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/hero-button.component.avoid.ts b/aio/content/examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/hero-button.component.avoid.ts new file mode 100644 index 0000000000..fd055bf64f --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/hero-button.component.avoid.ts @@ -0,0 +1,12 @@ +// #docplaster +import { Component } from '@angular/core'; +// #docregion example +/* avoid */ + +@Component({ + moduleId: module.id, + selector: 'tohHeroButton', + templateUrl: './hero-button.component.html' +}) +export class HeroButtonComponent {} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/hero-button.component.html b/aio/content/examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/hero-button.component.html new file mode 100644 index 0000000000..9ad67e50ac --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/hero-button.component.html @@ -0,0 +1 @@ + diff --git a/aio/content/examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/hero-button.component.ts b/aio/content/examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/hero-button.component.ts new file mode 100644 index 0000000000..8b9fbf95e5 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/hero-button.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +// #docregion example +@Component({ + moduleId: module.id, + selector: 'toh-hero-button', + templateUrl: './hero-button.component.html' +}) +export class HeroButtonComponent {} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/index.ts b/aio/content/examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/index.ts new file mode 100644 index 0000000000..6bb67c5670 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/index.ts @@ -0,0 +1 @@ +export * from './hero-button.component'; diff --git a/aio/content/examples/style-guide/ts/src/05-02/app/heroes/shared/index.ts b/aio/content/examples/style-guide/ts/src/05-02/app/heroes/shared/index.ts new file mode 100644 index 0000000000..2334d49c9a --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-02/app/heroes/shared/index.ts @@ -0,0 +1 @@ +export * from './hero-button'; diff --git a/aio/content/examples/style-guide/ts/src/05-02/app/index.ts b/aio/content/examples/style-guide/ts/src/05-02/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-02/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/05-03/app/app.component.avoid.html b/aio/content/examples/style-guide/ts/src/05-03/app/app.component.avoid.html new file mode 100644 index 0000000000..91bdbe403c --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-03/app/app.component.avoid.html @@ -0,0 +1,4 @@ + + + +
    diff --git a/aio/content/examples/style-guide/ts/src/05-03/app/app.component.html b/aio/content/examples/style-guide/ts/src/05-03/app/app.component.html new file mode 100644 index 0000000000..607d068557 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-03/app/app.component.html @@ -0,0 +1,2 @@ + + diff --git a/aio/content/examples/style-guide/ts/src/05-03/app/app.component.ts b/aio/content/examples/style-guide/ts/src/05-03/app/app.component.ts new file mode 100644 index 0000000000..d57163f06f --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-03/app/app.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'sg-app', + templateUrl: './app.component.html' +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/05-03/app/app.module.ts b/aio/content/examples/style-guide/ts/src/05-03/app/app.module.ts new file mode 100644 index 0000000000..1b754e3ee5 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-03/app/app.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { HeroButtonComponent } from './heroes'; + +@NgModule({ + imports: [ + RouterModule.forChild([{ path: '05-03', component: AppComponent }]) + ], + declarations: [ + AppComponent, + HeroButtonComponent + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/05-03/app/heroes/index.ts b/aio/content/examples/style-guide/ts/src/05-03/app/heroes/index.ts new file mode 100644 index 0000000000..c3da79f741 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-03/app/heroes/index.ts @@ -0,0 +1 @@ +export * from './shared'; diff --git a/aio/content/examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/hero-button.component.avoid.ts b/aio/content/examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/hero-button.component.avoid.ts new file mode 100644 index 0000000000..6edbc4cd9e --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/hero-button.component.avoid.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; +// #docregion example +/* avoid */ + +@Component({ + moduleId: module.id, + selector: '[tohHeroButton]', + templateUrl: './hero-button.component.html' +}) +export class HeroButtonComponent {} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/hero-button.component.html b/aio/content/examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/hero-button.component.html new file mode 100644 index 0000000000..9ad67e50ac --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/hero-button.component.html @@ -0,0 +1 @@ + diff --git a/aio/content/examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/hero-button.component.ts b/aio/content/examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/hero-button.component.ts new file mode 100644 index 0000000000..8b9fbf95e5 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/hero-button.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +// #docregion example +@Component({ + moduleId: module.id, + selector: 'toh-hero-button', + templateUrl: './hero-button.component.html' +}) +export class HeroButtonComponent {} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/index.ts b/aio/content/examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/index.ts new file mode 100644 index 0000000000..6bb67c5670 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/index.ts @@ -0,0 +1 @@ +export * from './hero-button.component'; diff --git a/aio/content/examples/style-guide/ts/src/05-03/app/heroes/shared/index.ts b/aio/content/examples/style-guide/ts/src/05-03/app/heroes/shared/index.ts new file mode 100644 index 0000000000..2334d49c9a --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-03/app/heroes/shared/index.ts @@ -0,0 +1 @@ +export * from './hero-button'; diff --git a/aio/content/examples/style-guide/ts/src/05-03/app/index.ts b/aio/content/examples/style-guide/ts/src/05-03/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-03/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/05-04/app/app.component.ts b/aio/content/examples/style-guide/ts/src/05-04/app/app.component.ts new file mode 100644 index 0000000000..0e43893f7f --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-04/app/app.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'sg-app', + template: '' +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/05-04/app/app.module.ts b/aio/content/examples/style-guide/ts/src/05-04/app/app.module.ts new file mode 100644 index 0000000000..07f97cc6e4 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-04/app/app.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { HeroesComponent } from './heroes'; +import { HeroService } from './heroes/shared'; + +@NgModule({ + imports: [ + BrowserModule, + RouterModule.forChild([{ path: '05-04', component: AppComponent }]) + ], + declarations: [ + AppComponent, + HeroesComponent + ], + exports: [ AppComponent ], + providers: [ HeroService ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/05-04/app/heroes/heroes.component.avoid.ts b/aio/content/examples/style-guide/ts/src/05-04/app/heroes/heroes.component.avoid.ts new file mode 100644 index 0000000000..0ceb37032d --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-04/app/heroes/heroes.component.avoid.ts @@ -0,0 +1,64 @@ +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; + +import { Hero, HeroService } from './shared'; + +// #docregion example +/* avoid */ + +@Component({ + selector: 'toh-heroes', + template: ` +
    +

    My Heroes

    +
      +
    • + {{hero.id}} {{hero.name}} +
    • +
    +
    +

    {{selectedHero.name | uppercase}} is my hero

    +
    +
    + `, + styles: [` + .heroes { + margin: 0 0 2em 0; list-style-type: none; padding: 0; width: 15em; + } + .heroes li { + cursor: pointer; + position: relative; + left: 0; + background-color: #EEE; + margin: .5em; + padding: .3em 0; + height: 1.6em; + border-radius: 4px; + } + .heroes .badge { + display: inline-block; + font-size: small; + color: white; + padding: 0.8em 0.7em 0 0.7em; + background-color: #607D8B; + line-height: 1em; + position: relative; + left: -1px; + top: -4px; + height: 1.8em; + margin-right: .8em; + border-radius: 4px 0 0 4px; + } + `] +}) +export class HeroesComponent implements OnInit { + heroes: Observable; + selectedHero: Hero; + + constructor(private heroService: HeroService) { } + + ngOnInit() { + this.heroes = this.heroService.getHeroes(); + } +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/05-04/app/heroes/heroes.component.css b/aio/content/examples/style-guide/ts/src/05-04/app/heroes/heroes.component.css new file mode 100644 index 0000000000..82f0c1d0ab --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-04/app/heroes/heroes.component.css @@ -0,0 +1,28 @@ +/* #docregion */ +.heroes { + margin: 0 0 2em 0; list-style-type: none; padding: 0; width: 15em; +} +.heroes li { + cursor: pointer; + position: relative; + left: 0; + background-color: #EEE; + margin: .5em; + padding: .3em 0; + height: 1.6em; + border-radius: 4px; +} +.heroes .badge { + display: inline-block; + font-size: small; + color: white; + padding: 0.8em 0.7em 0 0.7em; + background-color: #607D8B; + line-height: 1em; + position: relative; + left: -1px; + top: -4px; + height: 1.8em; + margin-right: .8em; + border-radius: 4px 0 0 4px; +} diff --git a/aio/content/examples/style-guide/ts/src/05-04/app/heroes/heroes.component.html b/aio/content/examples/style-guide/ts/src/05-04/app/heroes/heroes.component.html new file mode 100644 index 0000000000..bab05ceb2b --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-04/app/heroes/heroes.component.html @@ -0,0 +1,12 @@ + +
    +

    My Heroes

    +
      +
    • + {{hero.id}} {{hero.name}} +
    • +
    +
    +

    {{selectedHero.name | uppercase}} is my hero

    +
    +
    diff --git a/aio/content/examples/style-guide/ts/src/05-04/app/heroes/heroes.component.ts b/aio/content/examples/style-guide/ts/src/05-04/app/heroes/heroes.component.ts new file mode 100644 index 0000000000..ec5dadc4a0 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-04/app/heroes/heroes.component.ts @@ -0,0 +1,23 @@ +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; + +import { Hero, HeroService } from './shared'; + +// #docregion example +@Component({ + moduleId: module.id, + selector: 'toh-heroes', + templateUrl: './heroes.component.html', + styleUrls: ['./heroes.component.css'] +}) +export class HeroesComponent implements OnInit { + heroes: Observable; + selectedHero: Hero; + + constructor(private heroService: HeroService) { } + + ngOnInit() { + this.heroes = this.heroService.getHeroes(); + } +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/05-04/app/heroes/index.ts b/aio/content/examples/style-guide/ts/src/05-04/app/heroes/index.ts new file mode 100644 index 0000000000..a8d7f1d422 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-04/app/heroes/index.ts @@ -0,0 +1,2 @@ +export * from './shared'; +export * from './heroes.component'; diff --git a/aio/content/examples/style-guide/ts/src/05-04/app/heroes/shared/hero.model.ts b/aio/content/examples/style-guide/ts/src/05-04/app/heroes/shared/hero.model.ts new file mode 100644 index 0000000000..8f7cc205c8 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-04/app/heroes/shared/hero.model.ts @@ -0,0 +1,5 @@ +// #docregion +export class Hero { + id: number; + name: string; +} diff --git a/aio/content/examples/style-guide/ts/src/05-04/app/heroes/shared/hero.service.ts b/aio/content/examples/style-guide/ts/src/05-04/app/heroes/shared/hero.service.ts new file mode 100644 index 0000000000..9d388780a6 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-04/app/heroes/shared/hero.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { Http } from '@angular/http'; + +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/operator/map'; + +import { Hero } from './hero.model'; + +@Injectable() +export class HeroService { + + constructor(private http: Http) {} + + getHeroes(): Observable { + return this.http.get('api/heroes') + .map(resp => resp.json().data as Hero[]); + } +} diff --git a/aio/content/examples/style-guide/ts/src/05-04/app/heroes/shared/index.ts b/aio/content/examples/style-guide/ts/src/05-04/app/heroes/shared/index.ts new file mode 100644 index 0000000000..dbb150d3f8 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-04/app/heroes/shared/index.ts @@ -0,0 +1,2 @@ +export * from './hero.model'; +export * from './hero.service'; diff --git a/aio/content/examples/style-guide/ts/src/05-04/app/index.ts b/aio/content/examples/style-guide/ts/src/05-04/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-04/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/05-12/app/app.component.ts b/aio/content/examples/style-guide/ts/src/05-12/app/app.component.ts new file mode 100644 index 0000000000..dac40205c9 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-12/app/app.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'sg-app', + template: '' +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/05-12/app/app.module.ts b/aio/content/examples/style-guide/ts/src/05-12/app/app.module.ts new file mode 100644 index 0000000000..5177b2cc64 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-12/app/app.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { HeroButtonComponent } from './heroes'; + +@NgModule({ + imports: [ + RouterModule.forChild([{ path: '05-12', component: AppComponent }]) + ], + declarations: [ + AppComponent, + HeroButtonComponent + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/05-12/app/heroes/index.ts b/aio/content/examples/style-guide/ts/src/05-12/app/heroes/index.ts new file mode 100644 index 0000000000..c3da79f741 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-12/app/heroes/index.ts @@ -0,0 +1 @@ +export * from './shared'; diff --git a/aio/content/examples/style-guide/ts/src/05-12/app/heroes/shared/hero-button/hero-button.component.avoid.ts b/aio/content/examples/style-guide/ts/src/05-12/app/heroes/shared/hero-button/hero-button.component.avoid.ts new file mode 100644 index 0000000000..8f393ddd32 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-12/app/heroes/shared/hero-button/hero-button.component.avoid.ts @@ -0,0 +1,20 @@ +// #docregion +import { Component, EventEmitter } from '@angular/core'; +// #docregion example +/* avoid */ + +@Component({ + selector: 'toh-hero-button', + template: ``, + inputs: [ + 'label' + ], + outputs: [ + 'change' + ] +}) +export class HeroButtonComponent { + change = new EventEmitter(); + label: string; +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/05-12/app/heroes/shared/hero-button/hero-button.component.ts b/aio/content/examples/style-guide/ts/src/05-12/app/heroes/shared/hero-button/hero-button.component.ts new file mode 100644 index 0000000000..b299740765 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-12/app/heroes/shared/hero-button/hero-button.component.ts @@ -0,0 +1,13 @@ +// #docregion +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +// #docregion example +@Component({ + selector: 'toh-hero-button', + template: `` +}) +export class HeroButtonComponent { + @Output() change = new EventEmitter(); + @Input() label: string; +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/05-12/app/heroes/shared/hero-button/index.ts b/aio/content/examples/style-guide/ts/src/05-12/app/heroes/shared/hero-button/index.ts new file mode 100644 index 0000000000..6bb67c5670 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-12/app/heroes/shared/hero-button/index.ts @@ -0,0 +1 @@ +export * from './hero-button.component'; diff --git a/aio/content/examples/style-guide/ts/src/05-12/app/heroes/shared/index.ts b/aio/content/examples/style-guide/ts/src/05-12/app/heroes/shared/index.ts new file mode 100644 index 0000000000..2334d49c9a --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-12/app/heroes/shared/index.ts @@ -0,0 +1 @@ +export * from './hero-button'; diff --git a/aio/content/examples/style-guide/ts/src/05-12/app/index.ts b/aio/content/examples/style-guide/ts/src/05-12/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-12/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/05-13/app/app.component.avoid.html b/aio/content/examples/style-guide/ts/src/05-13/app/app.component.avoid.html new file mode 100644 index 0000000000..0a263a6a95 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-13/app/app.component.avoid.html @@ -0,0 +1,5 @@ + + + + + diff --git a/aio/content/examples/style-guide/ts/src/05-13/app/app.component.html b/aio/content/examples/style-guide/ts/src/05-13/app/app.component.html new file mode 100644 index 0000000000..3cd94ca772 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-13/app/app.component.html @@ -0,0 +1,6 @@ + + + + + +

    The Great Bombasto

    diff --git a/aio/content/examples/style-guide/ts/src/05-13/app/app.component.ts b/aio/content/examples/style-guide/ts/src/05-13/app/app.component.ts new file mode 100644 index 0000000000..d57163f06f --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-13/app/app.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'sg-app', + templateUrl: './app.component.html' +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/05-13/app/app.module.ts b/aio/content/examples/style-guide/ts/src/05-13/app/app.module.ts new file mode 100644 index 0000000000..7ebe91dbbc --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-13/app/app.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { HeroButtonComponent, HeroHighlightDirective } from './heroes'; + +@NgModule({ + imports: [ + RouterModule.forChild([{ path: '05-13', component: AppComponent }]) + ], + declarations: [ + AppComponent, + HeroButtonComponent, HeroHighlightDirective + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/05-13/app/heroes/index.ts b/aio/content/examples/style-guide/ts/src/05-13/app/heroes/index.ts new file mode 100644 index 0000000000..c3da79f741 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-13/app/heroes/index.ts @@ -0,0 +1 @@ +export * from './shared'; diff --git a/aio/content/examples/style-guide/ts/src/05-13/app/heroes/shared/hero-button/hero-button.component.avoid.ts b/aio/content/examples/style-guide/ts/src/05-13/app/heroes/shared/hero-button/hero-button.component.avoid.ts new file mode 100644 index 0000000000..4e67a14113 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-13/app/heroes/shared/hero-button/hero-button.component.avoid.ts @@ -0,0 +1,14 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +// #docregion example +/* avoid pointless aliasing */ + +@Component({ + selector: 'toh-hero-button', + template: `` +}) +export class HeroButtonComponent { + // Pointless aliases + @Output('changeEvent') change = new EventEmitter(); + @Input('labelAttribute') label: string; +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/05-13/app/heroes/shared/hero-button/hero-button.component.ts b/aio/content/examples/style-guide/ts/src/05-13/app/heroes/shared/hero-button/hero-button.component.ts new file mode 100644 index 0000000000..af6e7d46b7 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-13/app/heroes/shared/hero-button/hero-button.component.ts @@ -0,0 +1,14 @@ +// #docregion +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +// #docregion example +@Component({ + selector: 'toh-hero-button', + template: `` +}) +export class HeroButtonComponent { + // No aliases + @Output() change = new EventEmitter(); + @Input() label: string; +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/05-13/app/heroes/shared/hero-button/index.ts b/aio/content/examples/style-guide/ts/src/05-13/app/heroes/shared/hero-button/index.ts new file mode 100644 index 0000000000..6bb67c5670 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-13/app/heroes/shared/hero-button/index.ts @@ -0,0 +1 @@ +export * from './hero-button.component'; diff --git a/aio/content/examples/style-guide/ts/src/05-13/app/heroes/shared/hero-highlight.directive.ts b/aio/content/examples/style-guide/ts/src/05-13/app/heroes/shared/hero-highlight.directive.ts new file mode 100644 index 0000000000..737af31f4f --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-13/app/heroes/shared/hero-highlight.directive.ts @@ -0,0 +1,15 @@ +// #docregion +import { Directive, ElementRef, Input, OnChanges } from '@angular/core'; + +@Directive({ selector: '[heroHighlight]' }) +export class HeroHighlightDirective implements OnChanges { + + // Aliased because `color` is a better property name than `heroHighlight` + @Input('heroHighlight') color: string; + + constructor(private el: ElementRef) {} + + ngOnChanges() { + this.el.nativeElement.style.backgroundColor = this.color || 'yellow'; + } +} diff --git a/aio/content/examples/style-guide/ts/src/05-13/app/heroes/shared/index.ts b/aio/content/examples/style-guide/ts/src/05-13/app/heroes/shared/index.ts new file mode 100644 index 0000000000..565f46cf4f --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-13/app/heroes/shared/index.ts @@ -0,0 +1,2 @@ +export * from './hero-button'; +export * from './hero-highlight.directive'; diff --git a/aio/content/examples/style-guide/ts/src/05-13/app/index.ts b/aio/content/examples/style-guide/ts/src/05-13/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-13/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/05-14/app/app.component.ts b/aio/content/examples/style-guide/ts/src/05-14/app/app.component.ts new file mode 100644 index 0000000000..8ed6da4c82 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-14/app/app.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'sg-app', + template: `` +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/05-14/app/app.module.ts b/aio/content/examples/style-guide/ts/src/05-14/app/app.module.ts new file mode 100644 index 0000000000..0b294573d2 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-14/app/app.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { ToastComponent } from './shared'; + +@NgModule({ + imports: [ + RouterModule.forChild([{ path: '05-14', component: AppComponent }]) + ], + declarations: [ + AppComponent, + ToastComponent + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/05-14/app/index.ts b/aio/content/examples/style-guide/ts/src/05-14/app/index.ts new file mode 100644 index 0000000000..ebe5c92f03 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-14/app/index.ts @@ -0,0 +1,2 @@ +export * from './shared'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/05-14/app/shared/index.ts b/aio/content/examples/style-guide/ts/src/05-14/app/shared/index.ts new file mode 100644 index 0000000000..7ff6d415e7 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-14/app/shared/index.ts @@ -0,0 +1 @@ +export * from './toast'; diff --git a/aio/content/examples/style-guide/ts/src/05-14/app/shared/toast/index.ts b/aio/content/examples/style-guide/ts/src/05-14/app/shared/toast/index.ts new file mode 100644 index 0000000000..6502de796e --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-14/app/shared/toast/index.ts @@ -0,0 +1 @@ +export * from './toast.component'; diff --git a/aio/content/examples/style-guide/ts/src/05-14/app/shared/toast/toast.component.avoid.ts b/aio/content/examples/style-guide/ts/src/05-14/app/shared/toast/toast.component.avoid.ts new file mode 100644 index 0000000000..037ff2c8b5 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-14/app/shared/toast/toast.component.avoid.ts @@ -0,0 +1,40 @@ +// #docregion +import { OnInit } from '@angular/core'; +// #docregion example +/* avoid */ + +export class ToastComponent implements OnInit { + + private defaults = { + title: '', + message: 'May the Force be with you' + }; + message: string; + title: string; + private toastElement: any; + + ngOnInit() { + this.toastElement = document.getElementById('toh-toast'); + } + + // private methods + private hide() { + this.toastElement.style.opacity = 0; + window.setTimeout(() => this.toastElement.style.zIndex = 0, 400); + } + + activate(message = this.defaults.message, title = this.defaults.title) { + this.title = title; + this.message = message; + this.show(); + } + + private show() { + console.log(this.message); + this.toastElement.style.opacity = 1; + this.toastElement.style.zIndex = 9999; + + window.setTimeout(() => this.hide(), 2500); + } +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/05-14/app/shared/toast/toast.component.ts b/aio/content/examples/style-guide/ts/src/05-14/app/shared/toast/toast.component.ts new file mode 100644 index 0000000000..d7b8ea4af0 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-14/app/shared/toast/toast.component.ts @@ -0,0 +1,45 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'toh-toast', + template: `...` +}) +// #docregion example +export class ToastComponent implements OnInit { + // public properties + message: string; + title: string; + + // private fields + private defaults = { + title: '', + message: 'May the Force be with you' + }; + private toastElement: any; + + // public methods + activate(message = this.defaults.message, title = this.defaults.title) { + this.title = title; + this.message = message; + this.show(); + } + + ngOnInit() { + this.toastElement = document.getElementById('toh-toast'); + } + + // private methods + private hide() { + this.toastElement.style.opacity = 0; + window.setTimeout(() => this.toastElement.style.zIndex = 0, 400); + } + + private show() { + console.log(this.message); + this.toastElement.style.opacity = 1; + this.toastElement.style.zIndex = 9999; + window.setTimeout(() => this.hide(), 2500); + } +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/05-15/app/app.component.ts b/aio/content/examples/style-guide/ts/src/05-15/app/app.component.ts new file mode 100644 index 0000000000..91b569b1e7 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-15/app/app.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +import { HeroService } from './heroes'; + +@Component({ + selector: 'sg-app', + template: '', + providers: [HeroService] +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/05-15/app/app.module.ts b/aio/content/examples/style-guide/ts/src/05-15/app/app.module.ts new file mode 100644 index 0000000000..9bd4b8c9a2 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-15/app/app.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { HeroListComponent } from './heroes'; + +@NgModule({ + imports: [ + BrowserModule, + RouterModule.forChild([{ path: '05-15', component: AppComponent }]) + ], + declarations: [ + AppComponent, + HeroListComponent + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/05-15/app/heroes/hero-list/hero-list.component.avoid.ts b/aio/content/examples/style-guide/ts/src/05-15/app/heroes/hero-list/hero-list.component.avoid.ts new file mode 100644 index 0000000000..c323ba2b1c --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-15/app/heroes/hero-list/hero-list.component.avoid.ts @@ -0,0 +1,39 @@ +// #docregion +/* avoid */ + +import { OnInit } from '@angular/core'; +import { Http, Response } from '@angular/http'; + +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/operator/catch'; +import 'rxjs/add/operator/finally'; +import 'rxjs/add/operator/map'; + +import { Hero } from '../shared/hero.model'; + +const heroesUrl = 'http://angular.io'; + +export class HeroListComponent implements OnInit { + heroes: Hero[]; + constructor(private http: Http) {} + getHeroes() { + this.heroes = []; + this.http.get(heroesUrl) + .map((response: Response) => response.json().data) + .catch(this.catchBadResponse) + .finally(() => this.hideSpinner()) + .subscribe((heroes: Hero[]) => this.heroes = heroes); + } + ngOnInit() { + this.getHeroes(); + } + + private catchBadResponse(err: any, source: Observable) { + // log and handle the exception + return new Observable(); + } + + private hideSpinner() { + // hide the spinner + } +} diff --git a/aio/content/examples/style-guide/ts/src/05-15/app/heroes/hero-list/hero-list.component.ts b/aio/content/examples/style-guide/ts/src/05-15/app/heroes/hero-list/hero-list.component.ts new file mode 100644 index 0000000000..1fdb893c13 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-15/app/heroes/hero-list/hero-list.component.ts @@ -0,0 +1,23 @@ +// #docregion example +import { Component, OnInit } from '@angular/core'; + +import { Hero, HeroService } from '../shared'; + +@Component({ + selector: 'toh-hero-list', + template: `...` +}) +export class HeroListComponent implements OnInit { + heroes: Hero[]; + constructor(private heroService: HeroService) {} + getHeroes() { + this.heroes = []; + this.heroService.getHeroes() + .subscribe(heroes => this.heroes = heroes); + } + ngOnInit() { + this.getHeroes(); + } +} +// #enddocregion example + diff --git a/aio/content/examples/style-guide/ts/src/05-15/app/heroes/hero-list/index.ts b/aio/content/examples/style-guide/ts/src/05-15/app/heroes/hero-list/index.ts new file mode 100644 index 0000000000..c4bcb3278e --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-15/app/heroes/hero-list/index.ts @@ -0,0 +1 @@ +export * from './hero-list.component'; diff --git a/aio/content/examples/style-guide/ts/src/05-15/app/heroes/index.ts b/aio/content/examples/style-guide/ts/src/05-15/app/heroes/index.ts new file mode 100644 index 0000000000..f1112f1c7c --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-15/app/heroes/index.ts @@ -0,0 +1,2 @@ +export * from './hero-list'; +export * from './shared'; diff --git a/aio/content/examples/style-guide/ts/src/05-15/app/heroes/shared/hero.model.ts b/aio/content/examples/style-guide/ts/src/05-15/app/heroes/shared/hero.model.ts new file mode 100644 index 0000000000..8f7cc205c8 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-15/app/heroes/shared/hero.model.ts @@ -0,0 +1,5 @@ +// #docregion +export class Hero { + id: number; + name: string; +} diff --git a/aio/content/examples/style-guide/ts/src/05-15/app/heroes/shared/hero.service.ts b/aio/content/examples/style-guide/ts/src/05-15/app/heroes/shared/hero.service.ts new file mode 100644 index 0000000000..72d07bbed4 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-15/app/heroes/shared/hero.service.ts @@ -0,0 +1,15 @@ +// #docregion +import { Injectable } from '@angular/core'; + +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/observable/of'; + +import { Hero } from './hero.model'; + +@Injectable() +export class HeroService { + getHeroes() { + let heroes: Hero[] = []; + return Observable.of(heroes); + } +} diff --git a/aio/content/examples/style-guide/ts/src/05-15/app/heroes/shared/index.ts b/aio/content/examples/style-guide/ts/src/05-15/app/heroes/shared/index.ts new file mode 100644 index 0000000000..27516fdedd --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-15/app/heroes/shared/index.ts @@ -0,0 +1,3 @@ +// #docregion +export * from './hero.model'; +export * from './hero.service'; diff --git a/aio/content/examples/style-guide/ts/src/05-15/app/index.ts b/aio/content/examples/style-guide/ts/src/05-15/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-15/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/05-16/app/app.component.avoid.html b/aio/content/examples/style-guide/ts/src/05-16/app/app.component.avoid.html new file mode 100644 index 0000000000..2c0cea58e2 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-16/app/app.component.avoid.html @@ -0,0 +1,4 @@ + + + + diff --git a/aio/content/examples/style-guide/ts/src/05-16/app/app.component.html b/aio/content/examples/style-guide/ts/src/05-16/app/app.component.html new file mode 100644 index 0000000000..4883a6940a --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-16/app/app.component.html @@ -0,0 +1,2 @@ + + diff --git a/aio/content/examples/style-guide/ts/src/05-16/app/app.component.ts b/aio/content/examples/style-guide/ts/src/05-16/app/app.component.ts new file mode 100644 index 0000000000..d57163f06f --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-16/app/app.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'sg-app', + templateUrl: './app.component.html' +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/05-16/app/app.module.ts b/aio/content/examples/style-guide/ts/src/05-16/app/app.module.ts new file mode 100644 index 0000000000..c3fb36f8ac --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-16/app/app.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { HeroComponent } from './heroes'; + +@NgModule({ + imports: [ + BrowserModule, + RouterModule.forChild([{ path: '05-16', component: AppComponent }]) + ], + declarations: [ + AppComponent, + HeroComponent + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/05-16/app/heroes/hero.component.avoid.ts b/aio/content/examples/style-guide/ts/src/05-16/app/heroes/hero.component.avoid.ts new file mode 100644 index 0000000000..823aa2e1c7 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-16/app/heroes/hero.component.avoid.ts @@ -0,0 +1,13 @@ +// #docregion +import { Component, EventEmitter, Output } from '@angular/core'; +// #docregion example +/* avoid */ + +@Component({ + selector: 'toh-hero', + template: `...` +}) +export class HeroComponent { + @Output() onSavedTheDay = new EventEmitter(); +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/05-16/app/heroes/hero.component.ts b/aio/content/examples/style-guide/ts/src/05-16/app/heroes/hero.component.ts new file mode 100644 index 0000000000..bbd4a4b5f3 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-16/app/heroes/hero.component.ts @@ -0,0 +1,14 @@ +// #docregion +import { Component, EventEmitter, Output } from '@angular/core'; + +@Component({ + selector: 'toh-hero', + template: `...` +}) +// #docregion example +export class HeroComponent { + @Output() savedTheDay = new EventEmitter(); +} +// #enddocregion example + + diff --git a/aio/content/examples/style-guide/ts/src/05-16/app/heroes/index.ts b/aio/content/examples/style-guide/ts/src/05-16/app/heroes/index.ts new file mode 100644 index 0000000000..084f36d703 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-16/app/heroes/index.ts @@ -0,0 +1 @@ +export * from './hero.component'; diff --git a/aio/content/examples/style-guide/ts/src/05-16/app/index.ts b/aio/content/examples/style-guide/ts/src/05-16/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-16/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/05-17/app/app.component.ts b/aio/content/examples/style-guide/ts/src/05-17/app/app.component.ts new file mode 100644 index 0000000000..86728b8b80 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-17/app/app.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'sg-app', + template: '' +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/05-17/app/app.module.ts b/aio/content/examples/style-guide/ts/src/05-17/app/app.module.ts new file mode 100644 index 0000000000..e850d80ae3 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-17/app/app.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { HeroComponent, HeroListComponent } from './heroes'; + +@NgModule({ + imports: [ + BrowserModule, + RouterModule.forChild([{ path: '05-17', component: AppComponent }]) + ], + declarations: [ + AppComponent, + HeroComponent, + HeroListComponent + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/05-17/app/heroes/hero-list/hero-list.component.avoid.ts b/aio/content/examples/style-guide/ts/src/05-17/app/heroes/hero-list/hero-list.component.avoid.ts new file mode 100644 index 0000000000..f007512949 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-17/app/heroes/hero-list/hero-list.component.avoid.ts @@ -0,0 +1,24 @@ +// #docregion +import { Component } from '@angular/core'; + +import { Hero } from '../shared/hero.model'; +// #docregion example +/* avoid */ + +@Component({ + selector: 'toh-hero-list', + template: ` +
    + Our list of heroes: + + + Total powers: {{totalPowers}}
    + Average power: {{totalPowers / heroes.length}} +
    + ` +}) +export class HeroListComponent { + heroes: Hero[]; + totalPowers: number; +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/05-17/app/heroes/hero-list/hero-list.component.ts b/aio/content/examples/style-guide/ts/src/05-17/app/heroes/hero-list/hero-list.component.ts new file mode 100644 index 0000000000..5f18cc5b0c --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-17/app/heroes/hero-list/hero-list.component.ts @@ -0,0 +1,35 @@ +// #docplaster +// #docregion +import { Component } from '@angular/core'; + +import { Hero } from '../shared/hero.model'; + +// #docregion example +@Component({ + selector: 'toh-hero-list', + template: ` +
    + Our list of heroes: + + + Total powers: {{totalPowers}}
    + Average power: {{avgPower}} +
    + ` +}) +export class HeroListComponent { + heroes: Hero[]; + totalPowers: number; + + // #enddocregion example + // testing harness + constructor() { + this.heroes = []; + } + + // #docregion example + get avgPower() { + return this.totalPowers / this.heroes.length; + } +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/05-17/app/heroes/hero-list/index.ts b/aio/content/examples/style-guide/ts/src/05-17/app/heroes/hero-list/index.ts new file mode 100644 index 0000000000..c4bcb3278e --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-17/app/heroes/hero-list/index.ts @@ -0,0 +1 @@ +export * from './hero-list.component'; diff --git a/aio/content/examples/style-guide/ts/src/05-17/app/heroes/hero/hero.component.ts b/aio/content/examples/style-guide/ts/src/05-17/app/heroes/hero/hero.component.ts new file mode 100644 index 0000000000..334f836a7d --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-17/app/heroes/hero/hero.component.ts @@ -0,0 +1,13 @@ +import { Component, Input } from '@angular/core'; + +import { Hero } from '../shared/hero.model'; + +@Component({ + selector: 'toh-hero', + template: `...` +}) +export class HeroComponent { + @Input() hero: Hero; +} + + diff --git a/aio/content/examples/style-guide/ts/src/05-17/app/heroes/hero/index.ts b/aio/content/examples/style-guide/ts/src/05-17/app/heroes/hero/index.ts new file mode 100644 index 0000000000..084f36d703 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-17/app/heroes/hero/index.ts @@ -0,0 +1 @@ +export * from './hero.component'; diff --git a/aio/content/examples/style-guide/ts/src/05-17/app/heroes/index.ts b/aio/content/examples/style-guide/ts/src/05-17/app/heroes/index.ts new file mode 100644 index 0000000000..dcf3e79bd3 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-17/app/heroes/index.ts @@ -0,0 +1,3 @@ +export * from './hero'; +export * from './hero-list'; +export * from './shared'; diff --git a/aio/content/examples/style-guide/ts/src/05-17/app/heroes/shared/hero.model.ts b/aio/content/examples/style-guide/ts/src/05-17/app/heroes/shared/hero.model.ts new file mode 100644 index 0000000000..8f7cc205c8 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-17/app/heroes/shared/hero.model.ts @@ -0,0 +1,5 @@ +// #docregion +export class Hero { + id: number; + name: string; +} diff --git a/aio/content/examples/style-guide/ts/src/05-17/app/heroes/shared/index.ts b/aio/content/examples/style-guide/ts/src/05-17/app/heroes/shared/index.ts new file mode 100644 index 0000000000..0dceb684c4 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-17/app/heroes/shared/index.ts @@ -0,0 +1 @@ +export * from './hero.model'; diff --git a/aio/content/examples/style-guide/ts/src/05-17/app/index.ts b/aio/content/examples/style-guide/ts/src/05-17/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/05-17/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/06-01/app/app.component.html b/aio/content/examples/style-guide/ts/src/06-01/app/app.component.html new file mode 100644 index 0000000000..2ccf87d0f5 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/06-01/app/app.component.html @@ -0,0 +1,2 @@ + +
    Bombasta
    diff --git a/aio/content/examples/style-guide/ts/src/06-01/app/app.component.ts b/aio/content/examples/style-guide/ts/src/06-01/app/app.component.ts new file mode 100644 index 0000000000..d57163f06f --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/06-01/app/app.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'sg-app', + templateUrl: './app.component.html' +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/06-01/app/app.module.ts b/aio/content/examples/style-guide/ts/src/06-01/app/app.module.ts new file mode 100644 index 0000000000..318cd306d7 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/06-01/app/app.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { HighlightDirective } from './shared'; + +@NgModule({ + imports: [ + RouterModule.forChild([{ path: '06-01', component: AppComponent }]) + ], + declarations: [ + AppComponent, + HighlightDirective + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/06-01/app/index.ts b/aio/content/examples/style-guide/ts/src/06-01/app/index.ts new file mode 100644 index 0000000000..ebe5c92f03 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/06-01/app/index.ts @@ -0,0 +1,2 @@ +export * from './shared'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/06-01/app/shared/highlight.directive.ts b/aio/content/examples/style-guide/ts/src/06-01/app/shared/highlight.directive.ts new file mode 100644 index 0000000000..991a6c5d25 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/06-01/app/shared/highlight.directive.ts @@ -0,0 +1,13 @@ +// #docregion +import { Directive, HostListener } from '@angular/core'; + +// #docregion example +@Directive({ + selector: '[tohHighlight]' +}) +export class HighlightDirective { + @HostListener('mouseover') onMouseEnter() { + // do highlight work + } +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/06-01/app/shared/index.ts b/aio/content/examples/style-guide/ts/src/06-01/app/shared/index.ts new file mode 100644 index 0000000000..105a035680 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/06-01/app/shared/index.ts @@ -0,0 +1 @@ +export * from './highlight.directive'; diff --git a/aio/content/examples/style-guide/ts/src/06-03/app/app.component.ts b/aio/content/examples/style-guide/ts/src/06-03/app/app.component.ts new file mode 100644 index 0000000000..0d0a7d107b --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/06-03/app/app.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'sg-app', + template: ` + + ` +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/06-03/app/app.module.ts b/aio/content/examples/style-guide/ts/src/06-03/app/app.module.ts new file mode 100644 index 0000000000..b19f3fdc00 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/06-03/app/app.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { ValidatorDirective, Validator2Directive } from './shared'; + +@NgModule({ + imports: [ + RouterModule.forChild([{ path: '06-03', component: AppComponent }]) + ], + declarations: [ + AppComponent, + ValidatorDirective, Validator2Directive + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/06-03/app/index.ts b/aio/content/examples/style-guide/ts/src/06-03/app/index.ts new file mode 100644 index 0000000000..ebe5c92f03 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/06-03/app/index.ts @@ -0,0 +1,2 @@ +export * from './shared'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/06-03/app/shared/index.ts b/aio/content/examples/style-guide/ts/src/06-03/app/shared/index.ts new file mode 100644 index 0000000000..ba25e4c458 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/06-03/app/shared/index.ts @@ -0,0 +1,2 @@ +export * from './validator.directive'; +export * from './validator2.directive'; diff --git a/aio/content/examples/style-guide/ts/src/06-03/app/shared/validator.directive.ts b/aio/content/examples/style-guide/ts/src/06-03/app/shared/validator.directive.ts new file mode 100644 index 0000000000..d9e32c017f --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/06-03/app/shared/validator.directive.ts @@ -0,0 +1,12 @@ +// #docregion +import { Directive, HostBinding, HostListener } from '@angular/core'; + +@Directive({ + selector: '[tohValidator]' +}) +export class ValidatorDirective { + @HostBinding('attr.role') role = 'button'; + @HostListener('mouseenter') onMouseEnter() { + // do work + } +} diff --git a/aio/content/examples/style-guide/ts/src/06-03/app/shared/validator2.directive.ts b/aio/content/examples/style-guide/ts/src/06-03/app/shared/validator2.directive.ts new file mode 100644 index 0000000000..7936a83cb1 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/06-03/app/shared/validator2.directive.ts @@ -0,0 +1,16 @@ +// #docregion +import { Directive } from '@angular/core'; + +@Directive({ + selector: '[tohValidator2]', + host: { + 'attr.role': 'button', + '(mouseenter)': 'onMouseEnter()' + } +}) +export class Validator2Directive { + role = 'button'; + onMouseEnter() { + // do work + } +} diff --git a/aio/content/examples/style-guide/ts/src/07-01/app/app.component.html b/aio/content/examples/style-guide/ts/src/07-01/app/app.component.html new file mode 100644 index 0000000000..3c05329f3f --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-01/app/app.component.html @@ -0,0 +1,5 @@ +
      +
    • + {{hero.name}} +
    • +
    diff --git a/aio/content/examples/style-guide/ts/src/07-01/app/app.component.ts b/aio/content/examples/style-guide/ts/src/07-01/app/app.component.ts new file mode 100644 index 0000000000..638da66caf --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-01/app/app.component.ts @@ -0,0 +1,19 @@ +import { Component, OnInit } from '@angular/core'; + +import { Hero, HeroService } from './heroes'; + +@Component({ + moduleId: module.id, + selector: 'sg-app', + templateUrl: './app.component.html', + providers: [HeroService] +}) +export class AppComponent implements OnInit { + heroes: Hero[]; + + constructor(private heroService: HeroService) { } + + ngOnInit() { + this.heroService.getHeroes().subscribe(heroes => this.heroes = heroes); + } +} diff --git a/aio/content/examples/style-guide/ts/src/07-01/app/app.module.ts b/aio/content/examples/style-guide/ts/src/07-01/app/app.module.ts new file mode 100644 index 0000000000..0077500dea --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-01/app/app.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ + BrowserModule, + RouterModule.forChild([{ path: '07-01', component: AppComponent }]) + ], + declarations: [ + AppComponent + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/07-01/app/heroes/index.ts b/aio/content/examples/style-guide/ts/src/07-01/app/heroes/index.ts new file mode 100644 index 0000000000..c3da79f741 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-01/app/heroes/index.ts @@ -0,0 +1 @@ +export * from './shared'; diff --git a/aio/content/examples/style-guide/ts/src/07-01/app/heroes/shared/hero.model.ts b/aio/content/examples/style-guide/ts/src/07-01/app/heroes/shared/hero.model.ts new file mode 100644 index 0000000000..8f7cc205c8 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-01/app/heroes/shared/hero.model.ts @@ -0,0 +1,5 @@ +// #docregion +export class Hero { + id: number; + name: string; +} diff --git a/aio/content/examples/style-guide/ts/src/07-01/app/heroes/shared/hero.service.ts b/aio/content/examples/style-guide/ts/src/07-01/app/heroes/shared/hero.service.ts new file mode 100644 index 0000000000..b5aba5d00c --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-01/app/heroes/shared/hero.service.ts @@ -0,0 +1,17 @@ +// #docregion +import { Injectable } from '@angular/core'; +import { Http, Response } from '@angular/http'; + +import { Hero } from './hero.model'; + +@Injectable() +// #docregion example +export class HeroService { + constructor(private http: Http) { } + + getHeroes() { + return this.http.get('api/heroes') + .map((response: Response) => response.json().data); + } +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/07-01/app/heroes/shared/index.ts b/aio/content/examples/style-guide/ts/src/07-01/app/heroes/shared/index.ts new file mode 100644 index 0000000000..dbb150d3f8 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-01/app/heroes/shared/index.ts @@ -0,0 +1,2 @@ +export * from './hero.model'; +export * from './hero.service'; diff --git a/aio/content/examples/style-guide/ts/src/07-01/app/index.ts b/aio/content/examples/style-guide/ts/src/07-01/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-01/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/07-03/app/app.component.ts b/aio/content/examples/style-guide/ts/src/07-03/app/app.component.ts new file mode 100644 index 0000000000..f4d25e1ab6 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-03/app/app.component.ts @@ -0,0 +1,13 @@ +// #docregion +import { Component } from '@angular/core'; + +import { HeroService } from './heroes'; + +@Component({ + selector: 'toh-app', + template: ` + + `, + providers: [HeroService] +}) +export class AppComponent {} diff --git a/aio/content/examples/style-guide/ts/src/07-03/app/app.module.ts b/aio/content/examples/style-guide/ts/src/07-03/app/app.module.ts new file mode 100644 index 0000000000..8ba06d22be --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-03/app/app.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { HeroListComponent } from './heroes'; + +@NgModule({ + imports: [ + BrowserModule, + RouterModule.forChild([{ path: '07-03', component: AppComponent }]) + ], + declarations: [ + AppComponent, + HeroListComponent + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/07-03/app/heroes/hero-list/hero-list.component.ts b/aio/content/examples/style-guide/ts/src/07-03/app/heroes/hero-list/hero-list.component.ts new file mode 100644 index 0000000000..cf9bb19243 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-03/app/heroes/hero-list/hero-list.component.ts @@ -0,0 +1,20 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; + +import { Hero, HeroService } from '../shared'; + +@Component({ + selector: 'toh-heroes', + template: ` +
    {{heroes | json}}
    + ` +}) +export class HeroListComponent implements OnInit { + heroes: Hero[] = []; + + constructor(private heroService: HeroService) { } + + ngOnInit() { + this.heroService.getHeroes().subscribe(heroes => this.heroes = heroes); + } +} diff --git a/aio/content/examples/style-guide/ts/src/07-03/app/heroes/hero-list/index.ts b/aio/content/examples/style-guide/ts/src/07-03/app/heroes/hero-list/index.ts new file mode 100644 index 0000000000..c4bcb3278e --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-03/app/heroes/hero-list/index.ts @@ -0,0 +1 @@ +export * from './hero-list.component'; diff --git a/aio/content/examples/style-guide/ts/src/07-03/app/heroes/index.ts b/aio/content/examples/style-guide/ts/src/07-03/app/heroes/index.ts new file mode 100644 index 0000000000..f1112f1c7c --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-03/app/heroes/index.ts @@ -0,0 +1,2 @@ +export * from './hero-list'; +export * from './shared'; diff --git a/aio/content/examples/style-guide/ts/src/07-03/app/heroes/shared/hero.model.ts b/aio/content/examples/style-guide/ts/src/07-03/app/heroes/shared/hero.model.ts new file mode 100644 index 0000000000..8f7cc205c8 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-03/app/heroes/shared/hero.model.ts @@ -0,0 +1,5 @@ +// #docregion +export class Hero { + id: number; + name: string; +} diff --git a/aio/content/examples/style-guide/ts/src/07-03/app/heroes/shared/hero.service.ts b/aio/content/examples/style-guide/ts/src/07-03/app/heroes/shared/hero.service.ts new file mode 100644 index 0000000000..72d07bbed4 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-03/app/heroes/shared/hero.service.ts @@ -0,0 +1,15 @@ +// #docregion +import { Injectable } from '@angular/core'; + +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/observable/of'; + +import { Hero } from './hero.model'; + +@Injectable() +export class HeroService { + getHeroes() { + let heroes: Hero[] = []; + return Observable.of(heroes); + } +} diff --git a/aio/content/examples/style-guide/ts/src/07-03/app/heroes/shared/index.ts b/aio/content/examples/style-guide/ts/src/07-03/app/heroes/shared/index.ts new file mode 100644 index 0000000000..27516fdedd --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-03/app/heroes/shared/index.ts @@ -0,0 +1,3 @@ +// #docregion +export * from './hero.model'; +export * from './hero.service'; diff --git a/aio/content/examples/style-guide/ts/src/07-03/app/index.ts b/aio/content/examples/style-guide/ts/src/07-03/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-03/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/07-04/app/app.component.ts b/aio/content/examples/style-guide/ts/src/07-04/app/app.component.ts new file mode 100644 index 0000000000..b0bc9677fe --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-04/app/app.component.ts @@ -0,0 +1,19 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; + +import { HeroArena, HeroService, Hero } from './heroes'; + +@Component({ + selector: 'toh-app', + template: '
    {{heroes | json}}
    ', + providers: [HeroArena, HeroService] +}) +export class AppComponent implements OnInit { + heroes: Hero[] = []; + + constructor(private heroArena: HeroArena) { } + + ngOnInit() { + this.heroArena.getParticipants().subscribe(heroes => this.heroes = heroes); + } +} diff --git a/aio/content/examples/style-guide/ts/src/07-04/app/app.module.ts b/aio/content/examples/style-guide/ts/src/07-04/app/app.module.ts new file mode 100644 index 0000000000..71c515c9c9 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-04/app/app.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ + BrowserModule, + RouterModule.forChild([{ path: '07-04', component: AppComponent }]) + ], + declarations: [ + AppComponent + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/07-04/app/heroes/index.ts b/aio/content/examples/style-guide/ts/src/07-04/app/heroes/index.ts new file mode 100644 index 0000000000..c3da79f741 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-04/app/heroes/index.ts @@ -0,0 +1 @@ +export * from './shared'; diff --git a/aio/content/examples/style-guide/ts/src/07-04/app/heroes/shared/hero-arena.service.avoid.ts b/aio/content/examples/style-guide/ts/src/07-04/app/heroes/shared/hero-arena.service.avoid.ts new file mode 100644 index 0000000000..698fa65239 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-04/app/heroes/shared/hero-arena.service.avoid.ts @@ -0,0 +1,14 @@ +// #docregion +import { Inject } from '@angular/core'; +import { Http } from '@angular/http'; + +import { HeroService } from './hero.service'; +// #docregion example +/* avoid */ + +export class HeroArena { + constructor( + @Inject(HeroService) private heroService: HeroService, + @Inject(Http) private http: Http) {} +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/07-04/app/heroes/shared/hero-arena.service.ts b/aio/content/examples/style-guide/ts/src/07-04/app/heroes/shared/hero-arena.service.ts new file mode 100644 index 0000000000..42bc51f9e9 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-04/app/heroes/shared/hero-arena.service.ts @@ -0,0 +1,21 @@ +// #docplaster +// #docregion +import { Injectable } from '@angular/core'; +import { Http } from '@angular/http'; + +import { HeroService } from './index'; + +// #docregion example +@Injectable() +export class HeroArena { + constructor( + private heroService: HeroService, + private http: Http) {} + // #enddocregion example + // test harness + getParticipants() { + return this.heroService.getHeroes(); + } + // #docregion example +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/07-04/app/heroes/shared/hero.model.ts b/aio/content/examples/style-guide/ts/src/07-04/app/heroes/shared/hero.model.ts new file mode 100644 index 0000000000..8f7cc205c8 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-04/app/heroes/shared/hero.model.ts @@ -0,0 +1,5 @@ +// #docregion +export class Hero { + id: number; + name: string; +} diff --git a/aio/content/examples/style-guide/ts/src/07-04/app/heroes/shared/hero.service.ts b/aio/content/examples/style-guide/ts/src/07-04/app/heroes/shared/hero.service.ts new file mode 100644 index 0000000000..72d07bbed4 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-04/app/heroes/shared/hero.service.ts @@ -0,0 +1,15 @@ +// #docregion +import { Injectable } from '@angular/core'; + +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/observable/of'; + +import { Hero } from './hero.model'; + +@Injectable() +export class HeroService { + getHeroes() { + let heroes: Hero[] = []; + return Observable.of(heroes); + } +} diff --git a/aio/content/examples/style-guide/ts/src/07-04/app/heroes/shared/index.ts b/aio/content/examples/style-guide/ts/src/07-04/app/heroes/shared/index.ts new file mode 100644 index 0000000000..e8ba54b540 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-04/app/heroes/shared/index.ts @@ -0,0 +1,4 @@ +// #docregion +export * from './hero.model'; +export * from './hero.service'; +export * from './hero-arena.service'; diff --git a/aio/content/examples/style-guide/ts/src/07-04/app/index.ts b/aio/content/examples/style-guide/ts/src/07-04/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/07-04/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/09-01/app/app.component.ts b/aio/content/examples/style-guide/ts/src/09-01/app/app.component.ts new file mode 100644 index 0000000000..ebc904f722 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/09-01/app/app.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'sg-app', + template: '' +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/09-01/app/app.module.ts b/aio/content/examples/style-guide/ts/src/09-01/app/app.module.ts new file mode 100644 index 0000000000..5872e801d6 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/09-01/app/app.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { HeroButtonComponent } from './heroes'; + +@NgModule({ + imports: [ + RouterModule.forChild([{ path: '09-01', component: AppComponent }]) + ], + declarations: [ + AppComponent, + HeroButtonComponent + ], + exports: [ AppComponent ] +}) +export class AppModule {} diff --git a/aio/content/examples/style-guide/ts/src/09-01/app/heroes/index.ts b/aio/content/examples/style-guide/ts/src/09-01/app/heroes/index.ts new file mode 100644 index 0000000000..c3da79f741 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/09-01/app/heroes/index.ts @@ -0,0 +1 @@ +export * from './shared'; diff --git a/aio/content/examples/style-guide/ts/src/09-01/app/heroes/shared/hero-button/hero-button.component.avoid.ts b/aio/content/examples/style-guide/ts/src/09-01/app/heroes/shared/hero-button/hero-button.component.avoid.ts new file mode 100644 index 0000000000..7dc42c2f40 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/09-01/app/heroes/shared/hero-button/hero-button.component.avoid.ts @@ -0,0 +1,15 @@ +// #docregion +import { Component } from '@angular/core'; +// #docregion example +/* avoid */ + +@Component({ + selector: 'toh-hero-button', + template: `` +}) +export class HeroButtonComponent implements OnInit { + ngOnInit() { + console.log('The component is initialized'); + } +} +// #enddocregion example diff --git a/aio/content/examples/style-guide/ts/src/09-01/app/heroes/shared/hero-button/index.ts b/aio/content/examples/style-guide/ts/src/09-01/app/heroes/shared/hero-button/index.ts new file mode 100644 index 0000000000..6bb67c5670 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/09-01/app/heroes/shared/hero-button/index.ts @@ -0,0 +1 @@ +export * from './hero-button.component'; diff --git a/aio/content/examples/style-guide/ts/src/09-01/app/heroes/shared/index.ts b/aio/content/examples/style-guide/ts/src/09-01/app/heroes/shared/index.ts new file mode 100644 index 0000000000..2334d49c9a --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/09-01/app/heroes/shared/index.ts @@ -0,0 +1 @@ +export * from './hero-button'; diff --git a/aio/content/examples/style-guide/ts/src/09-01/app/index.ts b/aio/content/examples/style-guide/ts/src/09-01/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/09-01/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/aio/content/examples/style-guide/ts/src/app/app.component.html b/aio/content/examples/style-guide/ts/src/app/app.component.html new file mode 100644 index 0000000000..0680b43f9c --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/app/app.component.html @@ -0,0 +1 @@ + diff --git a/aio/content/examples/style-guide/ts/src/app/app.component.ts b/aio/content/examples/style-guide/ts/src/app/app.component.ts new file mode 100644 index 0000000000..ab2d3d9c33 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/app/app.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: './app.component.html' +}) +export class AppComponent { } diff --git a/aio/content/examples/style-guide/ts/src/app/app.routes.ts b/aio/content/examples/style-guide/ts/src/app/app.routes.ts new file mode 100644 index 0000000000..bce6b4df06 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/app/app.routes.ts @@ -0,0 +1,8 @@ +import { Routes } from '@angular/router'; + +import { AppComponent as S0101 } from '../01-01/app'; + +export const routes: Routes = [ + { path: '', redirectTo: '/01-01', pathMatch: 'full' }, + { path: '01-01', component: S0101 }, +]; diff --git a/aio/content/examples/style-guide/ts/src/app/hero-data.ts b/aio/content/examples/style-guide/ts/src/app/hero-data.ts new file mode 100644 index 0000000000..f3e6feb91c --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/app/hero-data.ts @@ -0,0 +1,11 @@ +export class HeroData { + createDb() { + let heroes = [ + { id: 1, name: 'Windstorm' }, + { id: 2, name: 'Bombasto' }, + { id: 3, name: 'Magneta' }, + { id: 4, name: 'Tornado' } + ]; + return {heroes}; + } +} diff --git a/aio/content/examples/style-guide/ts/src/index.html b/aio/content/examples/style-guide/ts/src/index.html new file mode 100644 index 0000000000..188c2c26a1 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/index.html @@ -0,0 +1,30 @@ + + + + + + + Style Guide Sample + + + + + + + + + + + + + + + + + loading... + + + + diff --git a/aio/content/examples/style-guide/ts/src/main.ts b/aio/content/examples/style-guide/ts/src/main.ts new file mode 100644 index 0000000000..5b4c98ba69 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/main.ts @@ -0,0 +1,99 @@ +import { NgModule } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { BrowserModule } from '@angular/platform-browser'; + +import { HttpModule } from '@angular/http'; +import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; + +import { RouterModule } from '@angular/router'; + +import { HashLocationStrategy, + LocationStrategy } from '@angular/common'; + +import 'rxjs/add/operator/map'; + +import { HeroData } from './app/hero-data'; +import { AppComponent } from './app/app.component'; + +import * as s0101 from './01-01/app/app.module'; +import * as s0205 from './02-05/app/app.module'; +import * as s0207 from './02-07/app/app.module'; +import * as s0208 from './02-08/app/app.module'; +import * as s0301 from './03-01/app/app.module'; +import * as s0302 from './03-02/app/app.module'; +import * as s0303 from './03-03/app/app.module'; +import * as s0304 from './03-04/app/app.module'; +import * as s0306 from './03-06/app/app.module'; +import * as s0408 from './04-08/app/app.module'; +import * as s0410 from './04-10/app/app.module'; +import * as s0411 from './04-11/app/app.module'; +import * as s0412 from './04-12/app/app.module'; +import * as s0502 from './05-02/app/app.module'; +import * as s0503 from './05-03/app/app.module'; +import * as s0504 from './05-04/app/app.module'; +import * as s0512 from './05-12/app/app.module'; +import * as s0513 from './05-13/app/app.module'; +import * as s0514 from './05-14/app/app.module'; +import * as s0515 from './05-15/app/app.module'; +import * as s0516 from './05-16/app/app.module'; +import * as s0517 from './05-17/app/app.module'; +import * as s0601 from './06-01/app/app.module'; +import * as s0603 from './06-03/app/app.module'; +import * as s0701 from './07-01/app/app.module'; +import * as s0703 from './07-03/app/app.module'; +import * as s0704 from './07-04/app/app.module'; +import * as s0901 from './09-01/app/app.module'; + +/////////////////// +const moduleMetadata = { + imports: [ + BrowserModule, + HttpModule, + InMemoryWebApiModule.forRoot(HeroData), + + s0101.AppModule, + s0205.AppModule, + s0207.AppModule, + s0208.AppModule, + s0301.AppModule, + s0302.AppModule, + s0303.AppModule, + s0304.AppModule, + s0306.AppModule, + s0408.AppModule, + s0410.AppModule, + s0411.AppModule, + s0412.AppModule, + s0502.AppModule, + s0503.AppModule, + s0504.AppModule, + s0512.AppModule, + s0513.AppModule, + s0514.AppModule, + s0515.AppModule, + s0516.AppModule, + s0517.AppModule, + s0601.AppModule, + s0603.AppModule, + s0701.AppModule, + s0703.AppModule, + s0704.AppModule, + s0901.AppModule, + + RouterModule.forRoot([ + { path: '', redirectTo: '/01-01', pathMatch: 'full' } + ], {/* enableTracing: true */}), + ], + providers: [ + { provide: LocationStrategy, useClass: HashLocationStrategy } + ], + declarations: [ AppComponent ], + bootstrap: [ AppComponent ] +}; + +@NgModule(moduleMetadata) +class MainModule { } + +platformBrowserDynamic().bootstrapModule(MainModule); + + diff --git a/aio/content/examples/style-guide/ts/src/systemjs.custom.js b/aio/content/examples/style-guide/ts/src/systemjs.custom.js new file mode 100644 index 0000000000..8181979346 --- /dev/null +++ b/aio/content/examples/style-guide/ts/src/systemjs.custom.js @@ -0,0 +1,51 @@ +(function(global) { + // extra local packages + var packageNames = [ + '01-01', '01-01/app', '01-01/app/heroes', '01-01/app/heroes/shared', + '02-05', '02-05/app', + '02-07', '02-07/app', '02-07/app/heroes', '02-07/app/users', + '02-08', '02-08/app', '02-08/app/shared', + '03-01', '03-01/app', '03-01/app/core', + '03-02', '03-02/app', '03-02/app/core', + '03-03', '03-03/app', '03-03/app/core', + '03-04', '03-04/app', '03-04/app/core', + '03-05', '03-05/app', '03-05/app/core', '03-05/app/core/spinner', '03-05/app/core/toast', + '03-05/app/heroes', '03-05/app/heroes/shared', + '03-06', '03-06/app', '03-06/app/core', '03-06/app/core/spinner', '03-06/app/core/toast', + '03-06/app/heroes', '03-06/app/heroes/shared', + '04-08', '04-08/app', '04-08/app/heroes', + '04-10', '04-10/app', '04-10/app/shared', '04-10/app/heroes', '04-10/app/shared/spinner', '04-10/app/shared/toast', + '04-10/app/shared/filter-text', + '04-11', '04-11/app', '04-11/app/core', '04-11/app/heroes', '04-11/app/core/spinner', + '04-11/app/core/nav', + '04-12', '04-12/app', '04-12/app/core', '04-12/app/heroes', '04-12/app/core/nav', + '05-02', '05-02/app', '05-02/app/heroes', '05-02/app/heroes/shared', '05-02/app/heroes/shared/hero-button', + '05-03', '05-03/app', '05-03/app/heroes', '05-03/app/heroes/shared', '05-03/app/heroes/shared/hero-button', + '05-04', '05-04/app', '05-04/app/heroes', '05-04/app/heroes/shared', + '05-12', '05-12/app', '05-12/app/heroes', '05-12/app/heroes/shared', '05-12/app/heroes/shared/hero-button', + '05-13', '05-13/app', '05-13/app/heroes', '05-13/app/heroes/shared', '05-13/app/heroes/shared/hero-button', + '05-14', '05-14/app', '05-14/app/shared', '05-14/app/shared/toast', + '05-15', '05-15/app', '05-15/app/heroes', '05-15/app/heroes/hero-list', '05-15/app/heroes/shared', + '05-16', '05-16/app', '05-16/app/heroes', + '05-17', '05-17/app', '05-17/app/heroes', '05-17/app/heroes/hero', '05-17/app/heroes/hero-list', + '05-17/app/heroes/shared', + '06-01', '06-01/app', '06-01/app/shared', + '06-03', '06-03/app', '06-03/app/shared', + '07-01', '07-01/app', '07-01/app/heroes', '07-01/app/heroes/shared', + '07-03', '07-03/app', '07-03/app/heroes', '07-03/app/heroes/hero-list', '07-03/app/heroes/shared', + '07-04', '07-04/app', '07-04/app/heroes', '07-04/app/heroes/shared', + '09-01', '09-01/app', '09-01/app/heroes', '09-01/app/heroes/shared', '09-01/app/heroes/shared/hero-button' + ]; + + var packages = {}; + packageNames.forEach(function(pkgName) { + packages[pkgName] = { main: 'index.js', defaultExtension: 'js' }; + }); + + var config = { + packages: packages + } + + System.config(config); + +})(this); diff --git a/aio/content/examples/styleguide/e2e-spec.ts b/aio/content/examples/styleguide/e2e-spec.ts new file mode 100644 index 0000000000..af10d2b71d --- /dev/null +++ b/aio/content/examples/styleguide/e2e-spec.ts @@ -0,0 +1,16 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('Documentation StyleGuide E2E Tests', function() { + + let expectedMsg = 'My First Angular App'; + + beforeEach(function () { + browser.get(''); + }); + + it('should display: ' + expectedMsg, function() { + expect(element(by.id('output')).getText()).toEqual(expectedMsg); + }); +}); diff --git a/aio/content/examples/styleguide/foo.dart b/aio/content/examples/styleguide/foo.dart new file mode 100644 index 0000000000..aeea7eb657 --- /dev/null +++ b/aio/content/examples/styleguide/foo.dart @@ -0,0 +1,17 @@ +library angular2.src.core.application_static; + +// #docregion import +import 'dart:async'; +import 'application_common.dart'; +import 'package:angular2/src/core/linker/dynamic_component_loader.dart' show ComponentRef; +// #enddocregion import +/// Starts an application from a root component. +/// +/// See [commonBootstrap] for detailed documentation. +Future bootstrapStatic(Type appComponentType, + [List componentInjectableBindings, void initReflector()]) { + if (initReflector != null) { + initReflector(); + } + return commonBootstrap(appComponentType, componentInjectableBindings); +} diff --git a/aio/content/examples/styleguide/foo.yaml b/aio/content/examples/styleguide/foo.yaml new file mode 100644 index 0000000000..37f8b7ab22 --- /dev/null +++ b/aio/content/examples/styleguide/foo.yaml @@ -0,0 +1,21 @@ +name: benchpress +version: <%= packageJson.version %> +authors: +<%= Object.keys(packageJson.contributors).map(function(name) { + return '- '+name+' <'+packageJson.contributors[name]+'>'; +}).join('\n') %> +description: Benchpress - a framework for e2e performance tests +homepage: <%= packageJson.homepage %> +environment: + sdk: '>=1.10.0 <2.0.0' +# #docregion dependencies +dependencies: + angular2: '^<%= packageJson.version %>' + stack_trace: '^1.1.1' + webdriver: '^0.9.0' +dev_dependencies: + guinness: '^0.1.17' +dependency_overrides: + angular2: + path: ../angular2 +# #enddocregion diff --git a/aio/content/examples/styleguide/js/example-config.json b/aio/content/examples/styleguide/js/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/styleguide/js/spec.js b/aio/content/examples/styleguide/js/spec.js new file mode 100644 index 0000000000..590ccc5dc4 --- /dev/null +++ b/aio/content/examples/styleguide/js/spec.js @@ -0,0 +1,8 @@ +// #docregion +describe("Jasmine sample test", function() { + + it("1+1 should be 2", function() { + var result = 1 + 1; + expect(result).toBe(2); + }); +}); \ No newline at end of file diff --git a/aio/content/examples/styleguide/js/src/app.js b/aio/content/examples/styleguide/js/src/app.js new file mode 100644 index 0000000000..d38669cd78 --- /dev/null +++ b/aio/content/examples/styleguide/js/src/app.js @@ -0,0 +1,55 @@ +(function(app) { + +// #docregion +// #docregion class-w-annotations +app.AppComponent = + // #docregion component + ng.core.Component({ + selector: 'my-app', + // #enddocregion + // #docregion view + template: '

    My First Angular App

    ' + }) + // #enddocregion + // #docregion class + .Class({ + constructor: function () { } + }); + // #enddocregion +// #enddocregion + +// #docregion bootstrap +app.AppModule = + ng.core.NgModule({ + imports: [ ng.platformBrowser.BrowserModule ], + declarations: [ app.AppComponent ], + bootstrap: [ app.AppComponent ] + }) + .Class({ + constructor: function() {} + }); + +document.addEventListener('DOMContentLoaded', function() { + ng.platformBrowserDynamic + .platformBrowserDynamic() + .bootstrapModule(app.AppModule); +}); +// #enddocregion +// #enddocregion + +})(window.app || (window.app = {})); + +/* Non DSL Approach */ +(function(app) { + +// #docregion no-dsl +app.AppComponent = function AppComponent () {} + +app.AppComponent.annotations = [ + new ng.core.Component({ + selector: 'my-app', + template: '

    My First Angular App

    ' + }) +]; +// #enddocregion +})(window.app || (window.app = {})); diff --git a/aio/content/examples/styleguide/js/src/index.html b/aio/content/examples/styleguide/js/src/index.html new file mode 100644 index 0000000000..ac2d34ba5d --- /dev/null +++ b/aio/content/examples/styleguide/js/src/index.html @@ -0,0 +1,27 @@ + + + + Documentation Style + + + + + + + + + + + + + + + + + + + + foo2 + + + diff --git a/aio/content/examples/styleguide/jsonly.zipconfig.json b/aio/content/examples/styleguide/jsonly.zipconfig.json new file mode 100644 index 0000000000..118c776bdc --- /dev/null +++ b/aio/content/examples/styleguide/jsonly.zipconfig.json @@ -0,0 +1,3 @@ +{ + "files": ["**/*.js"] +} \ No newline at end of file diff --git a/aio/content/examples/styleguide/package.1.json b/aio/content/examples/styleguide/package.1.json new file mode 100644 index 0000000000..481f99fb12 --- /dev/null +++ b/aio/content/examples/styleguide/package.1.json @@ -0,0 +1,23 @@ +{ + "name": "angular2-quickstart", + "version": "1.0.0", + "scripts": { + "tsc": "tsc", + "tsc:w": "tsc -w", + "lite": "lite-server", + "start": "concurrently \"npm run tsc:w\" \"npm run lite\" " + }, + "license": "MIT", + "dependencies": { + "angular2": "2.0.0-beta.0", + "systemjs": "0.19.6", + "core-js": "^2.4.0", + "rxjs": "5.0.0-beta.0", + "zone.js": "0.5.10" + }, + "devDependencies": { + "concurrently": "^1.0.0", + "lite-server": "^1.3.1", + "typescript": "^1.7.3" + } +} diff --git a/aio/content/examples/styleguide/ts/example-config.json b/aio/content/examples/styleguide/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/styleguide/ts/src/app/app.component.ts b/aio/content/examples/styleguide/ts/src/app/app.component.ts new file mode 100644 index 0000000000..8b71f6ddc4 --- /dev/null +++ b/aio/content/examples/styleguide/ts/src/app/app.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; +@Component({ + selector: 'my-app', + template: '

    My First Angular App

    ' +}) +export class AppComponent { } + diff --git a/aio/content/examples/styleguide/ts/src/app/app.module.ts b/aio/content/examples/styleguide/ts/src/app/app.module.ts new file mode 100644 index 0000000000..0a9ee6adf7 --- /dev/null +++ b/aio/content/examples/styleguide/ts/src/app/app.module.ts @@ -0,0 +1,11 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ BrowserModule ], + declarations: [ AppComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/styleguide/ts/src/index.html b/aio/content/examples/styleguide/ts/src/index.html new file mode 100644 index 0000000000..5cb8919509 --- /dev/null +++ b/aio/content/examples/styleguide/ts/src/index.html @@ -0,0 +1,25 @@ + + + + + Documentation Style + + + + + + + + + + + + + + + + + + diff --git a/aio/content/examples/styleguide/ts/src/main.ts b/aio/content/examples/styleguide/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/aio/content/examples/styleguide/ts/src/main.ts @@ -0,0 +1,5 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/styleguide/zipconfig.json b/aio/content/examples/styleguide/zipconfig.json new file mode 100644 index 0000000000..74ca947f2b --- /dev/null +++ b/aio/content/examples/styleguide/zipconfig.json @@ -0,0 +1,4 @@ +{ + "zipRegion": "class", + "files": ["**/*.*", "!**/*zipconfig.json"] +} \ No newline at end of file diff --git a/aio/content/examples/template-syntax/e2e-spec.ts b/aio/content/examples/template-syntax/e2e-spec.ts new file mode 100644 index 0000000000..71f1c58165 --- /dev/null +++ b/aio/content/examples/template-syntax/e2e-spec.ts @@ -0,0 +1,43 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +// Not yet complete +describe('Template Syntax', function () { + + beforeAll(function () { + browser.get(''); + }); + + it('should be able to use interpolation with a hero', function () { + let heroInterEle = element.all(by.css('h2+p')).get(0); + expect(heroInterEle.getText()).toEqual('My current hero is Hercules'); + }); + + it('should be able to use interpolation with a calculation', function () { + let theSumEles = element.all(by.cssContainingText('h3~p', 'The sum of')); + expect(theSumEles.count()).toBe(2); + expect(theSumEles.get(0).getText()).toEqual('The sum of 1 + 1 is 2'); + expect(theSumEles.get(1).getText()).toEqual('The sum of 1 + 1 is not 4'); + }); + + it('should be able to use class binding syntax', function () { + let specialEle = element(by.cssContainingText('div', 'Special')); + expect(specialEle.getAttribute('class')).toMatch('special'); + }); + + it('should be able to use style binding syntax', function () { + let specialButtonEle = element(by.cssContainingText('div.special~button', 'button')); + expect(specialButtonEle.getAttribute('style')).toMatch('color: red'); + }); + + it('should two-way bind to sizer', async () => { + let div = element(by.css('div#two-way-1')); + let incButton = div.element(by.buttonText('+')); + let input = div.element(by.css('input')); + let initSize = await input.getAttribute('value'); + incButton.click(); + expect(input.getAttribute('value')).toEqual((+initSize + 1).toString()); + }); +}); + diff --git a/aio/content/examples/template-syntax/ts/example-config.json b/aio/content/examples/template-syntax/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/template-syntax/ts/plnkr.json b/aio/content/examples/template-syntax/ts/plnkr.json new file mode 100644 index 0000000000..099616ad7c --- /dev/null +++ b/aio/content/examples/template-syntax/ts/plnkr.json @@ -0,0 +1,6 @@ +{ + "description": "Template Syntax Collection", + "basePath": "src/", + "files":["!**/*.d.ts", "!**/*.js"], + "tags": ["template"] +} diff --git a/aio/content/examples/template-syntax/ts/plnkr.no-link.html b/aio/content/examples/template-syntax/ts/plnkr.no-link.html new file mode 100644 index 0000000000..4650d4a520 --- /dev/null +++ b/aio/content/examples/template-syntax/ts/plnkr.no-link.html @@ -0,0 +1,1414 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/template-syntax/ts/src/app/app.component.css b/aio/content/examples/template-syntax/ts/src/app/app.component.css new file mode 100644 index 0000000000..23f9667623 --- /dev/null +++ b/aio/content/examples/template-syntax/ts/src/app/app.component.css @@ -0,0 +1,17 @@ +a.to-toc { margin: 30px 0; } +button { font-size: 100%; margin: 0 2px; } +div[clickable] {cursor: pointer; max-width: 200px; margin: 16px 0} +#noTrackByCnt, #withTrackByCnt {color: darkred; max-width: 450px; margin: 4px;} +img {height: 100px;} +.box {border: 1px solid black; padding: 6px; max-width: 450px;} +.child-div {margin-left: 1em; font-weight: normal} +.context {margin-left: 1em;} +.hidden {display: none} +.parent-div {margin-top: 1em; font-weight: bold} +.special {font-weight:bold; font-size: x-large} +.bad {color: red;} +.saveable {color: limegreen;} +.curly, .modified {font-family: "Brush Script MT"} +.toe {margin-left: 1em; font-style: italic;} +little-hero {color:blue; font-size: smaller; background-color: Turquoise } +.to-toc {margin-top: 10px; display: block} diff --git a/aio/content/examples/template-syntax/ts/src/app/app.component.html b/aio/content/examples/template-syntax/ts/src/app/app.component.html new file mode 100644 index 0000000000..b3d6c29be5 --- /dev/null +++ b/aio/content/examples/template-syntax/ts/src/app/app.component.html @@ -0,0 +1,822 @@ + + +

    Template Syntax

    +Interpolation
    +Expression context
    +Statement context
    +Mental Model
    +Buttons
    +Properties vs. Attributes
    +
    +Property Binding
    + +
    +Event Binding
    +Two-way Binding
    +
    +
    Directives
    + +
    +Template reference variables
    +Inputs and outputs
    +Pipes
    +Safe navigation operator ?.
    +Enums
    + + +

    Interpolation

    + + +

    My current hero is {{currentHero.name}}

    + + + +

    + {{title}} + +

    + + + + +

    The sum of 1 + 1 is {{1 + 1}}

    + + + + +

    The sum of 1 + 1 is not {{1 + 1 + getVal()}}

    + + +top + +

    Expression context

    + +

    Component expression context ({{title}}, [hidden]="isUnchanged")

    +
    + + {{title}} + changed + +
    + + +

    Template input variable expression context (let hero)

    + + + +

    Template reference variable expression context (#heroInput)

    +
    + Type something: + + {{heroInput.value}} + +
    + +top + +

    Statement context

    + +

    Component statement context ( (click)="onSave() ) +

    + + + +
    + +

    Template $event statement context

    +
    + + + +
    + +

    Template input variable statement context (let hero)

    + +
    + + + +
    + +

    Template reference variable statement context (#heroForm)

    +
    + +
    ...
    + +
    + +top + + +

    New Mental Model

    + + + + +
    Mental Model
    + + + +

    + +
    + + +
    Mental Model
    + + + +
    +

    + +
    + + + + +
    +

    + +
    + + + +
    + +
    +

    + + + + +
    click me
    + +{{clicked}} +

    + +
    + Hero Name: + + + + {{heroName}} +
    +

    + + + + +

    + + +
    Special
    + +

    + + + + +top + + +

    Property vs. Attribute (img examples)

    + + + +

    + + + + + +top + + +

    Buttons

    + + + + +

    + + +

    + + + +top + + +

    Property Binding

    + + + + + + + + +
    [ngClass] binding to the classes property
    + + + + + + + + + + + +
    + + + +
    + + + + + +

    is the interpolated image.

    +

    is the property bound image.

    + +

    "{{title}}" is the interpolated title.

    +

    "" is the property bound title.

    + + + + +

    "{{evilTitle}}" is the interpolated evil title.

    +

    "" is the property bound evil title.

    + + +top + + +

    Attribute Binding

    + + + + + + + + + + +
    One-Two
    FiveSix
    + + +
    + + + + +

    + + +
    + + + + + + + +
    + +top + + +

    Class Binding

    + + + +
    Bad curly special
    + + + + +
    Bad curly
    + + + + + +
    The class binding is special
    + + + +
    This one is not so special
    + + +
    This class binding is special too
    + +top + + +

    Style Binding

    + + + + + + + + + + + +top + + +

    Event Binding

    + + + + + + + + + +
    + + + +
    click with myClick
    + + +{{clickMessage}} +
    + + + + + + +
    + + + + + +
    Click me +
    Click me too!
    +
    + + + + +
    + +
    + + + + +
    + +
    + + +top + +

    Two-way Binding

    +
    + + +
    Resizable Text
    + + +
    +
    +
    +

    De-sugared two-way binding

    + + + +
    + +top + + +

    NgModel (two-way) Binding

    + +

    Result: {{currentHero.name}}

    + + + + +without NgModel +
    + + + +[(ngModel)] +
    + + + +bindon-ngModel +
    + + + +(ngModelChange) = "...name=$event" +
    + + + +(ngModelChange) = "setUppercaseName($event)" + +top + + +

    NgClass Binding

    + +

    currentClasses returns {{currentClasses | json}}

    + +
    This div is initially saveable, unchanged, and special
    + + + +
    + | + | + + +

    +
    + This div should be {{ canSave ? "": "not"}} saveable, + {{ isUnchanged ? "unchanged" : "modified" }} and, + {{ isSpecial ? "": "not"}} special after clicking "refresh".
    +

    + +
    This div is special
    + +
    Bad curly special
    +
    Curly special
    + +top + + +

    NgStyle Binding

    + + +
    + This div is x-large. +
    + + +

    [ngStyle] binding to `currentStyles` - CSS property names

    +

    currentStyles returns {{currentStyles | json}}

    + +
    + This div is initially italic, normal weight, and extra large (24px). +
    + + + +
    + | + | + + +

    +
    + This div should be {{ canSave ? "italic": "plain"}}, + {{ isUnchanged ? "normal weight" : "bold" }} and, + {{ isSpecial ? "extra large": "normal size"}} after clicking "refresh".
    + +top + + +

    NgIf Binding

    + + + + + + +
    Hello, {{currentHero.name}}
    +
    Hello, {{nullHero.name}}
    + + + + + + + +
    Hero Detail removed from DOM (via template) because isActive is false
    + + + + +
    Show with class
    +
    Hide with class
    + + + + +
    Show with style
    +
    Hide with style
    + + +top + + +

    NgFor Binding

    + +
    + +
    {{hero.name}}
    + +
    +
    + +
    + + + + +
    + +top + +

    *ngFor with index

    +

    with semi-colon separator

    +
    + +
    {{i + 1}} - {{hero.name}}
    + +
    + +

    with comma separator

    +
    + +
    {{i + 1}} - {{hero.name}}
    +
    + +top + +

    *ngFor trackBy

    + + + + +

    without trackBy

    +
    +
    ({{hero.id}}) {{hero.name}}
    + +
    + Hero DOM elements change #{{heroesNoTrackByCount}} without trackBy +
    +
    + +

    with trackBy

    +
    +
    ({{hero.id}}) {{hero.name}}
    + +
    + Hero DOM elements change #{{heroesWithTrackByCount}} with trackBy +
    +
    + +


    + +

    with trackBy and semi-colon separator

    +
    + +
    + ({{hero.id}}) {{hero.name}} +
    + +
    + +

    with trackBy and comma separator

    +
    +
    ({{hero.id}}) {{hero.name}}
    +
    + +

    with trackBy and space separator

    +
    +
    ({{hero.id}}) {{hero.name}}
    +
    + +

    with generic trackById function

    +
    +
    ({{hero.id}}) {{hero.name}}
    +
    + +top + + +

    NgSwitch Binding

    + +
    Pick your favorite hero
    +

    + + + +

    + + +
    + + + + + +
    Are you as confused as {{currentHero.name}}?
    + + + +
    + + +top + + +

    Template reference variables

    + + + + + + + + + + + + + + + + + + + + +

    Example Form

    + + +top + + +

    Inputs and Outputs

    + + + + + + + + + + + +
    myClick2
    +{{clickMessage2}} + +top + + +

    Pipes

    + + +
    Title through uppercase pipe: {{title | uppercase}}
    + + + + +
    + Title through a pipe chain: + {{title | uppercase | lowercase}} +
    + + + + +
    Birthdate: {{currentHero?.birthdate | date:'longDate'}}
    + + + +
    {{currentHero | json}}
    + + +
    Birthdate: {{(currentHero?.birthdate | date:'longDate') | uppercase}}
    + +
    + + {{product.price | currency:'USD':true}} +
    + +top + + +

    Safe navigation operator ?.

    + +
    + + The title is {{title}} + +
    + +
    + + The current hero's name is {{currentHero?.name}} + +
    + +
    + + The current hero's name is {{currentHero.name}} + +
    + + + + + + +
    The null hero's name is {{nullHero.name}}
    + + +
    + +The null hero's name is {{nullHero && nullHero.name}} + +
    + +
    + + + The null hero's name is {{nullHero?.name}} + +
    + +top + + + +

    Enums in binding

    + +

    + + The name of the Color.Red enum is {{Color[Color.Red]}}.
    + The current color is {{Color[color]}} and its number is {{color}}.
    + + +

    + +top diff --git a/aio/content/examples/template-syntax/ts/src/app/app.component.ts b/aio/content/examples/template-syntax/ts/src/app/app.component.ts new file mode 100644 index 0000000000..166948362f --- /dev/null +++ b/aio/content/examples/template-syntax/ts/src/app/app.component.ts @@ -0,0 +1,213 @@ +/* tslint:disable:forin member-ordering */ +// #docplaster + +import { AfterViewInit, Component, ElementRef, OnInit, QueryList, ViewChildren } from '@angular/core'; + +import { Hero } from './hero'; + +// Alerter fn: monkey patch during test +export function alerter(msg?: string) { + window.alert(msg); +} + +export enum Color {Red, Green, Blue}; + +/** + * Giant grab bag of stuff to drive the chapter + */ +@Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: './app.component.html', + styleUrls: [ './app.component.css' ] +}) +export class AppComponent implements AfterViewInit, OnInit { + + ngOnInit() { + this.resetHeroes(); + this.setCurrentClasses(); + this.setCurrentStyles(); + } + + ngAfterViewInit() { + // Detect effects of NgForTrackBy + trackChanges(this.heroesNoTrackBy, () => this.heroesNoTrackByCount += 1); + trackChanges(this.heroesWithTrackBy, () => this.heroesWithTrackByCount += 1); + } + + @ViewChildren('noTrackBy') heroesNoTrackBy: QueryList; + @ViewChildren('withTrackBy') heroesWithTrackBy: QueryList; + + actionName = 'Go for it'; + alert = alerter; + badCurly = 'bad curly'; + classes = 'special'; + + callFax(value: string) {this.alert(`Faxing ${value} ...`); } + callPhone(value: string) {this.alert(`Calling ${value} ...`); } + canSave = true; + + changeIds() { + this.resetHeroes(); + this.heroes.forEach(h => h.id += 10 * this.heroIdIncrement++); + this.heroesWithTrackByCountReset = -1; + } + + clearTrackByCounts() { + const trackByCountReset = this.heroesWithTrackByCountReset; + this.resetHeroes(); + this.heroesNoTrackByCount = -1; + this.heroesWithTrackByCount = trackByCountReset; + this.heroIdIncrement = 1; + } + + clicked = ''; + clickMessage = ''; + clickMessage2 = ''; + + Color = Color; + color = Color.Red; + colorToggle() {this.color = (this.color === Color.Red) ? Color.Blue : Color.Red; } + + currentHero: Hero; + + deleteHero(hero: Hero) { + this.alert(`Delete ${hero ? hero.name : 'the hero'}.`); + } + + // #docregion evil-title + evilTitle = 'Template Syntax'; + // #enddocregion evil-title + + fontSizePx = 16; + + title = 'Template Syntax'; + + getStyles(el: Element) { + let styles = window.getComputedStyle(el); + let showStyles = {}; + for (let p in this.currentStyles) { // only interested in these styles + showStyles[p] = styles[p]; + } + return JSON.stringify(showStyles); + } + + getVal() { return this.val; } + + hero: Hero; // defined to demonstrate template context precedence + heroes: Hero[]; + + // trackBy change counting + heroesNoTrackByCount = 0; + heroesWithTrackByCount = 0; + heroesWithTrackByCountReset = 0; + + heroIdIncrement = 1; + + // heroImageUrl = 'http://www.wpclipart.com/cartoon/people/hero/hero_silhoutte_T.png'; + // Public Domain terms of use: http://www.wpclipart.com/terms.html + heroImageUrl = 'images/hero.png'; + + iconUrl = 'images/ng-logo.png'; + isActive = false; + isSpecial = true; + isUnchanged = true; + + nullHero: Hero = null; + + onCancel(event: KeyboardEvent) { + let evtMsg = event ? ' Event target is ' + (event.target).innerHTML : ''; + this.alert('Canceled.' + evtMsg); + } + + onClickMe(event: KeyboardEvent) { + let evtMsg = event ? ' Event target class is ' + (event.target).className : ''; + this.alert('Click me.' + evtMsg); + } + + onSave(event: KeyboardEvent) { + let evtMsg = event ? ' Event target is ' + (event.target).innerText : ''; + this.alert('Saved.' + evtMsg); + } + + onSubmit() { /* referenced but not used */} + + product = { + name: 'frimfram', + price: 42 + }; + + // updates with fresh set of cloned heroes + resetHeroes() { + this.heroes = Hero.heroes.map(hero => hero.clone()); + this.currentHero = this.heroes[0]; + this.heroesWithTrackByCountReset = 0; + } + + private samenessCount = 5; + moreOfTheSame() { this.samenessCount++; }; + get sameAsItEverWas() { + let result: string[] = Array(this.samenessCount); + for ( let i = result.length; i-- > 0; ) { result[i] = 'same as it ever was ...'; } + return result; + // return [1,2,3,4,5].map(id => { + // return {id:id, text: 'same as it ever was ...'}; + // }); + } + + setUppercaseName(name: string) { + this.currentHero.name = name.toUpperCase(); + } + + // #docregion setClasses + currentClasses: {}; + setCurrentClasses() { + // CSS classes: added/removed per current state of component properties + this.currentClasses = { + saveable: this.canSave, + modified: !this.isUnchanged, + special: this.isSpecial + }; + } + // #enddocregion setClasses + + // #docregion setStyles + currentStyles: {}; + setCurrentStyles() { + this.currentStyles = { + // CSS styles: set per current state of component properties + 'font-style': this.canSave ? 'italic' : 'normal', + 'font-weight': !this.isUnchanged ? 'bold' : 'normal', + 'font-size': this.isSpecial ? '24px' : '12px' + }; + } + // #enddocregion setStyles + + // #docregion trackByHeroes + trackByHeroes(index: number, hero: Hero): number { return hero.id; } + // #enddocregion trackByHeroes + + // #docregion trackById + trackById(index: number, item: any): number { return item['id']; } + // #enddocregion trackById + + val = 2; + // villainImageUrl = 'http://www.clker.com/cliparts/u/s/y/L/x/9/villain-man-hi.png' + // Public Domain terms of use http://www.clker.com/disclaimer.html + villainImageUrl = 'images/villain.png'; +} + +// helper to track changes to viewChildren +function trackChanges(views: QueryList, changed: () => void) { + let oldRefs = views.toArray(); + views.changes.subscribe((changes: QueryList) => { + const changedRefs = changes.toArray(); + // Is every changed ElemRef the same as old and in the same position + const isSame = oldRefs.every((v, i) => v === changedRefs[i]); + if (!isSame) { + oldRefs = changedRefs; + // wait a tick because called after views are constructed + setTimeout(changed, 0); + } + }); +} diff --git a/aio/content/examples/template-syntax/ts/src/app/app.module.1.ts b/aio/content/examples/template-syntax/ts/src/app/app.module.1.ts new file mode 100644 index 0000000000..8ea0d3d207 --- /dev/null +++ b/aio/content/examples/template-syntax/ts/src/app/app.module.1.ts @@ -0,0 +1,15 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; // <--- JavaScript import from Angular + +/* Other imports */ + +@NgModule({ + imports: [ + BrowserModule, + FormsModule // <--- import into the NgModule + ], + /* Other module metadata */ +}) +export class AppModule { } diff --git a/aio/content/examples/template-syntax/ts/src/app/app.module.ts b/aio/content/examples/template-syntax/ts/src/app/app.module.ts new file mode 100644 index 0000000000..5c2fbed6f1 --- /dev/null +++ b/aio/content/examples/template-syntax/ts/src/app/app.module.ts @@ -0,0 +1,29 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; + +import { AppComponent } from './app.component'; +import { BigHeroDetailComponent, HeroDetailComponent } from './hero-detail.component'; +import { ClickDirective, ClickDirective2 } from './click.directive'; +import { HeroFormComponent } from './hero-form.component'; +import { heroSwitchComponents } from './hero-switch.components'; +import { SizerComponent } from './sizer.component'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule + ], + declarations: [ + AppComponent, + BigHeroDetailComponent, + HeroDetailComponent, + HeroFormComponent, + heroSwitchComponents, + ClickDirective, + ClickDirective2, + SizerComponent + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/template-syntax/ts/src/app/click.directive.ts b/aio/content/examples/template-syntax/ts/src/app/click.directive.ts new file mode 100644 index 0000000000..09757bfeaf --- /dev/null +++ b/aio/content/examples/template-syntax/ts/src/app/click.directive.ts @@ -0,0 +1,41 @@ +/* tslint:disable use-output-property-decorator */ +// #docplaster +import { Directive, ElementRef, EventEmitter, Output } from '@angular/core'; + +@Directive({selector: '[myClick]'}) +export class ClickDirective { + // #docregion output-myClick + @Output('myClick') clicks = new EventEmitter(); // @Output(alias) propertyName = ... + // #enddocregion output-myClick + + toggle = false; + + constructor(el: ElementRef) { + el.nativeElement + .addEventListener('click', (event: Event) => { + this.toggle = !this.toggle; + this.clicks.emit(this.toggle ? 'Click!' : ''); + }); + } +} + +// #docregion output-myClick2 +@Directive({ + // #enddocregion output-myClick2 + selector: '[myClick2]', + // #docregion output-myClick2 + outputs: ['clicks:myClick'] // propertyName:alias +}) +// #enddocregion output-myClick2 +export class ClickDirective2 { + clicks = new EventEmitter(); + toggle = false; + + constructor(el: ElementRef) { + el.nativeElement + .addEventListener('click', (event: Event) => { + this.toggle = !this.toggle; + this.clicks.emit(this.toggle ? 'Click2!' : ''); + }); + } +} diff --git a/aio/content/examples/template-syntax/ts/src/app/hero-detail.component.ts b/aio/content/examples/template-syntax/ts/src/app/hero-detail.component.ts new file mode 100644 index 0000000000..486e6ac370 --- /dev/null +++ b/aio/content/examples/template-syntax/ts/src/app/hero-detail.component.ts @@ -0,0 +1,80 @@ +/* tslint:disable use-input-property-decorator use-output-property-decorator */ +// #docplaster +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +import { Hero } from './hero'; + +// #docregion input-output-2 +@Component({ +// #enddocregion input-output-2 + selector: 'hero-detail', + // #docregion input-output-2 + inputs: ['hero'], + outputs: ['deleteRequest'], + // #enddocregion input-output-2 + styles: ['button { margin-left: 8px} div {margin: 8px 0} img {height:24px}'], + // #docregion template-1 + template: ` +
    + + + {{prefix}} {{hero?.name}} + + +
    ` + // #enddocregion template-1 +// #docregion input-output-2 +}) +// #enddocregion input-output-2 +export class HeroDetailComponent { + hero: Hero = new Hero(-1, '', 'Zzzzzzzz'); // default sleeping hero + // heroImageUrl = 'http://www.wpclipart.com/cartoon/people/hero/hero_silhoutte_T.png'; + // Public Domain terms of use: http://www.wpclipart.com/terms.html + heroImageUrl = 'images/hero.png'; + lineThrough = ''; + @Input() prefix = ''; + +// #docregion deleteRequest + // This component make a request but it can't actually delete a hero. + deleteRequest = new EventEmitter(); + + delete() { + this.deleteRequest.emit(this.hero); + // #enddocregion deleteRequest + this.lineThrough = this.lineThrough ? '' : 'line-through'; + // #docregion deleteRequest + } +// #enddocregion deleteRequest +} + +@Component({ + selector: 'big-hero-detail', + template: ` +
    + +
    {{hero?.name}}
    +
    Name: {{hero?.name}}
    +
    Emotion: {{hero?.emotion}}
    +
    Birthdate: {{hero?.birthdate | date:'longDate'}}
    + +
    Rate/hr: {{hero?.rate | currency:'EUR'}}
    +
    + +
    + `, + styles: [` + .detail { border: 1px solid black; padding: 4px; max-width: 450px; } + img { float: left; margin-right: 8px; height: 100px; } + `] +}) +export class BigHeroDetailComponent extends HeroDetailComponent { + + // #docregion input-output-1 + @Input() hero: Hero; + @Output() deleteRequest = new EventEmitter(); + // #enddocregion input-output-1 + + delete() { + this.deleteRequest.emit(this.hero); + } +} diff --git a/aio/content/examples/template-syntax/ts/src/app/hero-form.component.html b/aio/content/examples/template-syntax/ts/src/app/hero-form.component.html new file mode 100644 index 0000000000..e61e268f74 --- /dev/null +++ b/aio/content/examples/template-syntax/ts/src/app/hero-form.component.html @@ -0,0 +1,16 @@ +
    + +
    +
    + +
    + +
    +
    + {{submitMessage}} +
    + +
    + diff --git a/aio/content/examples/template-syntax/ts/src/app/hero-form.component.ts b/aio/content/examples/template-syntax/ts/src/app/hero-form.component.ts new file mode 100644 index 0000000000..83685284e5 --- /dev/null +++ b/aio/content/examples/template-syntax/ts/src/app/hero-form.component.ts @@ -0,0 +1,31 @@ +import { Component, Input, ViewChild } from '@angular/core'; +import { NgForm } from '@angular/forms'; + +import { Hero } from './hero'; + +@Component({ + moduleId: module.id, + selector: 'hero-form', + templateUrl: './hero-form.component.html', + styles: [` + button { margin: 6px 0; } + #heroForm { border: 1px solid black; margin: 20px 0; padding: 8px; max-width: 350px; } + `] +}) +export class HeroFormComponent { + @Input() hero: Hero; + @ViewChild('heroForm') form: NgForm; + + private _submitMessage = ''; + + get submitMessage() { + if (!this.form.valid) { + this._submitMessage = ''; + } + return this._submitMessage; + } + + onSubmit(form: NgForm) { + this._submitMessage = 'Submitted. form value is ' + JSON.stringify(form.value); + } +} diff --git a/aio/content/examples/template-syntax/ts/src/app/hero-switch.components.ts b/aio/content/examples/template-syntax/ts/src/app/hero-switch.components.ts new file mode 100644 index 0000000000..2dad8a0ba0 --- /dev/null +++ b/aio/content/examples/template-syntax/ts/src/app/hero-switch.components.ts @@ -0,0 +1,42 @@ +import { Component, Input } from '@angular/core'; +import { Hero } from './hero'; + +@Component({ + selector: 'happy-hero', + template: `Wow. You like {{hero.name}}. What a happy hero ... just like you.` +}) +export class HappyHeroComponent { + @Input() hero: Hero; +} + +@Component({ + selector: 'sad-hero', + template: `You like {{hero.name}}? Such a sad hero. Are you sad too?` +}) +export class SadHeroComponent { + @Input() hero: Hero; +} + +@Component({ + selector: 'confused-hero', + template: `Are you as confused as {{hero.name}}?` +}) +export class ConfusedHeroComponent { + @Input() hero: Hero; +} + +@Component({ + selector: 'unknown-hero', + template: `{{message}}` +}) +export class UnknownHeroComponent { + @Input() hero: Hero; + get message() { + return this.hero && this.hero.name ? + `${this.hero.name} is strange and mysterious.` : + 'Are you feeling indecisive?'; + } +} + +export const heroSwitchComponents = + [ HappyHeroComponent, SadHeroComponent, ConfusedHeroComponent, UnknownHeroComponent ]; diff --git a/aio/content/examples/template-syntax/ts/src/app/hero.ts b/aio/content/examples/template-syntax/ts/src/app/hero.ts new file mode 100644 index 0000000000..6331b62a7b --- /dev/null +++ b/aio/content/examples/template-syntax/ts/src/app/hero.ts @@ -0,0 +1,33 @@ +export class Hero { + static nextId = 1; + + static heroes: Hero[] = [ + new Hero( + 325, + 'Hercules', + 'happy', + new Date(1970, 1, 25), + 'http://www.imdb.com/title/tt0065832/' + ), + new Hero(1, 'Mr. Nice', 'happy'), + new Hero(2, 'Narco', 'sad' ), + new Hero(3, 'Windstorm', 'confused' ), + new Hero(4, 'Magneta') + ]; + + + constructor( + public id?: number, + public name?: string, + public emotion?: string, + public birthdate?: Date, + public url?: string, + public rate = 100, + ) { + this.id = id ? id : Hero.nextId++; + } + + clone(): Hero { + return Object.assign(new Hero(), this); + } +} diff --git a/aio/content/examples/template-syntax/ts/src/app/sizer.component.ts b/aio/content/examples/template-syntax/ts/src/app/sizer.component.ts new file mode 100644 index 0000000000..b6065c8cd1 --- /dev/null +++ b/aio/content/examples/template-syntax/ts/src/app/sizer.component.ts @@ -0,0 +1,24 @@ +// #docregion +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'my-sizer', + template: ` +
    + + + +
    ` +}) +export class SizerComponent { + @Input() size: number | string; + @Output() sizeChange = new EventEmitter(); + + dec() { this.resize(-1); } + inc() { this.resize(+1); } + + resize(delta: number) { + this.size = Math.min(40, Math.max(8, +this.size + delta)); + this.sizeChange.emit(this.size); + } +} diff --git a/aio/content/examples/template-syntax/ts/src/images/hero.png b/aio/content/examples/template-syntax/ts/src/images/hero.png new file mode 100644 index 0000000000..2a128ac367 Binary files /dev/null and b/aio/content/examples/template-syntax/ts/src/images/hero.png differ diff --git a/aio/content/examples/template-syntax/ts/src/images/ng-logo.png b/aio/content/examples/template-syntax/ts/src/images/ng-logo.png new file mode 100644 index 0000000000..1e488b1a49 Binary files /dev/null and b/aio/content/examples/template-syntax/ts/src/images/ng-logo.png differ diff --git a/aio/content/examples/template-syntax/ts/src/images/villain.png b/aio/content/examples/template-syntax/ts/src/images/villain.png new file mode 100644 index 0000000000..26697d1a42 Binary files /dev/null and b/aio/content/examples/template-syntax/ts/src/images/villain.png differ diff --git a/aio/content/examples/template-syntax/ts/src/index.html b/aio/content/examples/template-syntax/ts/src/index.html new file mode 100644 index 0000000000..3e711397cb --- /dev/null +++ b/aio/content/examples/template-syntax/ts/src/index.html @@ -0,0 +1,26 @@ + + + + Template Syntax + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/template-syntax/ts/src/main.ts b/aio/content/examples/template-syntax/ts/src/main.ts new file mode 100644 index 0000000000..311c44b76d --- /dev/null +++ b/aio/content/examples/template-syntax/ts/src/main.ts @@ -0,0 +1,5 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/template-syntax/ts/src/template-syntax.css b/aio/content/examples/template-syntax/ts/src/template-syntax.css new file mode 100644 index 0000000000..6ae25b396c --- /dev/null +++ b/aio/content/examples/template-syntax/ts/src/template-syntax.css @@ -0,0 +1,13 @@ +fieldset {border-style:none} +img {height: 100px;} +.box {border: 1px solid black; padding:3px} +.child-div {margin-left: 1em; font-weight: normal} +.hidden {display: none} +.parent-div {margin-top: 1em; font-weight: bold} +.special {font-weight:bold; font-size: x-large} +.bad {color: red;} +.saveable {color: limegreen;} +.curly, .modified {font-family: "Brush Script MT"} +.toe {margin-left: 1em; font-style: italic;} +little-hero {color:blue; font-size: smaller; background-color: Turquoise } +.to-toc {margin-top: 10px; display: block} diff --git a/aio/content/examples/testing/ts/.gitignore b/aio/content/examples/testing/ts/.gitignore new file mode 100644 index 0000000000..5374943421 --- /dev/null +++ b/aio/content/examples/testing/ts/.gitignore @@ -0,0 +1 @@ +!src/browser-test-shim.js diff --git a/aio/content/examples/testing/ts/1st-specs.plnkr.json b/aio/content/examples/testing/ts/1st-specs.plnkr.json new file mode 100644 index 0000000000..9fe15ddef1 --- /dev/null +++ b/aio/content/examples/testing/ts/1st-specs.plnkr.json @@ -0,0 +1,14 @@ +{ + "description": "Testing - 1st.specs", + "basePath": "src/", + "files":[ + "browser-test-shim.js", + "styles.css", + + "app/1st.spec.ts", + "1st-specs.html" + ], + "main": "1st-specs.html", + "open": "app/1st.spec.ts", + "tags": ["testing"] +} diff --git a/aio/content/examples/testing/ts/1st-specs.plnkr.no-link.html b/aio/content/examples/testing/ts/1st-specs.plnkr.no-link.html new file mode 100644 index 0000000000..41897d0b0d --- /dev/null +++ b/aio/content/examples/testing/ts/1st-specs.plnkr.no-link.html @@ -0,0 +1,339 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/testing/ts/app-specs.plnkr.json b/aio/content/examples/testing/ts/app-specs.plnkr.json new file mode 100644 index 0000000000..d97bf82c86 --- /dev/null +++ b/aio/content/examples/testing/ts/app-specs.plnkr.json @@ -0,0 +1,24 @@ +{ + "description": "Testing - app.specs", + "basePath": "src/", + "files":[ + "browser-test-shim.js", + "systemjs.config.extras.js", + "styles.css", + + "app/**/*.css", + "app/**/*.html", + "app/**/*.ts", + "app/**/*.spec.ts", + + "testing/*.ts", + + "!main.ts", + "!app/bag/*.*", + "!app/1st.spec.ts", + + "app-specs.html" + ], + "main": "app-specs.html", + "tags": ["testing"] +} diff --git a/aio/content/examples/testing/ts/app-specs.plnkr.no-link.html b/aio/content/examples/testing/ts/app-specs.plnkr.no-link.html new file mode 100644 index 0000000000..63c497e00e --- /dev/null +++ b/aio/content/examples/testing/ts/app-specs.plnkr.no-link.html @@ -0,0 +1,3378 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/testing/ts/bag-specs.plnkr.json b/aio/content/examples/testing/ts/bag-specs.plnkr.json new file mode 100644 index 0000000000..cd22e10c28 --- /dev/null +++ b/aio/content/examples/testing/ts/bag-specs.plnkr.json @@ -0,0 +1,21 @@ +{ + "description": "Testing - bag.specs", + "basePath": "src/", + "files":[ + "browser-test-shim.js", + "systemjs.config.extras.js", + "styles.css", + + "app/bag/**/*.html", + "app/bag/**/*.ts", + "app/bag/**/*.spec.ts", + + "!app/bag/bag-main.ts", + + "testing/*.ts", + + "bag-specs.html" + ], + "main": "bag-specs.html", + "tags": ["testing"] +} diff --git a/aio/content/examples/testing/ts/bag-specs.plnkr.no-link.html b/aio/content/examples/testing/ts/bag-specs.plnkr.no-link.html new file mode 100644 index 0000000000..04d30a5e0c --- /dev/null +++ b/aio/content/examples/testing/ts/bag-specs.plnkr.no-link.html @@ -0,0 +1,1828 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/testing/ts/bag.plnkr.json b/aio/content/examples/testing/ts/bag.plnkr.json new file mode 100644 index 0000000000..4bb0ac9c5b --- /dev/null +++ b/aio/content/examples/testing/ts/bag.plnkr.json @@ -0,0 +1,14 @@ +{ + "description": "Running the bag", + "basePath": "src/", + "files":[ + "styles.css", + + "app/bag/bag.ts", + "app/bag/bag-external-template.html", + "app/bag/bag-main.ts", + "bag.html" + ], + "main": "bag.html", + "tags": ["testing"] +} diff --git a/aio/content/examples/testing/ts/bag.plnkr.no-link.html b/aio/content/examples/testing/ts/bag.plnkr.no-link.html new file mode 100644 index 0000000000..c1b05024a3 --- /dev/null +++ b/aio/content/examples/testing/ts/bag.plnkr.no-link.html @@ -0,0 +1,623 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/testing/ts/banner-inline-specs.plnkr.json b/aio/content/examples/testing/ts/banner-inline-specs.plnkr.json new file mode 100644 index 0000000000..77b8c212cf --- /dev/null +++ b/aio/content/examples/testing/ts/banner-inline-specs.plnkr.json @@ -0,0 +1,15 @@ +{ + "description": "Testing - banner-inline.component.specs", + "basePath": "src/", + "files":[ + "browser-test-shim.js", + "systemjs.config.extras.js", + + "app/banner-inline.component.ts", + "app/banner-inline.component.spec.ts", + "banner-inline-specs.html" + ], + "main": "banner-inline-specs.html", + "open": "app/banner-inline.component.spec.ts", + "tags": ["testing"] +} diff --git a/aio/content/examples/testing/ts/banner-inline-specs.plnkr.no-link.html b/aio/content/examples/testing/ts/banner-inline-specs.plnkr.no-link.html new file mode 100644 index 0000000000..d7aadb1963 --- /dev/null +++ b/aio/content/examples/testing/ts/banner-inline-specs.plnkr.no-link.html @@ -0,0 +1,289 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/testing/ts/banner-specs.plnkr.json b/aio/content/examples/testing/ts/banner-specs.plnkr.json new file mode 100644 index 0000000000..6e5f20bccb --- /dev/null +++ b/aio/content/examples/testing/ts/banner-specs.plnkr.json @@ -0,0 +1,17 @@ +{ + "description": "Testing - banner.component.specs", + "basePath": "src/", + "files":[ + "browser-test-shim.js", + "systemjs.config.extras.js", + + "app/banner.component.css", + "app/banner.component.html", + "app/banner.component.ts", + "app/banner.component.spec.ts", + "banner-specs.html" + ], + "main": "banner-specs.html", + "open": "app/banner.component.spec.ts", + "tags": ["testing"] +} diff --git a/aio/content/examples/testing/ts/banner-specs.plnkr.no-link.html b/aio/content/examples/testing/ts/banner-specs.plnkr.no-link.html new file mode 100644 index 0000000000..4c0fcce861 --- /dev/null +++ b/aio/content/examples/testing/ts/banner-specs.plnkr.no-link.html @@ -0,0 +1,311 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/testing/ts/example-config.json b/aio/content/examples/testing/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/testing/ts/plnkr.json b/aio/content/examples/testing/ts/plnkr.json new file mode 100644 index 0000000000..899867159f --- /dev/null +++ b/aio/content/examples/testing/ts/plnkr.json @@ -0,0 +1,18 @@ +{ + "description": "Heroes Test App", + "basePath": "src/", + "files":[ + "styles.css", + "systemjs.config.extras.js", + + "app/**/*.css", + "app/**/*.html", + "app/**/*.ts", + + "!app/bag/*.*", + + "main.ts", + "index.html" + ], + "tags": ["testing"] +} diff --git a/aio/content/examples/testing/ts/plnkr.no-link.html b/aio/content/examples/testing/ts/plnkr.no-link.html new file mode 100644 index 0000000000..a85bd8674c --- /dev/null +++ b/aio/content/examples/testing/ts/plnkr.no-link.html @@ -0,0 +1,1160 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/testing/ts/src/1st-specs.html b/aio/content/examples/testing/ts/src/1st-specs.html new file mode 100644 index 0000000000..5876a65b9a --- /dev/null +++ b/aio/content/examples/testing/ts/src/1st-specs.html @@ -0,0 +1,41 @@ + + + + + + + 1st Specs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aio/content/examples/testing/ts/src/app-specs.html b/aio/content/examples/testing/ts/src/app-specs.html new file mode 100644 index 0000000000..7b7292ed7c --- /dev/null +++ b/aio/content/examples/testing/ts/src/app-specs.html @@ -0,0 +1,56 @@ + + + + + + + Sample App Specs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aio/content/examples/testing/ts/src/app/1st.spec.ts b/aio/content/examples/testing/ts/src/app/1st.spec.ts new file mode 100644 index 0000000000..63f1ab134c --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/1st.spec.ts @@ -0,0 +1,5 @@ +// #docplaster +// #docregion +describe('1st tests', () => { + it('true is true', () => expect(true).toBe(true)); +}); diff --git a/aio/content/examples/testing/ts/src/app/about.component.spec.ts b/aio/content/examples/testing/ts/src/app/about.component.spec.ts new file mode 100644 index 0000000000..0909e74434 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/about.component.spec.ts @@ -0,0 +1,27 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; + +import { AboutComponent } from './about.component'; +import { HighlightDirective } from './shared/highlight.directive'; + +let fixture: ComponentFixture; + +describe('AboutComponent (highlightDirective)', () => { + // #docregion tests + beforeEach(() => { + fixture = TestBed.configureTestingModule({ + declarations: [ AboutComponent, HighlightDirective], + schemas: [ NO_ERRORS_SCHEMA ] + }) + .createComponent(AboutComponent); + fixture.detectChanges(); // initial binding + }); + + it('should have skyblue

    ', () => { + const de = fixture.debugElement.query(By.css('h2')); + const bgColor = de.nativeElement.style.backgroundColor; + expect(bgColor).toBe('skyblue'); + }); + // #enddocregion tests +}); diff --git a/aio/content/examples/testing/ts/src/app/about.component.ts b/aio/content/examples/testing/ts/src/app/about.component.ts new file mode 100644 index 0000000000..90e7132b4c --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/about.component.ts @@ -0,0 +1,9 @@ +// #docregion +import { Component } from '@angular/core'; +@Component({ + template: ` +

    About

    + +

    All about this sample

    ` +}) +export class AboutComponent { } diff --git a/aio/content/examples/testing/ts/src/app/app-routing.module.ts b/aio/content/examples/testing/ts/src/app/app-routing.module.ts new file mode 100644 index 0000000000..6096a513df --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/app-routing.module.ts @@ -0,0 +1,16 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { AboutComponent } from './about.component'; + +@NgModule({ + imports: [ + RouterModule.forRoot([ + { path: '', redirectTo: 'dashboard', pathMatch: 'full'}, + { path: 'about', component: AboutComponent }, + { path: 'heroes', loadChildren: 'app/hero/hero.module#HeroModule'} + ]) + ], + exports: [ RouterModule ] // re-export the module declarations +}) +export class AppRoutingModule { }; diff --git a/aio/content/examples/testing/ts/src/app/app.component.html b/aio/content/examples/testing/ts/src/app/app.component.html new file mode 100644 index 0000000000..232bcebb6d --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/app.component.html @@ -0,0 +1,11 @@ + + + + + + + diff --git a/aio/content/examples/testing/ts/src/app/app.component.router.spec.ts b/aio/content/examples/testing/ts/src/app/app.component.router.spec.ts new file mode 100644 index 0000000000..de22db8ceb --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/app.component.router.spec.ts @@ -0,0 +1,198 @@ +// For more examples: +// https://github.com/angular/angular/blob/master/modules/@angular/router/test/integration.spec.ts + +import { async, ComponentFixture, fakeAsync, TestBed, tick, +} from '@angular/core/testing'; + +import { RouterTestingModule } from '@angular/router/testing'; +import { SpyLocation } from '@angular/common/testing'; + +import { click } from '../testing'; + +// r - for relatively obscure router symbols +import * as r from '@angular/router'; +import { Router, RouterLinkWithHref } from '@angular/router'; + +import { By } from '@angular/platform-browser'; +import { DebugElement, Type } from '@angular/core'; +import { Location } from '@angular/common'; + +import { AppModule } from './app.module'; +import { AppComponent } from './app.component'; +import { AboutComponent } from './about.component'; +import { DashboardHeroComponent } from './dashboard/dashboard-hero.component'; +import { TwainService } from './shared/twain.service'; + +let comp: AppComponent; +let fixture: ComponentFixture; +let page: Page; +let router: Router; +let location: SpyLocation; + +describe('AppComponent & RouterTestingModule', () => { + + beforeEach( async(() => { + TestBed.configureTestingModule({ + imports: [ AppModule, RouterTestingModule ] + }) + .compileComponents(); + })); + + it('should navigate to "Dashboard" immediately', fakeAsync(() => { + createComponent(); + expect(location.path()).toEqual('/dashboard', 'after initialNavigation()'); + expectElementOf(DashboardHeroComponent); + })); + + it('should navigate to "About" on click', fakeAsync(() => { + createComponent(); + click(page.aboutLinkDe); + // page.aboutLinkDe.nativeElement.click(); // ok but fails in phantom + + advance(); + expectPathToBe('/about'); + expectElementOf(AboutComponent); + + page.expectEvents([ + [r.NavigationStart, '/about'], [r.RoutesRecognized, '/about'], + [r.NavigationEnd, '/about'] + ]); + })); + + it('should navigate to "About" w/ browser location URL change', fakeAsync(() => { + createComponent(); + location.simulateHashChange('/about'); + // location.go('/about'); // also works ... except in plunker + advance(); + expectPathToBe('/about'); + expectElementOf(AboutComponent); + })); + + // Can't navigate to lazy loaded modules with this technique + xit('should navigate to "Heroes" on click', fakeAsync(() => { + createComponent(); + page.heroesLinkDe.nativeElement.click(); + advance(); + expectPathToBe('/heroes'); + })); + +}); + + +/////////////// +import { NgModuleFactoryLoader } from '@angular/core'; +import { SpyNgModuleFactoryLoader } from '@angular/router/testing'; + +import { HeroModule } from './hero/hero.module'; // should be lazy loaded +import { HeroListComponent } from './hero/hero-list.component'; + +let loader: SpyNgModuleFactoryLoader; + +///////// Can't get lazy loaded Heroes to work yet +xdescribe('AppComponent & Lazy Loading', () => { + + beforeEach( async(() => { + TestBed.configureTestingModule({ + imports: [ AppModule, RouterTestingModule ] + }) + .compileComponents(); + })); + + beforeEach(fakeAsync(() => { + createComponent(); + loader = TestBed.get(NgModuleFactoryLoader); + loader.stubbedModules = {expected: HeroModule}; + router.resetConfig([{path: 'heroes', loadChildren: 'expected'}]); + })); + + it('dummy', () => expect(true).toBe(true) ); + + + it('should navigate to "Heroes" on click', async(() => { + page.heroesLinkDe.nativeElement.click(); + advance(); + expectPathToBe('/heroes'); + expectElementOf(HeroListComponent); + })); + + xit('can navigate to "Heroes" w/ browser location URL change', fakeAsync(() => { + location.go('/heroes'); + advance(); + expectPathToBe('/heroes'); + expectElementOf(HeroListComponent); + + page.expectEvents([ + [r.NavigationStart, '/heroes'], [r.RoutesRecognized, '/heroes'], + [r.NavigationEnd, '/heroes'] + ]); + })); +}); + +////// Helpers ///////// + +/** Wait a tick, then detect changes */ +function advance(): void { + tick(); + fixture.detectChanges(); +} + +function createComponent() { + fixture = TestBed.createComponent(AppComponent); + comp = fixture.componentInstance; + + const injector = fixture.debugElement.injector; + location = injector.get(Location); + router = injector.get(Router); + router.initialNavigation(); + spyOn(injector.get(TwainService), 'getQuote') + .and.returnValue(Promise.resolve('Test Quote')); // fakes it + + advance(); + + page = new Page(); +} + +class Page { + aboutLinkDe: DebugElement; + dashboardLinkDe: DebugElement; + heroesLinkDe: DebugElement; + recordedEvents: any[] = []; + + // for debugging + comp: AppComponent; + location: SpyLocation; + router: Router; + fixture: ComponentFixture; + + expectEvents(pairs: any[]) { + const events = this.recordedEvents; + expect(events.length).toEqual(pairs.length, 'actual/expected events length mismatch'); + for (let i = 0; i < events.length; ++i) { + expect((events[i].constructor).name).toBe(pairs[i][0].name, 'unexpected event name'); + expect((events[i]).url).toBe(pairs[i][1], 'unexpected event url'); + } + } + + constructor() { + router.events.subscribe(e => this.recordedEvents.push(e)); + const links = fixture.debugElement.queryAll(By.directive(RouterLinkWithHref)); + this.aboutLinkDe = links[2]; + this.dashboardLinkDe = links[0]; + this.heroesLinkDe = links[1]; + + // for debugging + this.comp = comp; + this.fixture = fixture; + this.router = router; + } +} + +function expectPathToBe(path: string, expectationFailOutput?: any) { + expect(location.path()).toEqual(path, expectationFailOutput || 'location.path()'); +} + +function expectElementOf(type: Type): any { + const el = fixture.debugElement.query(By.directive(type)); + expect(el).toBeTruthy('expected an element for ' + type.name); + return el; +} diff --git a/aio/content/examples/testing/ts/src/app/app.component.spec.ts b/aio/content/examples/testing/ts/src/app/app.component.spec.ts new file mode 100644 index 0000000000..20c40767c0 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/app.component.spec.ts @@ -0,0 +1,148 @@ +// #docplaster +import { async, ComponentFixture, TestBed +} from '@angular/core/testing'; + +import { DebugElement } from '@angular/core'; +import { By } from '@angular/platform-browser'; + + // #docregion setup-schemas + import { NO_ERRORS_SCHEMA } from '@angular/core'; + // #enddocregion setup-schemas + // #docregion setup-stubs-w-imports + import { Component } from '@angular/core'; + // #docregion setup-schemas + import { AppComponent } from './app.component'; + // #enddocregion setup-schemas + import { BannerComponent } from './banner.component'; + import { RouterLinkStubDirective } from '../testing'; + // #docregion setup-schemas + import { RouterOutletStubComponent } from '../testing'; + + // #enddocregion setup-schemas + @Component({selector: 'app-welcome', template: ''}) + class WelcomeStubComponent {} + + // #enddocregion setup-stubs-w-imports + +let comp: AppComponent; +let fixture: ComponentFixture; + +describe('AppComponent & TestModule', () => { + // #docregion setup-stubs, setup-stubs-w-imports + beforeEach( async(() => { + TestBed.configureTestingModule({ + declarations: [ + AppComponent, + BannerComponent, WelcomeStubComponent, + RouterLinkStubDirective, RouterOutletStubComponent + ] + }) + + .compileComponents() + .then(() => { + fixture = TestBed.createComponent(AppComponent); + comp = fixture.componentInstance; + }); + })); + // #enddocregion setup-stubs, setup-stubs-w-imports + tests(); +}); + +//////// Testing w/ NO_ERRORS_SCHEMA ////// +describe('AppComponent & NO_ERRORS_SCHEMA', () => { + // #docregion setup-schemas + beforeEach( async(() => { + TestBed.configureTestingModule({ + declarations: [ AppComponent, RouterLinkStubDirective ], + schemas: [ NO_ERRORS_SCHEMA ] + }) + + .compileComponents() + .then(() => { + fixture = TestBed.createComponent(AppComponent); + comp = fixture.componentInstance; + }); + })); + // #enddocregion setup-schemas + tests(); +}); + +//////// Testing w/ real root module ////// +// Tricky because we are disabling the router and its configuration +// Better to use RouterTestingModule +import { AppModule } from './app.module'; +import { AppRoutingModule } from './app-routing.module'; + +describe('AppComponent & AppModule', () => { + + beforeEach( async(() => { + + TestBed.configureTestingModule({ + imports: [ AppModule ] + }) + + // Get rid of app's Router configuration otherwise many failures. + // Doing so removes Router declarations; add the Router stubs + .overrideModule(AppModule, { + remove: { + imports: [ AppRoutingModule ] + }, + add: { + declarations: [ RouterLinkStubDirective, RouterOutletStubComponent ] + } + }) + + .compileComponents() + + .then(() => { + fixture = TestBed.createComponent(AppComponent); + comp = fixture.componentInstance; + }); + })); + + tests(); +}); + +function tests() { + let links: RouterLinkStubDirective[]; + let linkDes: DebugElement[]; + + // #docregion test-setup + beforeEach(() => { + // trigger initial data binding + fixture.detectChanges(); + + // find DebugElements with an attached RouterLinkStubDirective + linkDes = fixture.debugElement + .queryAll(By.directive(RouterLinkStubDirective)); + + // get the attached link directive instances using the DebugElement injectors + links = linkDes + .map(de => de.injector.get(RouterLinkStubDirective) as RouterLinkStubDirective); + }); + // #enddocregion test-setup + + it('can instantiate it', () => { + expect(comp).not.toBeNull(); + }); + + // #docregion tests + it('can get RouterLinks from template', () => { + expect(links.length).toBe(3, 'should have 3 links'); + expect(links[0].linkParams).toBe('/dashboard', '1st link should go to Dashboard'); + expect(links[1].linkParams).toBe('/heroes', '1st link should go to Heroes'); + }); + + it('can click Heroes link in template', () => { + const heroesLinkDe = linkDes[1]; + const heroesLink = links[1]; + + expect(heroesLink.navigatedTo).toBeNull('link should not have navigated yet'); + + heroesLinkDe.triggerEventHandler('click', null); + fixture.detectChanges(); + + expect(heroesLink.navigatedTo).toBe('/heroes'); + }); + // #enddocregion tests +} diff --git a/aio/content/examples/testing/ts/src/app/app.component.ts b/aio/content/examples/testing/ts/src/app/app.component.ts new file mode 100644 index 0000000000..a917088410 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/app.component.ts @@ -0,0 +1,8 @@ +// #docregion +import { Component } from '@angular/core'; +@Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: './app.component.html' +}) +export class AppComponent { } diff --git a/aio/content/examples/testing/ts/src/app/app.module.ts b/aio/content/examples/testing/ts/src/app/app.module.ts new file mode 100644 index 0000000000..d3c288ad11 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/app.module.ts @@ -0,0 +1,29 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; +import { AppRoutingModule } from './app-routing.module'; + +import { AboutComponent } from './about.component'; +import { BannerComponent } from './banner.component'; +import { HeroService, + UserService } from './model'; +import { TwainService } from './shared/twain.service'; +import { WelcomeComponent } from './welcome.component'; + + +import { DashboardModule } from './dashboard/dashboard.module'; +import { SharedModule } from './shared/shared.module'; + +@NgModule({ + imports: [ + BrowserModule, + DashboardModule, + AppRoutingModule, + SharedModule + ], + providers: [ HeroService, TwainService, UserService ], + declarations: [ AppComponent, AboutComponent, BannerComponent, WelcomeComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/testing/ts/src/app/bag/async-helper.spec.ts b/aio/content/examples/testing/ts/src/app/bag/async-helper.spec.ts new file mode 100644 index 0000000000..5106361a58 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/bag/async-helper.spec.ts @@ -0,0 +1,68 @@ +// tslint:disable-next-line:no-unused-variable +import { async, fakeAsync, tick } from '@angular/core/testing'; + +import { Observable } from 'rxjs/Observable'; + +describe('Angular async helper', () => { + let actuallyDone = false; + + beforeEach(() => { actuallyDone = false; }); + + afterEach(() => { expect(actuallyDone).toBe(true, 'actuallyDone should be true'); }); + + it('should run normal test', () => { actuallyDone = true; }); + + it('should run normal async test', (done: DoneFn) => { + setTimeout(() => { + actuallyDone = true; + done(); + }, 0); + }); + + it('should run async test with task', + async(() => { setTimeout(() => { actuallyDone = true; }, 0); })); + + it('should run async test with successful promise', async(() => { + const p = new Promise(resolve => { setTimeout(resolve, 10); }); + p.then(() => { actuallyDone = true; }); + })); + + it('should run async test with failed promise', async(() => { + const p = new Promise((resolve, reject) => { setTimeout(reject, 10); }); + p.catch(() => { actuallyDone = true; }); + })); + + // Use done. Cannot use setInterval with async or fakeAsync + // See https://github.com/angular/angular/issues/10127 + it('should run async test with successful delayed Observable', done => { + const source = Observable.of(true).delay(10); + source.subscribe( + val => actuallyDone = true, + err => fail(err), + done + ); + }); + + // Cannot use setInterval from within an async zone test + // See https://github.com/angular/angular/issues/10127 + // xit('should run async test with successful delayed Observable', async(() => { + // const source = Observable.of(true).delay(10); + // source.subscribe( + // val => actuallyDone = true, + // err => fail(err) + // ); + // })); + + // // Fail message: Error: 1 periodic timer(s) still in the queue + // // See https://github.com/angular/angular/issues/10127 + // xit('should run async test with successful delayed Observable', fakeAsync(() => { + // const source = Observable.of(true).delay(10); + // source.subscribe( + // val => actuallyDone = true, + // err => fail(err) + // ); + + // tick(); + // })); + +}); diff --git a/aio/content/examples/testing/ts/src/app/bag/bag-external-template.html b/aio/content/examples/testing/ts/src/app/bag/bag-external-template.html new file mode 100644 index 0000000000..4c2b23755f --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/bag/bag-external-template.html @@ -0,0 +1 @@ +from external template diff --git a/aio/content/examples/testing/ts/src/app/bag/bag-main.ts b/aio/content/examples/testing/ts/src/app/bag/bag-main.ts new file mode 100644 index 0000000000..27b78200ae --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/bag/bag-main.ts @@ -0,0 +1,5 @@ +// main app entry point +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { BagModule } from './bag'; + +platformBrowserDynamic().bootstrapModule(BagModule); diff --git a/aio/content/examples/testing/ts/src/app/bag/bag.no-testbed.spec.ts b/aio/content/examples/testing/ts/src/app/bag/bag.no-testbed.spec.ts new file mode 100644 index 0000000000..6bdbe86cd0 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/bag/bag.no-testbed.spec.ts @@ -0,0 +1,130 @@ +// #docplaster +import { DependentService, FancyService } from './bag'; + +///////// Fakes ///////// +export class FakeFancyService extends FancyService { + value: string = 'faked value'; +} +//////////////////////// +// #docregion FancyService +// Straight Jasmine - no imports from Angular test libraries + +describe('FancyService without the TestBed', () => { + let service: FancyService; + + beforeEach(() => { service = new FancyService(); }); + + it('#getValue should return real value', () => { + expect(service.getValue()).toBe('real value'); + }); + + it('#getAsyncValue should return async value', done => { + service.getAsyncValue().then(value => { + expect(value).toBe('async value'); + done(); + }); + }); + + // #docregion getTimeoutValue + it('#getTimeoutValue should return timeout value', done => { + service = new FancyService(); + service.getTimeoutValue().then(value => { + expect(value).toBe('timeout value'); + done(); + }); + }); + // #enddocregion getTimeoutValue + + it('#getObservableValue should return observable value', done => { + service.getObservableValue().subscribe(value => { + expect(value).toBe('observable value'); + done(); + }); + }); + +}); +// #enddocregion FancyService + +// DependentService requires injection of a FancyService +// #docregion DependentService +describe('DependentService without the TestBed', () => { + let service: DependentService; + + it('#getValue should return real value by way of the real FancyService', () => { + service = new DependentService(new FancyService()); + expect(service.getValue()).toBe('real value'); + }); + + it('#getValue should return faked value by way of a fakeService', () => { + service = new DependentService(new FakeFancyService()); + expect(service.getValue()).toBe('faked value'); + }); + + it('#getValue should return faked value from a fake object', () => { + const fake = { getValue: () => 'fake value' }; + service = new DependentService(fake as FancyService); + expect(service.getValue()).toBe('fake value'); + }); + + it('#getValue should return stubbed value from a FancyService spy', () => { + const fancy = new FancyService(); + const stubValue = 'stub value'; + const spy = spyOn(fancy, 'getValue').and.returnValue(stubValue); + service = new DependentService(fancy); + + expect(service.getValue()).toBe(stubValue, 'service returned stub value'); + expect(spy.calls.count()).toBe(1, 'stubbed method was called once'); + expect(spy.calls.mostRecent().returnValue).toBe(stubValue); + }); +}); +// #enddocregion DependentService + +// #docregion ReversePipe +import { ReversePipe } from './bag'; + +describe('ReversePipe', () => { + let pipe: ReversePipe; + + beforeEach(() => { pipe = new ReversePipe(); }); + + it('transforms "abc" to "cba"', () => { + expect(pipe.transform('abc')).toBe('cba'); + }); + + it('no change to palindrome: "able was I ere I saw elba"', () => { + const palindrome = 'able was I ere I saw elba'; + expect(pipe.transform(palindrome)).toBe(palindrome); + }); + +}); +// #enddocregion ReversePipe + + +import { ButtonComponent } from './bag'; +// #docregion ButtonComp +describe('ButtonComp', () => { + let comp: ButtonComponent; + beforeEach(() => comp = new ButtonComponent()); + + it('#isOn should be false initially', () => { + expect(comp.isOn).toBe(false); + }); + + it('#clicked() should set #isOn to true', () => { + comp.clicked(); + expect(comp.isOn).toBe(true); + }); + + it('#clicked() should set #message to "is on"', () => { + comp.clicked(); + expect(comp.message).toMatch(/is on/i); + }); + + it('#clicked() should toggle #isOn', () => { + comp.clicked(); + expect(comp.isOn).toBe(true); + comp.clicked(); + expect(comp.isOn).toBe(false); + }); +}); +// #enddocregion ButtonComp diff --git a/aio/content/examples/testing/ts/src/app/bag/bag.spec.ts b/aio/content/examples/testing/ts/src/app/bag/bag.spec.ts new file mode 100644 index 0000000000..3890d8844d --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/bag/bag.spec.ts @@ -0,0 +1,680 @@ +// #docplaster +import { + BagModule, + BankAccountComponent, BankAccountParentComponent, + ButtonComponent, + Child1Component, Child2Component, Child3Component, + FancyService, + ExternalTemplateComponent, + InputComponent, + IoComponent, IoParentComponent, + MyIfComponent, MyIfChildComponent, MyIfParentComponent, + NeedsContentComponent, ParentComponent, + TestProvidersComponent, TestViewProvidersComponent, + ReversePipeComponent, ShellComponent +} from './bag'; + +import { By } from '@angular/platform-browser'; +import { Component, + DebugElement, + Injectable } from '@angular/core'; +import { FormsModule } from '@angular/forms'; + +// Forms symbols imported only for a specific test below +import { NgModel, NgControl } from '@angular/forms'; + +import { async, ComponentFixture, fakeAsync, inject, TestBed, tick +} from '@angular/core/testing'; + +import { addMatchers, newEvent, click } from '../../testing'; + +beforeEach( addMatchers ); + +//////// Service Tests ///////////// +// #docregion FancyService +describe('use inject helper in beforeEach', () => { + let service: FancyService; + + beforeEach(() => { + TestBed.configureTestingModule({ providers: [FancyService] }); + + // `TestBed.get` returns the injectable or an + // alternative object (including null) if the service provider is not found. + // Of course it will be found in this case because we're providing it. + // #docregion testbed-get + service = TestBed.get(FancyService, null); + // #enddocregion testbed-get + }); + + it('should use FancyService', () => { + expect(service.getValue()).toBe('real value'); + }); + + it('should use FancyService', () => { + expect(service.getValue()).toBe('real value'); + }); + + it('test should wait for FancyService.getAsyncValue', async(() => { + service.getAsyncValue().then( + value => expect(value).toBe('async value') + ); + })); + + it('test should wait for FancyService.getTimeoutValue', async(() => { + service.getTimeoutValue().then( + value => expect(value).toBe('timeout value') + ); + })); + + it('test should wait for FancyService.getObservableValue', async(() => { + service.getObservableValue().subscribe( + value => expect(value).toBe('observable value') + ); + })); + + // Must use done. See https://github.com/angular/angular/issues/10127 + it('test should wait for FancyService.getObservableDelayValue', done => { + service.getObservableDelayValue().subscribe(value => { + expect(value).toBe('observable delay value'); + done(); + }); + }); + + it('should allow the use of fakeAsync', fakeAsync(() => { + let value: any; + service.getAsyncValue().then((val: any) => value = val); + tick(); // Trigger JS engine cycle until all promises resolve. + expect(value).toBe('async value'); + })); +}); +// #enddocregion FancyService + +describe('use inject within `it`', () => { + // #docregion getTimeoutValue + beforeEach(() => { + TestBed.configureTestingModule({ providers: [FancyService] }); + }); + + // #enddocregion getTimeoutValue + + it('should use modified providers', + inject([FancyService], (service: FancyService) => { + service.setValue('value modified in beforeEach'); + expect(service.getValue()).toBe('value modified in beforeEach'); + }) + ); + + // #docregion getTimeoutValue + it('test should wait for FancyService.getTimeoutValue', + async(inject([FancyService], (service: FancyService) => { + + service.getTimeoutValue().then( + value => expect(value).toBe('timeout value') + ); + }))); + // #enddocregion getTimeoutValue +}); + +describe('using async(inject) within beforeEach', () => { + let serviceValue: string; + + beforeEach(() => { + TestBed.configureTestingModule({ providers: [FancyService] }); + }); + + beforeEach( async(inject([FancyService], (service: FancyService) => { + service.getAsyncValue().then(value => serviceValue = value); + }))); + + it('should use asynchronously modified value ... in synchronous test', () => { + expect(serviceValue).toBe('async value'); + }); +}); + + +/////////// Component Tests ////////////////// + +describe('TestBed Component Tests', () => { + + beforeEach( async(() => { + TestBed + .configureTestingModule({ + imports: [BagModule], + }) + // Compile everything in BagModule + .compileComponents(); + })); + + it('should create a component with inline template', () => { + const fixture = TestBed.createComponent(Child1Component); + fixture.detectChanges(); + + expect(fixture).toHaveText('Child'); + }); + + it('should create a component with external template', () => { + const fixture = TestBed.createComponent(ExternalTemplateComponent); + fixture.detectChanges(); + + expect(fixture).toHaveText('from external template'); + }); + + it('should allow changing members of the component', () => { + const fixture = TestBed.createComponent(MyIfComponent); + + fixture.detectChanges(); + expect(fixture).toHaveText('MyIf()'); + + fixture.componentInstance.showMore = true; + fixture.detectChanges(); + expect(fixture).toHaveText('MyIf(More)'); + }); + + it('should create a nested component bound to inputs/outputs', () => { + const fixture = TestBed.createComponent(IoParentComponent); + + fixture.detectChanges(); + const heroes = fixture.debugElement.queryAll(By.css('.hero')); + expect(heroes.length).toBeGreaterThan(0, 'has heroes'); + + const comp = fixture.componentInstance; + const hero = comp.heroes[0]; + + click(heroes[0]); + fixture.detectChanges(); + + const selected = fixture.debugElement.query(By.css('p')); + expect(selected).toHaveText(hero.name); + }); + + it('can access the instance variable of an `*ngFor` row', () => { + const fixture = TestBed.createComponent(IoParentComponent); + const comp = fixture.componentInstance; + + fixture.detectChanges(); + const heroEl = fixture.debugElement.query(By.css('.hero')); // first hero + + const ngForRow = heroEl.parent; // Angular's NgForRow wrapper element + + // jasmine.any is instance-of-type test. + expect(ngForRow.componentInstance).toEqual(jasmine.any(IoComponent), 'component is IoComp'); + + const hero = ngForRow.context['$implicit']; // the hero object + expect(hero.name).toBe(comp.heroes[0].name, '1st hero\'s name'); + }); + + + // #docregion ButtonComp + it('should support clicking a button', () => { + const fixture = TestBed.createComponent(ButtonComponent); + const btn = fixture.debugElement.query(By.css('button')); + const span = fixture.debugElement.query(By.css('span')).nativeElement; + + fixture.detectChanges(); + expect(span.textContent).toMatch(/is off/i, 'before click'); + + click(btn); + fixture.detectChanges(); + expect(span.textContent).toMatch(/is on/i, 'after click'); + }); + // #enddocregion ButtonComp + + // ngModel is async so we must wait for it with promise-based `whenStable` + it('should support entering text in input box (ngModel)', async(() => { + const expectedOrigName = 'John'; + const expectedNewName = 'Sally'; + + const fixture = TestBed.createComponent(InputComponent); + fixture.detectChanges(); + + const comp = fixture.componentInstance; + const input = fixture.debugElement.query(By.css('input')).nativeElement; + + expect(comp.name).toBe(expectedOrigName, + `At start name should be ${expectedOrigName} `); + + // wait until ngModel binds comp.name to input box + fixture.whenStable().then(() => { + expect(input.value).toBe(expectedOrigName, + `After ngModel updates input box, input.value should be ${expectedOrigName} `); + + // simulate user entering new name in input + input.value = expectedNewName; + + // that change doesn't flow to the component immediately + expect(comp.name).toBe(expectedOrigName, + `comp.name should still be ${expectedOrigName} after value change, before binding happens`); + + // dispatch a DOM event so that Angular learns of input value change. + // then wait while ngModel pushes input.box value to comp.name + input.dispatchEvent(newEvent('input')); + return fixture.whenStable(); + }) + .then(() => { + expect(comp.name).toBe(expectedNewName, + `After ngModel updates the model, comp.name should be ${expectedNewName} `); + }); + })); + + // fakeAsync version of ngModel input test enables sync test style + // synchronous `tick` replaces asynchronous promise-base `whenStable` + it('should support entering text in input box (ngModel) - fakeAsync', fakeAsync(() => { + const expectedOrigName = 'John'; + const expectedNewName = 'Sally'; + + const fixture = TestBed.createComponent(InputComponent); + fixture.detectChanges(); + + const comp = fixture.componentInstance; + const input = fixture.debugElement.query(By.css('input')).nativeElement; + + expect(comp.name).toBe(expectedOrigName, + `At start name should be ${expectedOrigName} `); + + // wait until ngModel binds comp.name to input box + tick(); + expect(input.value).toBe(expectedOrigName, + `After ngModel updates input box, input.value should be ${expectedOrigName} `); + + // simulate user entering new name in input + input.value = expectedNewName; + + // that change doesn't flow to the component immediately + expect(comp.name).toBe(expectedOrigName, + `comp.name should still be ${expectedOrigName} after value change, before binding happens`); + + // dispatch a DOM event so that Angular learns of input value change. + // then wait a tick while ngModel pushes input.box value to comp.name + input.dispatchEvent(newEvent('input')); + tick(); + expect(comp.name).toBe(expectedNewName, + `After ngModel updates the model, comp.name should be ${expectedNewName} `); + })); + + // #docregion ReversePipeComp + it('ReversePipeComp should reverse the input text', fakeAsync(() => { + const inputText = 'the quick brown fox.'; + const expectedText = '.xof nworb kciuq eht'; + + const fixture = TestBed.createComponent(ReversePipeComponent); + fixture.detectChanges(); + + const comp = fixture.componentInstance; + const input = fixture.debugElement.query(By.css('input')).nativeElement as HTMLInputElement; + const span = fixture.debugElement.query(By.css('span')).nativeElement as HTMLElement; + + // simulate user entering new name in input + input.value = inputText; + + // dispatch a DOM event so that Angular learns of input value change. + // then wait a tick while ngModel pushes input.box value to comp.text + // and Angular updates the output span + input.dispatchEvent(newEvent('input')); + tick(); + fixture.detectChanges(); + expect(span.textContent).toBe(expectedText, 'output span'); + expect(comp.text).toBe(inputText, 'component.text'); + })); + // #enddocregion ReversePipeComp + + // Use this technique to find attached directives of any kind + it('can examine attached directives and listeners', () => { + const fixture = TestBed.createComponent(InputComponent); + fixture.detectChanges(); + + const inputEl = fixture.debugElement.query(By.css('input')); + + expect(inputEl.providerTokens).toContain(NgModel, 'NgModel directive'); + + const ngControl = inputEl.injector.get(NgControl); + expect(ngControl).toEqual(jasmine.any(NgControl), 'NgControl directive'); + + expect(inputEl.listeners.length).toBeGreaterThan(2, 'several listeners attached'); + }); + + // #docregion dom-attributes + it('BankAccountComponent should set attributes, styles, classes, and properties', () => { + const fixture = TestBed.createComponent(BankAccountParentComponent); + fixture.detectChanges(); + const comp = fixture.componentInstance; + + // the only child is debugElement of the BankAccount component + const el = fixture.debugElement.children[0]; + const childComp = el.componentInstance as BankAccountComponent; + expect(childComp).toEqual(jasmine.any(BankAccountComponent)); + + expect(el.context).toBe(comp, 'context is the parent component'); + + expect(el.attributes['account']).toBe(childComp.id, 'account attribute'); + expect(el.attributes['bank']).toBe(childComp.bank, 'bank attribute'); + + expect(el.classes['closed']).toBe(true, 'closed class'); + expect(el.classes['open']).toBe(false, 'open class'); + + expect(el.styles['color']).toBe(comp.color, 'color style'); + expect(el.styles['width']).toBe(comp.width + 'px', 'width style'); + // #enddocregion dom-attributes + + // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future? + // expect(el.properties['customProperty']).toBe(true, 'customProperty'); + + // #docregion dom-attributes + }); + // #enddocregion dom-attributes + + +}); + +describe('TestBed Component Overrides:', () => { + + it('should override ChildComp\'s template', () => { + + const fixture = TestBed.configureTestingModule({ + declarations: [Child1Component], + }) + .overrideComponent(Child1Component, { + set: { template: 'Fake' } + }) + .createComponent(Child1Component); + + fixture.detectChanges(); + expect(fixture).toHaveText('Fake'); + }); + + it('should override TestProvidersComp\'s FancyService provider', () => { + const fixture = TestBed.configureTestingModule({ + declarations: [TestProvidersComponent], + }) + .overrideComponent(TestProvidersComponent, { + remove: { providers: [FancyService]}, + add: { providers: [{ provide: FancyService, useClass: FakeFancyService }] }, + + // Or replace them all (this component has only one provider) + // set: { providers: [{ provide: FancyService, useClass: FakeFancyService }] }, + }) + .createComponent(TestProvidersComponent); + + fixture.detectChanges(); + expect(fixture).toHaveText('injected value: faked value', 'text'); + + // Explore the providerTokens + const tokens = fixture.debugElement.providerTokens; + expect(tokens).toContain(fixture.componentInstance.constructor, 'component ctor'); + expect(tokens).toContain(TestProvidersComponent, 'TestProvidersComp'); + expect(tokens).toContain(FancyService, 'FancyService'); + }); + + it('should override TestViewProvidersComp\'s FancyService viewProvider', () => { + const fixture = TestBed.configureTestingModule({ + declarations: [TestViewProvidersComponent], + }) + .overrideComponent(TestViewProvidersComponent, { + // remove: { viewProviders: [FancyService]}, + // add: { viewProviders: [{ provide: FancyService, useClass: FakeFancyService }] }, + + // Or replace them all (this component has only one viewProvider) + set: { viewProviders: [{ provide: FancyService, useClass: FakeFancyService }] }, + }) + .createComponent(TestViewProvidersComponent); + + fixture.detectChanges(); + expect(fixture).toHaveText('injected value: faked value'); + }); + + it('injected provider should not be same as component\'s provider', () => { + + // TestComponent is parent of TestProvidersComponent + @Component({ template: '' }) + class TestComponent {} + + // 3 levels of FancyService provider: module, TestCompomponent, TestProvidersComponent + const fixture = TestBed.configureTestingModule({ + declarations: [TestComponent, TestProvidersComponent], + providers: [FancyService] + }) + .overrideComponent(TestComponent, { + set: { providers: [{ provide: FancyService, useValue: {} }] } + }) + .overrideComponent(TestProvidersComponent, { + set: { providers: [{ provide: FancyService, useClass: FakeFancyService }] } + }) + .createComponent(TestComponent); + + let testBedProvider: FancyService; + let tcProvider: {}; + let tpcProvider: FakeFancyService; + + // `inject` uses TestBed's injector + inject([FancyService], (s: FancyService) => testBedProvider = s)(); + tcProvider = fixture.debugElement.injector.get(FancyService); + tpcProvider = fixture.debugElement.children[0].injector.get(FancyService); + + expect(testBedProvider).not.toBe(tcProvider, 'testBed/tc not same providers'); + expect(testBedProvider).not.toBe(tpcProvider, 'testBed/tpc not same providers'); + + expect(testBedProvider instanceof FancyService).toBe(true, 'testBedProvider is FancyService'); + expect(tcProvider).toEqual({}, 'tcProvider is {}'); + expect(tpcProvider instanceof FakeFancyService).toBe(true, 'tpcProvider is FakeFancyService'); + }); + + it('can access template local variables as references', () => { + const fixture = TestBed.configureTestingModule({ + declarations: [ShellComponent, NeedsContentComponent, Child1Component, Child2Component, Child3Component], + }) + .overrideComponent(ShellComponent, { + set: { + selector: 'test-shell', + template: ` + + + + + +
    !
    +
    + ` + } + }) + .createComponent(ShellComponent); + + fixture.detectChanges(); + + // NeedsContentComp is the child of ShellComp + const el = fixture.debugElement.children[0]; + const comp = el.componentInstance; + + expect(comp.children.toArray().length).toBe(4, + 'three different child components and an ElementRef with #content'); + + expect(el.references['nc']).toBe(comp, '#nc reference to component'); + + // #docregion custom-predicate + // Filter for DebugElements with a #content reference + const contentRefs = el.queryAll( de => de.references['content']); + // #enddocregion custom-predicate + expect(contentRefs.length).toBe(4, 'elements w/ a #content reference'); + }); + +}); + +describe('Nested (one-deep) component override', () => { + + beforeEach( async(() => { + TestBed.configureTestingModule({ + declarations: [ParentComponent, FakeChildComponent] + }) + .compileComponents(); + })); + + it('ParentComp should use Fake Child component', () => { + const fixture = TestBed.createComponent(ParentComponent); + fixture.detectChanges(); + expect(fixture).toHaveText('Parent(Fake Child)'); + }); +}); + +describe('Nested (two-deep) component override', () => { + + beforeEach( async(() => { + TestBed.configureTestingModule({ + declarations: [ParentComponent, FakeChildWithGrandchildComponent, FakeGrandchildComponent] + }) + .compileComponents(); + })); + + it('should use Fake Grandchild component', () => { + const fixture = TestBed.createComponent(ParentComponent); + fixture.detectChanges(); + expect(fixture).toHaveText('Parent(Fake Child(Fake Grandchild))'); + }); +}); + +describe('Lifecycle hooks w/ MyIfParentComp', () => { + let fixture: ComponentFixture; + let parent: MyIfParentComponent; + let child: MyIfChildComponent; + + beforeEach( async(() => { + TestBed.configureTestingModule({ + imports: [FormsModule], + declarations: [MyIfChildComponent, MyIfParentComponent] + }) + .compileComponents().then(() => { + fixture = TestBed.createComponent(MyIfParentComponent); + parent = fixture.componentInstance; + }); + })); + + it('should instantiate parent component', () => { + expect(parent).not.toBeNull('parent component should exist'); + }); + + it('parent component OnInit should NOT be called before first detectChanges()', () => { + expect(parent.ngOnInitCalled).toBe(false); + }); + + it('parent component OnInit should be called after first detectChanges()', () => { + fixture.detectChanges(); + expect(parent.ngOnInitCalled).toBe(true); + }); + + it('child component should exist after OnInit', () => { + fixture.detectChanges(); + getChild(); + expect(child instanceof MyIfChildComponent).toBe(true, 'should create child'); + }); + + it('should have called child component\'s OnInit ', () => { + fixture.detectChanges(); + getChild(); + expect(child.ngOnInitCalled).toBe(true); + }); + + it('child component called OnChanges once', () => { + fixture.detectChanges(); + getChild(); + expect(child.ngOnChangesCounter).toBe(1); + }); + + it('changed parent value flows to child', () => { + fixture.detectChanges(); + getChild(); + + parent.parentValue = 'foo'; + fixture.detectChanges(); + + expect(child.ngOnChangesCounter).toBe(2, + 'expected 2 changes: initial value and changed value'); + expect(child.childValue).toBe('foo', + 'childValue should eq changed parent value'); + }); + + // must be async test to see child flow to parent + it('changed child value flows to parent', async(() => { + fixture.detectChanges(); + getChild(); + + child.childValue = 'bar'; + + return new Promise(resolve => { + // Wait one JS engine turn! + setTimeout(() => resolve(), 0); + }) + .then(() => { + fixture.detectChanges(); + + expect(child.ngOnChangesCounter).toBe(2, + 'expected 2 changes: initial value and changed value'); + expect(parent.parentValue).toBe('bar', + 'parentValue should eq changed parent value'); + }); + + })); + + it('clicking "Close Child" triggers child OnDestroy', () => { + fixture.detectChanges(); + getChild(); + + const btn = fixture.debugElement.query(By.css('button')); + click(btn); + + fixture.detectChanges(); + expect(child.ngOnDestroyCalled).toBe(true); + }); + + ////// helpers /// + /** + * Get the MyIfChildComp from parent; fail w/ good message if cannot. + */ + function getChild() { + + let childDe: DebugElement; // DebugElement that should hold the MyIfChildComp + + // The Hard Way: requires detailed knowledge of the parent template + try { + childDe = fixture.debugElement.children[4].children[0]; + } catch (err) { /* we'll report the error */ } + + // DebugElement.queryAll: if we wanted all of many instances: + childDe = fixture.debugElement + .queryAll(function (de) { return de.componentInstance instanceof MyIfChildComponent; })[0]; + + // WE'LL USE THIS APPROACH ! + // DebugElement.query: find first instance (if any) + childDe = fixture.debugElement + .query(function (de) { return de.componentInstance instanceof MyIfChildComponent; }); + + if (childDe && childDe.componentInstance) { + child = childDe.componentInstance; + } else { + fail('Unable to find MyIfChildComp within MyIfParentComp'); + } + + return child; + } +}); + +////////// Fakes /////////// + +@Component({ + selector: 'child-1', + template: `Fake Child` +}) +class FakeChildComponent { } + +@Component({ + selector: 'child-1', + template: `Fake Child()` +}) +class FakeChildWithGrandchildComponent { } + +@Component({ + selector: 'grandchild-1', + template: `Fake Grandchild` +}) +class FakeGrandchildComponent { } + +@Injectable() +class FakeFancyService extends FancyService { + value: string = 'faked value'; +} diff --git a/aio/content/examples/testing/ts/src/app/bag/bag.ts b/aio/content/examples/testing/ts/src/app/bag/bag.ts new file mode 100644 index 0000000000..41608d7acd --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/bag/bag.ts @@ -0,0 +1,456 @@ +/* tslint:disable:forin */ +import { Component, ContentChildren, Directive, EventEmitter, + Injectable, Input, Output, Optional, + HostBinding, HostListener, + OnInit, OnChanges, OnDestroy, + Pipe, PipeTransform, + SimpleChange } from '@angular/core'; + +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/observable/of'; +import 'rxjs/add/operator/delay'; + +////////// The App: Services and Components for the tests. ////////////// + +export class Hero { + name: string; +} + +////////// Services /////////////// +// #docregion FancyService +@Injectable() +export class FancyService { + protected value: string = 'real value'; + + getValue() { return this.value; } + setValue(value: string) { this.value = value; } + + getAsyncValue() { return Promise.resolve('async value'); } + + getObservableValue() { return Observable.of('observable value'); } + + getTimeoutValue() { + return new Promise((resolve) => { + setTimeout(() => { resolve('timeout value'); }, 10); + }); + } + + getObservableDelayValue() { + return Observable.of('observable delay value').delay(10); + } +} +// #enddocregion FancyService + +// #docregion DependentService +@Injectable() +export class DependentService { + constructor(private dependentService: FancyService) { } + getValue() { return this.dependentService.getValue(); } +} +// #enddocregion DependentService + +/////////// Pipe //////////////// +/* + * Reverse the input string. +*/ +// #docregion ReversePipe +@Pipe({ name: 'reverse' }) +export class ReversePipe implements PipeTransform { + transform(s: string) { + let r = ''; + for (let i = s.length; i; ) { r += s[--i]; }; + return r; + } +} +// #enddocregion ReversePipe + +//////////// Components ///////////// +@Component({ + selector: 'bank-account', + template: ` + Bank Name: {{bank}} + Account Id: {{id}} + ` +}) +export class BankAccountComponent { + @Input() bank: string; + @Input('account') id: string; + + // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future? + // constructor(private renderer: Renderer, private el: ElementRef ) { + // renderer.setElementProperty(el.nativeElement, 'customProperty', true); + // } +} + +/** A component with attributes, styles, classes, and property setting */ +@Component({ + selector: 'bank-account-parent', + template: ` + + + ` +}) +export class BankAccountParentComponent { + width = 200; + color = 'red'; + isClosed = true; +} + +// #docregion ButtonComp +@Component({ + selector: 'button-comp', + template: ` + + {{message}}` +}) +export class ButtonComponent { + isOn = false; + clicked() { this.isOn = !this.isOn; } + get message() { return `The light is ${this.isOn ? 'On' : 'Off'}`; } +} +// #enddocregion ButtonComp + +@Component({ + selector: 'child-1', + template: `Child-1({{text}})` +}) +export class Child1Component { + @Input() text = 'Original'; +} + +@Component({ + selector: 'child-2', + template: '
    Child-2({{text}})
    ' +}) +export class Child2Component { + @Input() text: string; +} + +@Component({ + selector: 'child-3', + template: '
    Child-3({{text}})
    ' +}) +export class Child3Component { + @Input() text: string; +} + +@Component({ + selector: 'input-comp', + template: `` +}) +export class InputComponent { + name = 'John'; +} + +/* Prefer this metadata syntax */ +// @Directive({ +// selector: 'input[value]', +// host: { +// '[value]': 'value', +// '(input)': 'valueChange.emit($event.target.value)' +// }, +// inputs: ['value'], +// outputs: ['valueChange'] +// }) +// export class InputValueBinderDirective { +// value: any; +// valueChange: EventEmitter = new EventEmitter(); +// } + +// As the style-guide recommends +@Directive({ selector: 'input[value]' }) +export class InputValueBinderDirective { + @HostBinding() + @Input() + value: any; + + @Output() + valueChange: EventEmitter = new EventEmitter(); + + @HostListener('input', ['$event.target.value']) + onInput(value: any) { this.valueChange.emit(value); } +} + +@Component({ + selector: 'input-value-comp', + template: ` + Name: {{name}} + ` +}) +export class InputValueBinderComponent { + name = 'Sally'; // initial value +} + +@Component({ + selector: 'parent-comp', + template: `Parent()` +}) +export class ParentComponent { } + +@Component({ + selector: 'io-comp', + template: `
    Original {{hero.name}}
    ` +}) +export class IoComponent { + @Input() hero: Hero; + @Output() selected = new EventEmitter(); + click() { this.selected.emit(this.hero); } +} + +@Component({ + selector: 'io-parent-comp', + template: ` +

    Click to select a hero

    +

    The selected hero is {{selectedHero.name}}

    + + + ` +}) +export class IoParentComponent { + heroes: Hero[] = [ {name: 'Bob'}, {name: 'Carol'}, {name: 'Ted'}, {name: 'Alice'} ]; + selectedHero: Hero; + onSelect(hero: Hero) { this.selectedHero = hero; } +} + +@Component({ + selector: 'my-if-comp', + template: `MyIf(More)` +}) +export class MyIfComponent { + showMore = false; +} + +@Component({ + selector: 'my-service-comp', + template: `injected value: {{fancyService.value}}`, + providers: [FancyService] +}) +export class TestProvidersComponent { + constructor(private fancyService: FancyService) {} +} + + +@Component({ + selector: 'my-service-comp', + template: `injected value: {{fancyService.value}}`, + viewProviders: [FancyService] +}) +export class TestViewProvidersComponent { + constructor(private fancyService: FancyService) {} +} + +@Component({ + moduleId: module.id, + selector: 'external-template-comp', + templateUrl: './bag-external-template.html' +}) +export class ExternalTemplateComponent implements OnInit { + serviceValue: string; + + constructor(@Optional() private service: FancyService) { } + + ngOnInit() { + if (this.service) { this.serviceValue = this.service.getValue(); } + } +} + +@Component({ + selector: 'comp-w-ext-comp', + template: ` +

    comp-w-ext-comp

    + + ` +}) +export class InnerCompWithExternalTemplateComponent { } + +@Component({ + moduleId: module.id, + selector: 'bad-template-comp', + templateUrl: './non-existant.html' +}) +export class BadTemplateUrlComponent { } + + + +@Component({selector: 'needs-content', template: ''}) +export class NeedsContentComponent { + // children with #content local variable + @ContentChildren('content') children: any; +} + +///////// MyIfChildComp //////// +@Component({ + selector: 'my-if-child-1', + + template: ` +

    MyIfChildComp

    +
    + +
    +

    Change log:

    +
    {{i + 1}} - {{log}}
    ` +}) +export class MyIfChildComponent implements OnInit, OnChanges, OnDestroy { + @Input() value = ''; + @Output() valueChange = new EventEmitter(); + + get childValue() { return this.value; } + set childValue(v: string) { + if (this.value === v) { return; } + this.value = v; + this.valueChange.emit(v); + } + + changeLog: string[] = []; + + ngOnInitCalled = false; + ngOnChangesCounter = 0; + ngOnDestroyCalled = false; + + ngOnInit() { + this.ngOnInitCalled = true; + this.changeLog.push('ngOnInit called'); + } + + ngOnDestroy() { + this.ngOnDestroyCalled = true; + this.changeLog.push('ngOnDestroy called'); + } + + ngOnChanges(changes: {[propertyName: string]: SimpleChange}) { + for (let propName in changes) { + this.ngOnChangesCounter += 1; + let prop = changes[propName]; + let cur = JSON.stringify(prop.currentValue); + let prev = JSON.stringify(prop.previousValue); + this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`); + } + } +} + +///////// MyIfParentComp //////// + +@Component({ + selector: 'my-if-parent-comp', + template: ` +

    MyIfParentComp

    + +
    +
    + +
    + ` +}) +export class MyIfParentComponent implements OnInit { + ngOnInitCalled = false; + parentValue = 'Hello, World'; + showChild = false; + toggleLabel = 'Unknown'; + + ngOnInit() { + this.ngOnInitCalled = true; + this.clicked(); + } + + clicked() { + this.showChild = !this.showChild; + this.toggleLabel = this.showChild ? 'Close' : 'Show'; + } +} + + +@Component({ + selector: 'reverse-pipe-comp', + template: ` + + {{text | reverse}} + ` +}) +export class ReversePipeComponent { + text = 'my dog has fleas.'; +} + +@Component({template: '
    Replace Me
    '}) +export class ShellComponent { } + +@Component({ + selector: 'bag-comp', + template: ` +

    Specs Bag

    + +
    +

    Input/Output Component

    + +
    +

    External Template Component

    + +
    +

    Component With External Template Component

    + +
    +

    Reverse Pipe

    + +
    +

    InputValueBinder Directive

    + +
    +

    Button Component

    + +
    +

    Needs Content

    + + + + + +
    !
    +
    + ` +}) +export class BagComponent { } +//////// Aggregations //////////// + +export const bagDeclarations = [ + BagComponent, + BankAccountComponent, BankAccountParentComponent, + ButtonComponent, + Child1Component, Child2Component, Child3Component, + ExternalTemplateComponent, InnerCompWithExternalTemplateComponent, + InputComponent, + InputValueBinderDirective, InputValueBinderComponent, + IoComponent, IoParentComponent, + MyIfComponent, MyIfChildComponent, MyIfParentComponent, + NeedsContentComponent, ParentComponent, + TestProvidersComponent, TestViewProvidersComponent, + ReversePipe, ReversePipeComponent, ShellComponent +]; + +export const bagProviders = [DependentService, FancyService]; + +//////////////////// +//////////// +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; + +@NgModule({ + imports: [BrowserModule, FormsModule], + declarations: bagDeclarations, + providers: bagProviders, + entryComponents: [BagComponent], + bootstrap: [BagComponent] +}) +export class BagModule { } + diff --git a/aio/content/examples/testing/ts/src/app/banner-inline.component.spec.ts b/aio/content/examples/testing/ts/src/app/banner-inline.component.spec.ts new file mode 100644 index 0000000000..7f35d6b956 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/banner-inline.component.spec.ts @@ -0,0 +1,55 @@ +// #docplaster +// #docregion imports +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { BannerComponent } from './banner-inline.component'; +// #enddocregion imports + +// #docregion setup +describe('BannerComponent (inline template)', () => { + + let comp: BannerComponent; + let fixture: ComponentFixture; + let de: DebugElement; + let el: HTMLElement; + +// #docregion before-each + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ BannerComponent ], // declare the test component + }); + + fixture = TestBed.createComponent(BannerComponent); + + comp = fixture.componentInstance; // BannerComponent test instance + + // query for the title

    by CSS element selector + de = fixture.debugElement.query(By.css('h1')); + el = de.nativeElement; + }); +// #enddocregion before-each +// #enddocregion setup + + // #docregion test-w-o-detect-changes + it('no title in the DOM until manually call `detectChanges`', () => { + expect(el.textContent).toEqual(''); + }); + // #enddocregion test-w-o-detect-changes + + // #docregion tests + it('should display original title', () => { + fixture.detectChanges(); + expect(el.textContent).toContain(comp.title); + }); + + it('should display a different test title', () => { + comp.title = 'Test Title'; + fixture.detectChanges(); + expect(el.textContent).toContain('Test Title'); + }); + // #enddocregion tests +// #docregion setup +}); +// #enddocregion setup diff --git a/aio/content/examples/testing/ts/src/app/banner-inline.component.ts b/aio/content/examples/testing/ts/src/app/banner-inline.component.ts new file mode 100644 index 0000000000..7ec4ef6999 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/banner-inline.component.ts @@ -0,0 +1,11 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-banner', + template: '

    {{title}}

    ' +}) +export class BannerComponent { + title = 'Test Tour of Heroes'; +} + diff --git a/aio/content/examples/testing/ts/src/app/banner.component.css b/aio/content/examples/testing/ts/src/app/banner.component.css new file mode 100644 index 0000000000..cd09a55b3c --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/banner.component.css @@ -0,0 +1 @@ +h1 { color: green; font-size: 350%} diff --git a/aio/content/examples/testing/ts/src/app/banner.component.detect-changes.spec.ts b/aio/content/examples/testing/ts/src/app/banner.component.detect-changes.spec.ts new file mode 100644 index 0000000000..412f5be586 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/banner.component.detect-changes.spec.ts @@ -0,0 +1,59 @@ +// #docplaster +// #docregion +// #docregion import-async +import { async } from '@angular/core/testing'; +// #enddocregion import-async +// #docregion import-ComponentFixtureAutoDetect +import { ComponentFixtureAutoDetect } from '@angular/core/testing'; +// #enddocregion import-ComponentFixtureAutoDetect +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { BannerComponent } from './banner.component'; + +describe('BannerComponent (AutoChangeDetect)', () => { + let comp: BannerComponent; + let fixture: ComponentFixture; + let de: DebugElement; + let el: HTMLElement; + + beforeEach(async(() => { + // #docregion auto-detect + TestBed.configureTestingModule({ + declarations: [ BannerComponent ], + providers: [ + { provide: ComponentFixtureAutoDetect, useValue: true } + ] + }) + // #enddocregion auto-detect + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(BannerComponent); + comp = fixture.componentInstance; + de = fixture.debugElement.query(By.css('h1')); + el = de.nativeElement; + }); + + // #docregion auto-detect-tests + it('should display original title', () => { + // Hooray! No `fixture.detectChanges()` needed + expect(el.textContent).toContain(comp.title); + }); + + it('should still see original title after comp.title change', () => { + const oldTitle = comp.title; + comp.title = 'Test Title'; + // Displayed title is old because Angular didn't hear the change :( + expect(el.textContent).toContain(oldTitle); + }); + + it('should display updated title after detectChanges', () => { + comp.title = 'Test Title'; + fixture.detectChanges(); // detect changes explicitly + expect(el.textContent).toContain(comp.title); + }); + // #enddocregion auto-detect-tests +}); diff --git a/aio/content/examples/testing/ts/src/app/banner.component.html b/aio/content/examples/testing/ts/src/app/banner.component.html new file mode 100644 index 0000000000..6213adcb47 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/banner.component.html @@ -0,0 +1 @@ +

    {{title}}

    diff --git a/aio/content/examples/testing/ts/src/app/banner.component.spec.ts b/aio/content/examples/testing/ts/src/app/banner.component.spec.ts new file mode 100644 index 0000000000..a564c45c85 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/banner.component.spec.ts @@ -0,0 +1,53 @@ +// #docplaster +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { BannerComponent } from './banner.component'; + +describe('BannerComponent (templateUrl)', () => { + + let comp: BannerComponent; + let fixture: ComponentFixture; + let de: DebugElement; + let el: HTMLElement; + + // #docregion async-before-each + // async beforeEach + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ BannerComponent ], // declare the test component + }) + .compileComponents(); // compile template and css + })); + // #enddocregion async-before-each + + // #docregion sync-before-each + // synchronous beforeEach + beforeEach(() => { + fixture = TestBed.createComponent(BannerComponent); + + comp = fixture.componentInstance; // BannerComponent test instance + + // query for the title

    by CSS element selector + de = fixture.debugElement.query(By.css('h1')); + el = de.nativeElement; + }); + // #enddocregion sync-before-each + + it('no title in the DOM until manually call `detectChanges`', () => { + expect(el.textContent).toEqual(''); + }); + + it('should display original title', () => { + fixture.detectChanges(); + expect(el.textContent).toContain(comp.title); + }); + + it('should display a different test title', () => { + comp.title = 'Test Title'; + fixture.detectChanges(); + expect(el.textContent).toContain('Test Title'); + }); + +}); diff --git a/aio/content/examples/testing/ts/src/app/banner.component.ts b/aio/content/examples/testing/ts/src/app/banner.component.ts new file mode 100644 index 0000000000..333f37bbcd --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/banner.component.ts @@ -0,0 +1,13 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'app-banner', + templateUrl: './banner.component.html', + styleUrls: ['./banner.component.css'] +}) +export class BannerComponent { + title = 'Test Tour of Heroes'; +} + diff --git a/aio/content/examples/testing/ts/src/app/dashboard/dashboard-hero.component.css b/aio/content/examples/testing/ts/src/app/dashboard/dashboard-hero.component.css new file mode 100644 index 0000000000..eb54d181d8 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/dashboard/dashboard-hero.component.css @@ -0,0 +1,28 @@ +.hero { + padding: 20px; + position: relative; + text-align: center; + color: #eee; + max-height: 120px; + min-width: 120px; + background-color: #607D8B; + border-radius: 2px; +} + +.hero:hover { + background-color: #EEE; + cursor: pointer; + color: #607d8b; +} + +@media (max-width: 600px) { + .hero { + font-size: 10px; + max-height: 75px; } +} + +@media (max-width: 1024px) { + .hero { + min-width: 60px; + } +} diff --git a/aio/content/examples/testing/ts/src/app/dashboard/dashboard-hero.component.html b/aio/content/examples/testing/ts/src/app/dashboard/dashboard-hero.component.html new file mode 100644 index 0000000000..ff49bd17a5 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/dashboard/dashboard-hero.component.html @@ -0,0 +1,4 @@ + +
    + {{hero.name | uppercase}} +
    diff --git a/aio/content/examples/testing/ts/src/app/dashboard/dashboard-hero.component.spec.ts b/aio/content/examples/testing/ts/src/app/dashboard/dashboard-hero.component.spec.ts new file mode 100644 index 0000000000..40c01571e6 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/dashboard/dashboard-hero.component.spec.ts @@ -0,0 +1,124 @@ +import { async, ComponentFixture, TestBed +} from '@angular/core/testing'; + +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { addMatchers, click } from '../../testing'; + +import { Hero } from '../model/hero'; +import { DashboardHeroComponent } from './dashboard-hero.component'; + +beforeEach( addMatchers ); + +describe('DashboardHeroComponent when tested directly', () => { + + let comp: DashboardHeroComponent; + let expectedHero: Hero; + let fixture: ComponentFixture; + let heroEl: DebugElement; + + // #docregion setup, compile-components + // async beforeEach + beforeEach( async(() => { + TestBed.configureTestingModule({ + declarations: [ DashboardHeroComponent ], + }) + .compileComponents(); // compile template and css + })); + // #enddocregion compile-components + + // synchronous beforeEach + beforeEach(() => { + fixture = TestBed.createComponent(DashboardHeroComponent); + comp = fixture.componentInstance; + heroEl = fixture.debugElement.query(By.css('.hero')); // find hero element + + // pretend that it was wired to something that supplied a hero + expectedHero = new Hero(42, 'Test Name'); + comp.hero = expectedHero; + fixture.detectChanges(); // trigger initial data binding + }); + // #enddocregion setup + + // #docregion name-test + it('should display hero name', () => { + const expectedPipedName = expectedHero.name.toUpperCase(); + expect(heroEl.nativeElement.textContent).toContain(expectedPipedName); + }); + // #enddocregion name-test + + // #docregion click-test + it('should raise selected event when clicked', () => { + let selectedHero: Hero; + comp.selected.subscribe((hero: Hero) => selectedHero = hero); + + // #docregion trigger-event-handler + heroEl.triggerEventHandler('click', null); + // #enddocregion trigger-event-handler + expect(selectedHero).toBe(expectedHero); + }); + // #enddocregion click-test + + // #docregion click-test-2 + it('should raise selected event when clicked', () => { + let selectedHero: Hero; + comp.selected.subscribe((hero: Hero) => selectedHero = hero); + + click(heroEl); // triggerEventHandler helper + expect(selectedHero).toBe(expectedHero); + }); + // #enddocregion click-test-2 +}); + +////////////////// + +describe('DashboardHeroComponent when inside a test host', () => { + let testHost: TestHostComponent; + let fixture: ComponentFixture; + let heroEl: DebugElement; + + // #docregion test-host-setup + beforeEach( async(() => { + TestBed.configureTestingModule({ + declarations: [ DashboardHeroComponent, TestHostComponent ], // declare both + }).compileComponents(); + })); + + beforeEach(() => { + // create TestHostComponent instead of DashboardHeroComponent + fixture = TestBed.createComponent(TestHostComponent); + testHost = fixture.componentInstance; + heroEl = fixture.debugElement.query(By.css('.hero')); // find hero + fixture.detectChanges(); // trigger initial data binding + }); + // #enddocregion test-host-setup + + // #docregion test-host-tests + it('should display hero name', () => { + const expectedPipedName = testHost.hero.name.toUpperCase(); + expect(heroEl.nativeElement.textContent).toContain(expectedPipedName); + }); + + it('should raise selected event when clicked', () => { + click(heroEl); + // selected hero should be the same data bound hero + expect(testHost.selectedHero).toBe(testHost.hero); + }); + // #enddocregion test-host-tests +}); + +////// Test Host Component ////// +import { Component } from '@angular/core'; + +// #docregion test-host +@Component({ + template: ` + ` +}) +class TestHostComponent { + hero = new Hero(42, 'Test Name'); + selectedHero: Hero; + onSelected(hero: Hero) { this.selectedHero = hero; } +} +// #enddocregion test-host diff --git a/aio/content/examples/testing/ts/src/app/dashboard/dashboard-hero.component.ts b/aio/content/examples/testing/ts/src/app/dashboard/dashboard-hero.component.ts new file mode 100644 index 0000000000..4a8c4974fd --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/dashboard/dashboard-hero.component.ts @@ -0,0 +1,18 @@ +// #docregion +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +import { Hero } from '../model'; + +// #docregion component +@Component({ + moduleId: module.id, + selector: 'dashboard-hero', + templateUrl: './dashboard-hero.component.html', + styleUrls: [ './dashboard-hero.component.css' ] +}) +export class DashboardHeroComponent { + @Input() hero: Hero; + @Output() selected = new EventEmitter(); + click() { this.selected.emit(this.hero); } +} +// #enddocregion component diff --git a/aio/content/examples/testing/ts/src/app/dashboard/dashboard.component.css b/aio/content/examples/testing/ts/src/app/dashboard/dashboard.component.css new file mode 100644 index 0000000000..98130b61c6 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/dashboard/dashboard.component.css @@ -0,0 +1,35 @@ +[class*='col-'] { + float: left; +} +*, *:after, *:before { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +h3 { + text-align: center; margin-bottom: 0; +} +[class*='col-'] { + padding-right: 20px; + padding-bottom: 20px; +} +[class*='col-']:last-of-type { + padding-right: 0; +} +.grid { + margin: 0; +} +.col-1-4 { + width: 25%; +} +.grid-pad { + padding: 10px 0; +} +.grid-pad > [class*='col-']:last-of-type { + padding-right: 20px; +} +@media (max-width: 1024px) { + .grid { + margin: 0; + } +} diff --git a/aio/content/examples/testing/ts/src/app/dashboard/dashboard.component.html b/aio/content/examples/testing/ts/src/app/dashboard/dashboard.component.html new file mode 100644 index 0000000000..b26e16b828 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/dashboard/dashboard.component.html @@ -0,0 +1,9 @@ +

    {{title}}

    + +
    + + + + +
    diff --git a/aio/content/examples/testing/ts/src/app/dashboard/dashboard.component.no-testbed.spec.ts b/aio/content/examples/testing/ts/src/app/dashboard/dashboard.component.no-testbed.spec.ts new file mode 100644 index 0000000000..125e5193b7 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/dashboard/dashboard.component.no-testbed.spec.ts @@ -0,0 +1,57 @@ +import { Router } from '@angular/router'; + +import { DashboardComponent } from './dashboard.component'; +import { Hero } from '../model'; + +import { addMatchers } from '../../testing'; +import { FakeHeroService } from '../model/testing'; + +class FakeRouter { + navigateByUrl(url: string) { return url; } +} + +describe('DashboardComponent: w/o Angular TestBed', () => { + let comp: DashboardComponent; + let heroService: FakeHeroService; + let router: Router; + + beforeEach(() => { + addMatchers(); + router = new FakeRouter() as any as Router; + heroService = new FakeHeroService(); + comp = new DashboardComponent(router, heroService); + }); + + it('should NOT have heroes before calling OnInit', () => { + expect(comp.heroes.length).toBe(0, + 'should not have heroes before OnInit'); + }); + + it('should NOT have heroes immediately after OnInit', () => { + comp.ngOnInit(); // ngOnInit -> getHeroes + expect(comp.heroes.length).toBe(0, + 'should not have heroes until service promise resolves'); + }); + + it('should HAVE heroes after HeroService gets them', (done: DoneFn) => { + comp.ngOnInit(); // ngOnInit -> getHeroes + heroService.lastPromise // the one from getHeroes + .then(() => { + // throw new Error('deliberate error'); // see it fail gracefully + expect(comp.heroes.length).toBeGreaterThan(0, + 'should have heroes after service promise resolves'); + }) + .then(done, done.fail); + }); + + it('should tell ROUTER to navigate by hero id', () => { + const hero = new Hero(42, 'Abbracadabra'); + const spy = spyOn(router, 'navigateByUrl'); + + comp.gotoDetail(hero); + + const navArgs = spy.calls.mostRecent().args[0]; + expect(navArgs).toBe('/heroes/42', 'should nav to HeroDetail for Hero 42'); + }); + +}); diff --git a/aio/content/examples/testing/ts/src/app/dashboard/dashboard.component.spec.ts b/aio/content/examples/testing/ts/src/app/dashboard/dashboard.component.spec.ts new file mode 100644 index 0000000000..0b0f9e213a --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/dashboard/dashboard.component.spec.ts @@ -0,0 +1,147 @@ +// #docplaster +import { async, inject, ComponentFixture, TestBed +} from '@angular/core/testing'; + +import { addMatchers, click } from '../../testing'; +import { HeroService } from '../model'; +import { FakeHeroService } from '../model/testing'; + +import { By } from '@angular/platform-browser'; +import { Router } from '@angular/router'; + +import { DashboardComponent } from './dashboard.component'; +import { DashboardModule } from './dashboard.module'; + +// #docregion router-stub +class RouterStub { + navigateByUrl(url: string) { return url; } +} +// #enddocregion router-stub + +beforeEach ( addMatchers ); + +let comp: DashboardComponent; +let fixture: ComponentFixture; + +//////// Deep //////////////// + +describe('DashboardComponent (deep)', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ DashboardModule ] + }); + }); + + compileAndCreate(); + + tests(clickForDeep); + + function clickForDeep() { + // get first
    DebugElement + const heroEl = fixture.debugElement.query(By.css('.hero')); + click(heroEl); + } +}); + +//////// Shallow //////////////// + +import { NO_ERRORS_SCHEMA } from '@angular/core'; + +describe('DashboardComponent (shallow)', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ DashboardComponent ], + schemas: [NO_ERRORS_SCHEMA] + }); + }); + + compileAndCreate(); + + tests(clickForShallow); + + function clickForShallow() { + // get first DebugElement + const heroEl = fixture.debugElement.query(By.css('dashboard-hero')); + heroEl.triggerEventHandler('selected', comp.heroes[0]); + } +}); + +/** Add TestBed providers, compile, and create DashboardComponent */ +function compileAndCreate() { + // #docregion compile-and-create-body + beforeEach( async(() => { + TestBed.configureTestingModule({ + providers: [ + { provide: HeroService, useClass: FakeHeroService }, + { provide: Router, useClass: RouterStub } + ] + }) + .compileComponents().then(() => { + fixture = TestBed.createComponent(DashboardComponent); + comp = fixture.componentInstance; + }); + // #enddocregion compile-and-create-body + })); +} + +/** + * The (almost) same tests for both. + * Only change: the way that the first hero is clicked + */ +function tests(heroClick: Function) { + + it('should NOT have heroes before ngOnInit', () => { + expect(comp.heroes.length).toBe(0, + 'should not have heroes before ngOnInit'); + }); + + it('should NOT have heroes immediately after ngOnInit', () => { + fixture.detectChanges(); // runs initial lifecycle hooks + + expect(comp.heroes.length).toBe(0, + 'should not have heroes until service promise resolves'); + }); + + describe('after get dashboard heroes', () => { + + // Trigger component so it gets heroes and binds to them + beforeEach( async(() => { + fixture.detectChanges(); // runs ngOnInit -> getHeroes + fixture.whenStable() // No need for the `lastPromise` hack! + .then(() => fixture.detectChanges()); // bind to heroes + })); + + it('should HAVE heroes', () => { + expect(comp.heroes.length).toBeGreaterThan(0, + 'should have heroes after service promise resolves'); + }); + + it('should DISPLAY heroes', () => { + // Find and examine the displayed heroes + // Look for them in the DOM by css class + const heroes = fixture.debugElement.queryAll(By.css('dashboard-hero')); + expect(heroes.length).toBe(4, 'should display 4 heroes'); + }); + + // #docregion navigate-test, inject + it('should tell ROUTER to navigate when hero clicked', + inject([Router], (router: Router) => { // ... + // #enddocregion inject + + const spy = spyOn(router, 'navigateByUrl'); + + heroClick(); // trigger click on first inner
    + + // args passed to router.navigateByUrl() + const navArgs = spy.calls.first().args[0]; + + // expecting to navigate to id of the component's first hero + const id = comp.heroes[0].id; + expect(navArgs).toBe('/heroes/' + id, + 'should nav to HeroDetail for first hero'); + // #docregion inject + })); + // #enddocregion navigate-test, inject + }); +} + diff --git a/aio/content/examples/testing/ts/src/app/dashboard/dashboard.component.ts b/aio/content/examples/testing/ts/src/app/dashboard/dashboard.component.ts new file mode 100644 index 0000000000..40581094f6 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/dashboard/dashboard.component.ts @@ -0,0 +1,41 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +import { Hero, HeroService } from '../model'; + +@Component({ + moduleId: module.id, + selector: 'app-dashboard', + templateUrl: './dashboard.component.html', + styleUrls: [ './dashboard.component.css' ] +}) +export class DashboardComponent implements OnInit { + + heroes: Hero[] = []; + + // #docregion ctor + constructor( + private router: Router, + private heroService: HeroService) { + } + // #enddocregion ctor + + ngOnInit() { + this.heroService.getHeroes() + .then(heroes => this.heroes = heroes.slice(1, 5)); + } + + // #docregion goto-detail + gotoDetail(hero: Hero) { + let url = `/heroes/${hero.id}`; + this.router.navigateByUrl(url); + } + // #enddocregion goto-detail + + get title() { + let cnt = this.heroes.length; + return cnt === 0 ? 'No Heroes' : + cnt === 1 ? 'Top Hero' : `Top ${cnt} Heroes`; + } +} diff --git a/aio/content/examples/testing/ts/src/app/dashboard/dashboard.module.ts b/aio/content/examples/testing/ts/src/app/dashboard/dashboard.module.ts new file mode 100644 index 0000000000..8a70c851a0 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/dashboard/dashboard.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { SharedModule } from '../shared/shared.module'; + +import { DashboardComponent } from './dashboard.component'; +import { DashboardHeroComponent } from './dashboard-hero.component'; + +const routes: Routes = [ + { path: 'dashboard', component: DashboardComponent }, +]; + +@NgModule({ + imports: [ + SharedModule, + RouterModule.forChild(routes) + ], + declarations: [ DashboardComponent, DashboardHeroComponent ] +}) +export class DashboardModule { } diff --git a/aio/content/examples/testing/ts/src/app/hero/hero-detail.component.css b/aio/content/examples/testing/ts/src/app/hero/hero-detail.component.css new file mode 100644 index 0000000000..f6139ba274 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/hero/hero-detail.component.css @@ -0,0 +1,29 @@ +label { + display: inline-block; + width: 3em; + margin: .5em 0; + color: #607D8B; + font-weight: bold; +} +input { + height: 2em; + font-size: 1em; + padding-left: .4em; +} +button { + margin-top: 20px; + font-family: Arial; + background-color: #eee; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; cursor: hand; +} +button:hover { + background-color: #cfd8dc; +} +button:disabled { + background-color: #eee; + color: #ccc; + cursor: auto; +} diff --git a/aio/content/examples/testing/ts/src/app/hero/hero-detail.component.html b/aio/content/examples/testing/ts/src/app/hero/hero-detail.component.html new file mode 100644 index 0000000000..7e86a668f6 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/hero/hero-detail.component.html @@ -0,0 +1,12 @@ + +
    +

    {{hero.name | titlecase}} Details

    +
    + {{hero.id}}
    +
    + + +
    + + +
    diff --git a/aio/content/examples/testing/ts/src/app/hero/hero-detail.component.no-testbed.spec.ts b/aio/content/examples/testing/ts/src/app/hero/hero-detail.component.no-testbed.spec.ts new file mode 100644 index 0000000000..a6c1af98d7 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/hero/hero-detail.component.no-testbed.spec.ts @@ -0,0 +1,58 @@ +import { HeroDetailComponent } from './hero-detail.component'; +import { Hero } from '../model'; + +import { ActivatedRouteStub } from '../../testing'; + +////////// Tests //////////////////// + +describe('HeroDetailComponent - no TestBed', () => { + let activatedRoute: ActivatedRouteStub; + let comp: HeroDetailComponent; + let expectedHero: Hero; + let hds: any; + let router: any; + + beforeEach( done => { + expectedHero = new Hero(42, 'Bubba'); + activatedRoute = new ActivatedRouteStub(); + activatedRoute.testParams = { id: expectedHero.id }; + + router = jasmine.createSpyObj('router', ['navigate']); + + hds = jasmine.createSpyObj('HeroDetailService', ['getHero', 'saveHero']); + hds.getHero.and.returnValue(Promise.resolve(expectedHero)); + hds.saveHero.and.returnValue(Promise.resolve(expectedHero)); + + comp = new HeroDetailComponent(hds, activatedRoute, router); + comp.ngOnInit(); + + // OnInit calls HDS.getHero; wait for it to get the fake hero + hds.getHero.calls.first().returnValue.then(done); + }); + + it('should expose the hero retrieved from the service', () => { + expect(comp.hero).toBe(expectedHero); + }); + + it('should navigate when click cancel', () => { + comp.cancel(); + expect(router.navigate.calls.any()).toBe(true, 'router.navigate called'); + }); + + it('should save when click save', () => { + comp.save(); + expect(hds.saveHero.calls.any()).toBe(true, 'HeroDetailService.save called'); + expect(router.navigate.calls.any()).toBe(false, 'router.navigate not called yet'); + }); + + it('should navigate when click save resolves', done => { + comp.save(); + // waits for async save to complete before navigating + hds.saveHero.calls.first().returnValue + .then(() => { + expect(router.navigate.calls.any()).toBe(true, 'router.navigate called'); + done(); + }); + }); + +}); diff --git a/aio/content/examples/testing/ts/src/app/hero/hero-detail.component.spec.ts b/aio/content/examples/testing/ts/src/app/hero/hero-detail.component.spec.ts new file mode 100644 index 0000000000..4e1ee034d7 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/hero/hero-detail.component.spec.ts @@ -0,0 +1,364 @@ +// #docplaster +import { + async, ComponentFixture, fakeAsync, inject, TestBed, tick +} from '@angular/core/testing'; + +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { + ActivatedRoute, ActivatedRouteStub, click, newEvent, Router, RouterStub +} from '../../testing'; + +import { Hero } from '../model'; +import { HeroDetailComponent } from './hero-detail.component'; +import { HeroDetailService } from './hero-detail.service'; +import { HeroModule } from './hero.module'; + +////// Testing Vars ////// +let activatedRoute: ActivatedRouteStub; +let comp: HeroDetailComponent; +let fixture: ComponentFixture; +let page: Page; + +////// Tests ////// +describe('HeroDetailComponent', () => { + beforeEach(() => { + activatedRoute = new ActivatedRouteStub(); + }); + describe('with HeroModule setup', heroModuleSetup); + describe('when override its provided HeroDetailService', overrideSetup); + describe('with FormsModule setup', formsModuleSetup); + describe('with SharedModule setup', sharedModuleSetup); +}); + +//////////////////// +function overrideSetup() { + // #docregion hds-spy + class HeroDetailServiceSpy { + testHero = new Hero(42, 'Test Hero'); + + getHero = jasmine.createSpy('getHero').and.callFake( + () => Promise + .resolve(true) + .then(() => Object.assign({}, this.testHero)) + ); + + saveHero = jasmine.createSpy('saveHero').and.callFake( + (hero: Hero) => Promise + .resolve(true) + .then(() => Object.assign(this.testHero, hero)) + ); + } + // #enddocregion hds-spy + + // the `id` value is irrelevant because ignored by service stub + beforeEach(() => activatedRoute.testParams = { id: 99999 } ); + + // #docregion setup-override + beforeEach( async(() => { + TestBed.configureTestingModule({ + imports: [ HeroModule ], + providers: [ + { provide: ActivatedRoute, useValue: activatedRoute }, + { provide: Router, useClass: RouterStub}, + // #enddocregion setup-override + // HeroDetailService at this level is IRRELEVANT! + { provide: HeroDetailService, useValue: {} } + // #docregion setup-override + ] + }) + + // Override component's own provider + // #docregion override-component-method + .overrideComponent(HeroDetailComponent, { + set: { + providers: [ + { provide: HeroDetailService, useClass: HeroDetailServiceSpy } + ] + } + }) + // #enddocregion override-component-method + + .compileComponents(); + })); + // #enddocregion setup-override + + // #docregion override-tests + let hdsSpy: HeroDetailServiceSpy; + + beforeEach( async(() => { + createComponent(); + // get the component's injected HeroDetailServiceSpy + hdsSpy = fixture.debugElement.injector.get(HeroDetailService); + })); + + it('should have called `getHero`', () => { + expect(hdsSpy.getHero.calls.count()).toBe(1, 'getHero called once'); + }); + + it('should display stub hero\'s name', () => { + expect(page.nameDisplay.textContent).toBe(hdsSpy.testHero.name); + }); + + it('should save stub hero change', fakeAsync(() => { + const origName = hdsSpy.testHero.name; + const newName = 'New Name'; + + page.nameInput.value = newName; + page.nameInput.dispatchEvent(newEvent('input')); // tell Angular + + expect(comp.hero.name).toBe(newName, 'component hero has new name'); + expect(hdsSpy.testHero.name).toBe(origName, 'service hero unchanged before save'); + + click(page.saveBtn); + expect(hdsSpy.saveHero.calls.count()).toBe(1, 'saveHero called once'); + + tick(); // wait for async save to complete + expect(hdsSpy.testHero.name).toBe(newName, 'service hero has new name after save'); + expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called'); + })); + // #enddocregion override-tests + + it('fixture injected service is not the component injected service', + inject([HeroDetailService], (service: HeroDetailService) => { + + expect(service).toEqual({}, 'service injected from fixture'); + expect(hdsSpy).toBeTruthy('service injected into component'); + })); +} + +//////////////////// +import { HEROES, FakeHeroService } from '../model/testing'; +import { HeroService } from '../model'; + +const firstHero = HEROES[0]; + +function heroModuleSetup() { + // #docregion setup-hero-module + beforeEach( async(() => { + TestBed.configureTestingModule({ + imports: [ HeroModule ], + // #enddocregion setup-hero-module + // declarations: [ HeroDetailComponent ], // NO! DOUBLE DECLARATION + // #docregion setup-hero-module + providers: [ + { provide: ActivatedRoute, useValue: activatedRoute }, + { provide: HeroService, useClass: FakeHeroService }, + { provide: Router, useClass: RouterStub}, + ] + }) + .compileComponents(); + })); + // #enddocregion setup-hero-module + + // #docregion route-good-id + describe('when navigate to existing hero', () => { + let expectedHero: Hero; + + beforeEach( async(() => { + expectedHero = firstHero; + activatedRoute.testParams = { id: expectedHero.id }; + createComponent(); + })); + + // #docregion selected-tests + it('should display that hero\'s name', () => { + expect(page.nameDisplay.textContent).toBe(expectedHero.name); + }); + // #enddocregion route-good-id + + it('should navigate when click cancel', () => { + click(page.cancelBtn); + expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called'); + }); + + it('should save when click save but not navigate immediately', () => { + // Get service injected into component and spy on its`saveHero` method. + // It delegates to fake `HeroService.updateHero` which delivers a safe test result. + const hds = fixture.debugElement.injector.get(HeroDetailService); + const saveSpy = spyOn(hds, 'saveHero').and.callThrough(); + + click(page.saveBtn); + expect(saveSpy.calls.any()).toBe(true, 'HeroDetailService.save called'); + expect(page.navSpy.calls.any()).toBe(false, 'router.navigate not called'); + }); + + it('should navigate when click save and save resolves', fakeAsync(() => { + click(page.saveBtn); + tick(); // wait for async save to complete + expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called'); + })); + + // #docregion title-case-pipe + it('should convert hero name to Title Case', () => { + const inputName = 'quick BROWN fox'; + const titleCaseName = 'Quick Brown Fox'; + + // simulate user entering new name into the input box + page.nameInput.value = inputName; + + // dispatch a DOM event so that Angular learns of input value change. + page.nameInput.dispatchEvent(newEvent('input')); + + // Tell Angular to update the output span through the title pipe + fixture.detectChanges(); + + expect(page.nameDisplay.textContent).toBe(titleCaseName); + }); + // #enddocregion title-case-pipe + // #enddocregion selected-tests + // #docregion route-good-id + }); + // #enddocregion route-good-id + + // #docregion route-no-id + describe('when navigate with no hero id', () => { + beforeEach( async( createComponent )); + + it('should have hero.id === 0', () => { + expect(comp.hero.id).toBe(0); + }); + + it('should display empty hero name', () => { + expect(page.nameDisplay.textContent).toBe(''); + }); + }); + // #enddocregion route-no-id + + // #docregion route-bad-id + describe('when navigate to non-existant hero id', () => { + beforeEach( async(() => { + activatedRoute.testParams = { id: 99999 }; + createComponent(); + })); + + it('should try to navigate back to hero list', () => { + expect(page.gotoSpy.calls.any()).toBe(true, 'comp.gotoList called'); + expect(page.navSpy.calls.any()).toBe(true, 'router.navigate called'); + }); + }); + // #enddocregion route-bad-id + + // Why we must use `fixture.debugElement.injector` in `Page()` + it('cannot use `inject` to get component\'s provided HeroDetailService', () => { + let service: HeroDetailService; + fixture = TestBed.createComponent(HeroDetailComponent); + expect( + // Throws because `inject` only has access to TestBed's injector + // which is an ancestor of the component's injector + inject([HeroDetailService], (hds: HeroDetailService) => service = hds ) + ) + .toThrowError(/No provider for HeroDetailService/); + + // get `HeroDetailService` with component's own injector + service = fixture.debugElement.injector.get(HeroDetailService); + expect(service).toBeDefined('debugElement.injector'); + }); +} + +///////////////////// +import { FormsModule } from '@angular/forms'; +import { TitleCasePipe } from '../shared/title-case.pipe'; + +function formsModuleSetup() { + // #docregion setup-forms-module + beforeEach( async(() => { + TestBed.configureTestingModule({ + imports: [ FormsModule ], + declarations: [ HeroDetailComponent, TitleCasePipe ], + providers: [ + { provide: ActivatedRoute, useValue: activatedRoute }, + { provide: HeroService, useClass: FakeHeroService }, + { provide: Router, useClass: RouterStub}, + ] + }) + .compileComponents(); + })); + // #enddocregion setup-forms-module + + it('should display 1st hero\'s name', fakeAsync(() => { + const expectedHero = firstHero; + activatedRoute.testParams = { id: expectedHero.id }; + createComponent().then(() => { + expect(page.nameDisplay.textContent).toBe(expectedHero.name); + }); + })); +} + +/////////////////////// +import { SharedModule } from '../shared/shared.module'; + +function sharedModuleSetup() { + // #docregion setup-shared-module + beforeEach( async(() => { + TestBed.configureTestingModule({ + imports: [ SharedModule ], + declarations: [ HeroDetailComponent ], + providers: [ + { provide: ActivatedRoute, useValue: activatedRoute }, + { provide: HeroService, useClass: FakeHeroService }, + { provide: Router, useClass: RouterStub}, + ] + }) + .compileComponents(); + })); + // #enddocregion setup-shared-module + + it('should display 1st hero\'s name', fakeAsync(() => { + const expectedHero = firstHero; + activatedRoute.testParams = { id: expectedHero.id }; + createComponent().then(() => { + expect(page.nameDisplay.textContent).toBe(expectedHero.name); + }); + })); +} + +/////////// Helpers ///// + +// #docregion create-component +/** Create the HeroDetailComponent, initialize it, set test variables */ +function createComponent() { + fixture = TestBed.createComponent(HeroDetailComponent); + comp = fixture.componentInstance; + page = new Page(); + + // 1st change detection triggers ngOnInit which gets a hero + fixture.detectChanges(); + return fixture.whenStable().then(() => { + // 2nd change detection displays the async-fetched hero + fixture.detectChanges(); + page.addPageElements(); + }); +} +// #enddocregion create-component + +// #docregion page +class Page { + gotoSpy: jasmine.Spy; + navSpy: jasmine.Spy; + + saveBtn: DebugElement; + cancelBtn: DebugElement; + nameDisplay: HTMLElement; + nameInput: HTMLInputElement; + + constructor() { + const router = TestBed.get(Router); // get router from root injector + this.gotoSpy = spyOn(comp, 'gotoList').and.callThrough(); + this.navSpy = spyOn(router, 'navigate'); + } + + /** Add page elements after hero arrives */ + addPageElements() { + if (comp.hero) { + // have a hero so these elements are now in the DOM + const buttons = fixture.debugElement.queryAll(By.css('button')); + this.saveBtn = buttons[0]; + this.cancelBtn = buttons[1]; + this.nameDisplay = fixture.debugElement.query(By.css('span')).nativeElement; + this.nameInput = fixture.debugElement.query(By.css('input')).nativeElement; + } + } +} +// #enddocregion page diff --git a/aio/content/examples/testing/ts/src/app/hero/hero-detail.component.ts b/aio/content/examples/testing/ts/src/app/hero/hero-detail.component.ts new file mode 100644 index 0000000000..763c921650 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/hero/hero-detail.component.ts @@ -0,0 +1,64 @@ +/* tslint:disable:member-ordering */ +// #docplaster +import { Component, Input, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import 'rxjs/add/operator/map'; + +import { Hero } from '../model'; +import { HeroDetailService } from './hero-detail.service'; + +// #docregion prototype +@Component({ + moduleId: module.id, + selector: 'app-hero-detail', + templateUrl: './hero-detail.component.html', + styleUrls: ['./hero-detail.component.css' ], + providers: [ HeroDetailService ] +}) +export class HeroDetailComponent implements OnInit { + // #docregion ctor + constructor( + private heroDetailService: HeroDetailService, + private route: ActivatedRoute, + private router: Router) { + } + // #enddocregion ctor +// #enddocregion prototype + + @Input() hero: Hero; + + // #docregion ng-on-init + ngOnInit(): void { + // get hero when `id` param changes + this.route.params.subscribe(p => this.getHero(p && p['id'])); + } + // #enddocregion ng-on-init + + private getHero(id: string): void { + // when no id or id===0, create new hero + if (!id) { + this.hero = new Hero(); + return; + } + + this.heroDetailService.getHero(id).then(hero => { + if (hero) { + this.hero = hero; + } else { + this.gotoList(); // id not found; navigate to list + } + }); + } + + save(): void { + this.heroDetailService.saveHero(this.hero).then(() => this.gotoList()); + } + + cancel() { this.gotoList(); } + + gotoList() { + this.router.navigate(['../'], {relativeTo: this.route}); + } +// #docregion prototype +} +// #enddocregion prototype diff --git a/aio/content/examples/testing/ts/src/app/hero/hero-detail.service.ts b/aio/content/examples/testing/ts/src/app/hero/hero-detail.service.ts new file mode 100644 index 0000000000..6239ae5b80 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/hero/hero-detail.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; + +import { Hero, HeroService } from '../model'; + +// #docregion prototype +@Injectable() +export class HeroDetailService { + constructor(private heroService: HeroService) { } +// #enddocregion prototype + + // Returns a clone which caller may modify safely + getHero(id: number | string): Promise { + if (typeof id === 'string') { + id = parseInt(id as string, 10); + } + return this.heroService.getHero(id).then(hero => { + return hero ? Object.assign({}, hero) : null; // clone or null + }); + } + + saveHero(hero: Hero) { + return this.heroService.updateHero(hero); + } +// #docregion prototype +} +// #enddocregion prototype diff --git a/aio/content/examples/testing/ts/src/app/hero/hero-list.component.css b/aio/content/examples/testing/ts/src/app/hero/hero-list.component.css new file mode 100644 index 0000000000..d939ab565d --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/hero/hero-list.component.css @@ -0,0 +1,59 @@ +.selected { + background-color: #CFD8DC !important; + color: white; +} +.heroes { + margin: 0 0 2em 0; + list-style-type: none; + padding: 0; + width: 10em; +} +.heroes li { + cursor: pointer; + position: relative; + left: 0; + background-color: #EEE; + margin: .5em; + padding: .3em 0; + height: 1.6em; + border-radius: 4px; +} +.heroes li:hover { + color: #607D8B; + background-color: #DDD; + left: .1em; +} +.heroes li.selected:hover { + background-color: #BBD8DC !important; + color: white; +} +.heroes .text { + position: relative; + top: -3px; +} +.heroes .badge { + display: inline-block; + font-size: small; + color: white; + padding: 0.8em 0.7em 0 0.7em; + background-color: #607D8B; + line-height: 1em; + position: relative; + left: -1px; + top: -4px; + height: 1.8em; + margin-right: .8em; + border-radius: 4px 0 0 4px; +} +button { + font-family: Arial; + background-color: #eee; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; + cursor: hand; +} +button:hover { + background-color: #cfd8dc; +} diff --git a/aio/content/examples/testing/ts/src/app/hero/hero-list.component.html b/aio/content/examples/testing/ts/src/app/hero/hero-list.component.html new file mode 100644 index 0000000000..cd37301fd6 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/hero/hero-list.component.html @@ -0,0 +1,8 @@ +

    My Heroes

    +
      +
    • + {{hero.id}} {{hero.name}} +
    • +
    diff --git a/aio/content/examples/testing/ts/src/app/hero/hero-list.component.spec.ts b/aio/content/examples/testing/ts/src/app/hero/hero-list.component.spec.ts new file mode 100644 index 0000000000..dbf9d37d71 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/hero/hero-list.component.spec.ts @@ -0,0 +1,139 @@ +import { async, ComponentFixture, fakeAsync, TestBed, tick +} from '@angular/core/testing'; + +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { addMatchers, newEvent, Router, RouterStub +} from '../../testing'; + +import { HEROES, FakeHeroService } from '../model/testing'; + +import { HeroModule } from './hero.module'; +import { HeroListComponent } from './hero-list.component'; +import { HighlightDirective } from '../shared/highlight.directive'; +import { HeroService } from '../model'; + +let comp: HeroListComponent; +let fixture: ComponentFixture; +let page: Page; + +/////// Tests ////// + +describe('HeroListComponent', () => { + + beforeEach( async(() => { + addMatchers(); + TestBed.configureTestingModule({ + imports: [HeroModule], + providers: [ + { provide: HeroService, useClass: FakeHeroService }, + { provide: Router, useClass: RouterStub} + ] + }) + .compileComponents() + .then(createComponent); + })); + + it('should display heroes', () => { + expect(page.heroRows.length).toBeGreaterThan(0); + }); + + it('1st hero should match 1st test hero', () => { + const expectedHero = HEROES[0]; + const actualHero = page.heroRows[0].textContent; + expect(actualHero).toContain(expectedHero.id, 'hero.id'); + expect(actualHero).toContain(expectedHero.name, 'hero.name'); + }); + + it('should select hero on click', fakeAsync(() => { + const expectedHero = HEROES[1]; + const li = page.heroRows[1]; + li.dispatchEvent(newEvent('click')); + tick(); + // `.toEqual` because selectedHero is clone of expectedHero; see FakeHeroService + expect(comp.selectedHero).toEqual(expectedHero); + })); + + it('should navigate to selected hero detail on click', fakeAsync(() => { + const expectedHero = HEROES[1]; + const li = page.heroRows[1]; + li.dispatchEvent(newEvent('click')); + tick(); + + // should have navigated + expect(page.navSpy.calls.any()).toBe(true, 'navigate called'); + + // composed hero detail will be URL like 'heroes/42' + // expect link array with the route path and hero id + // first argument to router.navigate is link array + const navArgs = page.navSpy.calls.first().args[0]; + expect(navArgs[0]).toContain('heroes', 'nav to heroes detail URL'); + expect(navArgs[1]).toBe(expectedHero.id, 'expected hero.id'); + + })); + + it('should find `HighlightDirective` with `By.directive', () => { + // #docregion by + // Can find DebugElement either by css selector or by directive + const h2 = fixture.debugElement.query(By.css('h2')); + const directive = fixture.debugElement.query(By.directive(HighlightDirective)); + // #enddocregion by + expect(h2).toBe(directive); + }); + + it('should color header with `HighlightDirective`', () => { + const h2 = page.highlightDe.nativeElement as HTMLElement; + const bgColor = h2.style.backgroundColor; + + // different browsers report color values differently + const isExpectedColor = bgColor === 'gold' || bgColor === 'rgb(255, 215, 0)'; + expect(isExpectedColor).toBe(true, 'backgroundColor'); + }); + + it('the `HighlightDirective` is among the element\'s providers', () => { + expect(page.highlightDe.providerTokens).toContain(HighlightDirective, 'HighlightDirective'); + }); +}); + +/////////// Helpers ///// + +/** Create the component and set the `page` test variables */ +function createComponent() { + fixture = TestBed.createComponent(HeroListComponent); + comp = fixture.componentInstance; + + // change detection triggers ngOnInit which gets a hero + fixture.detectChanges(); + + return fixture.whenStable().then(() => { + // got the heroes and updated component + // change detection updates the view + fixture.detectChanges(); + page = new Page(); + }); +} + +class Page { + /** Hero line elements */ + heroRows: HTMLLIElement[]; + + /** Highlighted element */ + highlightDe: DebugElement; + + /** Spy on router navigate method */ + navSpy: jasmine.Spy; + + constructor() { + this.heroRows = fixture.debugElement.queryAll(By.css('li')).map(de => de.nativeElement); + + // Find the first element with an attached HighlightDirective + this.highlightDe = fixture.debugElement.query(By.directive(HighlightDirective)); + + // Get the component's injected router and spy on it + const router = fixture.debugElement.injector.get(Router); + this.navSpy = spyOn(router, 'navigate'); + }; +} + + diff --git a/aio/content/examples/testing/ts/src/app/hero/hero-list.component.ts b/aio/content/examples/testing/ts/src/app/hero/hero-list.component.ts new file mode 100644 index 0000000000..501e0000b6 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/hero/hero-list.component.ts @@ -0,0 +1,28 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +import { Hero, HeroService } from '../model'; + +@Component({ + moduleId: module.id, + selector: 'app-heroes', + templateUrl: './hero-list.component.html', + styleUrls: [ './hero-list.component.css' ] +}) +export class HeroListComponent implements OnInit { + heroes: Promise; + selectedHero: Hero; + + constructor( + private router: Router, + private heroService: HeroService) { } + + ngOnInit() { + this.heroes = this.heroService.getHeroes(); + } + + onSelect(hero: Hero) { + this.selectedHero = hero; + this.router.navigate(['../heroes', this.selectedHero.id ]); + } +} diff --git a/aio/content/examples/testing/ts/src/app/hero/hero-routing.module.ts b/aio/content/examples/testing/ts/src/app/hero/hero-routing.module.ts new file mode 100644 index 0000000000..59ec14474c --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/hero/hero-routing.module.ts @@ -0,0 +1,18 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { HeroListComponent } from './hero-list.component'; +import { HeroDetailComponent } from './hero-detail.component'; + +const routes: Routes = [ + { path: '', component: HeroListComponent }, + { path: ':id', component: HeroDetailComponent } +]; + +export const routedComponents = [HeroDetailComponent, HeroListComponent]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class HeroRoutingModule {} diff --git a/aio/content/examples/testing/ts/src/app/hero/hero.module.ts b/aio/content/examples/testing/ts/src/app/hero/hero.module.ts new file mode 100644 index 0000000000..dfe33cc199 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/hero/hero.module.ts @@ -0,0 +1,9 @@ +import { NgModule } from '@angular/core'; +import { SharedModule } from '../shared/shared.module'; +import { routedComponents, HeroRoutingModule } from './hero-routing.module'; + +@NgModule({ + imports: [ SharedModule, HeroRoutingModule ], + declarations: [ routedComponents ] +}) +export class HeroModule { } diff --git a/aio/content/examples/testing/ts/src/app/model/hero.service.ts b/aio/content/examples/testing/ts/src/app/model/hero.service.ts new file mode 100644 index 0000000000..667d47312b --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/model/hero.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; + +import { Hero } from './hero'; +import { HEROES } from './test-heroes'; + +@Injectable() +/** Dummy HeroService. Pretend it makes real http requests */ +export class HeroService { + getHeroes() { + return Promise.resolve(HEROES); + } + + getHero(id: number | string): Promise { + if (typeof id === 'string') { + id = parseInt(id as string, 10); + } + return this.getHeroes().then( + heroes => heroes.find(hero => hero.id === id) + ); + } + + updateHero(hero: Hero): Promise { + return this.getHero(hero.id).then(h => { + if (!h) { + throw new Error(`Hero ${hero.id} not found`); + } + return Object.assign(h, hero); + }); + } +} diff --git a/aio/content/examples/testing/ts/src/app/model/hero.spec.ts b/aio/content/examples/testing/ts/src/app/model/hero.spec.ts new file mode 100644 index 0000000000..e8acf913f2 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/model/hero.spec.ts @@ -0,0 +1,20 @@ +// #docregion +import { Hero } from './hero'; + +describe('Hero', () => { + it('has name', () => { + const hero = new Hero(1, 'Super Cat'); + expect(hero.name).toBe('Super Cat'); + }); + + it('has id', () => { + const hero = new Hero(1, 'Super Cat'); + expect(hero.id).toBe(1); + }); + + it('can clone itself', () => { + const hero = new Hero(1, 'Super Cat'); + const clone = hero.clone(); + expect(hero).toEqual(clone); + }); +}); diff --git a/aio/content/examples/testing/ts/src/app/model/hero.ts b/aio/content/examples/testing/ts/src/app/model/hero.ts new file mode 100644 index 0000000000..6a98f0dfdc --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/model/hero.ts @@ -0,0 +1,4 @@ +export class Hero { + constructor(public id = 0, public name = '') { } + clone() { return new Hero(this.id, this.name); } +} diff --git a/aio/content/examples/testing/ts/src/app/model/http-hero.service.spec.ts b/aio/content/examples/testing/ts/src/app/model/http-hero.service.spec.ts new file mode 100644 index 0000000000..c16b421274 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/model/http-hero.service.spec.ts @@ -0,0 +1,127 @@ +import { + async, inject, TestBed +} from '@angular/core/testing'; + +import { + MockBackend, + MockConnection +} from '@angular/http/testing'; + +import { + HttpModule, Http, XHRBackend, Response, ResponseOptions +} from '@angular/http'; + +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/observable/of'; + +import 'rxjs/add/operator/catch'; +import 'rxjs/add/operator/do'; +import 'rxjs/add/operator/toPromise'; + +import { Hero } from './hero'; +import { HttpHeroService as HeroService } from './http-hero.service'; + +const makeHeroData = () => [ + { id: 1, name: 'Windstorm' }, + { id: 2, name: 'Bombasto' }, + { id: 3, name: 'Magneta' }, + { id: 4, name: 'Tornado' } +] as Hero[]; + +//////// Tests ///////////// +describe('Http-HeroService (mockBackend)', () => { + + beforeEach( async(() => { + TestBed.configureTestingModule({ + imports: [ HttpModule ], + providers: [ + HeroService, + { provide: XHRBackend, useClass: MockBackend } + ] + }) + .compileComponents(); + })); + + it('can instantiate service when inject service', + inject([HeroService], (service: HeroService) => { + expect(service instanceof HeroService).toBe(true); + })); + + + + it('can instantiate service with "new"', inject([Http], (http: Http) => { + expect(http).not.toBeNull('http should be provided'); + let service = new HeroService(http); + expect(service instanceof HeroService).toBe(true, 'new service should be ok'); + })); + + + it('can provide the mockBackend as XHRBackend', + inject([XHRBackend], (backend: MockBackend) => { + expect(backend).not.toBeNull('backend should be provided'); + })); + + describe('when getHeroes', () => { + let backend: MockBackend; + let service: HeroService; + let fakeHeroes: Hero[]; + let response: Response; + + beforeEach(inject([Http, XHRBackend], (http: Http, be: MockBackend) => { + backend = be; + service = new HeroService(http); + fakeHeroes = makeHeroData(); + let options = new ResponseOptions({status: 200, body: {data: fakeHeroes}}); + response = new Response(options); + })); + + it('should have expected fake heroes (then)', async(inject([], () => { + backend.connections.subscribe((c: MockConnection) => c.mockRespond(response)); + + service.getHeroes().toPromise() + // .then(() => Promise.reject('deliberate')) + .then(heroes => { + expect(heroes.length).toBe(fakeHeroes.length, + 'should have expected no. of heroes'); + }); + }))); + + it('should have expected fake heroes (Observable.do)', async(inject([], () => { + backend.connections.subscribe((c: MockConnection) => c.mockRespond(response)); + + service.getHeroes() + .do(heroes => { + expect(heroes.length).toBe(fakeHeroes.length, + 'should have expected no. of heroes'); + }) + .toPromise(); + }))); + + + it('should be OK returning no heroes', async(inject([], () => { + let resp = new Response(new ResponseOptions({status: 200, body: {data: []}})); + backend.connections.subscribe((c: MockConnection) => c.mockRespond(resp)); + + service.getHeroes() + .do(heroes => { + expect(heroes.length).toBe(0, 'should have no heroes'); + }) + .toPromise(); + }))); + + it('should treat 404 as an Observable error', async(inject([], () => { + let resp = new Response(new ResponseOptions({status: 404})); + backend.connections.subscribe((c: MockConnection) => c.mockRespond(resp)); + + service.getHeroes() + .do(heroes => { + fail('should not respond with heroes'); + }) + .catch(err => { + expect(err).toMatch(/Bad response status/, 'should catch bad response status code'); + return Observable.of(null); // failure is the expected test result + }) + .toPromise(); + }))); + }); +}); diff --git a/aio/content/examples/testing/ts/src/app/model/http-hero.service.ts b/aio/content/examples/testing/ts/src/app/model/http-hero.service.ts new file mode 100644 index 0000000000..a5fe46b801 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/model/http-hero.service.ts @@ -0,0 +1,68 @@ +// #docplaster +// #docregion +import { Injectable } from '@angular/core'; +import { Http, Response } from '@angular/http'; +import { Headers, RequestOptions } from '@angular/http'; +import { Hero } from './hero'; + +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/observable/throw'; + +import 'rxjs/add/operator/do'; +import 'rxjs/add/operator/catch'; +import 'rxjs/add/operator/map'; + +@Injectable() +export class HttpHeroService { + private _heroesUrl = 'app/heroes'; // URL to web api + + constructor (private http: Http) {} + + getHeroes (): Observable { + return this.http.get(this._heroesUrl) + .map(this.extractData) + // .do(data => console.log(data)) // eyeball results in the console + .catch(this.handleError); + } + + getHero(id: number | string) { + return this.http + .get('app/heroes/?id=${id}') + .map((r: Response) => r.json().data as Hero[]); + } + + addHero (name: string): Observable { + let body = JSON.stringify({ name }); + let headers = new Headers({ 'Content-Type': 'application/json' }); + let options = new RequestOptions({ headers: headers }); + + return this.http.post(this._heroesUrl, body, options) + .map(this.extractData) + .catch(this.handleError); + } + + updateHero (hero: Hero): Observable { + let body = JSON.stringify(hero); + let headers = new Headers({ 'Content-Type': 'application/json' }); + let options = new RequestOptions({ headers: headers }); + + return this.http.put(this._heroesUrl, body, options) + .map(this.extractData) + .catch(this.handleError); + } + + private extractData(res: Response) { + if (res.status < 200 || res.status >= 300) { + throw new Error('Bad response status: ' + res.status); + } + let body = res.json(); + return body.data || { }; + } + + private handleError (error: any) { + // In a real world app, we might send the error to remote logging infrastructure + let errMsg = error.message || 'Server error'; + console.error(errMsg); // log to console instead + return Observable.throw(errMsg); + } +} diff --git a/aio/content/examples/testing/ts/src/app/model/index.ts b/aio/content/examples/testing/ts/src/app/model/index.ts new file mode 100644 index 0000000000..227004d5be --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/model/index.ts @@ -0,0 +1,7 @@ +// Model barrel +export * from './hero'; +export * from './hero.service'; +export * from './http-hero.service'; +export * from './test-heroes'; + +export * from './user.service'; diff --git a/aio/content/examples/testing/ts/src/app/model/test-heroes.ts b/aio/content/examples/testing/ts/src/app/model/test-heroes.ts new file mode 100644 index 0000000000..d40ce5d564 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/model/test-heroes.ts @@ -0,0 +1,11 @@ +// #docregion +import { Hero } from './hero'; + +export var HEROES: Hero[] = [ + new Hero(11, 'Mr. Nice'), + new Hero(12, 'Narco'), + new Hero(13, 'Bombasto'), + new Hero(14, 'Celeritas'), + new Hero(15, 'Magneta'), + new Hero(16, 'RubberMan') +]; diff --git a/aio/content/examples/testing/ts/src/app/model/testing/fake-hero.service.ts b/aio/content/examples/testing/ts/src/app/model/testing/fake-hero.service.ts new file mode 100644 index 0000000000..79a865cc44 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/model/testing/fake-hero.service.ts @@ -0,0 +1,41 @@ +// re-export for tester convenience +export { Hero } from '../hero'; +export { HeroService } from '../hero.service'; + +import { Hero } from '../hero'; +import { HeroService } from '../hero.service'; + +export var HEROES: Hero[] = [ + new Hero(41, 'Bob'), + new Hero(42, 'Carol'), + new Hero(43, 'Ted'), + new Hero(44, 'Alice'), + new Hero(45, 'Speedy'), + new Hero(46, 'Stealthy') +]; + +export class FakeHeroService implements HeroService { + + heroes = HEROES.map(h => h.clone()); + lastPromise: Promise; // remember so we can spy on promise calls + + getHero(id: number | string) { + if (typeof id === 'string') { + id = parseInt(id as string, 10); + } + let hero = this.heroes.find(h => h.id === id); + return this.lastPromise = Promise.resolve(hero); + } + + getHeroes() { + return this.lastPromise = Promise.resolve(this.heroes); + } + + updateHero(hero: Hero): Promise { + return this.lastPromise = this.getHero(hero.id).then(h => { + return h ? + Object.assign(h, hero) : + Promise.reject(`Hero ${hero.id} not found`) as any as Promise; + }); + } +} diff --git a/aio/content/examples/testing/ts/src/app/model/testing/index.ts b/aio/content/examples/testing/ts/src/app/model/testing/index.ts new file mode 100644 index 0000000000..6da76e67db --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/model/testing/index.ts @@ -0,0 +1 @@ +export * from './fake-hero.service'; diff --git a/aio/content/examples/testing/ts/src/app/model/user.service.ts b/aio/content/examples/testing/ts/src/app/model/user.service.ts new file mode 100644 index 0000000000..280efefeec --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/model/user.service.ts @@ -0,0 +1,7 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class UserService { + isLoggedIn = true; + user = {name: 'Sam Spade'}; +} diff --git a/aio/content/examples/testing/ts/src/app/shared/highlight.directive.spec.ts b/aio/content/examples/testing/ts/src/app/shared/highlight.directive.spec.ts new file mode 100644 index 0000000000..b990f20b69 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/shared/highlight.directive.spec.ts @@ -0,0 +1,104 @@ +import { Component, DebugElement } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; + +import { HighlightDirective } from './highlight.directive'; +import { newEvent } from '../../testing'; + +// #docregion test-component +@Component({ + template: ` +

    Something Yellow

    +

    The Default (Gray)

    +

    No Highlight

    + ` +}) +class TestComponent { } +// #enddocregion test-component + +describe('HighlightDirective', () => { + + let fixture: ComponentFixture; + let des: DebugElement[]; // the three elements w/ the directive + let bareH2: DebugElement; // the

    w/o the directive + + // #docregion selected-tests + beforeEach(() => { + fixture = TestBed.configureTestingModule({ + declarations: [ HighlightDirective, TestComponent ] + }) + .createComponent(TestComponent); + + fixture.detectChanges(); // initial binding + + // all elements with an attached HighlightDirective + des = fixture.debugElement.queryAll(By.directive(HighlightDirective)); + + // the h2 without the HighlightDirective + bareH2 = fixture.debugElement.query(By.css('h2:not([highlight])')); + }); + + // color tests + it('should have three highlighted elements', () => { + expect(des.length).toBe(3); + }); + + it('should color 1st

    background "yellow"', () => { + const bgColor = des[0].nativeElement.style.backgroundColor; + expect(bgColor).toBe('yellow'); + }); + + it('should color 2nd

    background w/ default color', () => { + const dir = des[1].injector.get(HighlightDirective) as HighlightDirective; + const bgColor = des[1].nativeElement.style.backgroundColor; + expect(bgColor).toBe(dir.defaultColor); + }); + + it('should bind background to value color', () => { + // easier to work with nativeElement + const input = des[2].nativeElement as HTMLInputElement; + expect(input.style.backgroundColor).toBe('cyan', 'initial backgroundColor'); + + // dispatch a DOM event so that Angular responds to the input value change. + input.value = 'green'; + input.dispatchEvent(newEvent('input')); + fixture.detectChanges(); + + expect(input.style.backgroundColor).toBe('green', 'changed backgroundColor'); + }); + + + it('bare

    should not have a customProperty', () => { + expect(bareH2.properties['customProperty']).toBeUndefined(); + }); + // #enddocregion selected-tests + + // Removed on 12/02/2016 when ceased public discussion of the `Renderer`. Revive in future? + // // customProperty tests + // it('all highlighted elements should have a true customProperty', () => { + // const allTrue = des.map(de => !!de.properties['customProperty']).every(v => v === true); + // expect(allTrue).toBe(true); + // }); + + // injected directive + // attached HighlightDirective can be injected + it('can inject `HighlightDirective` in 1st

    ', () => { + const dir = des[0].injector.get(HighlightDirective); + expect(dir).toBeTruthy(); + }); + + it('cannot inject `HighlightDirective` in 3rd

    ', () => { + const dir = bareH2.injector.get(HighlightDirective, null); + expect(dir).toBe(null); + }); + + // DebugElement.providerTokens + // attached HighlightDirective should be listed in the providerTokens + it('should have `HighlightDirective` in 1st

    providerTokens', () => { + expect(des[0].providerTokens).toContain(HighlightDirective); + }); + + it('should not have `HighlightDirective` in 3rd

    providerTokens', () => { + expect(bareH2.providerTokens).not.toContain(HighlightDirective); + }); +}); diff --git a/aio/content/examples/testing/ts/src/app/shared/highlight.directive.ts b/aio/content/examples/testing/ts/src/app/shared/highlight.directive.ts new file mode 100644 index 0000000000..20901878c4 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/shared/highlight.directive.ts @@ -0,0 +1,20 @@ +// #docregion +import { Directive, ElementRef, Input, OnChanges } from '@angular/core'; + +@Directive({ selector: '[highlight]' }) +/** Set backgroundColor for the attached element to highlight color + * and set the element's customProperty to true */ +export class HighlightDirective implements OnChanges { + + defaultColor = 'rgb(211, 211, 211)'; // lightgray + + @Input('highlight') bgColor: string; + + constructor(private el: ElementRef) { + el.nativeElement.style.customProperty = true; + } + + ngOnChanges() { + this.el.nativeElement.style.backgroundColor = this.bgColor || this.defaultColor; + } +} diff --git a/aio/content/examples/testing/ts/src/app/shared/shared.module.ts b/aio/content/examples/testing/ts/src/app/shared/shared.module.ts new file mode 100644 index 0000000000..17c41c0410 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/shared/shared.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { HighlightDirective } from './highlight.directive'; +import { TitleCasePipe } from './title-case.pipe'; +import { TwainComponent } from './twain.component'; + +@NgModule({ + imports: [ CommonModule ], + exports: [ CommonModule, FormsModule, + HighlightDirective, TitleCasePipe, TwainComponent ], + declarations: [ HighlightDirective, TitleCasePipe, TwainComponent ] +}) +export class SharedModule { } diff --git a/aio/content/examples/testing/ts/src/app/shared/title-case.pipe.spec.ts b/aio/content/examples/testing/ts/src/app/shared/title-case.pipe.spec.ts new file mode 100644 index 0000000000..5dfc5d91b0 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/shared/title-case.pipe.spec.ts @@ -0,0 +1,34 @@ +// #docplaster +// #docregion +import { TitleCasePipe } from './title-case.pipe'; + +// #docregion excerpt, mini-excerpt +describe('TitleCasePipe', () => { + // This pipe is a pure, stateless function so no need for BeforeEach + let pipe = new TitleCasePipe(); + + it('transforms "abc" to "Abc"', () => { + expect(pipe.transform('abc')).toBe('Abc'); + }); +// #enddocregion mini-excerpt + + it('transforms "abc def" to "Abc Def"', () => { + expect(pipe.transform('abc def')).toBe('Abc Def'); + }); + + // ... more tests ... +// #enddocregion excerpt + it('leaves "Abc Def" unchanged', () => { + expect(pipe.transform('Abc Def')).toBe('Abc Def'); + }); + + it('transforms "abc-def" to "Abc-def"', () => { + expect(pipe.transform('abc-def')).toBe('Abc-def'); + }); + + it('transforms " abc def" to " Abc Def" (preserves spaces) ', () => { + expect(pipe.transform(' abc def')).toBe(' Abc Def'); + }); +// #docregion excerpt, mini-excerpt +}); +// #enddocregion excerpt, mini-excerpt diff --git a/aio/content/examples/testing/ts/src/app/shared/title-case.pipe.ts b/aio/content/examples/testing/ts/src/app/shared/title-case.pipe.ts new file mode 100644 index 0000000000..df2567778d --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/shared/title-case.pipe.ts @@ -0,0 +1,11 @@ +// #docregion +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({name: 'titlecase', pure: false}) +/** Transform to Title Case: uppercase the first letter of the words in a string.*/ +export class TitleCasePipe implements PipeTransform { + transform(input: string): string { + return input.length === 0 ? '' : + input.replace(/\w\S*/g, (txt => txt[0].toUpperCase() + txt.substr(1).toLowerCase() )); + } +} diff --git a/aio/content/examples/testing/ts/src/app/shared/twain.component.spec.ts b/aio/content/examples/testing/ts/src/app/shared/twain.component.spec.ts new file mode 100644 index 0000000000..b80993cc0b --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/shared/twain.component.spec.ts @@ -0,0 +1,92 @@ +// #docplaster +import { async, fakeAsync, ComponentFixture, TestBed, tick } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { TwainService } from './twain.service'; +import { TwainComponent } from './twain.component'; + +describe('TwainComponent', () => { + + let comp: TwainComponent; + let fixture: ComponentFixture; + + let spy: jasmine.Spy; + let de: DebugElement; + let el: HTMLElement; + let twainService: TwainService; // the actually injected service + + const testQuote = 'Test Quote'; + + // #docregion setup + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ TwainComponent ], + providers: [ TwainService ], + }); + + fixture = TestBed.createComponent(TwainComponent); + comp = fixture.componentInstance; + + // TwainService actually injected into the component + twainService = fixture.debugElement.injector.get(TwainService); + + // Setup spy on the `getQuote` method + // #docregion spy + spy = spyOn(twainService, 'getQuote') + .and.returnValue(Promise.resolve(testQuote)); + // #enddocregion spy + + // Get the Twain quote element by CSS selector (e.g., by class name) + de = fixture.debugElement.query(By.css('.twain')); + el = de.nativeElement; + }); + // #enddocregion setup + + // #docregion tests + it('should not show quote before OnInit', () => { + expect(el.textContent).toBe('', 'nothing displayed'); + expect(spy.calls.any()).toBe(false, 'getQuote not yet called'); + }); + + it('should still not show quote after component initialized', () => { + fixture.detectChanges(); + // getQuote service is async => still has not returned with quote + expect(el.textContent).toBe('...', 'no quote yet'); + expect(spy.calls.any()).toBe(true, 'getQuote called'); + }); + + // #docregion async-test + it('should show quote after getQuote promise (async)', async(() => { + fixture.detectChanges(); + + fixture.whenStable().then(() => { // wait for async getQuote + fixture.detectChanges(); // update view with quote + expect(el.textContent).toBe(testQuote); + }); + })); + // #enddocregion async-test + + // #docregion fake-async-test + it('should show quote after getQuote promise (fakeAsync)', fakeAsync(() => { + fixture.detectChanges(); + tick(); // wait for async getQuote + fixture.detectChanges(); // update view with quote + expect(el.textContent).toBe(testQuote); + })); + // #enddocregion fake-async-test + // #enddocregion tests + + // #docregion done-test + it('should show quote after getQuote promise (done)', done => { + fixture.detectChanges(); + + // get the spy promise and wait for it to resolve + spy.calls.mostRecent().returnValue.then(() => { + fixture.detectChanges(); // update view with quote + expect(el.textContent).toBe(testQuote); + done(); + }); + }); + // #enddocregion done-test +}); diff --git a/aio/content/examples/testing/ts/src/app/shared/twain.component.timer.spec.ts.no-work b/aio/content/examples/testing/ts/src/app/shared/twain.component.timer.spec.ts.no-work new file mode 100644 index 0000000000..74dec3e766 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/shared/twain.component.timer.spec.ts.no-work @@ -0,0 +1,116 @@ +// #docplaster +// When AppComponent learns to present quote with intervalTimer +import { async, discardPeriodicTasks, fakeAsync, ComponentFixture, TestBed, tick } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { TwainService } from './model'; +import { TwainComponent } from './twain.component'; + +xdescribe('TwainComponent', () => { + + let comp: TwainComponent; + let fixture: ComponentFixture; + + const quotes = [ + 'Test Quote 1', + 'Test Quote 2', + 'Test Quote 3' + ]; + + let spy: jasmine.Spy; + let twainEl: DebugElement; // the element with the Twain quote + let twainService: TwainService; // the actually injected service + + function getQuote() { return twainEl.nativeElement.textContent; } + + // #docregion setup + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ TwainComponent ], + providers: [ TwainService ], + }); + + fixture = TestBed.createComponent(TwainComponent); + comp = fixture.componentInstance; + + // TwainService actually injected into the component + twainService = fixture.debugElement.injector.get(TwainService); + + // Setup spy on the `getQuote` method + spy = spyOn(twainService, 'getQuote') + .and.returnValues(...quotes.map(q => Promise.resolve(q))); + + // Get the Twain quote element by CSS selector (e.g., by class name) + twainEl = fixture.debugElement.query(By.css('.twain')); + }); + + afterEach(() => { + // destroy component to stop the component timer + fixture.destroy(); + }); + // #enddocregion setup + + // #docregion tests + it('should not show quote before OnInit', () => { + expect(getQuote()).toBe(''); + }); + + it('should still not show quote after component initialized', () => { + // because the getQuote service is async + fixture.detectChanges(); // trigger data binding + expect(getQuote()).toContain('not initialized'); + }); + + // WIP + // If go this way, add jasmine.clock().uninstall(); to afterEach + // it('should show quote after Angular "settles"', async(() => { + // //jasmine.clock().install(); + // fixture.detectChanges(); // trigger data binding + // fixture.whenStable().then(() => { + // fixture.detectChanges(); // update view with the quote + // expect(getQuote()).toBe(quotes[0]); + // }); + // // jasmine.clock().tick(5000); + // // fixture.whenStable().then(() => { + // // fixture.detectChanges(); // update view with the quote + // // expect(getQuote()).toBe(quotes[1]); + // // }); + // })); + + it('should show quote after getQuote promise returns', fakeAsync(() => { + fixture.detectChanges(); // trigger data binding + tick(); // wait for first async getQuote to return + fixture.detectChanges(); // update view with the quote + expect(getQuote()).toBe(quotes[0]); + + // destroy component to stop the component timer before test ends + // else test errors because still have timer in the queue + fixture.destroy(); + })); + + it('should show 2nd quote after 5 seconds pass', fakeAsync(() => { + fixture.detectChanges(); // trigger data binding + tick(5000); // wait for second async getQuote to return + fixture.detectChanges(); // update view with the quote + expect(getQuote()).toBe(quotes[1]); + + // still have intervalTimer queuing requres + // discardPeriodicTasks() else test errors + discardPeriodicTasks(); + })); + + fit('should show 3rd quote after 10 seconds pass', fakeAsync(() => { + fixture.detectChanges(); // trigger data binding + tick(5000); // wait for second async getQuote to return + fixture.detectChanges(); // update view with the 2nd quote + tick(5000); // wait for third async getQuote to return + fixture.detectChanges(); // update view with the 3rd quote + expect(getQuote()).toBe(quotes[2]); + + // still have intervalTimer queuing requres + // discardPeriodicTasks() else test errors + discardPeriodicTasks(); + })); + // #enddocregion tests +}); diff --git a/aio/content/examples/testing/ts/src/app/shared/twain.component.timer.ts.no-work b/aio/content/examples/testing/ts/src/app/shared/twain.component.timer.ts.no-work new file mode 100644 index 0000000000..d3dc1f205d --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/shared/twain.component.timer.ts.no-work @@ -0,0 +1,27 @@ +// #docregion +import { Component, OnInit, OnDestroy } from '@angular/core'; + +import { TwainService } from './twain.service'; + +@Component({ + selector: 'twain-quote', + template: '

    {{quote}}

    ' +}) +export class TwainComponent implements OnInit, OnDestroy { + intervalId: number; + quote = '-- not initialized yet --'; + constructor(private twainService: TwainService) { } + + getQuote() { + this.twainService.getQuote().then(quote => this.quote = quote); + } + + ngOnInit(): void { + this.getQuote(); + this.intervalId = window.setInterval(() => this.getQuote(), 5000); + } + + ngOnDestroy(): void { + clearInterval(this.intervalId); + } +} diff --git a/aio/content/examples/testing/ts/src/app/shared/twain.component.ts b/aio/content/examples/testing/ts/src/app/shared/twain.component.ts new file mode 100644 index 0000000000..29f24459ab --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/shared/twain.component.ts @@ -0,0 +1,20 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; + +import { TwainService } from './twain.service'; + +// #docregion component +@Component({ + selector: 'twain-quote', + template: '

    {{quote}}

    ' +}) +export class TwainComponent implements OnInit { + intervalId: number; + quote = '...'; + constructor(private twainService: TwainService) { } + + ngOnInit(): void { + this.twainService.getQuote().then(quote => this.quote = quote); + } +} +// #enddocregion component diff --git a/aio/content/examples/testing/ts/src/app/shared/twain.service.ts b/aio/content/examples/testing/ts/src/app/shared/twain.service.ts new file mode 100644 index 0000000000..9e394df1ee --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/shared/twain.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; + +const quotes = [ +'Always do right. This will gratify some people and astonish the rest.', +'I have never let my schooling interfere with my education.', +'Don\'t go around saying the world owes you a living. The world owes you nothing. It was here first.', +'Whenever you find yourself on the side of the majority, it is time to pause and reflect.', +'If you tell the truth, you don\'t have to remember anything.', +'Clothes make the man. Naked people have little or no influence on society.', +'It\'s not the size of the dog in the fight, it\'s the size of the fight in the dog.', +'Truth is stranger than fiction, but it is because Fiction is obliged to stick to possibilities; Truth isn\'t.', +'The man who does not read good books has no advantage over the man who cannot read them.', +'Get your facts first, and then you can distort them as much as you please.', +]; + +@Injectable() +export class TwainService { + private next = 0; + + // Imaginary todo: get quotes from a remote quote service + // returns quote after delay simulating server latency + getQuote(): Promise { + return new Promise(resolve => { + setTimeout( () => resolve(this.nextQuote()), 500 ); + }); + } + + private nextQuote() { + if (this.next === quotes.length) { this.next = 0; } + return quotes[ this.next++ ]; + } +} diff --git a/aio/content/examples/testing/ts/src/app/welcome.component.spec.ts b/aio/content/examples/testing/ts/src/app/welcome.component.spec.ts new file mode 100644 index 0000000000..e506dda396 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/welcome.component.spec.ts @@ -0,0 +1,108 @@ +// #docplaster +import { ComponentFixture, inject, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { UserService } from './model'; +import { WelcomeComponent } from './welcome.component'; + +describe('WelcomeComponent', () => { + + let comp: WelcomeComponent; + let fixture: ComponentFixture; + let componentUserService: UserService; // the actually injected service + let userService: UserService; // the TestBed injected service + let de: DebugElement; // the DebugElement with the welcome message + let el: HTMLElement; // the DOM element with the welcome message + + let userServiceStub: { + isLoggedIn: boolean; + user: { name: string} + }; + + // #docregion setup + beforeEach(() => { + // stub UserService for test purposes + // #docregion user-service-stub + userServiceStub = { + isLoggedIn: true, + user: { name: 'Test User'} + }; + // #enddocregion user-service-stub + + // #docregion config-test-module + TestBed.configureTestingModule({ + declarations: [ WelcomeComponent ], + // #enddocregion setup + // providers: [ UserService ] // NO! Don't provide the real service! + // Provide a test-double instead + // #docregion setup + providers: [ {provide: UserService, useValue: userServiceStub } ] + }); + // #enddocregion config-test-module + + fixture = TestBed.createComponent(WelcomeComponent); + comp = fixture.componentInstance; + + // #enddocregion setup + // #docregion injected-service + // UserService actually injected into the component + userService = fixture.debugElement.injector.get(UserService); + // #enddocregion injected-service + componentUserService = userService; + // #docregion setup + // #docregion inject-from-testbed + // UserService from the root injector + userService = TestBed.get(UserService); + // #enddocregion inject-from-testbed + + // get the "welcome" element by CSS selector (e.g., by class name) + de = fixture.debugElement.query(By.css('.welcome')); + el = de.nativeElement; + }); + // #enddocregion setup + + // #docregion tests + it('should welcome the user', () => { + fixture.detectChanges(); + const content = el.textContent; + expect(content).toContain('Welcome', '"Welcome ..."'); + expect(content).toContain('Test User', 'expected name'); + }); + + it('should welcome "Bubba"', () => { + userService.user.name = 'Bubba'; // welcome message hasn't been shown yet + fixture.detectChanges(); + expect(el.textContent).toContain('Bubba'); + }); + + it('should request login if not logged in', () => { + userService.isLoggedIn = false; // welcome message hasn't been shown yet + fixture.detectChanges(); + const content = el.textContent; + expect(content).not.toContain('Welcome', 'not welcomed'); + expect(content).toMatch(/log in/i, '"log in"'); + }); + // #enddocregion tests + + // #docregion inject-it + it('should inject the component\'s UserService instance', + inject([UserService], (service: UserService) => { + expect(service).toBe(componentUserService); + })); + // #enddocregion inject-it + + it('TestBed and Component UserService should be the same', () => { + expect(userService === componentUserService).toBe(true); + }); + + // #docregion stub-not-injected + it('stub object and injected UserService should not be the same', () => { + expect(userServiceStub === userService).toBe(false); + + // Changing the stub object has no effect on the injected service + userServiceStub.isLoggedIn = false; + expect(userService.isLoggedIn).toBe(true); + }); + // #enddocregion stub-not-injected +}); diff --git a/aio/content/examples/testing/ts/src/app/welcome.component.ts b/aio/content/examples/testing/ts/src/app/welcome.component.ts new file mode 100644 index 0000000000..35958cc5c9 --- /dev/null +++ b/aio/content/examples/testing/ts/src/app/welcome.component.ts @@ -0,0 +1,18 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; +import { UserService } from './model'; + +@Component({ + selector: 'app-welcome', + template: '

    {{welcome}}

    ' +}) +export class WelcomeComponent implements OnInit { + welcome = '-- not initialized yet --'; + constructor(private userService: UserService) { } + + ngOnInit(): void { + this.welcome = this.userService.isLoggedIn ? + 'Welcome, ' + this.userService.user.name : + 'Please log in.'; + } +} diff --git a/aio/content/examples/testing/ts/src/bag-specs.html b/aio/content/examples/testing/ts/src/bag-specs.html new file mode 100644 index 0000000000..89b46f7056 --- /dev/null +++ b/aio/content/examples/testing/ts/src/bag-specs.html @@ -0,0 +1,42 @@ + + + + + + + Specs Bag + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aio/content/examples/testing/ts/src/bag.html b/aio/content/examples/testing/ts/src/bag.html new file mode 100644 index 0000000000..3e0fcb9025 --- /dev/null +++ b/aio/content/examples/testing/ts/src/bag.html @@ -0,0 +1,26 @@ + + + + + + Specs Bag + + + + + + + + + + + + + + + + Loading ... + + diff --git a/aio/content/examples/testing/ts/src/banner-inline-specs.html b/aio/content/examples/testing/ts/src/banner-inline-specs.html new file mode 100644 index 0000000000..2a512a5647 --- /dev/null +++ b/aio/content/examples/testing/ts/src/banner-inline-specs.html @@ -0,0 +1,40 @@ + + + + + + + Banner Component (inline template) Specs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aio/content/examples/testing/ts/src/banner-specs.html b/aio/content/examples/testing/ts/src/banner-specs.html new file mode 100644 index 0000000000..d16dd977a4 --- /dev/null +++ b/aio/content/examples/testing/ts/src/banner-specs.html @@ -0,0 +1,40 @@ + + + + + + + Banner Component Specs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aio/content/examples/testing/ts/src/browser-test-shim.js b/aio/content/examples/testing/ts/src/browser-test-shim.js new file mode 100644 index 0000000000..ee21831e22 --- /dev/null +++ b/aio/content/examples/testing/ts/src/browser-test-shim.js @@ -0,0 +1,87 @@ +// BROWSER TESTING SHIM +// Keep it in-sync with what karma-test-shim does +// #docregion +/*global jasmine, __karma__, window*/ +(function () { + +Error.stackTraceLimit = 0; // "No stacktrace"" is usually best for app testing. + +// Uncomment to get full stacktrace output. Sometimes helpful, usually not. +// Error.stackTraceLimit = Infinity; // + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000; + +var baseURL = document.baseURI; +baseURL = baseURL + baseURL[baseURL.length-1] ? '' : '/'; + +System.config({ + baseURL: baseURL, + // Extend usual application package list with test folder + packages: { 'testing': { main: 'index.js', defaultExtension: 'js' } }, + + // Assume npm: is set in `paths` in systemjs.config + // Map the angular testing umd bundles + map: { + '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js', + '@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js', + '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js', + '@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js', + '@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js', + '@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js', + '@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js', + '@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js', + }, +}); + +System.import('systemjs.config.js') + .then(importSystemJsExtras) + .then(initTestBed) + .then(initTesting); + +/** Optional SystemJS configuration extras. Keep going w/o it */ +function importSystemJsExtras(){ + return System.import('systemjs.config.extras.js') + .catch(function(reason) { + console.log( + 'Note: System.import could not load "systemjs.config.extras.js" where you might have added more configuration. It is an optional file so we will continue without it.' + ); + console.log(reason); + }); +} + +function initTestBed(){ + return Promise.all([ + System.import('@angular/core/testing'), + System.import('@angular/platform-browser-dynamic/testing') + ]) + + .then(function (providers) { + var coreTesting = providers[0]; + var browserTesting = providers[1]; + + coreTesting.TestBed.initTestEnvironment( + browserTesting.BrowserDynamicTestingModule, + browserTesting.platformBrowserDynamicTesting()); + }) +} + +// Import all spec files defined in the html (__spec_files__) +// and start Jasmine testrunner +function initTesting () { + console.log('loading spec files: '+__spec_files__.join(', ')); + return Promise.all( + __spec_files__.map(function(spec) { + return System.import(spec); + }) + ) + // After all imports load, re-execute `window.onload` which + // triggers the Jasmine test-runner start or explain what went wrong + .then(success, console.error.bind(console)); + + function success () { + console.log('Spec files loaded; starting Jasmine testrunner'); + window.onload(); + } +} + +})(); diff --git a/aio/content/examples/testing/ts/src/index.html b/aio/content/examples/testing/ts/src/index.html new file mode 100644 index 0000000000..fff2464efc --- /dev/null +++ b/aio/content/examples/testing/ts/src/index.html @@ -0,0 +1,27 @@ + + + + + + App Under Test + + + + + + + + + + + + + + + + + Loading... + + diff --git a/aio/content/examples/testing/ts/src/main.ts b/aio/content/examples/testing/ts/src/main.ts new file mode 100644 index 0000000000..fadce2f3c1 --- /dev/null +++ b/aio/content/examples/testing/ts/src/main.ts @@ -0,0 +1,5 @@ +// main app entry point +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/testing/ts/src/testing/index.ts b/aio/content/examples/testing/ts/src/testing/index.ts new file mode 100644 index 0000000000..e3de5164ca --- /dev/null +++ b/aio/content/examples/testing/ts/src/testing/index.ts @@ -0,0 +1,43 @@ +import { DebugElement } from '@angular/core'; +import { tick, ComponentFixture } from '@angular/core/testing'; + +export * from './jasmine-matchers'; +export * from './router-stubs'; + +///// Short utilities ///// + +/** Wait a tick, then detect changes */ +export function advance(f: ComponentFixture): void { + tick(); + f.detectChanges(); +} + +/** + * Create custom DOM event the old fashioned way + * + * https://developer.mozilla.org/en-US/docs/Web/API/Event/initEvent + * Although officially deprecated, some browsers (phantom) don't accept the preferred "new Event(eventName)" + */ +export function newEvent(eventName: string, bubbles = false, cancelable = false) { + let evt = document.createEvent('CustomEvent'); // MUST be 'CustomEvent' + evt.initCustomEvent(eventName, bubbles, cancelable, null); + return evt; +} + +// See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button +// #docregion click-event +/** Button events to pass to `DebugElement.triggerEventHandler` for RouterLink event handler */ +export const ButtonClickEvents = { + left: { button: 0 }, + right: { button: 2 } +}; + +/** Simulate element click. Defaults to mouse left-button click event. */ +export function click(el: DebugElement | HTMLElement, eventObj: any = ButtonClickEvents.left): void { + if (el instanceof HTMLElement) { + el.click(); + } else { + el.triggerEventHandler('click', eventObj); + } +} +// #enddocregion click-event diff --git a/aio/content/examples/testing/ts/src/testing/jasmine-matchers.d.ts b/aio/content/examples/testing/ts/src/testing/jasmine-matchers.d.ts new file mode 100644 index 0000000000..f1c5acf77c --- /dev/null +++ b/aio/content/examples/testing/ts/src/testing/jasmine-matchers.d.ts @@ -0,0 +1,5 @@ +declare namespace jasmine { + interface Matchers { + toHaveText(actual: any, expectationFailOutput?: any): jasmine.CustomMatcher; + } +} diff --git a/aio/content/examples/testing/ts/src/testing/jasmine-matchers.ts b/aio/content/examples/testing/ts/src/testing/jasmine-matchers.ts new file mode 100644 index 0000000000..4cab02e148 --- /dev/null +++ b/aio/content/examples/testing/ts/src/testing/jasmine-matchers.ts @@ -0,0 +1,45 @@ +/// + +//// Jasmine Custom Matchers //// +// Be sure to extend jasmine-matchers.d.ts when adding matchers + +export function addMatchers(): void { + jasmine.addMatchers({ + toHaveText: toHaveText + }); +} + +function toHaveText(): jasmine.CustomMatcher { + return { + compare: function (actual: any, expectedText: string, expectationFailOutput?: any): jasmine.CustomMatcherResult { + const actualText = elementText(actual); + const pass = actualText.indexOf(expectedText) > -1; + const message = pass ? '' : composeMessage(); + return { pass, message }; + + function composeMessage () { + const a = (actualText.length < 100 ? actualText : actualText.substr(0, 100) + '...'); + const efo = expectationFailOutput ? ` '${expectationFailOutput}'` : ''; + return `Expected element to have text content '${expectedText}' instead of '${a}'${efo}`; + } + } + }; +} + +function elementText(n: any): string { + if (n instanceof Array) { + return n.map(elementText).join(''); + } + + if (n.nodeType === Node.COMMENT_NODE) { + return ''; + } + + if (n.nodeType === Node.ELEMENT_NODE && n.hasChildNodes()) { + return elementText(Array.prototype.slice.call(n.childNodes)); + } + + if (n.nativeElement) { n = n.nativeElement; } + + return n.textContent; +} diff --git a/aio/content/examples/testing/ts/src/testing/router-stubs.ts b/aio/content/examples/testing/ts/src/testing/router-stubs.ts new file mode 100644 index 0000000000..75a2858f65 --- /dev/null +++ b/aio/content/examples/testing/ts/src/testing/router-stubs.ts @@ -0,0 +1,57 @@ + // export for convenience. +export { ActivatedRoute, Router, RouterLink, RouterOutlet} from '@angular/router'; + +import { Component, Directive, Injectable, Input } from '@angular/core'; +import { NavigationExtras } from '@angular/router'; + +// #docregion router-link +@Directive({ + selector: '[routerLink]', + host: { + '(click)': 'onClick()' + } +}) +export class RouterLinkStubDirective { + @Input('routerLink') linkParams: any; + navigatedTo: any = null; + + onClick() { + this.navigatedTo = this.linkParams; + } +} +// #enddocregion router-link + +@Component({selector: 'router-outlet', template: ''}) +export class RouterOutletStubComponent { } + +@Injectable() +export class RouterStub { + navigate(commands: any[], extras?: NavigationExtras) { } +} + + +// Only implements params and part of snapshot.params +// #docregion activated-route-stub +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; + +@Injectable() +export class ActivatedRouteStub { + + // ActivatedRoute.params is Observable + private subject = new BehaviorSubject(this.testParams); + params = this.subject.asObservable(); + + // Test parameters + private _testParams: {}; + get testParams() { return this._testParams; } + set testParams(params: {}) { + this._testParams = params; + this.subject.next(params); + } + + // ActivatedRoute.snapshot.params + get snapshot() { + return { params: this.testParams }; + } +} +// #enddocregion activated-route-stub diff --git a/aio/content/examples/toh-1/dart-snippets/app_component_snippets_pt1.dart b/aio/content/examples/toh-1/dart-snippets/app_component_snippets_pt1.dart new file mode 100644 index 0000000000..3bfa89762d --- /dev/null +++ b/aio/content/examples/toh-1/dart-snippets/app_component_snippets_pt1.dart @@ -0,0 +1,37 @@ +// #docregion show-hero +template: '

    {{title}}

    {{hero}} details!

    ' +// #enddocregion show-hero + +// #docregion show-hero-2 +template: '

    {{title}}

    {{hero.name}} details!

    ' +// #enddocregion show-hero-2 + +// #docregion show-hero-properties +template: '

    {{title}}

    {{hero.name}} details!

    {{hero.id}}
    {{hero.name}}
    ' +// #enddocregion show-hero-properties + +// #docregion multi-line-strings +template: ''' +

    {{title}}

    +

    {{hero.name}} details!

    +
    {{hero.id}}
    +
    {{hero.name}}
    ''' +// #enddocregion multi-line-strings + +// #docregion editing-Hero +template: ''' +

    {{title}}

    +

    {{hero.name}} details!

    +
    {{hero.id}}
    +
    + + +
    ''' +// #enddocregion editing-Hero + +// #docregion app-component-1 +class AppComponent { + String title = 'Tour of Heroes'; + var hero = 'Windstorm'; +} +// #enddocregion app-component-1 diff --git a/aio/content/examples/toh-1/e2e-spec.ts b/aio/content/examples/toh-1/e2e-spec.ts new file mode 100644 index 0000000000..75f99788a6 --- /dev/null +++ b/aio/content/examples/toh-1/e2e-spec.ts @@ -0,0 +1,70 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by, ElementFinder } from 'protractor'; +import { promise } from 'selenium-webdriver'; + +const expectedH1 = 'Tour of Heroes'; +const expectedTitle = `Angular ${expectedH1}`; + +class Hero { + id: number; + name: string; + + // Factory method + // Get hero id and name from the given detail element. + static async fromDetail(detail: ElementFinder): Promise { + // Get hero id from the first
    + let _id = await detail.all(by.css('div')).first().getText(); + // Get name from the h2 + let _name = await detail.element(by.css('h2')).getText(); + return { + id: +_id.substr(_id.indexOf(' ') + 1), + name: _name.substr(0, _name.lastIndexOf(' ')) + }; + } +} + +const nameSuffix = 'X'; +function addToHeroName(text: string): promise.Promise { + let input = element(by.css('input')); + return input.sendKeys(text); +} + +describe('Tutorial part 1', () => { + + const expectedHero = { id: 1, name: 'Windstorm' }; + + beforeAll(() => browser.get('')); + + it(`has title '${expectedTitle}'`, () => { + expect(browser.getTitle()).toEqual(expectedTitle); + }); + + it(`has h1 '${expectedH1}'`, () => { + let hText = element(by.css('h1')).getText(); + expect(hText).toEqual(expectedH1, 'h1'); + }); + + it(`shows initial hero details`, async () => { + let page = getPageElts(); + let hero = await Hero.fromDetail(page.heroDetail); + expect(hero.id).toEqual(expectedHero.id); + expect(hero.name).toEqual(expectedHero.name); + }); + + it(`shows updated hero name`, async () => { + addToHeroName(nameSuffix); + let page = getPageElts(); + let hero = await Hero.fromDetail(page.heroDetail); + let newName = expectedHero.name + nameSuffix; + expect(hero.id).toEqual(expectedHero.id); + expect(hero.name).toEqual(newName); + }); + +}); + +function getPageElts() { + return { + heroDetail: element(by.css('my-app')) + }; +} diff --git a/aio/content/examples/toh-1/ts-snippets/app.component.snippets.pt1.ts b/aio/content/examples/toh-1/ts-snippets/app.component.snippets.pt1.ts new file mode 100644 index 0000000000..05180b0c83 --- /dev/null +++ b/aio/content/examples/toh-1/ts-snippets/app.component.snippets.pt1.ts @@ -0,0 +1,39 @@ +// #docregion show-hero +template: '

    {{title}}

    {{hero}} details!

    ' +// #enddocregion show-hero + +// #docregion show-hero-2 +template: '

    {{title}}

    {{hero.name}} details!

    ' +// #enddocregion show-hero-2 + +// #docregion show-hero-properties +template: '

    {{title}}

    {{hero.name}} details!

    {{hero.id}}
    {{hero.name}}
    ' +// #enddocregion show-hero-properties + +// #docregion multi-line-strings +template:` +

    {{title}}

    +

    {{hero.name}} details!

    +
    {{hero.id}}
    +
    {{hero.name}}
    + ` +// #enddocregion multi-line-strings + +// #docregion editing-Hero +template:` +

    {{title}}

    +

    {{hero.name}} details!

    +
    {{hero.id}}
    +
    + + +
    + ` +// #enddocregion editing-Hero + +// #docregion app-component-1 +export class AppComponent { + title = 'Tour of Heroes'; + hero = 'Windstorm'; +} +// #enddocregion app-component-1 diff --git a/aio/content/examples/toh-1/ts/example-config.json b/aio/content/examples/toh-1/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/toh-1/ts/plnkr.json b/aio/content/examples/toh-1/ts/plnkr.json new file mode 100644 index 0000000000..b5d016f282 --- /dev/null +++ b/aio/content/examples/toh-1/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Tour of Heroes: Part 1", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js" + ], + "tags": ["tutorial", "tour", "heroes"] +} diff --git a/aio/content/examples/toh-1/ts/plnkr.no-link.html b/aio/content/examples/toh-1/ts/plnkr.no-link.html new file mode 100644 index 0000000000..47d25fa4af --- /dev/null +++ b/aio/content/examples/toh-1/ts/plnkr.no-link.html @@ -0,0 +1,215 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/toh-1/ts/src/app/app.component.ts b/aio/content/examples/toh-1/ts/src/app/app.component.ts new file mode 100644 index 0000000000..b61fac3c0b --- /dev/null +++ b/aio/content/examples/toh-1/ts/src/app/app.component.ts @@ -0,0 +1,32 @@ +// #docregion pt1 +import { Component } from '@angular/core'; + +// #docregion hero-class-1 +export class Hero { + id: number; + name: string; +} +// #enddocregion hero-class-1 + +@Component({ + selector: 'my-app', + template: ` +

    {{title}}

    +

    {{hero.name}} details!

    +
    {{hero.id}}
    +
    + + +
    + ` +}) +export class AppComponent { + title = 'Tour of Heroes'; + // #docregion hero-property-1 + hero: Hero = { + id: 1, + name: 'Windstorm' + }; + // #enddocregion hero-property-1 +} +// #enddocregion pt1 diff --git a/aio/content/examples/toh-1/ts/src/app/app.module.ts b/aio/content/examples/toh-1/ts/src/app/app.module.ts new file mode 100644 index 0000000000..4c0b77ea48 --- /dev/null +++ b/aio/content/examples/toh-1/ts/src/app/app.module.ts @@ -0,0 +1,18 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; + +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule + ], + declarations: [ + AppComponent + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/toh-1/ts/src/index.html b/aio/content/examples/toh-1/ts/src/index.html new file mode 100644 index 0000000000..a217238c6c --- /dev/null +++ b/aio/content/examples/toh-1/ts/src/index.html @@ -0,0 +1,25 @@ + + + + Angular Tour of Heroes + + + + + + + + + + + + + + + + + Loading... + + diff --git a/aio/content/examples/toh-1/ts/src/main.ts b/aio/content/examples/toh-1/ts/src/main.ts new file mode 100644 index 0000000000..80ece654a5 --- /dev/null +++ b/aio/content/examples/toh-1/ts/src/main.ts @@ -0,0 +1,6 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); +// #enddocregion diff --git a/aio/content/examples/toh-2/dart-snippets/app_component_snippets_pt2.dart b/aio/content/examples/toh-2/dart-snippets/app_component_snippets_pt2.dart new file mode 100644 index 0000000000..9f805c5bc5 --- /dev/null +++ b/aio/content/examples/toh-2/dart-snippets/app_component_snippets_pt2.dart @@ -0,0 +1,69 @@ +// #docregion ng-for +
  • + {{hero.id}} {{hero.name}} +
  • +// #enddocregion ng-for + +// #docregion heroes-styled +

    My Heroes

    +
      +
    • + {{hero.id}} {{hero.name}} +
    • +
    +// #enddocregion heroes-styled + +// #docregion selectedHero-click +
  • + {{hero.id}} {{hero.name}} +
  • +// #enddocregion selectedHero-click + +// #docregion selectedHero-details +

    {{selectedHero.name}} details!

    +
    {{selectedHero.id}}
    +
    + + +
    +// #enddocregion selectedHero-details + +// #docregion ng-if +
    +

    {{selectedHero.name}} details!

    +
    {{selectedHero.id}}
    +
    + + +
    +
    +// #enddocregion ng-if + +// #docregion hero-array-1 +final List heroes = mockHeroes; +// #enddocregion hero-array-1 + +// #docregion heroes-template-1 +

    My Heroes

    +
      +
    • + +
    • +
    +// #enddocregion heroes-template-1 + +// #docregion heroes-ngfor-1 +
  • +// #enddocregion heroes-ngfor-1 + +// #docregion class-selected-1 +[class.selected]="hero == selectedHero" +// #enddocregion class-selected-1 + +// #docregion class-selected-2 +
  • + {{hero.id}} {{hero.name}} +
  • +// #enddocregion class-selected-2 diff --git a/aio/content/examples/toh-2/e2e-spec.ts b/aio/content/examples/toh-2/e2e-spec.ts new file mode 100644 index 0000000000..b5df938cd3 --- /dev/null +++ b/aio/content/examples/toh-2/e2e-spec.ts @@ -0,0 +1,133 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by, ElementFinder } from 'protractor'; +import { promise } from 'selenium-webdriver'; + +const expectedH1 = 'Tour of Heroes'; +const expectedTitle = `Angular ${expectedH1}`; +const expectedH2 = 'My Heroes'; +const targetHero = { id: 16, name: 'RubberMan' }; +const nameSuffix = 'X'; + +class Hero { + id: number; + name: string; + + // Factory methods + + // Get hero from s formatted as ' '. + static fromString(s: string): Hero { + return { + id: +s.substr(0, s.indexOf(' ')), + name: s.substr(s.indexOf(' ') + 1), + }; + } + + // Get hero id and name from the given detail element. + static async fromDetail(detail: ElementFinder): Promise { + // Get hero id from the first
    + let _id = await detail.all(by.css('div')).first().getText(); + // Get name from the h2 + let _name = await detail.element(by.css('h2')).getText(); + return { + id: +_id.substr(_id.indexOf(' ') + 1), + name: _name.substr(0, _name.lastIndexOf(' ')) + }; + } +} + +describe('Tutorial part 2', () => { + beforeAll(() => browser.get('')); + describe('Initial page', initialPageTests); + describe('Select hero', selectHeroTests); + describe('Update hero', updateHeroTests); +}); + +function initialPageTests() { + it(`has title '${expectedTitle}'`, () => { + expect(browser.getTitle()).toEqual(expectedTitle); + }); + + it(`has h1 '${expectedH1}'`, () => { + expectHeading(1, expectedH1); + }); + + it(`has h2 '${expectedH2}'`, () => { + expectHeading(2, expectedH2); + }); + + it('has the right number of heroes', () => { + let page = getPageElts(); + expect(page.heroes.count()).toEqual(10); + }); + + it('has no selected hero and no hero details', function () { + let page = getPageElts(); + expect(page.selected.isPresent()).toBeFalsy('selected hero'); + expect(page.heroDetail.isPresent()).toBeFalsy('no hero detail'); + }); +} + +function selectHeroTests() { + it(`selects ${targetHero.name} from hero list`, function () { + let hero = element(by.cssContainingText('li span.badge', targetHero.id.toString())); + hero.click(); + // Nothing specific to expect other than lack of exceptions. + }); + + it(`has selected ${targetHero.name}`, function () { + let page = getPageElts(); + let expectedText = `${targetHero.id} ${targetHero.name}`; + expect(page.selected.getText()).toBe(expectedText); + }); + + it('shows selected hero details', async () => { + let page = getPageElts(); + let hero = await Hero.fromDetail(page.heroDetail); + expect(hero.id).toEqual(targetHero.id); + expect(hero.name).toEqual(targetHero.name); + }); +} + +function updateHeroTests() { + it(`can update hero name`, () => { + addToHeroName(nameSuffix); + // Nothing specific to expect other than lack of exceptions. + }); + + it(`shows updated hero name in details`, async () => { + let page = getPageElts(); + let hero = await Hero.fromDetail(page.heroDetail); + let newName = targetHero.name + nameSuffix; + expect(hero.id).toEqual(targetHero.id); + expect(hero.name).toEqual(newName); + }); + + it(`shows updated hero name in list`, async () => { + let page = getPageElts(); + let hero = Hero.fromString(await page.selected.getText()); + let newName = targetHero.name + nameSuffix; + expect(hero.id).toEqual(targetHero.id); + expect(hero.name).toEqual(newName); + }); + +} + +function addToHeroName(text: string): promise.Promise { + let input = element(by.css('input')); + return input.sendKeys(text); +} + +function expectHeading(hLevel: number, expectedText: string): void { + let hTag = `h${hLevel}`; + let hText = element(by.css(hTag)).getText(); + expect(hText).toEqual(expectedText, hTag); +}; + +function getPageElts() { + return { + heroes: element.all(by.css('my-app li')), + selected: element(by.css('my-app li.selected')), + heroDetail: element(by.css('my-app > div, my-app > my-hero-detail > div')) + }; +} diff --git a/aio/content/examples/toh-2/ts-snippets/app.component.snippets.pt2.ts b/aio/content/examples/toh-2/ts-snippets/app.component.snippets.pt2.ts new file mode 100644 index 0000000000..801c550130 --- /dev/null +++ b/aio/content/examples/toh-2/ts-snippets/app.component.snippets.pt2.ts @@ -0,0 +1,69 @@ +// #docregion ng-for +
  • + {{hero.id}} {{hero.name}} +
  • +// #enddocregion ng-for + +// #docregion heroes-styled +

    My Heroes

    +
      +
    • + {{hero.id}} {{hero.name}} +
    • +
    +// #enddocregion heroes-styled + +// #docregion selectedHero-click +
  • + {{hero.id}} {{hero.name}} +
  • +// #enddocregion selectedHero-click + +// #docregion selectedHero-details +

    {{selectedHero.name}} details!

    +
    {{selectedHero.id}}
    +
    + + +
    +// #enddocregion selectedHero-details + +// #docregion ng-if +
    +

    {{selectedHero.name}} details!

    +
    {{selectedHero.id}}
    +
    + + +
    +
    +// #enddocregion ng-if + +// #docregion hero-array-1 +heroes = HEROES; +// #enddocregion hero-array-1 + +// #docregion heroes-template-1 +

    My Heroes

    +
      +
    • + +
    • +
    +// #enddocregion heroes-template-1 + +// #docregion heroes-ngfor-1 +
  • +// #enddocregion heroes-ngfor-1 + +// #docregion class-selected-1 +[class.selected]="hero === selectedHero" +// #enddocregion class-selected-1 + +// #docregion class-selected-2 +
  • + {{hero.id}} {{hero.name}} +
  • +// #enddocregion class-selected-2 diff --git a/aio/content/examples/toh-2/ts/example-config.json b/aio/content/examples/toh-2/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/toh-2/ts/plnkr.json b/aio/content/examples/toh-2/ts/plnkr.json new file mode 100644 index 0000000000..19c4e7f720 --- /dev/null +++ b/aio/content/examples/toh-2/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Tour of Heroes: Part 2", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js" + ], + "tags": ["tutorial", "tour", "heroes"] +} diff --git a/aio/content/examples/toh-2/ts/plnkr.no-link.html b/aio/content/examples/toh-2/ts/plnkr.no-link.html new file mode 100644 index 0000000000..254fbe58d8 --- /dev/null +++ b/aio/content/examples/toh-2/ts/plnkr.no-link.html @@ -0,0 +1,289 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/toh-2/ts/src/app/app.component.ts b/aio/content/examples/toh-2/ts/src/app/app.component.ts new file mode 100644 index 0000000000..3e7c86f150 --- /dev/null +++ b/aio/content/examples/toh-2/ts/src/app/app.component.ts @@ -0,0 +1,109 @@ +// #docregion +import { Component } from '@angular/core'; + +export class Hero { + id: number; + name: string; +} + +// #docregion hero-array +const HEROES: Hero[] = [ + { id: 11, name: 'Mr. Nice' }, + { id: 12, name: 'Narco' }, + { id: 13, name: 'Bombasto' }, + { id: 14, name: 'Celeritas' }, + { id: 15, name: 'Magneta' }, + { id: 16, name: 'RubberMan' }, + { id: 17, name: 'Dynama' }, + { id: 18, name: 'Dr IQ' }, + { id: 19, name: 'Magma' }, + { id: 20, name: 'Tornado' } +]; +// #enddocregion hero-array + +@Component({ + selector: 'my-app', + template: ` +

    {{title}}

    +

    My Heroes

    +
      +
    • + {{hero.id}} {{hero.name}} +
    • +
    +
    +

    {{selectedHero.name}} details!

    +
    {{selectedHero.id}}
    +
    + + +
    +
    + `, + // #docregion styles + styles: [` + .selected { + background-color: #CFD8DC !important; + color: white; + } + .heroes { + margin: 0 0 2em 0; + list-style-type: none; + padding: 0; + width: 15em; + } + .heroes li { + cursor: pointer; + position: relative; + left: 0; + background-color: #EEE; + margin: .5em; + padding: .3em 0; + height: 1.6em; + border-radius: 4px; + } + .heroes li.selected:hover { + background-color: #BBD8DC !important; + color: white; + } + .heroes li:hover { + color: #607D8B; + background-color: #DDD; + left: .1em; + } + .heroes .text { + position: relative; + top: -3px; + } + .heroes .badge { + display: inline-block; + font-size: small; + color: white; + padding: 0.8em 0.7em 0 0.7em; + background-color: #607D8B; + line-height: 1em; + position: relative; + left: -1px; + top: -4px; + height: 1.8em; + margin-right: .8em; + border-radius: 4px 0 0 4px; + } + `] + // #enddocregion styles +}) +export class AppComponent { + title = 'Tour of Heroes'; + heroes = HEROES; + // #docregion selected-hero + selectedHero: Hero; + // #enddocregion selected-hero + + // #docregion on-select + onSelect(hero: Hero): void { + this.selectedHero = hero; + } + // #enddocregion on-select +} diff --git a/aio/content/examples/toh-2/ts/src/app/app.module.ts b/aio/content/examples/toh-2/ts/src/app/app.module.ts new file mode 100644 index 0000000000..4c0b77ea48 --- /dev/null +++ b/aio/content/examples/toh-2/ts/src/app/app.module.ts @@ -0,0 +1,18 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; + +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule + ], + declarations: [ + AppComponent + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/toh-2/ts/src/index.html b/aio/content/examples/toh-2/ts/src/index.html new file mode 100644 index 0000000000..a217238c6c --- /dev/null +++ b/aio/content/examples/toh-2/ts/src/index.html @@ -0,0 +1,25 @@ + + + + Angular Tour of Heroes + + + + + + + + + + + + + + + + + Loading... + + diff --git a/aio/content/examples/toh-2/ts/src/main.ts b/aio/content/examples/toh-2/ts/src/main.ts new file mode 100644 index 0000000000..80ece654a5 --- /dev/null +++ b/aio/content/examples/toh-2/ts/src/main.ts @@ -0,0 +1,6 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); +// #enddocregion diff --git a/aio/content/examples/toh-3/e2e-spec.ts b/aio/content/examples/toh-3/e2e-spec.ts new file mode 100644 index 0000000000..cce266dcaa --- /dev/null +++ b/aio/content/examples/toh-3/e2e-spec.ts @@ -0,0 +1,133 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by, ElementFinder } from 'protractor'; +import { promise } from 'selenium-webdriver'; + +const expectedH1 = 'Tour of Heroes'; +const expectedTitle = `Angular ${expectedH1}`; +const expectedH2 = 'My Heroes'; +const targetHero = { id: 16, name: 'RubberMan' }; +const nameSuffix = 'X'; + +class Hero { + id: number; + name: string; + + // Factory methods + + // Get hero from s formatted as ' '. + static fromString(s: string): Hero { + return { + id: +s.substr(0, s.indexOf(' ')), + name: s.substr(s.indexOf(' ') + 1), + }; + } + + // Get hero id and name from the given detail element. + static async fromDetail(detail: ElementFinder): Promise { + // Get hero id from the first
    + let _id = await detail.all(by.css('div')).first().getText(); + // Get name from the h2 + let _name = await detail.element(by.css('h2')).getText(); + return { + id: +_id.substr(_id.indexOf(' ') + 1), + name: _name.substr(0, _name.lastIndexOf(' ')) + }; + } +} + +describe('Tutorial part 3', () => { + beforeAll(() => browser.get('')); + describe('Initial page', initialPageTests); + describe('Select hero', selectHeroTests); + describe('Update hero', updateHeroTests); +}); + +function initialPageTests() { + it(`has title '${expectedTitle}'`, () => { + expect(browser.getTitle()).toEqual(expectedTitle); + }); + + it(`has h1 '${expectedH1}'`, () => { + expectHeading(1, expectedH1); + }); + + it(`has h2 '${expectedH2}'`, () => { + expectHeading(2, expectedH2); + }); + + it('has the right number of heroes', () => { + let page = getPageElts(); + expect(page.heroes.count()).toEqual(10); + }); + + it('has no selected hero and no hero details', function () { + let page = getPageElts(); + expect(page.selected.isPresent()).toBeFalsy('selected hero'); + expect(page.heroDetail.isPresent()).toBeFalsy('no hero detail'); + }); +} + +function selectHeroTests() { + it(`selects ${targetHero.name} from hero list`, function () { + let hero = element(by.cssContainingText('li span.badge', targetHero.id.toString())); + hero.click(); + // Nothing specific to expect other than lack of exceptions. + }); + + it(`has selected ${targetHero.name}`, function () { + let page = getPageElts(); + let expectedText = `${targetHero.id} ${targetHero.name}`; + expect(page.selected.getText()).toBe(expectedText); + }); + + it('shows selected hero details', async () => { + let page = getPageElts(); + let hero = await Hero.fromDetail(page.heroDetail); + expect(hero.id).toEqual(targetHero.id); + expect(hero.name).toEqual(targetHero.name); + }); +} + +function updateHeroTests() { + it(`can update hero name`, () => { + addToHeroName(nameSuffix); + // Nothing specific to expect other than lack of exceptions. + }); + + it(`shows updated hero name in details`, async () => { + let page = getPageElts(); + let hero = await Hero.fromDetail(page.heroDetail); + let newName = targetHero.name + nameSuffix; + expect(hero.id).toEqual(targetHero.id); + expect(hero.name).toEqual(newName); + }); + + it(`shows updated hero name in list`, async () => { + let page = getPageElts(); + let hero = Hero.fromString(await page.selected.getText()); + let newName = targetHero.name + nameSuffix; + expect(hero.id).toEqual(targetHero.id); + expect(hero.name).toEqual(newName); + }); + +} + +function addToHeroName(text: string): promise.Promise { + let input = element(by.css('input')); + return input.sendKeys(text); +} + +function expectHeading(hLevel: number, expectedText: string): void { + let hTag = `h${hLevel}`; + let hText = element(by.css(hTag)).getText(); + expect(hText).toEqual(expectedText, hTag); +}; + +function getPageElts() { + return { + heroes: element.all(by.css('my-app li')), + selected: element(by.css('my-app li.selected')), + heroDetail: element(by.css('my-app > div, my-app > my-hero-detail > div')) + }; +} diff --git a/aio/content/examples/toh-3/ts/example-config.json b/aio/content/examples/toh-3/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/toh-3/ts/plnkr.json b/aio/content/examples/toh-3/ts/plnkr.json new file mode 100644 index 0000000000..086f8bd05c --- /dev/null +++ b/aio/content/examples/toh-3/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Tour of Heroes: Part 3", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js" + ], + "tags": ["tutorial", "tour", "heroes"] +} diff --git a/aio/content/examples/toh-3/ts/plnkr.no-link.html b/aio/content/examples/toh-3/ts/plnkr.no-link.html new file mode 100644 index 0000000000..766bb4578e --- /dev/null +++ b/aio/content/examples/toh-3/ts/plnkr.no-link.html @@ -0,0 +1,318 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/toh-3/ts/src/app/app.component.ts b/aio/content/examples/toh-3/ts/src/app/app.component.ts new file mode 100644 index 0000000000..022efc31eb --- /dev/null +++ b/aio/content/examples/toh-3/ts/src/app/app.component.ts @@ -0,0 +1,95 @@ +// #docregion +import { Component } from '@angular/core'; + +// #docregion hero-import +import { Hero } from './hero'; +// #enddocregion hero-import + +const HEROES: Hero[] = [ + { id: 11, name: 'Mr. Nice' }, + { id: 12, name: 'Narco' }, + { id: 13, name: 'Bombasto' }, + { id: 14, name: 'Celeritas' }, + { id: 15, name: 'Magneta' }, + { id: 16, name: 'RubberMan' }, + { id: 17, name: 'Dynama' }, + { id: 18, name: 'Dr IQ' }, + { id: 19, name: 'Magma' }, + { id: 20, name: 'Tornado' } +]; + +@Component({ + selector: 'my-app', +// #docregion hero-detail-template + template: ` +

    {{title}}

    +

    My Heroes

    +
      +
    • + {{hero.id}} {{hero.name}} +
    • +
    + + `, +// #enddocregion hero-detail-template + styles: [` + .selected { + background-color: #CFD8DC !important; + color: white; + } + .heroes { + margin: 0 0 2em 0; + list-style-type: none; + padding: 0; + width: 15em; + } + .heroes li { + cursor: pointer; + position: relative; + left: 0; + background-color: #EEE; + margin: .5em; + padding: .3em 0; + height: 1.6em; + border-radius: 4px; + } + .heroes li.selected:hover { + background-color: #BBD8DC !important; + color: white; + } + .heroes li:hover { + color: #607D8B; + background-color: #DDD; + left: .1em; + } + .heroes .text { + position: relative; + top: -3px; + } + .heroes .badge { + display: inline-block; + font-size: small; + color: white; + padding: 0.8em 0.7em 0 0.7em; + background-color: #607D8B; + line-height: 1em; + position: relative; + left: -1px; + top: -4px; + height: 1.8em; + margin-right: .8em; + border-radius: 4px 0 0 4px; + } + `] +}) +export class AppComponent { + title = 'Tour of Heroes'; + heroes = HEROES; + selectedHero: Hero; + + onSelect(hero: Hero): void { + this.selectedHero = hero; + } +} diff --git a/aio/content/examples/toh-3/ts/src/app/app.module.ts b/aio/content/examples/toh-3/ts/src/app/app.module.ts new file mode 100644 index 0000000000..26b63938ad --- /dev/null +++ b/aio/content/examples/toh-3/ts/src/app/app.module.ts @@ -0,0 +1,24 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; + +import { AppComponent } from './app.component'; +// #docregion hero-detail-import +import { HeroDetailComponent } from './hero-detail.component'; +// #enddocregion hero-detail-import + +// #docregion declarations +@NgModule({ + imports: [ + BrowserModule, + FormsModule + ], + declarations: [ + AppComponent, + HeroDetailComponent + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } +// #enddocregion declarations diff --git a/aio/content/examples/toh-3/ts/src/app/hero-detail.component.ts b/aio/content/examples/toh-3/ts/src/app/hero-detail.component.ts new file mode 100644 index 0000000000..a2f1942904 --- /dev/null +++ b/aio/content/examples/toh-3/ts/src/app/hero-detail.component.ts @@ -0,0 +1,39 @@ +// #docplaster +// #docregion +// #docregion v1 +import { Component, Input } from '@angular/core'; + +// #enddocregion v1 +// #docregion hero-import +import { Hero } from './hero'; +// #enddocregion hero-import + +// #docregion v1 +@Component({ + selector: 'my-hero-detail', +// #enddocregion v1 + // #docregion template + template: ` +
    +

    {{hero.name}} details!

    +
    {{hero.id}}
    +
    + + +
    +
    + ` + // #enddocregion template +// #docregion v1 +}) +export class HeroDetailComponent { +// #enddocregion v1 +// #docregion hero-input + @Input() +// #docregion hero + hero: Hero; +// #enddocregion hero +// #enddocregion hero-input +// #docregion v1 +} +// #enddocregion v1 diff --git a/aio/content/examples/toh-3/ts/src/app/hero.ts b/aio/content/examples/toh-3/ts/src/app/hero.ts new file mode 100644 index 0000000000..f4b0cd6b35 --- /dev/null +++ b/aio/content/examples/toh-3/ts/src/app/hero.ts @@ -0,0 +1,6 @@ +// #docregion +export class Hero { + id: number; + name: string; +} +// #enddocregion diff --git a/aio/content/examples/toh-3/ts/src/index.html b/aio/content/examples/toh-3/ts/src/index.html new file mode 100644 index 0000000000..a217238c6c --- /dev/null +++ b/aio/content/examples/toh-3/ts/src/index.html @@ -0,0 +1,25 @@ + + + + Angular Tour of Heroes + + + + + + + + + + + + + + + + + Loading... + + diff --git a/aio/content/examples/toh-3/ts/src/main.ts b/aio/content/examples/toh-3/ts/src/main.ts new file mode 100644 index 0000000000..aa939b3241 --- /dev/null +++ b/aio/content/examples/toh-3/ts/src/main.ts @@ -0,0 +1,6 @@ +// #docregion pt1 +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); +// #enddocregion pt1 diff --git a/aio/content/examples/toh-4/e2e-spec.ts b/aio/content/examples/toh-4/e2e-spec.ts new file mode 100644 index 0000000000..2307ba17c9 --- /dev/null +++ b/aio/content/examples/toh-4/e2e-spec.ts @@ -0,0 +1,133 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by, ElementFinder } from 'protractor'; +import { promise } from 'selenium-webdriver'; + +const expectedH1 = 'Tour of Heroes'; +const expectedTitle = `Angular ${expectedH1}`; +const expectedH2 = 'My Heroes'; +const targetHero = { id: 16, name: 'RubberMan' }; +const nameSuffix = 'X'; + +class Hero { + id: number; + name: string; + + // Factory methods + + // Get hero from s formatted as ' '. + static fromString(s: string): Hero { + return { + id: +s.substr(0, s.indexOf(' ')), + name: s.substr(s.indexOf(' ') + 1), + }; + } + + // Get hero id and name from the given detail element. + static async fromDetail(detail: ElementFinder): Promise { + // Get hero id from the first
    + let _id = await detail.all(by.css('div')).first().getText(); + // Get name from the h2 + let _name = await detail.element(by.css('h2')).getText(); + return { + id: +_id.substr(_id.indexOf(' ') + 1), + name: _name.substr(0, _name.lastIndexOf(' ')) + }; + } +} + +describe('Tutorial part 4', () => { + beforeAll(() => browser.get('')); + describe('Initial page', initialPageTests); + describe('Select hero', selectHeroTests); + describe('Update hero', updateHeroTests); +}); + +function initialPageTests() { + it(`has title '${expectedTitle}'`, () => { + expect(browser.getTitle()).toEqual(expectedTitle); + }); + + it(`has h1 '${expectedH1}'`, () => { + expectHeading(1, expectedH1); + }); + + it(`has h2 '${expectedH2}'`, () => { + expectHeading(2, expectedH2); + }); + + it('has the right number of heroes', () => { + let page = getPageElts(); + expect(page.heroes.count()).toEqual(10); + }); + + it('has no selected hero and no hero details', function () { + let page = getPageElts(); + expect(page.selected.isPresent()).toBeFalsy('selected hero'); + expect(page.heroDetail.isPresent()).toBeFalsy('no hero detail'); + }); +} + +function selectHeroTests() { + it(`selects ${targetHero.name} from hero list`, function () { + let hero = element(by.cssContainingText('li span.badge', targetHero.id.toString())); + hero.click(); + // Nothing specific to expect other than lack of exceptions. + }); + + it(`has selected ${targetHero.name}`, function () { + let page = getPageElts(); + let expectedText = `${targetHero.id} ${targetHero.name}`; + expect(page.selected.getText()).toBe(expectedText); + }); + + it('shows selected hero details', async () => { + let page = getPageElts(); + let hero = await Hero.fromDetail(page.heroDetail); + expect(hero.id).toEqual(targetHero.id); + expect(hero.name).toEqual(targetHero.name); + }); +} + +function updateHeroTests() { + it(`can update hero name`, () => { + addToHeroName(nameSuffix); + // Nothing specific to expect other than lack of exceptions. + }); + + it(`shows updated hero name in details`, async () => { + let page = getPageElts(); + let hero = await Hero.fromDetail(page.heroDetail); + let newName = targetHero.name + nameSuffix; + expect(hero.id).toEqual(targetHero.id); + expect(hero.name).toEqual(newName); + }); + + it(`shows updated hero name in list`, async () => { + let page = getPageElts(); + let hero = Hero.fromString(await page.selected.getText()); + let newName = targetHero.name + nameSuffix; + expect(hero.id).toEqual(targetHero.id); + expect(hero.name).toEqual(newName); + }); + +} + +function addToHeroName(text: string): promise.Promise { + let input = element(by.css('input')); + return input.sendKeys(text); +} + +function expectHeading(hLevel: number, expectedText: string): void { + let hTag = `h${hLevel}`; + let hText = element(by.css(hTag)).getText(); + expect(hText).toEqual(expectedText, hTag); +}; + +function getPageElts() { + return { + heroes: element.all(by.css('my-app li')), + selected: element(by.css('my-app li.selected')), + heroDetail: element(by.css('my-app > div, my-app > my-hero-detail > div')) + }; +} diff --git a/aio/content/examples/toh-4/ts/example-config.json b/aio/content/examples/toh-4/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/toh-4/ts/plnkr.json b/aio/content/examples/toh-4/ts/plnkr.json new file mode 100644 index 0000000000..95987d95ce --- /dev/null +++ b/aio/content/examples/toh-4/ts/plnkr.json @@ -0,0 +1,10 @@ +{ + "description": "Tour of Heroes: Part 4", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1,2].*" + ], + "tags": ["tutorial", "tour", "heroes"] +} diff --git a/aio/content/examples/toh-4/ts/plnkr.no-link.html b/aio/content/examples/toh-4/ts/plnkr.no-link.html new file mode 100644 index 0000000000..265b4fa6ce --- /dev/null +++ b/aio/content/examples/toh-4/ts/plnkr.no-link.html @@ -0,0 +1,362 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/toh-4/ts/src/app/app.component.1.ts b/aio/content/examples/toh-4/ts/src/app/app.component.1.ts new file mode 100644 index 0000000000..675c7a64d0 --- /dev/null +++ b/aio/content/examples/toh-4/ts/src/app/app.component.1.ts @@ -0,0 +1,65 @@ +// #docplaster +// #docregion on-init +import { OnInit } from '@angular/core'; + +// #enddocregion on-init +import { Component } from '@angular/core'; + +import { Hero } from './hero'; +// #docregion hero-service-import +import { HeroService } from './hero.service.2'; +// #enddocregion hero-service-import + +// Testable but never shown +@Component({ + selector: 'my-app', + template: ` +
    + {{hero.name}} +
    + + `, + // #docregion providers + providers: [HeroService] + // #enddocregion providers +}) +// #docregion on-init +export class AppComponent implements OnInit { + // #enddocregion on-init + title = 'Tour of Heroes'; + // #docregion heroes-prop + heroes: Hero[]; + // #enddocregion heroes-prop + selectedHero: Hero; + + /* + // #docregion new-service + heroService = new HeroService(); // don't do this + // #enddocregion new-service + */ + // #docregion ctor + constructor(private heroService: HeroService) { } + // #enddocregion ctor + // #docregion getHeroes + getHeroes(): void { + // #docregion get-heroes + this.heroes = this.heroService.getHeroes(); + // #enddocregion get-heroes + } + // #enddocregion getHeroes + + // #docregion ng-on-init + // #docregion on-init + ngOnInit(): void { + // #enddocregion on-init + this.getHeroes(); + // #docregion on-init + } + // #enddocregion on-init + // #enddocregion ng-on-init + + onSelect(hero: Hero): void { + this.selectedHero = hero; + } + // #docregion on-init +} diff --git a/aio/content/examples/toh-4/ts/src/app/app.component.ts b/aio/content/examples/toh-4/ts/src/app/app.component.ts new file mode 100644 index 0000000000..b16acb8375 --- /dev/null +++ b/aio/content/examples/toh-4/ts/src/app/app.component.ts @@ -0,0 +1,97 @@ +// #docplaster +// #docregion +import { Component, OnInit } from '@angular/core'; + +import { Hero } from './hero'; +// #docregion hero-service-import +import { HeroService } from './hero.service'; +// #enddocregion hero-service-import + +@Component({ + selector: 'my-app', + // #docregion template + template: ` +

    {{title}}

    +

    My Heroes

    +
      +
    • + {{hero.id}} {{hero.name}} +
    • +
    + + `, + // #enddocregion template + styles: [` + .selected { + background-color: #CFD8DC !important; + color: white; + } + .heroes { + margin: 0 0 2em 0; + list-style-type: none; + padding: 0; + width: 15em; + } + .heroes li { + cursor: pointer; + position: relative; + left: 0; + background-color: #EEE; + margin: .5em; + padding: .3em 0; + height: 1.6em; + border-radius: 4px; + } + .heroes li.selected:hover { + background-color: #BBD8DC !important; + color: white; + } + .heroes li:hover { + color: #607D8B; + background-color: #DDD; + left: .1em; + } + .heroes .text { + position: relative; + top: -3px; + } + .heroes .badge { + display: inline-block; + font-size: small; + color: white; + padding: 0.8em 0.7em 0 0.7em; + background-color: #607D8B; + line-height: 1em; + position: relative; + left: -1px; + top: -4px; + height: 1.8em; + margin-right: .8em; + border-radius: 4px 0 0 4px; + } + `], + providers: [HeroService] +}) +export class AppComponent implements OnInit { + title = 'Tour of Heroes'; + heroes: Hero[]; + selectedHero: Hero; + + constructor(private heroService: HeroService) { } + +// #docregion get-heroes + getHeroes(): void { + this.heroService.getHeroes().then(heroes => this.heroes = heroes); + } +// #enddocregion get-heroes + + ngOnInit(): void { + this.getHeroes(); + } + + onSelect(hero: Hero): void { + this.selectedHero = hero; + } +} diff --git a/aio/content/examples/toh-4/ts/src/app/app.module.ts b/aio/content/examples/toh-4/ts/src/app/app.module.ts new file mode 100644 index 0000000000..3df186c62a --- /dev/null +++ b/aio/content/examples/toh-4/ts/src/app/app.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; + +import { AppComponent } from './app.component'; +import { HeroDetailComponent } from './hero-detail.component'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule + ], + declarations: [ + AppComponent, + HeroDetailComponent + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/toh-4/ts/src/app/hero-detail.component.ts b/aio/content/examples/toh-4/ts/src/app/hero-detail.component.ts new file mode 100644 index 0000000000..b36b0ae36c --- /dev/null +++ b/aio/content/examples/toh-4/ts/src/app/hero-detail.component.ts @@ -0,0 +1,22 @@ +// #docregion +import { Component, Input } from '@angular/core'; +import { Hero } from './hero'; + +@Component({ + selector: 'my-hero-detail', + template: ` +
    +

    {{hero.name}} details!

    +
    + {{hero.id}} +
    +
    + + +
    +
    + ` +}) +export class HeroDetailComponent { + @Input() hero: Hero; +} diff --git a/aio/content/examples/toh-4/ts/src/app/hero.service.1.ts b/aio/content/examples/toh-4/ts/src/app/hero.service.1.ts new file mode 100644 index 0000000000..2366215259 --- /dev/null +++ b/aio/content/examples/toh-4/ts/src/app/hero.service.1.ts @@ -0,0 +1,24 @@ +// #docplaster +// #docregion +// #docregion empty-class, full +import { Injectable } from '@angular/core'; + +// #enddocregion empty-class +import { Hero } from './hero'; +import { HEROES } from './mock-heroes'; + +// #docregion empty-class, getHeroes-stub +@Injectable() +export class HeroService { + // #enddocregion empty-class, getHeroes-stub, full + /* + // #docregion getHeroes-stub + getHeroes(): void {} // stub + // #enddocregion getHeroes-stub + */ + // #docregion full + getHeroes(): Hero[] { + return HEROES; + } + // #docregion empty-class, getHeroes-stub +} diff --git a/aio/content/examples/toh-4/ts/src/app/hero.service.2.ts b/aio/content/examples/toh-4/ts/src/app/hero.service.2.ts new file mode 100644 index 0000000000..d14fe02410 --- /dev/null +++ b/aio/content/examples/toh-4/ts/src/app/hero.service.2.ts @@ -0,0 +1,13 @@ +// #docregion +import { Injectable } from '@angular/core'; + +import { Hero } from './hero'; +import { HEROES } from './mock-heroes'; + +@Injectable() +export class HeroService { + + getHeroes(): Hero[] { + return HEROES; + } +} diff --git a/aio/content/examples/toh-4/ts/src/app/hero.service.ts b/aio/content/examples/toh-4/ts/src/app/hero.service.ts new file mode 100644 index 0000000000..03a1c10a4a --- /dev/null +++ b/aio/content/examples/toh-4/ts/src/app/hero.service.ts @@ -0,0 +1,29 @@ +// #docplaster +// #docregion +// #docregion just-get-heroes +import { Injectable } from '@angular/core'; + +import { Hero } from './hero'; +import { HEROES } from './mock-heroes'; + +@Injectable() +export class HeroService { + // #docregion get-heroes + getHeroes(): Promise { + return Promise.resolve(HEROES); + } + // #enddocregion get-heroes, just-get-heroes + // #enddocregion + + // See the "Take it slow" appendix + // #docregion get-heroes-slowly + getHeroesSlowly(): Promise { + return new Promise(resolve => { + // Simulate server latency with 2 second delay + setTimeout(() => resolve(this.getHeroes()), 2000); + }); + } + // #enddocregion get-heroes-slowly + // #docregion + // #docregion just-get-heroes +} diff --git a/aio/content/examples/toh-4/ts/src/app/hero.ts b/aio/content/examples/toh-4/ts/src/app/hero.ts new file mode 100644 index 0000000000..e3eac516da --- /dev/null +++ b/aio/content/examples/toh-4/ts/src/app/hero.ts @@ -0,0 +1,4 @@ +export class Hero { + id: number; + name: string; +} diff --git a/aio/content/examples/toh-4/ts/src/app/mock-heroes.ts b/aio/content/examples/toh-4/ts/src/app/mock-heroes.ts new file mode 100644 index 0000000000..6f7c5d83a0 --- /dev/null +++ b/aio/content/examples/toh-4/ts/src/app/mock-heroes.ts @@ -0,0 +1,15 @@ +// #docregion +import { Hero } from './hero'; + +export const HEROES: Hero[] = [ + {id: 11, name: 'Mr. Nice'}, + {id: 12, name: 'Narco'}, + {id: 13, name: 'Bombasto'}, + {id: 14, name: 'Celeritas'}, + {id: 15, name: 'Magneta'}, + {id: 16, name: 'RubberMan'}, + {id: 17, name: 'Dynama'}, + {id: 18, name: 'Dr IQ'}, + {id: 19, name: 'Magma'}, + {id: 20, name: 'Tornado'} +]; diff --git a/aio/content/examples/toh-4/ts/src/index.html b/aio/content/examples/toh-4/ts/src/index.html new file mode 100644 index 0000000000..a217238c6c --- /dev/null +++ b/aio/content/examples/toh-4/ts/src/index.html @@ -0,0 +1,25 @@ + + + + Angular Tour of Heroes + + + + + + + + + + + + + + + + + Loading... + + diff --git a/aio/content/examples/toh-4/ts/src/main.1.ts b/aio/content/examples/toh-4/ts/src/main.1.ts new file mode 100644 index 0000000000..f22933ba8e --- /dev/null +++ b/aio/content/examples/toh-4/ts/src/main.1.ts @@ -0,0 +1,4 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/toh-4/ts/src/main.ts b/aio/content/examples/toh-4/ts/src/main.ts new file mode 100644 index 0000000000..f22933ba8e --- /dev/null +++ b/aio/content/examples/toh-4/ts/src/main.ts @@ -0,0 +1,4 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/toh-5/e2e-spec.ts b/aio/content/examples/toh-5/e2e-spec.ts new file mode 100644 index 0000000000..9da4fc7765 --- /dev/null +++ b/aio/content/examples/toh-5/e2e-spec.ts @@ -0,0 +1,190 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by, ElementFinder } from 'protractor'; +import { promise } from 'selenium-webdriver'; + +const expectedH1 = 'Tour of Heroes'; +const expectedTitle = `Angular ${expectedH1}`; +const targetHero = { id: 15, name: 'Magneta' }; +const targetHeroDashboardIndex = 3; +const nameSuffix = 'X'; +const newHeroName = targetHero.name + nameSuffix; + +class Hero { + id: number; + name: string; + + // Factory methods + + // Get hero from s formatted as ' '. + static fromString(s: string): Hero { + return { + id: +s.substr(0, s.indexOf(' ')), + name: s.substr(s.indexOf(' ') + 1), + }; + } + + // Get hero id and name from the given detail element. + static async fromDetail(detail: ElementFinder): Promise { + // Get hero id from the first
    + let _id = await detail.all(by.css('div')).first().getText(); + // Get name from the h2 + let _name = await detail.element(by.css('h2')).getText(); + return { + id: +_id.substr(_id.indexOf(' ') + 1), + name: _name.substr(0, _name.lastIndexOf(' ')) + }; + } +} + +describe('Tutorial part 5', () => { + + beforeAll(() => browser.get('')); + + function getPageElts() { + let navElts = element.all(by.css('my-app nav a')); + + return { + navElts: navElts, + + myDashboardHref: navElts.get(0), + myDashboard: element(by.css('my-app my-dashboard')), + topHeroes: element.all(by.css('my-app my-dashboard > div h4')), + + myHeroesHref: navElts.get(1), + myHeroes: element(by.css('my-app my-heroes')), + allHeroes: element.all(by.css('my-app my-heroes li')), + selectedHero: element(by.css('my-app li.selected')), + selectedHeroSubview: element(by.css('my-app my-heroes > div')), + + heroDetail: element(by.css('my-app my-hero-detail > div')) + }; + } + + describe('Initial page', () => { + + it(`has title '${expectedTitle}'`, () => { + expect(browser.getTitle()).toEqual(expectedTitle); + }); + + it(`has h1 '${expectedH1}'`, () => { + expectHeading(1, expectedH1); + }); + + const expectedViewNames = ['Dashboard', 'Heroes']; + it(`has views ${expectedViewNames}`, () => { + let viewNames = getPageElts().navElts.map((el: ElementFinder) => el.getText()); + expect(viewNames).toEqual(expectedViewNames); + }); + + it('has dashboard as the active view', () => { + let page = getPageElts(); + expect(page.myDashboard.isPresent()).toBeTruthy(); + }); + + }); + + describe('Dashboard tests', () => { + + beforeAll(() => browser.get('')); + + it('has top heroes', () => { + let page = getPageElts(); + expect(page.topHeroes.count()).toEqual(4); + }); + + it(`selects and routes to ${targetHero.name} details`, dashboardSelectTargetHero); + + it(`updates hero name (${newHeroName}) in details view`, updateHeroNameInDetailView); + + it(`saves and shows ${newHeroName} in Dashboard`, () => { + element(by.buttonText('Back')).click(); + let targetHeroElt = getPageElts().topHeroes.get(targetHeroDashboardIndex); + expect(targetHeroElt.getText()).toEqual(newHeroName); + }); + + }); + + describe('Heroes tests', () => { + + beforeAll(() => browser.get('')); + + it('can switch to Heroes view', () => { + getPageElts().myHeroesHref.click(); + let page = getPageElts(); + expect(page.myHeroes.isPresent()).toBeTruthy(); + expect(page.allHeroes.count()).toEqual(10, 'number of heroes'); + }); + + it(`selects and shows ${targetHero.name} as selected in list`, () => { + getHeroLiEltById(targetHero.id).click(); + let expectedText = `${targetHero.id} ${targetHero.name}`; + expect(getPageElts().selectedHero.getText()).toBe(expectedText); + }); + + it('shows selected hero subview', async () => { + let page = getPageElts(); + let title = page.selectedHeroSubview.element(by.css('h2')).getText(); + let expectedTitle = `${targetHero.name.toUpperCase()} is my hero`; + expect(title).toEqual(expectedTitle); + }); + + it('can route to hero details', async () => { + element(by.buttonText('View Details')).click(); + + let page = getPageElts(); + expect(page.heroDetail.isPresent()).toBeTruthy('shows hero detail'); + let hero = await Hero.fromDetail(page.heroDetail); + expect(hero.id).toEqual(targetHero.id); + expect(hero.name).toEqual(targetHero.name); + }); + + it(`updates hero name (${newHeroName}) in details view`, updateHeroNameInDetailView); + + it(`shows ${newHeroName} in Heroes list`, () => { + element(by.buttonText('Back')).click(); + let expectedText = `${targetHero.id} ${newHeroName}`; + expect(getHeroLiEltById(targetHero.id).getText()).toEqual(expectedText); + }); + + }); + + async function dashboardSelectTargetHero() { + let targetHeroElt = getPageElts().topHeroes.get(targetHeroDashboardIndex); + expect(targetHeroElt.getText()).toEqual(targetHero.name); + targetHeroElt.click(); + + let page = getPageElts(); + expect(page.heroDetail.isPresent()).toBeTruthy('shows hero detail'); + let hero = await Hero.fromDetail(page.heroDetail); + expect(hero.id).toEqual(targetHero.id); + expect(hero.name).toEqual(targetHero.name); + } + + async function updateHeroNameInDetailView() { + // Assumes that the current view is the hero details view. + addToHeroName(nameSuffix); + + let page = getPageElts(); + let hero = await Hero.fromDetail(page.heroDetail); + expect(hero.id).toEqual(targetHero.id); + expect(hero.name).toEqual(newHeroName); + } + +}); + +function addToHeroName(text: string): promise.Promise { + let input = element(by.css('input')); + return input.sendKeys(text); +} + +function expectHeading(hLevel: number, expectedText: string): void { + let hTag = `h${hLevel}`; + let hText = element(by.css(hTag)).getText(); + expect(hText).toEqual(expectedText, hTag); +}; + +function getHeroLiEltById(id: number) { + let spanForId = element(by.cssContainingText('li span.badge', id.toString())); + return spanForId.element(by.xpath('..')); +} diff --git a/aio/content/examples/toh-5/ts/example-config.json b/aio/content/examples/toh-5/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/toh-5/ts/plnkr.json b/aio/content/examples/toh-5/ts/plnkr.json new file mode 100644 index 0000000000..db4b15d160 --- /dev/null +++ b/aio/content/examples/toh-5/ts/plnkr.json @@ -0,0 +1,10 @@ +{ + "description": "Tour of Heroes: Part 5", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1,2,3].*" + ], + "tags": ["tutorial", "tour", "heroes", "router"] +} diff --git a/aio/content/examples/toh-5/ts/plnkr.no-link.html b/aio/content/examples/toh-5/ts/plnkr.no-link.html new file mode 100644 index 0000000000..12a5e23630 --- /dev/null +++ b/aio/content/examples/toh-5/ts/plnkr.no-link.html @@ -0,0 +1,661 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/toh-5/ts/src/app/app-routing.module.ts b/aio/content/examples/toh-5/ts/src/app/app-routing.module.ts new file mode 100644 index 0000000000..dfd957782b --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/app-routing.module.ts @@ -0,0 +1,20 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { DashboardComponent } from './dashboard.component'; +import { HeroesComponent } from './heroes.component'; +import { HeroDetailComponent } from './hero-detail.component'; + +const routes: Routes = [ + { path: '', redirectTo: '/dashboard', pathMatch: 'full' }, + { path: 'dashboard', component: DashboardComponent }, + { path: 'detail/:id', component: HeroDetailComponent }, + { path: 'heroes', component: HeroesComponent } +]; + +@NgModule({ + imports: [ RouterModule.forRoot(routes) ], + exports: [ RouterModule ] +}) +export class AppRoutingModule {} diff --git a/aio/content/examples/toh-5/ts/src/app/app.component.1.ts b/aio/content/examples/toh-5/ts/src/app/app.component.1.ts new file mode 100644 index 0000000000..c9f5db9712 --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/app.component.1.ts @@ -0,0 +1,42 @@ +// #docplaster +// #docregion , v2 +import { Component } from '@angular/core'; + +// #enddocregion v2 +@Component({ + selector: 'my-app', + template: ` +

    {{title}}

    + + ` +}) +// #enddocregion +// #docregion v2 +@Component({ + selector: 'my-app', + // #docregion template-v2 + template: ` +

    {{title}}

    + Heroes + + ` + // #enddocregion template-v2 +}) +// #enddocregion +@Component({ + selector: 'my-app', + // #docregion template-v3 + template: ` +

    {{title}}

    + + + ` + // #enddocregion template-v3 +}) +// #docregion , v2 +export class AppComponent { + title = 'Tour of Heroes'; +} diff --git a/aio/content/examples/toh-5/ts/src/app/app.component.css b/aio/content/examples/toh-5/ts/src/app/app.component.css new file mode 100644 index 0000000000..071e665767 --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/app.component.css @@ -0,0 +1,29 @@ +/* #docregion */ +h1 { + font-size: 1.2em; + color: #999; + margin-bottom: 0; +} +h2 { + font-size: 2em; + margin-top: 0; + padding-top: 0; +} +nav a { + padding: 5px 10px; + text-decoration: none; + margin-top: 10px; + display: inline-block; + background-color: #eee; + border-radius: 4px; +} +nav a:visited, a:link { + color: #607D8B; +} +nav a:hover { + color: #039be5; + background-color: #CFD8DC; +} +nav a.active { + color: #039be5; +} diff --git a/aio/content/examples/toh-5/ts/src/app/app.component.ts b/aio/content/examples/toh-5/ts/src/app/app.component.ts new file mode 100644 index 0000000000..c3f92ec46b --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/app.component.ts @@ -0,0 +1,23 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + // #docregion template + template: ` +

    {{title}}

    + + + `, + // #enddocregion template + // #docregion styleUrls + styleUrls: ['./app.component.css'], + // #enddocregion styleUrls +}) +export class AppComponent { + title = 'Tour of Heroes'; +} diff --git a/aio/content/examples/toh-5/ts/src/app/app.module.1.ts b/aio/content/examples/toh-5/ts/src/app/app.module.1.ts new file mode 100644 index 0000000000..e1cda9b620 --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/app.module.1.ts @@ -0,0 +1,28 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; + +import { AppComponent } from './app.component'; +import { HeroDetailComponent } from './hero-detail.component'; +import { HeroesComponent } from './heroes.component'; +import { HeroService } from './hero.service'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule + ], + declarations: [ + AppComponent, + HeroDetailComponent, + HeroesComponent + ], + providers: [ + HeroService + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { +} +// #enddocregion diff --git a/aio/content/examples/toh-5/ts/src/app/app.module.2.ts b/aio/content/examples/toh-5/ts/src/app/app.module.2.ts new file mode 100644 index 0000000000..00876570f3 --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/app.module.2.ts @@ -0,0 +1,48 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { HeroDetailComponent } from './hero-detail.component'; +import { HeroesComponent } from './heroes.component'; +import { HeroService } from './hero.service'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule, + RouterModule.forRoot([ + { + path: 'heroes', + component: HeroesComponent + } + ]) + ], + declarations: [ + AppComponent, + HeroDetailComponent, + HeroesComponent + ], + providers: [ + HeroService + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { +} +// #enddocregion +/* +// #docregion heroes, routing +import { RouterModule } from '@angular/router'; + +RouterModule.forRoot([ + { + path: 'heroes', + component: HeroesComponent + } +]) +// #enddocregion heroes, routing +*/ diff --git a/aio/content/examples/toh-5/ts/src/app/app.module.3.ts b/aio/content/examples/toh-5/ts/src/app/app.module.3.ts new file mode 100644 index 0000000000..306d9958f0 --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/app.module.3.ts @@ -0,0 +1,58 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { HeroDetailComponent } from './hero-detail.component'; +import { DashboardComponent } from './dashboard.component'; +import { HeroesComponent } from './heroes.component'; +import { HeroService } from './hero.service'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule, + RouterModule.forRoot([ + // #docregion redirect + { + path: '', + redirectTo: '/dashboard', + pathMatch: 'full' + }, + // #enddocregion redirect + // #docregion dashboard + { + path: 'dashboard', + component: DashboardComponent + }, + // #enddocregion dashboard + // #docregion hero-detail + { + path: 'detail/:id', + component: HeroDetailComponent + }, + // #enddocregion hero-detail + // #docregion heroes, routing + { + path: 'heroes', + component: HeroesComponent + } + // #enddocregion heroes, routing + ]) + ], + declarations: [ + AppComponent, + DashboardComponent, + HeroDetailComponent, + HeroesComponent + ], + providers: [ + HeroService + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { +} +// #enddocregion diff --git a/aio/content/examples/toh-5/ts/src/app/app.module.ts b/aio/content/examples/toh-5/ts/src/app/app.module.ts new file mode 100644 index 0000000000..b376d69aba --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/app.module.ts @@ -0,0 +1,35 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; + +import { AppComponent } from './app.component'; +import { DashboardComponent } from './dashboard.component'; +import { HeroDetailComponent } from './hero-detail.component'; +import { HeroesComponent } from './heroes.component'; +import { HeroService } from './hero.service'; + +// #docregion routing-module +import { AppRoutingModule } from './app-routing.module'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule, + AppRoutingModule + ], +// #enddocregion routing-module + // #docregion dashboard + declarations: [ + AppComponent, + DashboardComponent, + HeroDetailComponent, + HeroesComponent + ], + // #enddocregion dashboard + providers: [ HeroService ], + bootstrap: [ AppComponent ] +// #docregion routing-module +}) +export class AppModule { } +// #enddocregion routing-module diff --git a/aio/content/examples/toh-5/ts/src/app/dashboard.component.1.html b/aio/content/examples/toh-5/ts/src/app/dashboard.component.1.html new file mode 100644 index 0000000000..0c556b8de0 --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/dashboard.component.1.html @@ -0,0 +1,9 @@ + +

    Top Heroes

    +
    +
    +
    +

    {{hero.name}}

    +
    +
    +
    diff --git a/aio/content/examples/toh-5/ts/src/app/dashboard.component.1.ts b/aio/content/examples/toh-5/ts/src/app/dashboard.component.1.ts new file mode 100644 index 0000000000..3c92b205c8 --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/dashboard.component.1.ts @@ -0,0 +1,8 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-dashboard', + template: '

    My Dashboard

    ' +}) +export class DashboardComponent { } diff --git a/aio/content/examples/toh-5/ts/src/app/dashboard.component.css b/aio/content/examples/toh-5/ts/src/app/dashboard.component.css new file mode 100644 index 0000000000..dc7fb7ce06 --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/dashboard.component.css @@ -0,0 +1,62 @@ +/* #docregion */ +[class*='col-'] { + float: left; + padding-right: 20px; + padding-bottom: 20px; +} +[class*='col-']:last-of-type { + padding-right: 0; +} +a { + text-decoration: none; +} +*, *:after, *:before { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +h3 { + text-align: center; margin-bottom: 0; +} +h4 { + position: relative; +} +.grid { + margin: 0; +} +.col-1-4 { + width: 25%; +} +.module { + padding: 20px; + text-align: center; + color: #eee; + max-height: 120px; + min-width: 120px; + background-color: #607D8B; + border-radius: 2px; +} +.module:hover { + background-color: #EEE; + cursor: pointer; + color: #607d8b; +} +.grid-pad { + padding: 10px 0; +} +.grid-pad > [class*='col-']:last-of-type { + padding-right: 20px; +} +@media (max-width: 600px) { + .module { + font-size: 10px; + max-height: 75px; } +} +@media (max-width: 1024px) { + .grid { + margin: 0; + } + .module { + min-width: 60px; + } +} diff --git a/aio/content/examples/toh-5/ts/src/app/dashboard.component.html b/aio/content/examples/toh-5/ts/src/app/dashboard.component.html new file mode 100644 index 0000000000..49e77c460c --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/dashboard.component.html @@ -0,0 +1,11 @@ + +

    Top Heroes

    + diff --git a/aio/content/examples/toh-5/ts/src/app/dashboard.component.ts b/aio/content/examples/toh-5/ts/src/app/dashboard.component.ts new file mode 100644 index 0000000000..eb026eb6be --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/dashboard.component.ts @@ -0,0 +1,34 @@ +// #docplaster +// #docregion , imports +import { Component, OnInit } from '@angular/core'; + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; +// #enddocregion imports + +// #docregion metadata +@Component({ + moduleId: module.id, + selector: 'my-dashboard', + templateUrl: './dashboard.component.html', + // #enddocregion metadata + // #docregion css + styleUrls: [ './dashboard.component.css' ] + // #enddocregion css + // #docregion metadata +}) +// #enddocregion metadata +// #docregion class +export class DashboardComponent implements OnInit { + + heroes: Hero[] = []; + + // #docregion ctor + constructor(private heroService: HeroService) { } + // #enddocregion ctor + + ngOnInit(): void { + this.heroService.getHeroes() + .then(heroes => this.heroes = heroes.slice(1, 5)); + } +} diff --git a/aio/content/examples/toh-5/ts/src/app/hero-detail.component.1.ts b/aio/content/examples/toh-5/ts/src/app/hero-detail.component.1.ts new file mode 100644 index 0000000000..6713ce4dc7 --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/hero-detail.component.1.ts @@ -0,0 +1,29 @@ +// Imports in comments cause problems when the app is executed +// (some error about 'traceur' missing). Hence this separate file +// is solely for containing the transitory state of the imports. + +// #docregion added-imports +// Keep the Input import for now, we'll remove it later: +import { Component, Input, OnInit } from '@angular/core'; +import { ActivatedRoute, Params } from '@angular/router'; +import { Location } from '@angular/common'; + +import { HeroService } from './hero.service'; +// #enddocregion added-imports + +// Bogus code below this point. It is only here to make lint happy. +import { Hero } from './hero'; + +@Component({}) +export class HeroDetailComponent implements OnInit { + @Input() hero: Hero; + bogus: Params; + + constructor( + private heroService: HeroService, + private route: ActivatedRoute, + private location: Location + ) {} + + ngOnInit() {} +} diff --git a/aio/content/examples/toh-5/ts/src/app/hero-detail.component.css b/aio/content/examples/toh-5/ts/src/app/hero-detail.component.css new file mode 100644 index 0000000000..ab2437efd8 --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/hero-detail.component.css @@ -0,0 +1,30 @@ +/* #docregion */ +label { + display: inline-block; + width: 3em; + margin: .5em 0; + color: #607D8B; + font-weight: bold; +} +input { + height: 2em; + font-size: 1em; + padding-left: .4em; +} +button { + margin-top: 20px; + font-family: Arial; + background-color: #eee; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; cursor: hand; +} +button:hover { + background-color: #cfd8dc; +} +button:disabled { + background-color: #eee; + color: #ccc; + cursor: auto; +} diff --git a/aio/content/examples/toh-5/ts/src/app/hero-detail.component.html b/aio/content/examples/toh-5/ts/src/app/hero-detail.component.html new file mode 100644 index 0000000000..8f2ff9d90c --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/hero-detail.component.html @@ -0,0 +1,14 @@ + + +
    +

    {{hero.name}} details!

    +
    + {{hero.id}}
    +
    + + +
    + + + +
    diff --git a/aio/content/examples/toh-5/ts/src/app/hero-detail.component.ts b/aio/content/examples/toh-5/ts/src/app/hero-detail.component.ts new file mode 100644 index 0000000000..5b9bea89fa --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/hero-detail.component.ts @@ -0,0 +1,47 @@ +// #docplaster +// #docregion , v2, rxjs-import +import 'rxjs/add/operator/switchMap'; +// #enddocregion rxjs-import +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Params } from '@angular/router'; +import { Location } from '@angular/common'; + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; +// #docregion metadata +@Component({ + moduleId: module.id, + selector: 'my-hero-detail', + templateUrl: './hero-detail.component.html', + // #enddocregion metadata, v2 + styleUrls: [ './hero-detail.component.css' ] + // #docregion metadata, v2 +}) +// #enddocregion metadata +// #docregion implement +export class HeroDetailComponent implements OnInit { +// #enddocregion implement + hero: Hero; + + // #docregion ctor + constructor( + private heroService: HeroService, + private route: ActivatedRoute, + private location: Location + ) {} + // #enddocregion ctor + + // #docregion ngOnInit + ngOnInit(): void { + this.route.params + .switchMap((params: Params) => this.heroService.getHero(+params['id'])) + .subscribe(hero => this.hero = hero); + } + // #enddocregion ngOnInit + + // #docregion goBack + goBack(): void { + this.location.back(); + } +// #enddocregion goBack +} diff --git a/aio/content/examples/toh-5/ts/src/app/hero.service.ts b/aio/content/examples/toh-5/ts/src/app/hero.service.ts new file mode 100644 index 0000000000..ee5a684762 --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/hero.service.ts @@ -0,0 +1,26 @@ +// #docplaster +// #docregion +import { Hero } from './hero'; +import { HEROES } from './mock-heroes'; +import { Injectable } from '@angular/core'; + +@Injectable() +export class HeroService { + getHeroes(): Promise { + return Promise.resolve(HEROES); + } + + getHeroesSlowly(): Promise { + return new Promise(resolve => { + // Simulate server latency with 2 second delay + setTimeout(() => resolve(this.getHeroes()), 2000); + }); + } + + // #docregion getHero + getHero(id: number): Promise { + return this.getHeroes() + .then(heroes => heroes.find(hero => hero.id === id)); + } + // #enddocregion getHero +} diff --git a/aio/content/examples/toh-5/ts/src/app/hero.ts b/aio/content/examples/toh-5/ts/src/app/hero.ts new file mode 100644 index 0000000000..e3eac516da --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/hero.ts @@ -0,0 +1,4 @@ +export class Hero { + id: number; + name: string; +} diff --git a/aio/content/examples/toh-5/ts/src/app/heroes.component.css b/aio/content/examples/toh-5/ts/src/app/heroes.component.css new file mode 100644 index 0000000000..35e45af98d --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/heroes.component.css @@ -0,0 +1,59 @@ +.selected { + background-color: #CFD8DC !important; + color: white; +} +.heroes { + margin: 0 0 2em 0; + list-style-type: none; + padding: 0; + width: 15em; +} +.heroes li { + cursor: pointer; + position: relative; + left: 0; + background-color: #EEE; + margin: .5em; + padding: .3em 0; + height: 1.6em; + border-radius: 4px; +} +.heroes li:hover { + color: #607D8B; + background-color: #DDD; + left: .1em; +} +.heroes li.selected:hover { + background-color: #BBD8DC !important; + color: white; +} +.heroes .text { + position: relative; + top: -3px; +} +.heroes .badge { + display: inline-block; + font-size: small; + color: white; + padding: 0.8em 0.7em 0 0.7em; + background-color: #607D8B; + line-height: 1em; + position: relative; + left: -1px; + top: -4px; + height: 1.8em; + margin-right: .8em; + border-radius: 4px 0 0 4px; +} +button { + font-family: Arial; + background-color: #eee; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; + cursor: hand; +} +button:hover { + background-color: #cfd8dc; +} diff --git a/aio/content/examples/toh-5/ts/src/app/heroes.component.html b/aio/content/examples/toh-5/ts/src/app/heroes.component.html new file mode 100644 index 0000000000..db41c4692e --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/heroes.component.html @@ -0,0 +1,19 @@ + + +

    My Heroes

    +
      +
    • + {{hero.id}} {{hero.name}} +
    • +
    + +
    +

    + + {{selectedHero.name | uppercase}} is my hero + +

    + +
    diff --git a/aio/content/examples/toh-5/ts/src/app/heroes.component.ts b/aio/content/examples/toh-5/ts/src/app/heroes.component.ts new file mode 100644 index 0000000000..ab6928addd --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/heroes.component.ts @@ -0,0 +1,49 @@ +// #docplaster +// #docregion +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; + +// #docregion renaming, metadata +@Component({ + // #enddocregion renaming + moduleId: module.id, + // #docregion renaming + selector: 'my-heroes', + // #enddocregion renaming + templateUrl: './heroes.component.html', + styleUrls: [ './heroes.component.css' ] + // #docregion renaming +}) +// #enddocregion metadata +// #docregion class +export class HeroesComponent implements OnInit { + // #enddocregion renaming + heroes: Hero[]; + selectedHero: Hero; + + constructor( + private router: Router, + private heroService: HeroService) { } + + getHeroes(): void { + this.heroService.getHeroes().then(heroes => this.heroes = heroes); + } + + ngOnInit(): void { + this.getHeroes(); + } + + onSelect(hero: Hero): void { + this.selectedHero = hero; + } + + // #docregion gotoDetail + gotoDetail(): void { + this.router.navigate(['/detail', this.selectedHero.id]); + } + // #enddocregion gotoDetail + // #docregion renaming +} diff --git a/aio/content/examples/toh-5/ts/src/app/mock-heroes.ts b/aio/content/examples/toh-5/ts/src/app/mock-heroes.ts new file mode 100644 index 0000000000..69afde3d34 --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/app/mock-heroes.ts @@ -0,0 +1,15 @@ +// #docregion +import { Hero } from './hero'; + +export var HEROES: Hero[] = [ + {id: 11, name: 'Mr. Nice'}, + {id: 12, name: 'Narco'}, + {id: 13, name: 'Bombasto'}, + {id: 14, name: 'Celeritas'}, + {id: 15, name: 'Magneta'}, + {id: 16, name: 'RubberMan'}, + {id: 17, name: 'Dynama'}, + {id: 18, name: 'Dr IQ'}, + {id: 19, name: 'Magma'}, + {id: 20, name: 'Tornado'} +]; diff --git a/aio/content/examples/toh-5/ts/src/index.html b/aio/content/examples/toh-5/ts/src/index.html new file mode 100644 index 0000000000..cbacc9b83b --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/index.html @@ -0,0 +1,33 @@ + + + + + + + + Angular Tour of Heroes + + + + + + + + + + + + + + + + + + + + + Loading... + + diff --git a/aio/content/examples/toh-5/ts/src/main.ts b/aio/content/examples/toh-5/ts/src/main.ts new file mode 100644 index 0000000000..505f60b35b --- /dev/null +++ b/aio/content/examples/toh-5/ts/src/main.ts @@ -0,0 +1,6 @@ +// #docregion +// main entry point +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/toh-6/e2e-spec.ts b/aio/content/examples/toh-6/e2e-spec.ts new file mode 100644 index 0000000000..c4a68aec52 --- /dev/null +++ b/aio/content/examples/toh-6/e2e-spec.ts @@ -0,0 +1,283 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by, ElementFinder, ElementArrayFinder } from 'protractor'; +import { promise } from 'selenium-webdriver'; + +const expectedH1 = 'Tour of Heroes'; +const expectedTitle = `Angular ${expectedH1}`; +const targetHero = { id: 15, name: 'Magneta' }; +const targetHeroDashboardIndex = 3; +const nameSuffix = 'X'; +const newHeroName = targetHero.name + nameSuffix; + +class Hero { + id: number; + name: string; + + // Factory methods + + // Hero from string formatted as ' '. + static fromString(s: string): Hero { + return { + id: +s.substr(0, s.indexOf(' ')), + name: s.substr(s.indexOf(' ') + 1), + }; + } + + // Hero from hero list
  • element. + static async fromLi(li: ElementFinder): Promise { + let strings = await li.all(by.xpath('span')).getText(); + return { id: +strings[0], name: strings[1] }; + } + + // Hero id and name from the given detail element. + static async fromDetail(detail: ElementFinder): Promise { + // Get hero id from the first
    + let _id = await detail.all(by.css('div')).first().getText(); + // Get name from the h2 + let _name = await detail.element(by.css('h2')).getText(); + return { + id: +_id.substr(_id.indexOf(' ') + 1), + name: _name.substr(0, _name.lastIndexOf(' ')) + }; + } +} + +describe('Tutorial part 6', () => { + + beforeAll(() => browser.get('')); + + function getPageElts() { + let navElts = element.all(by.css('my-app nav a')); + + return { + navElts: navElts, + + myDashboardHref: navElts.get(0), + myDashboard: element(by.css('my-app my-dashboard')), + topHeroes: element.all(by.css('my-app my-dashboard > div h4')), + + myHeroesHref: navElts.get(1), + myHeroes: element(by.css('my-app my-heroes')), + allHeroes: element.all(by.css('my-app my-heroes li')), + selectedHero: element(by.css('my-app li.selected')), + selectedHeroSubview: element(by.css('my-app my-heroes > div:last-child')), + + heroDetail: element(by.css('my-app my-hero-detail > div')), + + searchBox: element(by.css('#search-box')), + searchResults: element.all(by.css('.search-result')) + }; + } + + describe('Initial page', () => { + + it(`has title '${expectedTitle}'`, () => { + expect(browser.getTitle()).toEqual(expectedTitle); + }); + + it(`has h1 '${expectedH1}'`, () => { + expectHeading(1, expectedH1); + }); + + const expectedViewNames = ['Dashboard', 'Heroes']; + it(`has views ${expectedViewNames}`, () => { + let viewNames = getPageElts().navElts.map((el: ElementFinder) => el.getText()); + expect(viewNames).toEqual(expectedViewNames); + }); + + it('has dashboard as the active view', () => { + let page = getPageElts(); + expect(page.myDashboard.isPresent()).toBeTruthy(); + }); + + }); + + describe('Dashboard tests', () => { + + beforeAll(() => browser.get('')); + + it('has top heroes', () => { + let page = getPageElts(); + expect(page.topHeroes.count()).toEqual(4); + }); + + it(`selects and routes to ${targetHero.name} details`, dashboardSelectTargetHero); + + it(`updates hero name (${newHeroName}) in details view`, updateHeroNameInDetailView); + + it(`cancels and shows ${targetHero.name} in Dashboard`, () => { + element(by.buttonText('Back')).click(); + browser.waitForAngular(); // seems necessary to gets tests to past for toh-6 + + let targetHeroElt = getPageElts().topHeroes.get(targetHeroDashboardIndex); + expect(targetHeroElt.getText()).toEqual(targetHero.name); + }); + + it(`selects and routes to ${targetHero.name} details`, dashboardSelectTargetHero); + + it(`updates hero name (${newHeroName}) in details view`, updateHeroNameInDetailView); + + it(`saves and shows ${newHeroName} in Dashboard`, () => { + element(by.buttonText('Save')).click(); + browser.waitForAngular(); // seems necessary to gets tests to past for toh-6 + + let targetHeroElt = getPageElts().topHeroes.get(targetHeroDashboardIndex); + expect(targetHeroElt.getText()).toEqual(newHeroName); + }); + + }); + + describe('Heroes tests', () => { + + beforeAll(() => browser.get('')); + + it('can switch to Heroes view', () => { + getPageElts().myHeroesHref.click(); + let page = getPageElts(); + expect(page.myHeroes.isPresent()).toBeTruthy(); + expect(page.allHeroes.count()).toEqual(10, 'number of heroes'); + }); + + it(`selects and shows ${targetHero.name} as selected in list`, () => { + getHeroLiEltById(targetHero.id).click(); + expect(Hero.fromLi(getPageElts().selectedHero)).toEqual(targetHero); + }); + + it('shows selected hero subview', () => { + let page = getPageElts(); + let title = page.selectedHeroSubview.element(by.css('h2')).getText(); + let expectedTitle = `${targetHero.name.toUpperCase()} is my hero`; + expect(title).toEqual(expectedTitle); + }); + + it('can route to hero details', () => { + element(by.buttonText('View Details')).click(); + + let page = getPageElts(); + expect(page.heroDetail.isPresent()).toBeTruthy('shows hero detail'); + let hero = Hero.fromDetail(page.heroDetail); + expect(hero).toEqual(targetHero); + }); + + it(`updates hero name (${newHeroName}) in details view`, updateHeroNameInDetailView); + + it(`shows ${newHeroName} in Heroes list`, () => { + element(by.buttonText('Save')).click(); + browser.waitForAngular(); // seems necessary to gets tests to past for toh-6 + let expectedHero = {id: targetHero.id, name: newHeroName}; + expect(Hero.fromLi(getHeroLiEltById(targetHero.id))).toEqual(expectedHero); + }); + + it(`deletes ${newHeroName} from Heroes list`, async () => { + const heroesBefore = await toHeroArray(getPageElts().allHeroes); + const li = getHeroLiEltById(targetHero.id); + li.element(by.buttonText('x')).click(); + + const page = getPageElts(); + expect(page.myHeroes.isPresent()).toBeTruthy(); + expect(page.allHeroes.count()).toEqual(9, 'number of heroes'); + const heroesAfter = await toHeroArray(page.allHeroes); + const expectedHeroes = heroesBefore.filter(h => h.name !== newHeroName); + expect(heroesAfter).toEqual(expectedHeroes); + expect(page.selectedHeroSubview.isPresent()).toBeFalsy(); + }); + + it(`adds back ${targetHero.name}`, async () => { + const newHeroName = 'Alice'; + const heroesBefore = await toHeroArray(getPageElts().allHeroes); + const numHeroes = heroesBefore.length; + + element(by.css('input')).sendKeys(newHeroName); + element(by.buttonText('Add')).click(); + + let page = getPageElts(); + let heroesAfter = await toHeroArray(page.allHeroes); + expect(heroesAfter.length).toEqual(numHeroes + 1, 'number of heroes'); + + expect(heroesAfter.slice(0, numHeroes)).toEqual(heroesBefore, 'Old heroes are still there'); + + const maxId = heroesBefore[heroesBefore.length - 1].id; + expect(heroesAfter[numHeroes]).toEqual({id: maxId + 1, name: newHeroName}); + }); + }); + + describe('Progressive hero search', () => { + + beforeAll(() => browser.get('')); + + it(`searches for 'Ma'`, async () => { + getPageElts().searchBox.sendKeys('Ma'); + browser.sleep(1000); + expect(getPageElts().searchResults.count()).toBe(4); + }); + + it(`continues search with 'g'`, async () => { + getPageElts().searchBox.sendKeys('g'); + browser.sleep(1000); + expect(getPageElts().searchResults.count()).toBe(2); + }); + + it(`continues search with 'n' and gets ${targetHero.name}`, async () => { + getPageElts().searchBox.sendKeys('n'); + browser.sleep(1000); + let page = getPageElts(); + expect(page.searchResults.count()).toBe(1); + let hero = page.searchResults.get(0); + expect(hero.getText()).toEqual(targetHero.name); + }); + + it(`navigates to ${targetHero.name} details view`, async () => { + let hero = getPageElts().searchResults.get(0); + expect(hero.getText()).toEqual(targetHero.name); + hero.click(); + + let page = getPageElts(); + expect(page.heroDetail.isPresent()).toBeTruthy('shows hero detail'); + expect(Hero.fromDetail(page.heroDetail)).toEqual(targetHero); + }); + }); + + function dashboardSelectTargetHero() { + let targetHeroElt = getPageElts().topHeroes.get(targetHeroDashboardIndex); + expect(targetHeroElt.getText()).toEqual(targetHero.name); + targetHeroElt.click(); + browser.waitForAngular(); // seems necessary to gets tests to past for toh-6 + + let page = getPageElts(); + expect(page.heroDetail.isPresent()).toBeTruthy('shows hero detail'); + let hero = Hero.fromDetail(page.heroDetail); + expect(hero).toEqual(targetHero); + } + + async function updateHeroNameInDetailView() { + // Assumes that the current view is the hero details view. + addToHeroName(nameSuffix); + + let hero = await Hero.fromDetail(getPageElts().heroDetail); + expect(hero).toEqual({id: targetHero.id, name: newHeroName}); + } + +}); + +function addToHeroName(text: string): promise.Promise { + let input = element(by.css('input')); + return input.sendKeys(text); +} + +function expectHeading(hLevel: number, expectedText: string): void { + let hTag = `h${hLevel}`; + let hText = element(by.css(hTag)).getText(); + expect(hText).toEqual(expectedText, hTag); +}; + +function getHeroLiEltById(id: number): ElementFinder { + let spanForId = element(by.cssContainingText('li span.badge', id.toString())); + return spanForId.element(by.xpath('..')); +} + +async function toHeroArray(allHeroes: ElementArrayFinder): Promise { + let promisedHeroes = await allHeroes.map(Hero.fromLi); + // The cast is necessary to get around issuing with the signature of Promise.all() + return > Promise.all(promisedHeroes); +} diff --git a/aio/content/examples/toh-6/ts/.gitignore b/aio/content/examples/toh-6/ts/.gitignore new file mode 100644 index 0000000000..804879d44a --- /dev/null +++ b/aio/content/examples/toh-6/ts/.gitignore @@ -0,0 +1,8 @@ +aot/**/*.ts +**/*.ngfactory.ts +**/*.ngsummary.json +**/*.metadata.json +**/*.js +dist +!app/tsconfig.json +!rollup-config.js diff --git a/aio/content/examples/toh-6/ts/aot/index.html b/aio/content/examples/toh-6/ts/aot/index.html new file mode 100644 index 0000000000..1a64ca8cac --- /dev/null +++ b/aio/content/examples/toh-6/ts/aot/index.html @@ -0,0 +1,22 @@ + + + + + + Angular Tour of Heroes + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/toh-6/ts/aot/styles.css b/aio/content/examples/toh-6/ts/aot/styles.css new file mode 100644 index 0000000000..d81835d0cd --- /dev/null +++ b/aio/content/examples/toh-6/ts/aot/styles.css @@ -0,0 +1,116 @@ +/* #docregion , quickstart, toh */ +/* Master Styles */ +h1 { + color: #369; + font-family: Arial, Helvetica, sans-serif; + font-size: 250%; +} +h2, h3 { + color: #444; + font-family: Arial, Helvetica, sans-serif; + font-weight: lighter; +} +body { + margin: 2em; +} +/* #enddocregion quickstart */ +body, input[text], button { + color: #888; + font-family: Cambria, Georgia; +} +/* #enddocregion toh */ +a { + cursor: pointer; + cursor: hand; +} +button { + font-family: Arial; + background-color: #eee; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; + cursor: hand; +} +button:hover { + background-color: #cfd8dc; +} +button:disabled { + background-color: #eee; + color: #aaa; + cursor: auto; +} + +/* Navigation link styles */ +nav a { + padding: 5px 10px; + text-decoration: none; + margin-right: 10px; + margin-top: 10px; + display: inline-block; + background-color: #eee; + border-radius: 4px; +} +nav a:visited, a:link { + color: #607D8B; +} +nav a:hover { + color: #039be5; + background-color: #CFD8DC; +} +nav a.active { + color: #039be5; +} + +/* items class */ +.items { + margin: 0 0 2em 0; + list-style-type: none; + padding: 0; + width: 24em; +} +.items li { + cursor: pointer; + position: relative; + left: 0; + background-color: #EEE; + margin: .5em; + padding: .3em 0; + height: 1.6em; + border-radius: 4px; +} +.items li:hover { + color: #607D8B; + background-color: #DDD; + left: .1em; +} +.items li.selected { + background-color: #CFD8DC; + color: white; +} +.items li.selected:hover { + background-color: #BBD8DC; +} +.items .text { + position: relative; + top: -3px; +} +.items .badge { + display: inline-block; + font-size: small; + color: white; + padding: 0.8em 0.7em 0 0.7em; + background-color: #607D8B; + line-height: 1em; + position: relative; + left: -1px; + top: -4px; + height: 1.8em; + margin-right: .8em; + border-radius: 4px 0 0 4px; +} +/* #docregion toh */ +/* everywhere else */ +* { + font-family: Arial, Helvetica, sans-serif; +} diff --git a/aio/content/examples/toh-6/ts/bs-config.aot.json b/aio/content/examples/toh-6/ts/bs-config.aot.json new file mode 100644 index 0000000000..e59a7403a0 --- /dev/null +++ b/aio/content/examples/toh-6/ts/bs-config.aot.json @@ -0,0 +1,14 @@ +{ + "open": false, + "logLevel": "silent", + "port": 8080, + "server": { + "baseDir": "aot", + "routes": { + "/node_modules": "node_modules" + }, + "middleware": { + "0": null + } + } +} diff --git a/aio/content/examples/toh-6/ts/example-config.json b/aio/content/examples/toh-6/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/toh-6/ts/plnkr.json b/aio/content/examples/toh-6/ts/plnkr.json new file mode 100644 index 0000000000..d355bc9ff2 --- /dev/null +++ b/aio/content/examples/toh-6/ts/plnkr.json @@ -0,0 +1,10 @@ +{ + "description": "Tour of Heroes: Part 6", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1,2].*" + ], + "tags": ["tutorial", "tour", "heroes", "http"] +} diff --git a/aio/content/examples/toh-6/ts/plnkr.no-link.html b/aio/content/examples/toh-6/ts/plnkr.no-link.html new file mode 100644 index 0000000000..0407009dc2 --- /dev/null +++ b/aio/content/examples/toh-6/ts/plnkr.no-link.html @@ -0,0 +1,883 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/toh-6/ts/rollup-config.js b/aio/content/examples/toh-6/ts/rollup-config.js new file mode 100644 index 0000000000..d5d07e07e5 --- /dev/null +++ b/aio/content/examples/toh-6/ts/rollup-config.js @@ -0,0 +1,32 @@ +// #docregion +import rollup from 'rollup' +import nodeResolve from 'rollup-plugin-node-resolve' +import commonjs from 'rollup-plugin-commonjs'; +import uglify from 'rollup-plugin-uglify' + +//paths are relative to the execution path +export default { + entry: 'src/main-aot.js', + dest: 'aot/dist/build.js', // output a single application bundle + sourceMap: true, + sourceMapFile: 'aot/dist/build.js.map', + format: 'iife', + onwarn: function(warning) { + // Skip certain warnings + + // should intercept ... but doesn't in some rollup versions + if ( warning.code === 'THIS_IS_UNDEFINED' ) { return; } + // intercepts in some rollup versions + if ( warning.indexOf("The 'this' keyword is equivalent to 'undefined'") > -1 ) { return; } + + // console.warn everything else + console.warn( warning.message ); + }, + plugins: [ + nodeResolve({jsnext: true, module: true}), + commonjs({ + include: ['node_modules/rxjs/**'] + }), + uglify() + ] +} diff --git a/aio/content/examples/toh-6/ts/src/app/app-routing.module.ts b/aio/content/examples/toh-6/ts/src/app/app-routing.module.ts new file mode 100644 index 0000000000..bc070f6c31 --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/app/app-routing.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { DashboardComponent } from './dashboard.component'; +import { HeroesComponent } from './heroes.component'; +import { HeroDetailComponent } from './hero-detail.component'; + +const routes: Routes = [ + { path: '', redirectTo: '/dashboard', pathMatch: 'full' }, + { path: 'dashboard', component: DashboardComponent }, + { path: 'detail/:id', component: HeroDetailComponent }, + { path: 'heroes', component: HeroesComponent } +]; + +@NgModule({ + imports: [ RouterModule.forRoot(routes) ], + exports: [ RouterModule ] +}) +export class AppRoutingModule {} diff --git a/aio/content/examples/toh-6/ts/src/app/app.component.css b/aio/content/examples/toh-6/ts/src/app/app.component.css new file mode 100644 index 0000000000..071e665767 --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/app/app.component.css @@ -0,0 +1,29 @@ +/* #docregion */ +h1 { + font-size: 1.2em; + color: #999; + margin-bottom: 0; +} +h2 { + font-size: 2em; + margin-top: 0; + padding-top: 0; +} +nav a { + padding: 5px 10px; + text-decoration: none; + margin-top: 10px; + display: inline-block; + background-color: #eee; + border-radius: 4px; +} +nav a:visited, a:link { + color: #607D8B; +} +nav a:hover { + color: #039be5; + background-color: #CFD8DC; +} +nav a.active { + color: #039be5; +} diff --git a/aio/content/examples/toh-6/ts/src/app/app.component.ts b/aio/content/examples/toh-6/ts/src/app/app.component.ts new file mode 100644 index 0000000000..d8a8507985 --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/app/app.component.ts @@ -0,0 +1,20 @@ +// #docplaster +// #docregion +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + template: ` +

    {{title}}

    + + + `, + styleUrls: ['./app.component.css'] +}) +export class AppComponent { + title = 'Tour of Heroes'; +} diff --git a/aio/content/examples/toh-6/ts/src/app/app.module.ts b/aio/content/examples/toh-6/ts/src/app/app.module.ts new file mode 100644 index 0000000000..58eeb10c54 --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/app/app.module.ts @@ -0,0 +1,52 @@ +// #docplaster +// #docregion +// #docregion v1, v2 +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; +import { HttpModule } from '@angular/http'; + +import { AppRoutingModule } from './app-routing.module'; + +// #enddocregion v1 +// Imports for loading & configuring the in-memory web api +import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; +import { InMemoryDataService } from './in-memory-data.service'; + +// #docregion v1 +import { AppComponent } from './app.component'; +import { DashboardComponent } from './dashboard.component'; +import { HeroesComponent } from './heroes.component'; +import { HeroDetailComponent } from './hero-detail.component'; +import { HeroService } from './hero.service'; +// #enddocregion v1, v2 +import { HeroSearchComponent } from './hero-search.component'; +// #docregion v1, v2 + +@NgModule({ + imports: [ + BrowserModule, + FormsModule, + HttpModule, + // #enddocregion v1 + // #docregion in-mem-web-api + InMemoryWebApiModule.forRoot(InMemoryDataService), + // #enddocregion in-mem-web-api + // #docregion v1 + AppRoutingModule + ], + // #docregion search + declarations: [ + AppComponent, + DashboardComponent, + HeroDetailComponent, + HeroesComponent, + // #enddocregion v1, v2 + HeroSearchComponent + // #docregion v1, v2 + ], + // #enddocregion search + providers: [ HeroService ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/toh-6/ts/src/app/dashboard.component.css b/aio/content/examples/toh-6/ts/src/app/dashboard.component.css new file mode 100644 index 0000000000..dc7fb7ce06 --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/app/dashboard.component.css @@ -0,0 +1,62 @@ +/* #docregion */ +[class*='col-'] { + float: left; + padding-right: 20px; + padding-bottom: 20px; +} +[class*='col-']:last-of-type { + padding-right: 0; +} +a { + text-decoration: none; +} +*, *:after, *:before { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +h3 { + text-align: center; margin-bottom: 0; +} +h4 { + position: relative; +} +.grid { + margin: 0; +} +.col-1-4 { + width: 25%; +} +.module { + padding: 20px; + text-align: center; + color: #eee; + max-height: 120px; + min-width: 120px; + background-color: #607D8B; + border-radius: 2px; +} +.module:hover { + background-color: #EEE; + cursor: pointer; + color: #607d8b; +} +.grid-pad { + padding: 10px 0; +} +.grid-pad > [class*='col-']:last-of-type { + padding-right: 20px; +} +@media (max-width: 600px) { + .module { + font-size: 10px; + max-height: 75px; } +} +@media (max-width: 1024px) { + .grid { + margin: 0; + } + .module { + min-width: 60px; + } +} diff --git a/aio/content/examples/toh-6/ts/src/app/dashboard.component.html b/aio/content/examples/toh-6/ts/src/app/dashboard.component.html new file mode 100644 index 0000000000..db8546ccd2 --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/app/dashboard.component.html @@ -0,0 +1,10 @@ + +

    Top Heroes

    + + diff --git a/aio/content/examples/toh-6/ts/src/app/dashboard.component.ts b/aio/content/examples/toh-6/ts/src/app/dashboard.component.ts new file mode 100644 index 0000000000..1eb93f9c01 --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/app/dashboard.component.ts @@ -0,0 +1,23 @@ +// #docregion , search +import { Component, OnInit } from '@angular/core'; + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; + +@Component({ + moduleId: module.id, + selector: 'my-dashboard', + templateUrl: './dashboard.component.html', + styleUrls: [ './dashboard.component.css' ] +}) +// #enddocregion search +export class DashboardComponent implements OnInit { + heroes: Hero[] = []; + + constructor(private heroService: HeroService) { } + + ngOnInit(): void { + this.heroService.getHeroes() + .then(heroes => this.heroes = heroes.slice(1, 5)); + } +} diff --git a/aio/content/examples/toh-6/ts/src/app/hero-detail.component.css b/aio/content/examples/toh-6/ts/src/app/hero-detail.component.css new file mode 100644 index 0000000000..ab2437efd8 --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/app/hero-detail.component.css @@ -0,0 +1,30 @@ +/* #docregion */ +label { + display: inline-block; + width: 3em; + margin: .5em 0; + color: #607D8B; + font-weight: bold; +} +input { + height: 2em; + font-size: 1em; + padding-left: .4em; +} +button { + margin-top: 20px; + font-family: Arial; + background-color: #eee; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; cursor: hand; +} +button:hover { + background-color: #cfd8dc; +} +button:disabled { + background-color: #eee; + color: #ccc; + cursor: auto; +} diff --git a/aio/content/examples/toh-6/ts/src/app/hero-detail.component.html b/aio/content/examples/toh-6/ts/src/app/hero-detail.component.html new file mode 100644 index 0000000000..32fe6d4391 --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/app/hero-detail.component.html @@ -0,0 +1,14 @@ + +
    +

    {{hero.name}} details!

    +
    + {{hero.id}}
    +
    + + +
    + + + + +
    diff --git a/aio/content/examples/toh-6/ts/src/app/hero-detail.component.ts b/aio/content/examples/toh-6/ts/src/app/hero-detail.component.ts new file mode 100644 index 0000000000..3f1c7b72f9 --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/app/hero-detail.component.ts @@ -0,0 +1,41 @@ +// #docregion +import 'rxjs/add/operator/switchMap'; +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Params } from '@angular/router'; +import { Location } from '@angular/common'; + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; + +@Component({ + moduleId: module.id, + selector: 'my-hero-detail', + templateUrl: './hero-detail.component.html', + styleUrls: [ './hero-detail.component.css' ] +}) +export class HeroDetailComponent implements OnInit { + hero: Hero; + + constructor( + private heroService: HeroService, + private route: ActivatedRoute, + private location: Location + ) {} + + ngOnInit(): void { + this.route.params + .switchMap((params: Params) => this.heroService.getHero(+params['id'])) + .subscribe(hero => this.hero = hero); + } + + // #docregion save + save(): void { + this.heroService.update(this.hero) + .then(() => this.goBack()); + } + // #enddocregion save + + goBack(): void { + this.location.back(); + } +} diff --git a/aio/content/examples/toh-6/ts/src/app/hero-search.component.css b/aio/content/examples/toh-6/ts/src/app/hero-search.component.css new file mode 100644 index 0000000000..741b379356 --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/app/hero-search.component.css @@ -0,0 +1,16 @@ +/* #docregion */ +.search-result{ + border-bottom: 1px solid gray; + border-left: 1px solid gray; + border-right: 1px solid gray; + width:195px; + height: 20px; + padding: 5px; + background-color: white; + cursor: pointer; +} + +#search-box{ + width: 200px; + height: 20px; +} diff --git a/aio/content/examples/toh-6/ts/src/app/hero-search.component.html b/aio/content/examples/toh-6/ts/src/app/hero-search.component.html new file mode 100644 index 0000000000..08c0560c5b --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/app/hero-search.component.html @@ -0,0 +1,11 @@ + +
    +

    Hero Search

    + +
    +
    + {{hero.name}} +
    +
    +
    diff --git a/aio/content/examples/toh-6/ts/src/app/hero-search.component.ts b/aio/content/examples/toh-6/ts/src/app/hero-search.component.ts new file mode 100644 index 0000000000..949ef897db --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/app/hero-search.component.ts @@ -0,0 +1,70 @@ +// #docplaster +// #docregion +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +// #docregion rxjs-imports +import { Observable } from 'rxjs/Observable'; +import { Subject } from 'rxjs/Subject'; + +// Observable class extensions +import 'rxjs/add/observable/of'; + +// Observable operators +import 'rxjs/add/operator/catch'; +import 'rxjs/add/operator/debounceTime'; +import 'rxjs/add/operator/distinctUntilChanged'; +// #enddocregion rxjs-imports + +import { HeroSearchService } from './hero-search.service'; +import { Hero } from './hero'; + +@Component({ + moduleId: module.id, + selector: 'hero-search', + templateUrl: './hero-search.component.html', + styleUrls: [ './hero-search.component.css' ], + providers: [HeroSearchService] +}) +export class HeroSearchComponent implements OnInit { + // #docregion search + heroes: Observable; + // #enddocregion search + // #docregion searchTerms + private searchTerms = new Subject(); + // #enddocregion searchTerms + + constructor( + private heroSearchService: HeroSearchService, + private router: Router) {} + // #docregion searchTerms + + // Push a search term into the observable stream. + search(term: string): void { + this.searchTerms.next(term); + } + // #enddocregion searchTerms + // #docregion search + + ngOnInit(): void { + this.heroes = this.searchTerms + .debounceTime(300) // wait 300ms after each keystroke before considering the term + .distinctUntilChanged() // ignore if next search term is same as previous + .switchMap(term => term // switch to new observable each time the term changes + // return the http search observable + ? this.heroSearchService.search(term) + // or the observable of empty heroes if there was no search term + : Observable.of([])) + .catch(error => { + // TODO: add real error handling + console.log(error); + return Observable.of([]); + }); + } + // #enddocregion search + + gotoDetail(hero: Hero): void { + let link = ['/detail', hero.id]; + this.router.navigate(link); + } +} diff --git a/aio/content/examples/toh-6/ts/src/app/hero-search.service.ts b/aio/content/examples/toh-6/ts/src/app/hero-search.service.ts new file mode 100644 index 0000000000..d24e0fba41 --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/app/hero-search.service.ts @@ -0,0 +1,20 @@ +// #docregion +import { Injectable } from '@angular/core'; +import { Http } from '@angular/http'; + +import { Observable } from 'rxjs/Observable'; +import 'rxjs/add/operator/map'; + +import { Hero } from './hero'; + +@Injectable() +export class HeroSearchService { + + constructor(private http: Http) {} + + search(term: string): Observable { + return this.http + .get(`app/heroes/?name=${term}`) + .map(response => response.json().data as Hero[]); + } +} diff --git a/aio/content/examples/toh-6/ts/src/app/hero.service.ts b/aio/content/examples/toh-6/ts/src/app/hero.service.ts new file mode 100644 index 0000000000..18af476123 --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/app/hero.service.ts @@ -0,0 +1,87 @@ +// #docplaster +// #docregion , imports +import { Injectable } from '@angular/core'; +import { Headers, Http } from '@angular/http'; + +// #docregion rxjs +import 'rxjs/add/operator/toPromise'; +// #enddocregion rxjs + +import { Hero } from './hero'; +// #enddocregion imports + +@Injectable() +export class HeroService { + + // #docregion update + private headers = new Headers({'Content-Type': 'application/json'}); + // #enddocregion update + // #docregion getHeroes + private heroesUrl = 'api/heroes'; // URL to web api + + constructor(private http: Http) { } + + getHeroes(): Promise { + return this.http.get(this.heroesUrl) + // #docregion to-promise + .toPromise() + // #enddocregion to-promise + // #docregion to-data + .then(response => response.json().data as Hero[]) + // #enddocregion to-data + // #docregion catch + .catch(this.handleError); + // #enddocregion catch + } + + // #enddocregion getHeroes + + // #docregion getHero + getHero(id: number): Promise { + const url = `${this.heroesUrl}/${id}`; + return this.http.get(url) + .toPromise() + .then(response => response.json().data as Hero) + .catch(this.handleError); + } + // #enddocregion getHero + + // #docregion delete + delete(id: number): Promise { + const url = `${this.heroesUrl}/${id}`; + return this.http.delete(url, {headers: this.headers}) + .toPromise() + .then(() => null) + .catch(this.handleError); + } + // #enddocregion delete + + // #docregion create + create(name: string): Promise { + return this.http + .post(this.heroesUrl, JSON.stringify({name: name}), {headers: this.headers}) + .toPromise() + .then(res => res.json().data) + .catch(this.handleError); + } + // #enddocregion create + // #docregion update + + update(hero: Hero): Promise { + const url = `${this.heroesUrl}/${hero.id}`; + return this.http + .put(url, JSON.stringify(hero), {headers: this.headers}) + .toPromise() + .then(() => hero) + .catch(this.handleError); + } + // #enddocregion update + + // #docregion getHeroes, handleError + private handleError(error: any): Promise { + console.error('An error occurred', error); // for demo purposes only + return Promise.reject(error.message || error); + } + // #enddocregion getHeroes, handleError +} + diff --git a/aio/content/examples/toh-6/ts/src/app/hero.ts b/aio/content/examples/toh-6/ts/src/app/hero.ts new file mode 100644 index 0000000000..e3eac516da --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/app/hero.ts @@ -0,0 +1,4 @@ +export class Hero { + id: number; + name: string; +} diff --git a/aio/content/examples/toh-6/ts/src/app/heroes.component.css b/aio/content/examples/toh-6/ts/src/app/heroes.component.css new file mode 100644 index 0000000000..d2c958a911 --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/app/heroes.component.css @@ -0,0 +1,68 @@ +/* #docregion */ +.selected { + background-color: #CFD8DC !important; + color: white; +} +.heroes { + margin: 0 0 2em 0; + list-style-type: none; + padding: 0; + width: 15em; +} +.heroes li { + cursor: pointer; + position: relative; + left: 0; + background-color: #EEE; + margin: .5em; + padding: .3em 0; + height: 1.6em; + border-radius: 4px; +} +.heroes li:hover { + color: #607D8B; + background-color: #DDD; + left: .1em; +} +.heroes li.selected:hover { + background-color: #BBD8DC !important; + color: white; +} +.heroes .text { + position: relative; + top: -3px; +} +.heroes .badge { + display: inline-block; + font-size: small; + color: white; + padding: 0.8em 0.7em 0 0.7em; + background-color: #607D8B; + line-height: 1em; + position: relative; + left: -1px; + top: -4px; + height: 1.8em; + margin-right: .8em; + border-radius: 4px 0 0 4px; +} +button { + font-family: Arial; + background-color: #eee; + border: none; + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; + cursor: hand; +} +button:hover { + background-color: #cfd8dc; +} +/* #docregion additions */ +button.delete { + float:right; + margin-top: 2px; + margin-right: .8em; + background-color: gray !important; + color:white; +} diff --git a/aio/content/examples/toh-6/ts/src/app/heroes.component.html b/aio/content/examples/toh-6/ts/src/app/heroes.component.html new file mode 100644 index 0000000000..392d241d52 --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/app/heroes.component.html @@ -0,0 +1,29 @@ + +

    My Heroes

    + +
    + + +
    + +
      + +
    • + {{hero.id}} + {{hero.name}} + + + +
    • + +
    +
    +

    + {{selectedHero.name | uppercase}} is my hero +

    + +
    diff --git a/aio/content/examples/toh-6/ts/src/app/heroes.component.ts b/aio/content/examples/toh-6/ts/src/app/heroes.component.ts new file mode 100644 index 0000000000..2a92adfaa8 --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/app/heroes.component.ts @@ -0,0 +1,62 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; + +@Component({ + moduleId: module.id, + selector: 'my-heroes', + templateUrl: './heroes.component.html', + styleUrls: [ './heroes.component.css' ] +}) +export class HeroesComponent implements OnInit { + heroes: Hero[]; + selectedHero: Hero; + + constructor( + private heroService: HeroService, + private router: Router) { } + + getHeroes(): void { + this.heroService + .getHeroes() + .then(heroes => this.heroes = heroes); + } + + // #docregion add + add(name: string): void { + name = name.trim(); + if (!name) { return; } + this.heroService.create(name) + .then(hero => { + this.heroes.push(hero); + this.selectedHero = null; + }); + } + // #enddocregion add + + // #docregion delete + delete(hero: Hero): void { + this.heroService + .delete(hero.id) + .then(() => { + this.heroes = this.heroes.filter(h => h !== hero); + if (this.selectedHero === hero) { this.selectedHero = null; } + }); + } + // #enddocregion delete + + ngOnInit(): void { + this.getHeroes(); + } + + onSelect(hero: Hero): void { + this.selectedHero = hero; + } + + gotoDetail(): void { + this.router.navigate(['/detail', this.selectedHero.id]); + } +} diff --git a/aio/content/examples/toh-6/ts/src/app/in-memory-data.service.ts b/aio/content/examples/toh-6/ts/src/app/in-memory-data.service.ts new file mode 100644 index 0000000000..c915955e22 --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/app/in-memory-data.service.ts @@ -0,0 +1,19 @@ +// #docregion , init +import { InMemoryDbService } from 'angular-in-memory-web-api'; +export class InMemoryDataService implements InMemoryDbService { + createDb() { + let heroes = [ + {id: 11, name: 'Mr. Nice'}, + {id: 12, name: 'Narco'}, + {id: 13, name: 'Bombasto'}, + {id: 14, name: 'Celeritas'}, + {id: 15, name: 'Magneta'}, + {id: 16, name: 'RubberMan'}, + {id: 17, name: 'Dynama'}, + {id: 18, name: 'Dr IQ'}, + {id: 19, name: 'Magma'}, + {id: 20, name: 'Tornado'} + ]; + return {heroes}; + } +} diff --git a/aio/content/examples/toh-6/ts/src/index.html b/aio/content/examples/toh-6/ts/src/index.html new file mode 100644 index 0000000000..18977969bb --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/index.html @@ -0,0 +1,26 @@ + + + + + + Angular Tour of Heroes + + + + + + + + + + + + + + + + Loading... + + diff --git a/aio/content/examples/toh-6/ts/src/main-aot.ts b/aio/content/examples/toh-6/ts/src/main-aot.ts new file mode 100644 index 0000000000..bd2ca604a3 --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/main-aot.ts @@ -0,0 +1,6 @@ +// #docregion +import { platformBrowser } from '@angular/platform-browser'; + +import { AppModuleNgFactory } from '../aot/src/app/app.module.ngfactory'; + +platformBrowser().bootstrapModuleFactory(AppModuleNgFactory); diff --git a/aio/content/examples/toh-6/ts/src/main.ts b/aio/content/examples/toh-6/ts/src/main.ts new file mode 100644 index 0000000000..f332d1d245 --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/main.ts @@ -0,0 +1,6 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/toh-6/ts/src/tsconfig.1.json b/aio/content/examples/toh-6/ts/src/tsconfig.1.json new file mode 100644 index 0000000000..05839ec2ff --- /dev/null +++ b/aio/content/examples/toh-6/ts/src/tsconfig.1.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ "es2015", "dom" ], + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true, + "typeRoots": [ + "../../../node_modules/@types/" + ] + }, + "compileOnSave": true, + "exclude": [ + "node_modules/*", + "**/*-aot.ts" + ] +} diff --git a/aio/content/examples/toh-6/ts/tsconfig-aot.json b/aio/content/examples/toh-6/ts/tsconfig-aot.json new file mode 100644 index 0000000000..fe1e6df520 --- /dev/null +++ b/aio/content/examples/toh-6/ts/tsconfig-aot.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "es2015", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": ["es2015", "dom"], + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true, + "typeRoots": [ + "../../node_modules/@types/" + ] + }, + + "files": [ + "src/app/app.module.ts", + "src/main-aot.ts" + ], + + "angularCompilerOptions": { + "genDir": "aot", + "skipMetadataEmit" : true + } +} diff --git a/aio/content/examples/toh-6/ts/tsconfig-aot.json.annotated b/aio/content/examples/toh-6/ts/tsconfig-aot.json.annotated new file mode 100644 index 0000000000..d11a88c599 --- /dev/null +++ b/aio/content/examples/toh-6/ts/tsconfig-aot.json.annotated @@ -0,0 +1,28 @@ +// #docregion +{ + "compilerOptions": { + "target": "es5", + "module": "es2015", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": ["es2015", "dom"], + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true, + "typeRoots": [ + "../../node_modules/@types/" + ] + }, + + "files": [ + "src/app/app.module.ts", + "src/main-aot.ts" + ], + + "angularCompilerOptions": { + "genDir": "aot", + "skipMetadataEmit" : true + } +} +// #enddocregion \ No newline at end of file diff --git a/aio/content/examples/tsconfig.json b/aio/content/examples/tsconfig.json new file mode 100644 index 0000000000..41a6efb13d --- /dev/null +++ b/aio/content/examples/tsconfig.json @@ -0,0 +1,24 @@ +// this tsconfig is used for protractor tests +// see _boilerplate/tsconfig.json for the the tsconfig used in examples +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": ["es2015", "dom"], + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true, + "typeRoots": [ + "node_modules/@types" + ] + }, + "files": [ + "protractor-helpers.ts" + ], + "include": [ + "*/e2e-spec.ts" + ] +} diff --git a/aio/content/examples/upgrade-module/e2e-spec.ts b/aio/content/examples/upgrade-module/e2e-spec.ts new file mode 100644 index 0000000000..0c45fb78b8 --- /dev/null +++ b/aio/content/examples/upgrade-module/e2e-spec.ts @@ -0,0 +1,182 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; +import { setProtractorToHybridMode } from '../protractor-helpers'; + +describe('Upgrade Tests', function () { + + beforeAll(function () { + setProtractorToHybridMode(); + }); + + describe('AngularJS Auto-bootstrap', function() { + + beforeAll(function () { + browser.get('/index-ng-app.html'); + }); + + it('bootstraps as expected', function () { + expect(element(by.css('#message')).getText()).toEqual('Hello world'); + }); + + }); + + describe('AngularJS JavaScript Bootstrap', function() { + + beforeAll(function () { + browser.get('/index-bootstrap.html'); + }); + + it('bootstraps as expected', function () { + expect(element(by.css('#message')).getText()).toEqual('Hello world'); + }); + + }); + + describe('AngularJS-Angular Hybrid Bootstrap', function() { + + beforeAll(function () { + browser.get('/index-ajs-a-hybrid-bootstrap.html'); + }); + + it('bootstraps as expected', function () { + expect(element(by.css('#message')).getText()).toEqual('Hello world'); + }); + + }); + + describe('Upgraded static component', function() { + + beforeAll(function () { + browser.get('/index-upgrade-static.html'); + }); + + it('renders', function () { + expect(element(by.css('h2')).getText()).toEqual('Windstorm details!'); + }); + + }); + + + describe('Upgraded component with IO', function() { + + beforeAll(function () { + browser.get('/index-upgrade-io.html'); + }); + + it('has inputs', function () { + expect(element(by.css('h2')).getText()).toEqual('Windstorm details!'); + }); + + it('has outputs', function () { + element(by.buttonText('Delete')).click(); + expect(element(by.css('h2')).getText()).toEqual('Ex-Windstorm details!'); + }); + + }); + + + describe('Downgraded static component', function() { + + beforeAll(function () { + browser.get('/index-downgrade-static.html'); + }); + + it('renders', function () { + expect(element(by.css('h2')).getText()).toEqual('Windstorm details!'); + }); + + }); + + describe('Downgraded component with IO', function() { + + beforeAll(function () { + browser.get('/index-downgrade-io.html'); + }); + + it('has inputs', function () { + expect(element.all(by.css('h2')).first().getText()).toEqual('Windstorm details!'); + }); + + it('has outputs', function () { + element.all(by.buttonText('Delete')).first().click(); + expect(element.all(by.css('h2')).first().getText()).toEqual('Ex-Windstorm details!'); + }); + + it('supports ng-repeat', function () { + expect(element.all(by.css('hero-detail')).count()).toBe(3); + }); + + }); + + + describe('Downgraded component with content projection', function() { + + beforeAll(function () { + browser.get('/index-ajs-to-a-projection.html'); + }); + + it('can be transcluded into', function () { + expect(element(by.css('hero-detail')).getText()).toContain('Specific powers of controlling winds'); + }); + + }); + + + describe('Upgraded component with transclusion', function() { + + beforeAll(function () { + browser.get('/index-a-to-ajs-transclusion.html'); + }); + + it('can be projected into', function () { + expect(element(by.css('hero-detail')).getText()).toContain('Specific powers of controlling winds'); + }); + + }); + + + describe('Upgrading AngularJS Providers', function() { + + beforeAll(function () { + browser.get('/index-ajs-to-a-providers.html'); + }); + + it('works', function () { + expect(element(by.css('h2')).getText()).toBe('1: Windstorm'); + }); + + }); + + + describe('Downgrading Angular Providers', function() { + + beforeAll(function () { + browser.get('/index-a-to-ajs-providers.html'); + }); + + it('works', function () { + expect(element(by.css('h2')).getText()).toBe('1: Windstorm'); + }); + + }); + + describe('Dividing routes', function() { + + beforeAll(function () { + browser.get('/index-divide-routes.html'); + }); + + it('allows ng1 routes', function () { + browser.get('/index-divide-routes.html#/villain'); + expect(element(by.css('h2')).getText()).toBe('Mr. Nice - No More Mr. Nice Guy'); + }); + + it('allows ng2 routes', function () { + browser.get('/index-divide-routes.html#/hero'); + expect(element(by.css('h2')).getText()).toBe('Windstorm - Specific powers of controlling winds'); + }); + + }); + +}); diff --git a/aio/content/examples/upgrade-module/ts/.gitignore b/aio/content/examples/upgrade-module/ts/.gitignore new file mode 100644 index 0000000000..7f5c313a3e --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/.gitignore @@ -0,0 +1,7 @@ +**/*.js +aot/**/* +!aot/bs-config.json +!aot/index.html +!copy-dist-files.js +!rollup-config.js +!systemjs.config.1.js diff --git a/aio/content/examples/upgrade-module/ts/example-config.json b/aio/content/examples/upgrade-module/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/upgrade-module/ts/src/app/a-to-ajs-providers/app.module.ts b/aio/content/examples/upgrade-module/ts/src/app/a-to-ajs-providers/app.module.ts new file mode 100644 index 0000000000..91235a1485 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/a-to-ajs-providers/app.module.ts @@ -0,0 +1,34 @@ +declare var angular: angular.IAngularStatic; +import { NgModule } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { BrowserModule } from '@angular/platform-browser'; +import { UpgradeModule } from '@angular/upgrade/static'; + +import { heroDetailComponent } from './hero-detail.component'; + +// #docregion ngmodule +import { Heroes } from './heroes'; + +@NgModule({ + imports: [ + BrowserModule, + UpgradeModule + ], + providers: [ Heroes ] +}) +export class AppModule { + ngDoBootstrap() {} +} +// #enddocregion ngmodule +// #docregion register +import { downgradeInjectable } from '@angular/upgrade/static'; + +angular.module('heroApp', []) + .factory('heroes', downgradeInjectable(Heroes)) + .component('heroDetail', heroDetailComponent); +// #enddocregion register + +platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => { + const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule; + upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true}); +}); diff --git a/aio/content/examples/upgrade-module/ts/src/app/a-to-ajs-providers/hero-detail.component.ts b/aio/content/examples/upgrade-module/ts/src/app/a-to-ajs-providers/hero-detail.component.ts new file mode 100644 index 0000000000..dd7aa10d79 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/a-to-ajs-providers/hero-detail.component.ts @@ -0,0 +1,11 @@ +import { Heroes } from './heroes'; + +// #docregion +export const heroDetailComponent = { + template: ` +

    {{$ctrl.hero.id}}: {{$ctrl.hero.name}}

    + `, + controller: ['heroes', function(heroes: Heroes) { + this.hero = heroes.get()[0]; + }] +}; diff --git a/aio/content/examples/upgrade-module/ts/src/app/a-to-ajs-providers/heroes.ts b/aio/content/examples/upgrade-module/ts/src/app/a-to-ajs-providers/heroes.ts new file mode 100644 index 0000000000..f5f6d87ed8 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/a-to-ajs-providers/heroes.ts @@ -0,0 +1,13 @@ +// #docregion +import { Injectable } from '@angular/core'; +import { Hero } from '../hero'; + +@Injectable() +export class Heroes { + get() { + return [ + new Hero(1, 'Windstorm'), + new Hero(2, 'Spiderman') + ]; + } +} diff --git a/aio/content/examples/upgrade-module/ts/src/app/a-to-ajs-transclusion/app.module.ts b/aio/content/examples/upgrade-module/ts/src/app/a-to-ajs-transclusion/app.module.ts new file mode 100644 index 0000000000..599e711b5e --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/a-to-ajs-transclusion/app.module.ts @@ -0,0 +1,39 @@ +declare var angular: angular.IAngularStatic; +import { NgModule } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { BrowserModule } from '@angular/platform-browser'; +import { UpgradeModule, downgradeComponent } from '@angular/upgrade/static'; + +import { heroDetail, HeroDetailDirective } from './hero-detail.component'; +import { ContainerComponent } from './container.component'; + +// #docregion heroupgrade +@NgModule({ + imports: [ + BrowserModule, + UpgradeModule + ], + declarations: [ + ContainerComponent, + HeroDetailDirective + ], + entryComponents: [ + ContainerComponent + ] +}) +export class AppModule { + ngDoBootstrap() {} +} +// #enddocregion heroupgrade + +angular.module('heroApp', []) + .component('heroDetail', heroDetail) + .directive( + 'myContainer', + downgradeComponent({component: ContainerComponent}) as angular.IDirectiveFactory + ); + +platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => { + const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule; + upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true}); +}); diff --git a/aio/content/examples/upgrade-module/ts/src/app/a-to-ajs-transclusion/container.component.ts b/aio/content/examples/upgrade-module/ts/src/app/a-to-ajs-transclusion/container.component.ts new file mode 100644 index 0000000000..1b740d6554 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/a-to-ajs-transclusion/container.component.ts @@ -0,0 +1,16 @@ +// #docregion +import { Component } from '@angular/core'; +import { Hero } from '../hero'; + +@Component({ + selector: 'my-container', + template: ` + + +

    {{hero.description}}

    +
    + ` +}) +export class ContainerComponent { + hero = new Hero(1, 'Windstorm', 'Specific powers of controlling winds'); +} diff --git a/aio/content/examples/upgrade-module/ts/src/app/a-to-ajs-transclusion/hero-detail.component.ts b/aio/content/examples/upgrade-module/ts/src/app/a-to-ajs-transclusion/hero-detail.component.ts new file mode 100644 index 0000000000..a1bec385e0 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/a-to-ajs-transclusion/hero-detail.component.ts @@ -0,0 +1,28 @@ +// #docregion +export const heroDetail = { + bindings: { + hero: '=' + }, + template: ` +

    {{$ctrl.hero.name}}

    +
    + +
    + ` +}; +// #enddocregion + +import { Directive, ElementRef, Injector, Input } from '@angular/core'; +import { UpgradeComponent } from '@angular/upgrade/static'; +import { Hero } from '../hero'; + +@Directive({ + selector: 'hero-detail' +}) +export class HeroDetailDirective extends UpgradeComponent { + @Input() hero: Hero; + + constructor(elementRef: ElementRef, injector: Injector) { + super('heroDetail', elementRef, injector); + } +} diff --git a/aio/content/examples/upgrade-module/ts/src/app/ajs-a-hybrid-bootstrap/app.module.ts b/aio/content/examples/upgrade-module/ts/src/app/ajs-a-hybrid-bootstrap/app.module.ts new file mode 100644 index 0000000000..7a6b82cf92 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/ajs-a-hybrid-bootstrap/app.module.ts @@ -0,0 +1,29 @@ +declare var angular: angular.IAngularStatic; +// #docregion ngmodule +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { UpgradeModule } from '@angular/upgrade/static'; + +@NgModule({ + imports: [ + BrowserModule, + UpgradeModule + ] +}) +export class AppModule { + ngDoBootstrap() {} +} +// #enddocregion ngmodule +angular.module('heroApp', []) + .controller('MainCtrl', function() { + this.message = 'Hello world'; + }); + +// #docregion bootstrap +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => { + const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule; + upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true}); +}); +// #enddocregion bootstrap diff --git a/aio/content/examples/upgrade-module/ts/src/app/ajs-bootstrap/app.module.ts b/aio/content/examples/upgrade-module/ts/src/app/ajs-bootstrap/app.module.ts new file mode 100644 index 0000000000..639b780d1b --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/ajs-bootstrap/app.module.ts @@ -0,0 +1,10 @@ +angular.module('heroApp', []) + .controller('MainCtrl', function() { + this.message = 'Hello world'; + }); + +document.addEventListener('DOMContentLoaded', function() { + // #docregion bootstrap + angular.bootstrap(document.body, ['heroApp'], {strictDi: true}); + // #enddocregion bootstrap +}); diff --git a/aio/content/examples/upgrade-module/ts/src/app/ajs-ng-app/app.module.ts b/aio/content/examples/upgrade-module/ts/src/app/ajs-ng-app/app.module.ts new file mode 100644 index 0000000000..904f7578b8 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/ajs-ng-app/app.module.ts @@ -0,0 +1,4 @@ +angular.module('heroApp', []) + .controller('MainCtrl', function() { + this.message = 'Hello world'; + }); diff --git a/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-projection/app.module.ts b/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-projection/app.module.ts new file mode 100644 index 0000000000..080f80f0ef --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-projection/app.module.ts @@ -0,0 +1,36 @@ +declare var angular: angular.IAngularStatic; +import { NgModule } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { BrowserModule } from '@angular/platform-browser'; +import { UpgradeModule, downgradeComponent } from '@angular/upgrade/static'; + +import { MainController } from './main.controller'; +import { HeroDetailComponent } from './hero-detail.component'; + +@NgModule({ + imports: [ + BrowserModule, + UpgradeModule + ], + declarations: [ + HeroDetailComponent + ], + entryComponents: [ + HeroDetailComponent + ] +}) +export class AppModule { + ngDoBootstrap() {} +} + +angular.module('heroApp', []) + .controller('MainController', MainController) + .directive('heroDetail', downgradeComponent({ + component: HeroDetailComponent, + inputs: ['hero'] + }) as angular.IDirectiveFactory); + +platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => { + const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule; + upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true}); +}); diff --git a/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-projection/hero-detail.component.ts b/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-projection/hero-detail.component.ts new file mode 100644 index 0000000000..7a2956eb26 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-projection/hero-detail.component.ts @@ -0,0 +1,16 @@ +// #docregion +import { Component, Input } from '@angular/core'; +import { Hero } from '../hero'; + +@Component({ + selector: 'hero-detail', + template: ` +

    {{hero.name}}

    +
    + +
    + ` +}) +export class HeroDetailComponent { + @Input() hero: Hero; +} diff --git a/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-projection/main.controller.ts b/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-projection/main.controller.ts new file mode 100644 index 0000000000..c6041ac788 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-projection/main.controller.ts @@ -0,0 +1,5 @@ +import { Hero } from '../hero'; + +export class MainController { + hero = new Hero(1, 'Windstorm', 'Specific powers of controlling winds'); +} diff --git a/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-providers/ajs-upgraded-providers.ts b/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-providers/ajs-upgraded-providers.ts new file mode 100644 index 0000000000..52d4e74a1f --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-providers/ajs-upgraded-providers.ts @@ -0,0 +1,12 @@ +// #docregion +import { HeroesService } from './heroes.service'; + +export function heroesServiceFactory(i: any) { + return i.get('heroes'); +} + +export const heroesServiceProvider = { + provide: HeroesService, + useFactory: heroesServiceFactory, + deps: ['$injector'] +}; diff --git a/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-providers/app.module.ts b/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-providers/app.module.ts new file mode 100644 index 0000000000..4e0eca003a --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-providers/app.module.ts @@ -0,0 +1,44 @@ +declare var angular: angular.IAngularStatic; +import { NgModule } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { BrowserModule } from '@angular/platform-browser'; +import { UpgradeModule, downgradeComponent } from '@angular/upgrade/static'; + +import { HeroDetailComponent } from './hero-detail.component'; +import { HeroesService } from './heroes.service'; +// #docregion register +import { heroesServiceProvider } from './ajs-upgraded-providers'; + +@NgModule({ + imports: [ + BrowserModule, + UpgradeModule + ], + providers: [ + heroesServiceProvider + ], + // #enddocregion register + declarations: [ + HeroDetailComponent + ], + entryComponents: [ + HeroDetailComponent + ] +// #docregion register +}) +export class AppModule { + ngDoBootstrap() {} +} +// #enddocregion register + +angular.module('heroApp', []) + .service('heroes', HeroesService) + .directive( + 'heroDetail', + downgradeComponent({component: HeroDetailComponent}) as angular.IDirectiveFactory + ); + +platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => { + const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule; + upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true}); +}); diff --git a/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-providers/hero-detail.component.ts b/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-providers/hero-detail.component.ts new file mode 100644 index 0000000000..b02f85b99a --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-providers/hero-detail.component.ts @@ -0,0 +1,17 @@ +// #docregion +import { Component } from '@angular/core'; +import { HeroesService } from './heroes.service'; +import { Hero } from '../hero'; + +@Component({ + selector: 'hero-detail', + template: ` +

    {{hero.id}}: {{hero.name}}

    + ` +}) +export class HeroDetailComponent { + hero: Hero; + constructor(heroes: HeroesService) { + this.hero = heroes.get()[0]; + } +} diff --git a/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-providers/heroes.service.ts b/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-providers/heroes.service.ts new file mode 100644 index 0000000000..4a258e205a --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/ajs-to-a-providers/heroes.service.ts @@ -0,0 +1,11 @@ +// #docregion +import { Hero } from '../hero'; + +export class HeroesService { + get() { + return [ + new Hero(1, 'Windstorm'), + new Hero(2, 'Spiderman') + ]; + } +} diff --git a/aio/content/examples/upgrade-module/ts/src/app/divide-routes/app.component.ts b/aio/content/examples/upgrade-module/ts/src/app/divide-routes/app.component.ts new file mode 100644 index 0000000000..1fae3f84c6 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/divide-routes/app.component.ts @@ -0,0 +1,11 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: ` + +
    + `, +}) +export class AppComponent { } diff --git a/aio/content/examples/upgrade-module/ts/src/app/divide-routes/app.module.ts b/aio/content/examples/upgrade-module/ts/src/app/divide-routes/app.module.ts new file mode 100644 index 0000000000..7d85a23c94 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/divide-routes/app.module.ts @@ -0,0 +1,62 @@ +// #docregion +declare var angular: angular.IAngularStatic; +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { UpgradeModule } from '@angular/upgrade/static'; + +import { HeroModule } from './hero.module'; + +// #docregion router-config +import { HashLocationStrategy, LocationStrategy } from '@angular/common'; +import { RouterModule, UrlHandlingStrategy, UrlTree } from '@angular/router'; +import { AppComponent } from './app.component'; + +class HybridUrlHandlingStrategy implements UrlHandlingStrategy { + // use only process the `/hero` url + shouldProcessUrl(url: UrlTree) { return url.toString().startsWith('/hero'); } + extract(url: UrlTree) { return url; } + merge(url: UrlTree, whole: UrlTree) { return url; } +} + +@NgModule({ + imports: [ + BrowserModule, + UpgradeModule, + HeroModule, + RouterModule.forRoot([]) + ], + providers: [ + // use hash location strategy + { provide: LocationStrategy, useClass: HashLocationStrategy }, + // use custom url handling strategy + { provide: UrlHandlingStrategy, useClass: HybridUrlHandlingStrategy } + ], + declarations: [ AppComponent ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } +// #enddocregion router-config + +import { Villain } from '../villain'; + +export const villainDetail = { + template: ` +

    Villain detail

    +

    {{$ctrl.villain.name}} - {{$ctrl.villain.description}}

    + `, + controller: function() { + this.villain = new Villain(1, 'Mr. Nice', 'No More Mr. Nice Guy'); + } +}; + +angular.module('heroApp', ['ngRoute']) + .component('villainDetail', villainDetail) + .config(['$locationProvider', '$routeProvider', + function config($locationProvider: angular.ILocationProvider, + $routeProvider: angular.route.IRouteProvider) { + // #docregion ajs-route + $routeProvider + .when('/villain', { template: '' }); + // #enddocregion ajs-route + } + ]); diff --git a/aio/content/examples/upgrade-module/ts/src/app/divide-routes/hero.module.ts b/aio/content/examples/upgrade-module/ts/src/app/divide-routes/hero.module.ts new file mode 100644 index 0000000000..33099d0a9f --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/divide-routes/hero.module.ts @@ -0,0 +1,32 @@ +// #docregion +import { Component } from '@angular/core'; +import { Hero } from '../hero'; + +@Component({ + template: ` +

    Hero detail

    +

    {{hero.name}} - {{hero.description}}

    + ` +}) +export class HeroDetailComponent { + hero = new Hero(1, 'Windstorm', 'Specific powers of controlling winds'); +} + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; + +@NgModule({ + imports: [ + CommonModule, + // #docregion a-route + RouterModule.forChild([ + { path: 'hero', children: [ + { path: '', component: HeroDetailComponent }, + ] }, + ]) + // #enddocregion a-route + ], + declarations: [ HeroDetailComponent ] +}) +export class HeroModule {} diff --git a/aio/content/examples/upgrade-module/ts/src/app/divide-routes/main.ts b/aio/content/examples/upgrade-module/ts/src/app/divide-routes/main.ts new file mode 100644 index 0000000000..9b4d37ebc3 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/divide-routes/main.ts @@ -0,0 +1,10 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { UpgradeModule } from '@angular/upgrade/static'; + +import { AppModule } from './app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => { + const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule; + upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true}); +}); diff --git a/aio/content/examples/upgrade-module/ts/src/app/downgrade-io/app.module.ts b/aio/content/examples/upgrade-module/ts/src/app/downgrade-io/app.module.ts new file mode 100644 index 0000000000..1e836cfc9d --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/downgrade-io/app.module.ts @@ -0,0 +1,43 @@ +declare var angular: angular.IAngularStatic; +import { NgModule } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { BrowserModule } from '@angular/platform-browser'; +import { UpgradeModule, downgradeComponent } from '@angular/upgrade/static'; + +import { MainController } from './main.controller'; + +// #docregion downgradecomponent +import { HeroDetailComponent } from './hero-detail.component'; + +// #enddocregion downgradecomponent +@NgModule({ + imports: [ + BrowserModule, + UpgradeModule + ], + declarations: [ + HeroDetailComponent + ], + entryComponents: [ + HeroDetailComponent + ] +}) +export class AppModule { + ngDoBootstrap() {} +} +// #docregion downgradecomponent + +angular.module('heroApp', []) + .controller('MainController', MainController) + .directive('heroDetail', downgradeComponent({ + component: HeroDetailComponent, + inputs: ['hero'], + outputs: ['deleted'] + }) as angular.IDirectiveFactory); + +// #enddocregion downgradecomponent + +platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => { + const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule; + upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true}); +}); diff --git a/aio/content/examples/upgrade-module/ts/src/app/downgrade-io/hero-detail.component.ts b/aio/content/examples/upgrade-module/ts/src/app/downgrade-io/hero-detail.component.ts new file mode 100644 index 0000000000..94082813fd --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/downgrade-io/hero-detail.component.ts @@ -0,0 +1,19 @@ +// #docregion +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { Hero } from '../hero'; + +@Component({ + selector: 'hero-detail', + template: ` +

    {{hero.name}} details!

    +
    {{hero.id}}
    + + ` +}) +export class HeroDetailComponent { + @Input() hero: Hero; + @Output() deleted = new EventEmitter(); + onDelete() { + this.deleted.emit(this.hero); + } +} diff --git a/aio/content/examples/upgrade-module/ts/src/app/downgrade-io/main.controller.ts b/aio/content/examples/upgrade-module/ts/src/app/downgrade-io/main.controller.ts new file mode 100644 index 0000000000..d50272073f --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/downgrade-io/main.controller.ts @@ -0,0 +1,12 @@ +import { Hero } from '../hero'; + +export class MainController { + hero = new Hero(1, 'Windstorm'); + heroes = [ + new Hero(2, 'Superman'), + new Hero(3, 'Spiderman') + ]; + onDelete(hero: Hero) { + hero.name = 'Ex-' + hero.name; + } +} diff --git a/aio/content/examples/upgrade-module/ts/src/app/downgrade-static/app.module.ts b/aio/content/examples/upgrade-module/ts/src/app/downgrade-static/app.module.ts new file mode 100644 index 0000000000..4d621d2f03 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/downgrade-static/app.module.ts @@ -0,0 +1,42 @@ +declare var angular: angular.IAngularStatic; +import { NgModule } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { BrowserModule } from '@angular/platform-browser'; +import { UpgradeModule } from '@angular/upgrade/static'; + +// #docregion downgradecomponent, ngmodule +import { HeroDetailComponent } from './hero-detail.component'; + +// #enddocregion downgradecomponent +@NgModule({ + imports: [ + BrowserModule, + UpgradeModule + ], + declarations: [ + HeroDetailComponent + ], + entryComponents: [ + HeroDetailComponent + ] +}) +export class AppModule { + ngDoBootstrap() {} +} +// #enddocregion ngmodule +// #docregion downgradecomponent + +import { downgradeComponent } from '@angular/upgrade/static'; + +angular.module('heroApp', []) + .directive( + 'heroDetail', + downgradeComponent({component: HeroDetailComponent}) as angular.IDirectiveFactory + ); + +// #enddocregion downgradecomponent + +platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => { + const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule; + upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true}); +}); diff --git a/aio/content/examples/upgrade-module/ts/src/app/downgrade-static/hero-detail.component.ts b/aio/content/examples/upgrade-module/ts/src/app/downgrade-static/hero-detail.component.ts new file mode 100644 index 0000000000..df4a705f37 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/downgrade-static/hero-detail.component.ts @@ -0,0 +1,11 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'hero-detail', + template: ` +

    Windstorm details!

    +
    1
    + ` +}) +export class HeroDetailComponent { } diff --git a/aio/content/examples/upgrade-module/ts/src/app/hero-detail.directive.ts b/aio/content/examples/upgrade-module/ts/src/app/hero-detail.directive.ts new file mode 100644 index 0000000000..e1b14d2dfc --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/hero-detail.directive.ts @@ -0,0 +1,22 @@ +// #docregion +export function heroDetailDirective() { + return { + restrict: 'E', + scope: {}, + bindToController: { + hero: '=', + deleted: '&' + }, + template: ` +

    {{ctrl.hero.name}} details!

    +
    {{ctrl.hero.id}}
    + + `, + controller: function() { + this.onDelete = () => { + this.deleted({hero: this.hero}); + }; + }, + controllerAs: 'ctrl' + }; +} diff --git a/aio/content/examples/upgrade-module/ts/src/app/hero.ts b/aio/content/examples/upgrade-module/ts/src/app/hero.ts new file mode 100644 index 0000000000..5dcb5664eb --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/hero.ts @@ -0,0 +1,5 @@ +export class Hero { + constructor(public id: number, + public name: string, + public description?: string) { } +} diff --git a/aio/content/examples/upgrade-module/ts/src/app/upgrade-io/app.module.ts b/aio/content/examples/upgrade-module/ts/src/app/upgrade-io/app.module.ts new file mode 100644 index 0000000000..599e711b5e --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/upgrade-io/app.module.ts @@ -0,0 +1,39 @@ +declare var angular: angular.IAngularStatic; +import { NgModule } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { BrowserModule } from '@angular/platform-browser'; +import { UpgradeModule, downgradeComponent } from '@angular/upgrade/static'; + +import { heroDetail, HeroDetailDirective } from './hero-detail.component'; +import { ContainerComponent } from './container.component'; + +// #docregion heroupgrade +@NgModule({ + imports: [ + BrowserModule, + UpgradeModule + ], + declarations: [ + ContainerComponent, + HeroDetailDirective + ], + entryComponents: [ + ContainerComponent + ] +}) +export class AppModule { + ngDoBootstrap() {} +} +// #enddocregion heroupgrade + +angular.module('heroApp', []) + .component('heroDetail', heroDetail) + .directive( + 'myContainer', + downgradeComponent({component: ContainerComponent}) as angular.IDirectiveFactory + ); + +platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => { + const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule; + upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true}); +}); diff --git a/aio/content/examples/upgrade-module/ts/src/app/upgrade-io/container.component.ts b/aio/content/examples/upgrade-module/ts/src/app/upgrade-io/container.component.ts new file mode 100644 index 0000000000..8d76085174 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/upgrade-io/container.component.ts @@ -0,0 +1,19 @@ +// #docregion +import { Component } from '@angular/core'; +import { Hero } from '../hero'; + +@Component({ + selector: 'my-container', + template: ` +

    Tour of Heroes

    + + + ` +}) +export class ContainerComponent { + hero = new Hero(1, 'Windstorm'); + heroDeleted(hero: Hero) { + hero.name = 'Ex-' + hero.name; + } +} diff --git a/aio/content/examples/upgrade-module/ts/src/app/upgrade-io/hero-detail.component.ts b/aio/content/examples/upgrade-module/ts/src/app/upgrade-io/hero-detail.component.ts new file mode 100644 index 0000000000..90c3273010 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/upgrade-io/hero-detail.component.ts @@ -0,0 +1,37 @@ +// #docregion +// #docregion hero-detail-io +export const heroDetail = { + bindings: { + hero: '<', + deleted: '&' + }, + template: ` +

    {{$ctrl.hero.name}} details!

    +
    {{$ctrl.hero.id}}
    + + `, + controller: function() { + this.onDelete = () => { + this.deleted(this.hero); + }; + } +}; +// #enddocregion hero-detail-io + +// #docregion hero-detail-io-upgrade +import { Directive, ElementRef, Injector, Input, Output, EventEmitter } from '@angular/core'; +import { UpgradeComponent } from '@angular/upgrade/static'; +import { Hero } from '../hero'; + +@Directive({ + selector: 'hero-detail' +}) +export class HeroDetailDirective extends UpgradeComponent { + @Input() hero: Hero; + @Output() deleted: EventEmitter; + + constructor(elementRef: ElementRef, injector: Injector) { + super('heroDetail', elementRef, injector); + } +} +// #enddocregion hero-detail-io-upgrade diff --git a/aio/content/examples/upgrade-module/ts/src/app/upgrade-static/app.module.ts b/aio/content/examples/upgrade-module/ts/src/app/upgrade-static/app.module.ts new file mode 100644 index 0000000000..401963c35e --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/upgrade-static/app.module.ts @@ -0,0 +1,41 @@ +declare var angular: angular.IAngularStatic; +import { NgModule } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { BrowserModule } from '@angular/platform-browser'; +import { UpgradeModule, downgradeComponent } from '@angular/upgrade/static'; + +import { heroDetail, HeroDetailDirective } from './hero-detail.component'; +import { ContainerComponent } from './container.component'; + +// #docregion hero-detail-upgrade +@NgModule({ + imports: [ + BrowserModule, + UpgradeModule + ], + declarations: [ + HeroDetailDirective, + // #enddocregion hero-detail-upgrade + ContainerComponent + ], + entryComponents: [ + ContainerComponent + // #docregion hero-detail-upgrade + ] +}) +export class AppModule { + ngDoBootstrap() {} +} +// #enddocregion hero-detail-upgrade + +angular.module('heroApp', []) + .component('heroDetail', heroDetail) + .directive( + 'myContainer', + downgradeComponent({component: ContainerComponent}) as angular.IDirectiveFactory + ); + +platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => { + const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule; + upgrade.bootstrap(document.body, ['heroApp'], {strictDi: true}); +}); diff --git a/aio/content/examples/upgrade-module/ts/src/app/upgrade-static/container.component.ts b/aio/content/examples/upgrade-module/ts/src/app/upgrade-static/container.component.ts new file mode 100644 index 0000000000..c28e5ea42d --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/upgrade-static/container.component.ts @@ -0,0 +1,11 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-container', + template: ` +

    Tour of Heroes

    + + ` +}) +export class ContainerComponent { } diff --git a/aio/content/examples/upgrade-module/ts/src/app/upgrade-static/hero-detail.component.ts b/aio/content/examples/upgrade-module/ts/src/app/upgrade-static/hero-detail.component.ts new file mode 100644 index 0000000000..02ddd293eb --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/upgrade-static/hero-detail.component.ts @@ -0,0 +1,25 @@ +// #docregion +// #docregion hero-detail +export const heroDetail = { + template: ` +

    Windstorm details!

    +
    1
    + `, + controller: function() { + } +}; +// #enddocregion hero-detail + +// #docregion hero-detail-upgrade +import { Directive, ElementRef, Injector } from '@angular/core'; +import { UpgradeComponent } from '@angular/upgrade/static'; + +@Directive({ + selector: 'hero-detail' +}) +export class HeroDetailDirective extends UpgradeComponent { + constructor(elementRef: ElementRef, injector: Injector) { + super('heroDetail', elementRef, injector); + } +} +// #enddocregion hero-detail-upgrade diff --git a/aio/content/examples/upgrade-module/ts/src/app/villain.ts b/aio/content/examples/upgrade-module/ts/src/app/villain.ts new file mode 100644 index 0000000000..ef3d014f11 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/app/villain.ts @@ -0,0 +1,5 @@ +export class Villain { + constructor(public id: number, + public name: string, + public description?: string) { } +} diff --git a/aio/content/examples/upgrade-module/ts/src/index-a-to-ajs-providers.html b/aio/content/examples/upgrade-module/ts/src/index-a-to-ajs-providers.html new file mode 100644 index 0000000000..693655a53c --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/index-a-to-ajs-providers.html @@ -0,0 +1,31 @@ + + + + Angular Upgrade + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aio/content/examples/upgrade-module/ts/src/index-a-to-ajs-transclusion.html b/aio/content/examples/upgrade-module/ts/src/index-a-to-ajs-transclusion.html new file mode 100644 index 0000000000..43bc09c64b --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/index-a-to-ajs-transclusion.html @@ -0,0 +1,30 @@ + + + + Angular Upgrade + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aio/content/examples/upgrade-module/ts/src/index-ajs-a-hybrid-bootstrap.html b/aio/content/examples/upgrade-module/ts/src/index-ajs-a-hybrid-bootstrap.html new file mode 100644 index 0000000000..1115e931f5 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/index-ajs-a-hybrid-bootstrap.html @@ -0,0 +1,28 @@ + + + + Angular Upgrade + + + + + + + + + + + + + + + + + + +
    {{ mainCtrl.message }}
    + + diff --git a/aio/content/examples/upgrade-module/ts/src/index-ajs-to-a-projection.html b/aio/content/examples/upgrade-module/ts/src/index-ajs-to-a-projection.html new file mode 100644 index 0000000000..3456957077 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/index-ajs-to-a-projection.html @@ -0,0 +1,37 @@ + + + + Angular Upgrade + + + + + + + + + + + + + + + + + + + + +
    + + +

    {{mainCtrl.hero.description}}

    +
    +
    + +
    + + diff --git a/aio/content/examples/upgrade-module/ts/src/index-ajs-to-a-providers.html b/aio/content/examples/upgrade-module/ts/src/index-ajs-to-a-providers.html new file mode 100644 index 0000000000..7d1f2b7a05 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/index-ajs-to-a-providers.html @@ -0,0 +1,31 @@ + + + + Angular Upgrade + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aio/content/examples/upgrade-module/ts/src/index-bootstrap.html b/aio/content/examples/upgrade-module/ts/src/index-bootstrap.html new file mode 100644 index 0000000000..bc4614e562 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/index-bootstrap.html @@ -0,0 +1,13 @@ + + + + + + + + + + +
    {{ mainCtrl.message }}
    + + diff --git a/aio/content/examples/upgrade-module/ts/src/index-divide-routes.html b/aio/content/examples/upgrade-module/ts/src/index-divide-routes.html new file mode 100644 index 0000000000..ee9a56ab36 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/index-divide-routes.html @@ -0,0 +1,31 @@ + + + + Angular 2 Upgrade + + + + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/upgrade-module/ts/src/index-downgrade-io.html b/aio/content/examples/upgrade-module/ts/src/index-downgrade-io.html new file mode 100644 index 0000000000..71e55db355 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/index-downgrade-io.html @@ -0,0 +1,44 @@ + + + + Angular Upgrade + + + + + + + + + + + + + + + + + + + + +
    + + +
    + + +
    + + +
    + +
    + + diff --git a/aio/content/examples/upgrade-module/ts/src/index-downgrade-static.html b/aio/content/examples/upgrade-module/ts/src/index-downgrade-static.html new file mode 100644 index 0000000000..47ec27e8f4 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/index-downgrade-static.html @@ -0,0 +1,32 @@ + + + + Angular Upgrade + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aio/content/examples/upgrade-module/ts/src/index-ng-app.html b/aio/content/examples/upgrade-module/ts/src/index-ng-app.html new file mode 100644 index 0000000000..0bd661faae --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/index-ng-app.html @@ -0,0 +1,15 @@ + + + + + + + + + + +
    + {{ mainCtrl.message }} +
    + + diff --git a/aio/content/examples/upgrade-module/ts/src/index-upgrade-io.html b/aio/content/examples/upgrade-module/ts/src/index-upgrade-io.html new file mode 100644 index 0000000000..2dca0d44d4 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/index-upgrade-io.html @@ -0,0 +1,30 @@ + + + + Angular Upgrade + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aio/content/examples/upgrade-module/ts/src/index-upgrade-static.html b/aio/content/examples/upgrade-module/ts/src/index-upgrade-static.html new file mode 100644 index 0000000000..9134cfbc32 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/index-upgrade-static.html @@ -0,0 +1,30 @@ + + + + Angular Upgrade + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aio/content/examples/upgrade-module/ts/src/systemjs.config.1.js b/aio/content/examples/upgrade-module/ts/src/systemjs.config.1.js new file mode 100644 index 0000000000..a8c57d1792 --- /dev/null +++ b/aio/content/examples/upgrade-module/ts/src/systemjs.config.1.js @@ -0,0 +1,43 @@ +/** + * System configuration for Angular samples + * Adjust as necessary for your application needs. + */ +(function (global) { + System.config({ + paths: { + // paths serve as alias + 'npm:': 'node_modules/' + }, + // map tells the System loader where to look for things + map: { + // our app is within the app folder + app: 'app', + // angular bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', + // #docregion upgrade-static-umd + '@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js', + // #enddocregion upgrade-static-umd + + // other libraries + 'rxjs': 'npm:rxjs', + 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js' + }, + // packages tells the System loader how to load when no filename and/or no extension + packages: { + app: { + main: './main.js', + defaultExtension: 'js' + }, + rxjs: { + defaultExtension: 'js' + } + } + }); +})(this); diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/README.md b/aio/content/examples/upgrade-phonecat-1-typescript/README.md new file mode 100644 index 0000000000..f3433b901a --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/README.md @@ -0,0 +1,34 @@ +This is the Angular Phonecat application adjusted to fit our boilerplate project +structure. + +The following changes from vanilla Phonecat are applied: + +* The TypeScript config file shown in the guide is `tsconfig.ajs.json` instead + of the default, because we don't want to enable `noImplicitAny` for migration. +* Karma config for unit tests is in karma.conf.ajs.js because the boilerplate + Karma config is not compatible with the way AngularJS tests need to be run. + The shell script run-unit-tests.sh can be used to run the unit tests. +* Instead of using Bower, AngularJS and its dependencies are fetched from a CDN + in index.html and karma.conf.ajs.js. +* E2E tests have been moved to the parent directory, where `gulp run-e2e-tests` can + discover and run them along with all the other examples. +* Most of the phone JSON and image data removed in the interest of keeping + repo weight down. Keeping enough to retain testability of the app. + +## Running the app + +Start like any example + + npm run start + +You'll find the app under the /app path: http://localhost:3002/app/index.html + +## Running unit tests + + ./run-unit-tests.sh + +## Running E2E tests + +Like for any example (at the project root): + + gulp run-e2e-tests --filter=phonecat-1 diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/e2e-spec.ts b/aio/content/examples/upgrade-phonecat-1-typescript/e2e-spec.ts new file mode 100644 index 0000000000..4598a7f6dc --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/e2e-spec.ts @@ -0,0 +1,107 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by, ElementFinder } from 'protractor'; +import { setProtractorToNg1Mode } from '../protractor-helpers'; + +// Angular E2E Testing Guide: +// https://docs.angularjs.org/guide/e2e-testing + +describe('PhoneCat Application', function() { + + beforeAll(function() { + browser.baseUrl = 'http://localhost:8080/app/'; + setProtractorToNg1Mode(); + }); + + it('should redirect `index.html` to `index.html#!/phones', function() { + browser.get('index.html'); + expect(browser.getLocationAbsUrl()).toBe('/phones'); + }); + + describe('View: Phone list', function() { + + beforeEach(function() { + browser.get('index.html#!/phones'); + }); + + it('should filter the phone list as a user types into the search box', function() { + let phoneList = element.all(by.repeater('phone in $ctrl.phones')); + let query = element(by.model('$ctrl.query')); + + expect(phoneList.count()).toBe(20); + + query.sendKeys('nexus'); + expect(phoneList.count()).toBe(1); + + query.clear(); + query.sendKeys('motorola'); + expect(phoneList.count()).toBe(8); + }); + + it('should be possible to control phone order via the drop-down menu', function() { + let queryField = element(by.model('$ctrl.query')); + let orderSelect = element(by.model('$ctrl.orderProp')); + let nameOption = orderSelect.element(by.css('option[value="name"]')); + let phoneNameColumn = element.all(by.repeater('phone in $ctrl.phones').column('phone.name')); + + function getNames() { + return phoneNameColumn.map(function(elem: ElementFinder) { + return elem.getText(); + }); + } + + queryField.sendKeys('tablet'); // Let's narrow the dataset to make the assertions shorter + + expect(getNames()).toEqual([ + 'Motorola XOOM\u2122 with Wi-Fi', + 'MOTOROLA XOOM\u2122' + ]); + + nameOption.click(); + + expect(getNames()).toEqual([ + 'MOTOROLA XOOM\u2122', + 'Motorola XOOM\u2122 with Wi-Fi' + ]); + }); + + it('should render phone specific links', function() { + let query = element(by.model('$ctrl.query')); + query.sendKeys('nexus'); + + element.all(by.css('.phones li a')).first().click(); + expect(browser.getLocationAbsUrl()).toBe('/phones/nexus-s'); + }); + + }); + + describe('View: Phone detail', function() { + + beforeEach(function() { + browser.get('index.html#!/phones/nexus-s'); + }); + + it('should display the `nexus-s` page', function() { + expect(element(by.binding('$ctrl.phone.name')).getText()).toBe('Nexus S'); + }); + + it('should display the first phone image as the main phone image', function() { + let mainImage = element(by.css('img.phone.selected')); + + expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/); + }); + + it('should swap the main image when clicking on a thumbnail image', function() { + let mainImage = element(by.css('img.phone.selected')); + let thumbnails = element.all(by.css('.phone-thumbs img')); + + thumbnails.get(2).click(); + expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.2.jpg/); + + thumbnails.get(0).click(); + expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/); + }); + + }); + +}); diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/.gitignore b/aio/content/examples/upgrade-phonecat-1-typescript/ts/.gitignore new file mode 100644 index 0000000000..448efbe632 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/.gitignore @@ -0,0 +1 @@ +!karma.conf.ajs.js diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/app.animations.css b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/app.animations.css new file mode 100644 index 0000000000..175320b509 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/app.animations.css @@ -0,0 +1,67 @@ +/* Animate `ngRepeat` in `phoneList` component */ +.phone-list-item.ng-enter, +.phone-list-item.ng-leave, +.phone-list-item.ng-move { + overflow: hidden; + transition: 0.5s linear all; +} + +.phone-list-item.ng-enter, +.phone-list-item.ng-leave.ng-leave-active, +.phone-list-item.ng-move { + height: 0; + margin-bottom: 0; + opacity: 0; + padding-bottom: 0; + padding-top: 0; +} + +.phone-list-item.ng-enter.ng-enter-active, +.phone-list-item.ng-leave, +.phone-list-item.ng-move.ng-move-active { + height: 120px; + margin-bottom: 20px; + opacity: 1; + padding-bottom: 4px; + padding-top: 15px; +} + +/* Animate view transitions with `ngView` */ +.view-container { + position: relative; +} + +.view-frame { + margin-top: 20px; +} + +.view-frame.ng-enter, +.view-frame.ng-leave { + background: white; + left: 0; + position: absolute; + right: 0; + top: 0; +} + +.view-frame.ng-enter { + animation: 1s fade-in; + z-index: 100; +} + +.view-frame.ng-leave { + animation: 1s fade-out; + z-index: 99; +} + +@keyframes fade-in { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes fade-out { + from { opacity: 1; } + to { opacity: 0; } +} + +/* Older browsers might need vendor-prefixes for keyframes and animation! */ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/app.animations.ts b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/app.animations.ts new file mode 100644 index 0000000000..f0739b6405 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/app.animations.ts @@ -0,0 +1,43 @@ +'use strict'; + +angular. + module('phonecatApp'). + animation('.phone', function phoneAnimationFactory() { + return { + addClass: animateIn, + removeClass: animateOut + }; + + function animateIn(element: JQuery, className: string, done: () => void) { + if (className !== 'selected') { return; } + + element.css({ + display: 'block', + position: 'absolute', + top: 500, + left: 0 + }).animate({ + top: 0 + }, done); + + return function animateInEnd(wasCanceled: boolean) { + if (wasCanceled) { element.stop(); } + }; + } + + function animateOut(element: JQuery, className: string, done: () => void) { + if (className !== 'selected') { return; } + + element.css({ + position: 'absolute', + top: 0, + left: 0 + }).animate({ + top: -500 + }, done); + + return function animateOutEnd(wasCanceled: boolean) { + if (wasCanceled) { element.stop(); } + }; + } + }); diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/app.config.ts b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/app.config.ts new file mode 100644 index 0000000000..1d16f67fbe --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/app.config.ts @@ -0,0 +1,18 @@ +// #docregion +angular. + module('phonecatApp'). + config(['$locationProvider', '$routeProvider', + function config($locationProvider: angular.ILocationProvider, + $routeProvider: angular.route.IRouteProvider) { + $locationProvider.hashPrefix('!'); + + $routeProvider. + when('/phones', { + template: '' + }). + when('/phones/:phoneId', { + template: '' + }). + otherwise('/phones'); + } + ]); diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/app.css b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/app.css new file mode 100644 index 0000000000..f4b45b02a5 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/app.css @@ -0,0 +1,93 @@ +body { + padding: 20px; +} + +h1 { + border-bottom: 1px solid gray; + margin-top: 0; +} + +/* View: Phone list */ +.phones { + list-style: none; +} + +.phones li { + clear: both; + height: 115px; + padding-top: 15px; +} + +.thumb { + float: left; + height: 100px; + margin: -0.5em 1em 1.5em 0; + padding-bottom: 1em; + width: 100px; +} + +/* View: Phone detail */ +.phone { + background-color: white; + display: none; + float: left; + height: 400px; + margin-bottom: 2em; + margin-right: 3em; + padding: 2em; + width: 400px; +} + +.phone:first-child { + display: block; +} + +.phone-images { + background-color: white; + float: left; + height: 450px; + overflow: hidden; + position: relative; + width: 450px; +} + +.phone-thumbs { + list-style: none; + margin: 0; +} + +.phone-thumbs img { + height: 100px; + padding: 1em; + width: 100px; +} + +.phone-thumbs li { + background-color: white; + border: 1px solid black; + cursor: pointer; + display: inline-block; + margin: 1em; +} + +.specs { + clear: both; + list-style: none; + margin: 0; + padding: 0; +} + +.specs dt { + font-weight: bold; +} + +.specs > li { + display: inline-block; + vertical-align: top; + width: 200px; +} + +.specs > li > span { + font-size: 1.2em; + font-weight: bold; +} diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/app.module.ts b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/app.module.ts new file mode 100644 index 0000000000..ab6d353eeb --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/app.module.ts @@ -0,0 +1,10 @@ +'use strict'; + +// Define the `phonecatApp` module +angular.module('phonecatApp', [ + 'ngAnimate', + 'ngRoute', + 'core', + 'phoneDetail', + 'phoneList', +]); diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/core/checkmark/checkmark.filter.spec.ts b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/core/checkmark/checkmark.filter.spec.ts new file mode 100644 index 0000000000..1b2d77c30c --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/core/checkmark/checkmark.filter.spec.ts @@ -0,0 +1,14 @@ +'use strict'; + +describe('checkmark', () => { + + beforeEach(angular.mock.module('core')); + + it('should convert boolean values to unicode checkmark or cross', + inject(function(checkmarkFilter: (v: boolean) => string) { + expect(checkmarkFilter(true)).toBe('\u2713'); + expect(checkmarkFilter(false)).toBe('\u2718'); + }) + ); + +}); diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/core/checkmark/checkmark.filter.ts b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/core/checkmark/checkmark.filter.ts new file mode 100644 index 0000000000..3114dc9681 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/core/checkmark/checkmark.filter.ts @@ -0,0 +1,8 @@ +// #docregion +angular. + module('core'). + filter('checkmark', function() { + return function(input: boolean) { + return input ? '\u2713' : '\u2718'; + }; + }); diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/core/core.module.ts b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/core/core.module.ts new file mode 100644 index 0000000000..84a91dc7a6 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/core/core.module.ts @@ -0,0 +1,4 @@ +'use strict'; + +// Define the `core` module +angular.module('core', ['core.phone']); diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/core/phone/phone.module.ts b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/core/phone/phone.module.ts new file mode 100644 index 0000000000..0b6b348899 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/core/phone/phone.module.ts @@ -0,0 +1,4 @@ +'use strict'; + +// Define the `core.phone` module +angular.module('core.phone', ['ngResource']); diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/core/phone/phone.service.spec.ts b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/core/phone/phone.service.spec.ts new file mode 100644 index 0000000000..312036d71d --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/core/phone/phone.service.spec.ts @@ -0,0 +1,43 @@ +'use strict'; + +describe('Phone', () => { + let $httpBackend: angular.IHttpBackendService; + let Phone: any; + let phonesData = [ + {name: 'Phone X'}, + {name: 'Phone Y'}, + {name: 'Phone Z'} + ]; + + // Add a custom equality tester before each test + beforeEach(function() { + jasmine.addCustomEqualityTester(angular.equals); + }); + + // Load the module that contains the `Phone` service before each test + beforeEach(angular.mock.module('core.phone')); + + // Instantiate the service and "train" `$httpBackend` before each test + beforeEach(inject(function(_$httpBackend_: angular.IHttpBackendService, _Phone_: any) { + $httpBackend = _$httpBackend_; + $httpBackend.expectGET('phones/phones.json').respond(phonesData); + + Phone = _Phone_; + })); + + // Verify that there are no outstanding expectations or requests after each test + afterEach(() => { + $httpBackend.verifyNoOutstandingExpectation(); + $httpBackend.verifyNoOutstandingRequest(); + }); + + it('should fetch the phones data from `/phones/phones.json`', () => { + let phones = Phone.query(); + + expect(phones).toEqual([]); + + $httpBackend.flush(); + expect(phones).toEqual(phonesData); + }); + +}); diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/core/phone/phone.service.ts b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/core/phone/phone.service.ts new file mode 100644 index 0000000000..c6204bc896 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/core/phone/phone.service.ts @@ -0,0 +1,14 @@ +// #docregion +angular. + module('core.phone'). + factory('Phone', ['$resource', + function($resource: angular.resource.IResourceService) { + return $resource('phones/:phoneId.json', {}, { + query: { + method: 'GET', + params: {phoneId: 'phones'}, + isArray: true + } + }); + } + ]); diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/.gitkeep b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.0.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.0.jpg new file mode 100644 index 0000000000..7ce0dce4ee Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.1.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.1.jpg new file mode 100644 index 0000000000..ed8cad89fb Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.1.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.2.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.2.jpg new file mode 100644 index 0000000000..c83529e0f9 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.2.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.3.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.3.jpg new file mode 100644 index 0000000000..cd2c30280d Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.3.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.4.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.4.jpg new file mode 100644 index 0000000000..b4d8472da8 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.4.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-atrix-4g.0.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-atrix-4g.0.jpg new file mode 100644 index 0000000000..2446159e93 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-atrix-4g.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-atrix-4g.1.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-atrix-4g.1.jpg new file mode 100644 index 0000000000..867f207459 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-atrix-4g.1.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-atrix-4g.2.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-atrix-4g.2.jpg new file mode 100644 index 0000000000..27d78338c4 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-atrix-4g.2.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-atrix-4g.3.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-atrix-4g.3.jpg new file mode 100644 index 0000000000..29459a68a4 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-atrix-4g.3.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.0.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.0.jpg new file mode 100644 index 0000000000..a6c993291e Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.1.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.1.jpg new file mode 100644 index 0000000000..400b89e2df Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.1.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.2.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.2.jpg new file mode 100644 index 0000000000..86b55d28dd Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.2.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.3.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.3.jpg new file mode 100644 index 0000000000..85ec293ae5 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.3.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.4.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.4.jpg new file mode 100644 index 0000000000..75ef1464cb Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.4.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.5.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.5.jpg new file mode 100644 index 0000000000..4d42db4330 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.5.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom.0.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom.0.jpg new file mode 100644 index 0000000000..bf6954bbd5 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom.1.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom.1.jpg new file mode 100644 index 0000000000..659688a47d Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom.1.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom.2.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom.2.jpg new file mode 100644 index 0000000000..ce0ff1002e Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom.2.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/nexus-s.0.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/nexus-s.0.jpg new file mode 100644 index 0000000000..0952bc79c2 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/nexus-s.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/nexus-s.1.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/nexus-s.1.jpg new file mode 100644 index 0000000000..f33004dd7f Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/nexus-s.1.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/nexus-s.2.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/nexus-s.2.jpg new file mode 100644 index 0000000000..c5c63621f1 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/nexus-s.2.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/nexus-s.3.jpg b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/nexus-s.3.jpg new file mode 100644 index 0000000000..e51f75b0ed Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/img/phones/nexus-s.3.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/index.html b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/index.html new file mode 100644 index 0000000000..82717fb7ee --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/index.html @@ -0,0 +1,35 @@ + + + + + Google Phone Gallery + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-detail/phone-detail.component.spec.ts b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-detail/phone-detail.component.spec.ts new file mode 100644 index 0000000000..0998b638f0 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-detail/phone-detail.component.spec.ts @@ -0,0 +1,38 @@ +// #docregion +describe('phoneDetail', () => { + + // Load the module that contains the `phoneDetail` component before each test + beforeEach(angular.mock.module('phoneDetail')); + + // Test the controller + describe('PhoneDetailController', () => { + let $httpBackend: angular.IHttpBackendService; + let ctrl: any; + let xyzPhoneData = { + name: 'phone xyz', + images: ['image/url1.png', 'image/url2.png'] + }; + + beforeEach(inject(($componentController: any, + _$httpBackend_: angular.IHttpBackendService, + $routeParams: angular.route.IRouteParamsService) => { + $httpBackend = _$httpBackend_; + $httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData); + + $routeParams['phoneId'] = 'xyz'; + + ctrl = $componentController('phoneDetail'); + })); + + it('should fetch the phone details', () => { + jasmine.addCustomEqualityTester(angular.equals); + + expect(ctrl.phone).toEqual({}); + + $httpBackend.flush(); + expect(ctrl.phone).toEqual(xyzPhoneData); + }); + + }); + +}); diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-detail/phone-detail.component.ts b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-detail/phone-detail.component.ts new file mode 100644 index 0000000000..079b31e2c2 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-detail/phone-detail.component.ts @@ -0,0 +1,24 @@ +// #docregion +class PhoneDetailController { + phone: any; + mainImageUrl: string; + + static $inject = ['$routeParams', 'Phone']; + constructor($routeParams: angular.route.IRouteParamsService, Phone: any) { + let phoneId = $routeParams['phoneId']; + this.phone = Phone.get({phoneId}, (phone: any) => { + this.setImage(phone.images[0]); + }); + } + + setImage(imageUrl: string) { + this.mainImageUrl = imageUrl; + } +} + +angular. + module('phoneDetail'). + component('phoneDetail', { + templateUrl: 'phone-detail/phone-detail.template.html', + controller: PhoneDetailController + }); diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-detail/phone-detail.module.ts b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-detail/phone-detail.module.ts new file mode 100644 index 0000000000..fd7cb3b920 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-detail/phone-detail.module.ts @@ -0,0 +1,7 @@ +'use strict'; + +// Define the `phoneDetail` module +angular.module('phoneDetail', [ + 'ngRoute', + 'core.phone' +]); diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-detail/phone-detail.template.html b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-detail/phone-detail.template.html new file mode 100644 index 0000000000..f48657803c --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-detail/phone-detail.template.html @@ -0,0 +1,117 @@ +
    + +
    + +

    {{$ctrl.phone.name}}

    + +

    {{$ctrl.phone.description}}

    + +
      +
    • + +
    • +
    + +
      +
    • + Availability and Networks +
      +
      Availability
      +
      {{availability}}
      +
      +
    • +
    • + Battery +
      +
      Type
      +
      {{$ctrl.phone.battery.type}}
      +
      Talk Time
      +
      {{$ctrl.phone.battery.talkTime}}
      +
      Standby time (max)
      +
      {{$ctrl.phone.battery.standbyTime}}
      +
      +
    • +
    • + Storage and Memory +
      +
      RAM
      +
      {{$ctrl.phone.storage.ram}}
      +
      Internal Storage
      +
      {{$ctrl.phone.storage.flash}}
      +
      +
    • +
    • + Connectivity +
      +
      Network Support
      +
      {{$ctrl.phone.connectivity.cell}}
      +
      WiFi
      +
      {{$ctrl.phone.connectivity.wifi}}
      +
      Bluetooth
      +
      {{$ctrl.phone.connectivity.bluetooth}}
      +
      Infrared
      +
      {{$ctrl.phone.connectivity.infrared | checkmark}}
      +
      GPS
      +
      {{$ctrl.phone.connectivity.gps | checkmark}}
      +
      +
    • +
    • + Android +
      +
      OS Version
      +
      {{$ctrl.phone.android.os}}
      +
      UI
      +
      {{$ctrl.phone.android.ui}}
      +
      +
    • +
    • + Size and Weight +
      +
      Dimensions
      +
      {{dim}}
      +
      Weight
      +
      {{$ctrl.phone.sizeAndWeight.weight}}
      +
      +
    • +
    • + Display +
      +
      Screen size
      +
      {{$ctrl.phone.display.screenSize}}
      +
      Screen resolution
      +
      {{$ctrl.phone.display.screenResolution}}
      +
      Touch screen
      +
      {{$ctrl.phone.display.touchScreen | checkmark}}
      +
      +
    • +
    • + Hardware +
      +
      CPU
      +
      {{$ctrl.phone.hardware.cpu}}
      +
      USB
      +
      {{$ctrl.phone.hardware.usb}}
      +
      Audio / headphone jack
      +
      {{$ctrl.phone.hardware.audioJack}}
      +
      FM Radio
      +
      {{$ctrl.phone.hardware.fmRadio | checkmark}}
      +
      Accelerometer
      +
      {{$ctrl.phone.hardware.accelerometer | checkmark}}
      +
      +
    • +
    • + Camera +
      +
      Primary
      +
      {{$ctrl.phone.camera.primary}}
      +
      Features
      +
      {{$ctrl.phone.camera.features.join(', ')}}
      +
      +
    • +
    • + Additional Features +
      {{$ctrl.phone.additionalFeatures}}
      +
    • +
    diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.component.spec.ts b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.component.spec.ts new file mode 100644 index 0000000000..19e1890817 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.component.spec.ts @@ -0,0 +1,36 @@ +'use strict'; + +describe('phoneList', () => { + + // Load the module that contains the `phoneList` component before each test + beforeEach(angular.mock.module('phoneList')); + + // Test the controller + describe('PhoneListController', () => { + let $httpBackend: angular.IHttpBackendService; + let ctrl: any; + + beforeEach(inject(($componentController: any, _$httpBackend_: angular.IHttpBackendService) => { + $httpBackend = _$httpBackend_; + $httpBackend.expectGET('phones/phones.json') + .respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]); + + ctrl = $componentController('phoneList'); + })); + + it('should create a `phones` property with 2 phones fetched with `$http`', () => { + jasmine.addCustomEqualityTester(angular.equals); + + expect(ctrl.phones).toEqual([]); + + $httpBackend.flush(); + expect(ctrl.phones).toEqual([{name: 'Nexus S'}, {name: 'Motorola DROID'}]); + }); + + it('should set a default value for the `orderProp` property', () => { + expect(ctrl.orderProp).toBe('age'); + }); + + }); + +}); diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.component.ts b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.component.ts new file mode 100644 index 0000000000..e2f2855ae6 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.component.ts @@ -0,0 +1,20 @@ +// #docregion +class PhoneListController { + phones: any[]; + orderProp: string; + query: string; + + static $inject = ['Phone']; + constructor(Phone: any) { + this.phones = Phone.query(); + this.orderProp = 'age'; + } + +} + +angular. + module('phoneList'). + component('phoneList', { + templateUrl: 'phone-list/phone-list.template.html', + controller: PhoneListController + }); diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.module.ts b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.module.ts new file mode 100644 index 0000000000..8ade7c5b88 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.module.ts @@ -0,0 +1,4 @@ +'use strict'; + +// Define the `phoneList` module +angular.module('phoneList', ['core.phone']); diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.template.html b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.template.html new file mode 100644 index 0000000000..90548f9f91 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.template.html @@ -0,0 +1,36 @@ +
    +
    +
    + + +

    + Search: + +

    + +

    + Sort by: + +

    + +
    +
    + + + + +
    +
    +
    diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phones/dell-streak-7.json b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phones/dell-streak-7.json new file mode 100644 index 0000000000..a32eb6ff98 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phones/dell-streak-7.json @@ -0,0 +1,64 @@ +{ + "additionalFeatures": "Front Facing 1.3MP Camera", + "android": { + "os": "Android 2.2", + "ui": "Dell Stage" + }, + "availability": [ + "T-Mobile" + ], + "battery": { + "standbyTime": "", + "talkTime": "", + "type": "Lithium Ion (Li-Ion) (2780 mAH)" + }, + "camera": { + "features": [ + "Flash", + "Video" + ], + "primary": "5.0 megapixels" + }, + "connectivity": { + "bluetooth": "Bluetooth 2.1", + "cell": "T-mobile HSPA+ @ 2100/1900/AWS/850 MHz", + "gps": true, + "infrared": false, + "wifi": "802.11 b/g" + }, + "description": "Introducing Dell\u2122 Streak 7. Share photos, videos and movies together. It\u2019s small enough to carry around, big enough to gather around. Android\u2122 2.2-based tablet with over-the-air upgrade capability for future OS releases. A vibrant 7-inch, multitouch display with full Adobe\u00ae Flash 10.1 pre-installed. Includes a 1.3 MP front-facing camera for face-to-face chats on popular services such as Qik or Skype. 16 GB of internal storage, plus Wi-Fi, Bluetooth and built-in GPS keeps you in touch with the world around you. Connect on your terms. Save with 2-year contract or flexibility with prepaid pay-as-you-go plans", + "display": { + "screenResolution": "WVGA (800 x 480)", + "screenSize": "7.0 inches", + "touchScreen": true + }, + "hardware": { + "accelerometer": true, + "audioJack": "3.5mm", + "cpu": "nVidia Tegra T20", + "fmRadio": false, + "physicalKeyboard": false, + "usb": "USB 2.0" + }, + "id": "dell-streak-7", + "images": [ + "img/phones/dell-streak-7.0.jpg", + "img/phones/dell-streak-7.1.jpg", + "img/phones/dell-streak-7.2.jpg", + "img/phones/dell-streak-7.3.jpg", + "img/phones/dell-streak-7.4.jpg" + ], + "name": "Dell Streak 7", + "sizeAndWeight": { + "dimensions": [ + "199.9 mm (w)", + "119.8 mm (h)", + "12.4 mm (d)" + ], + "weight": "450.0 grams" + }, + "storage": { + "flash": "16000MB", + "ram": "512MB" + } +} diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phones/motorola-atrix-4g.json b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phones/motorola-atrix-4g.json new file mode 100644 index 0000000000..ccca00e3b2 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phones/motorola-atrix-4g.json @@ -0,0 +1,62 @@ +{ + "additionalFeatures": "", + "android": { + "os": "Android 2.2", + "ui": "MOTOBLUR" + }, + "availability": [ + "AT&T" + ], + "battery": { + "standbyTime": "400 hours", + "talkTime": "5 hours", + "type": "Lithium Ion (Li-Ion) (1930 mAH)" + }, + "camera": { + "features": [ + "" + ], + "primary": "" + }, + "connectivity": { + "bluetooth": "Bluetooth 2.1", + "cell": "WCDMA 850/1900/2100, GSM 850/900/1800/1900, HSDPA 14Mbps (Category 10) Edge Class 12, GPRS Class 12, eCompass, AGPS", + "gps": true, + "infrared": false, + "wifi": "802.11 a/b/g/n" + }, + "description": "MOTOROLA ATRIX 4G gives you dual-core processing power and the revolutionary webtop application. With webtop and a compatible Motorola docking station, sold separately, you can surf the Internet with a full Firefox browser, create and edit docs, or access multimedia on a large screen almost anywhere.", + "display": { + "screenResolution": "QHD (960 x 540)", + "screenSize": "4.0 inches", + "touchScreen": true + }, + "hardware": { + "accelerometer": true, + "audioJack": "3.5mm", + "cpu": "1 GHz Dual Core", + "fmRadio": false, + "physicalKeyboard": false, + "usb": "USB 2.0" + }, + "id": "motorola-atrix-4g", + "images": [ + "img/phones/motorola-atrix-4g.0.jpg", + "img/phones/motorola-atrix-4g.1.jpg", + "img/phones/motorola-atrix-4g.2.jpg", + "img/phones/motorola-atrix-4g.3.jpg" + ], + "name": "MOTOROLA ATRIX\u2122 4G", + "sizeAndWeight": { + "dimensions": [ + "63.5 mm (w)", + "117.75 mm (h)", + "10.95 mm (d)" + ], + "weight": "135.0 grams" + }, + "storage": { + "flash": "", + "ram": "" + } +} diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phones/motorola-xoom-with-wi-fi.json b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phones/motorola-xoom-with-wi-fi.json new file mode 100644 index 0000000000..4ba9c8d5b5 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phones/motorola-xoom-with-wi-fi.json @@ -0,0 +1,65 @@ +{ + "additionalFeatures": "Sensors: proximity, ambient light, barometer, gyroscope", + "android": { + "os": "Android 3.0", + "ui": "Honeycomb" + }, + "availability": [ + "" + ], + "battery": { + "standbyTime": "336 hours", + "talkTime": "24 hours", + "type": "Other ( mAH)" + }, + "camera": { + "features": [ + "Flash", + "Video" + ], + "primary": "5.0 megapixels" + }, + "connectivity": { + "bluetooth": "Bluetooth 2.1", + "cell": "", + "gps": true, + "infrared": false, + "wifi": "802.11 b/g/n" + }, + "description": "Motorola XOOM with Wi-Fi has a super-powerful dual-core processor and Android\u2122 3.0 (Honeycomb) \u2014 the Android platform designed specifically for tablets. With its 10.1-inch HD widescreen display, you\u2019ll enjoy HD video in a thin, light, powerful and upgradeable tablet.", + "display": { + "screenResolution": "WXGA (1200 x 800)", + "screenSize": "10.1 inches", + "touchScreen": true + }, + "hardware": { + "accelerometer": true, + "audioJack": "3.5mm", + "cpu": "1 GHz Dual Core Tegra 2", + "fmRadio": false, + "physicalKeyboard": false, + "usb": "USB 2.0" + }, + "id": "motorola-xoom-with-wi-fi", + "images": [ + "img/phones/motorola-xoom-with-wi-fi.0.jpg", + "img/phones/motorola-xoom-with-wi-fi.1.jpg", + "img/phones/motorola-xoom-with-wi-fi.2.jpg", + "img/phones/motorola-xoom-with-wi-fi.3.jpg", + "img/phones/motorola-xoom-with-wi-fi.4.jpg", + "img/phones/motorola-xoom-with-wi-fi.5.jpg" + ], + "name": "Motorola XOOM\u2122 with Wi-Fi", + "sizeAndWeight": { + "dimensions": [ + "249.1 mm (w)", + "167.8 mm (h)", + "12.9 mm (d)" + ], + "weight": "708.0 grams" + }, + "storage": { + "flash": "32000MB", + "ram": "1000MB" + } +} diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phones/motorola-xoom.json b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phones/motorola-xoom.json new file mode 100644 index 0000000000..f0f0c8711d --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phones/motorola-xoom.json @@ -0,0 +1,62 @@ +{ + "additionalFeatures": "Front-facing camera. Sensors: proximity, ambient light, barometer, gyroscope.", + "android": { + "os": "Android 3.0", + "ui": "Android" + }, + "availability": [ + "Verizon" + ], + "battery": { + "standbyTime": "336 hours", + "talkTime": "24 hours", + "type": "Other (3250 mAH)" + }, + "camera": { + "features": [ + "Flash", + "Video" + ], + "primary": "5.0 megapixels" + }, + "connectivity": { + "bluetooth": "Bluetooth 2.1", + "cell": "CDMA 800 /1900 LTE 700, Rx diversity in all bands", + "gps": true, + "infrared": false, + "wifi": "802.11 a/b/g/n" + }, + "description": "MOTOROLA XOOM has a super-powerful dual-core processor and Android\u2122 3.0 (Honeycomb) \u2014 the Android platform designed specifically for tablets. With its 10.1-inch HD widescreen display, you\u2019ll enjoy HD video in a thin, light, powerful and upgradeable tablet.", + "display": { + "screenResolution": "WXGA (1200 x 800)", + "screenSize": "10.1 inches", + "touchScreen": true + }, + "hardware": { + "accelerometer": true, + "audioJack": "3.5mm", + "cpu": "1 GHz Dual Core Tegra 2", + "fmRadio": false, + "physicalKeyboard": false, + "usb": "USB 2.0" + }, + "id": "motorola-xoom", + "images": [ + "img/phones/motorola-xoom.0.jpg", + "img/phones/motorola-xoom.1.jpg", + "img/phones/motorola-xoom.2.jpg" + ], + "name": "MOTOROLA XOOM\u2122", + "sizeAndWeight": { + "dimensions": [ + "249.0 mm (w)", + "168.0 mm (h)", + "12.7 mm (d)" + ], + "weight": "726.0 grams" + }, + "storage": { + "flash": "32000MB", + "ram": "1000MB" + } +} diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phones/nexus-s.json b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phones/nexus-s.json new file mode 100644 index 0000000000..5e712e2ff8 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phones/nexus-s.json @@ -0,0 +1,69 @@ +{ + "additionalFeatures": "Contour Display, Near Field Communications (NFC), Three-axis gyroscope, Anti-fingerprint display coating, Internet Calling support (VoIP/SIP)", + "android": { + "os": "Android 2.3", + "ui": "Android" + }, + "availability": [ + "M1,", + "O2,", + "Orange,", + "Singtel,", + "StarHub,", + "T-Mobile,", + "Vodafone" + ], + "battery": { + "standbyTime": "428 hours", + "talkTime": "6 hours", + "type": "Lithium Ion (Li-Ion) (1500 mAH)" + }, + "camera": { + "features": [ + "Flash", + "Video" + ], + "primary": "5.0 megapixels" + }, + "connectivity": { + "bluetooth": "Bluetooth 2.1", + "cell": "Quad-band GSM: 850, 900, 1800, 1900\r\nTri-band HSPA: 900, 2100, 1700\r\nHSPA type: HSDPA (7.2Mbps) HSUPA (5.76Mbps)", + "gps": true, + "infrared": false, + "wifi": "802.11 b/g/n" + }, + "description": "Nexus S is the next generation of Nexus devices, co-developed by Google and Samsung. The latest Android platform (Gingerbread), paired with a 1 GHz Hummingbird processor and 16GB of memory, makes Nexus S one of the fastest phones on the market. It comes pre-installed with the best of Google apps and enabled with new and popular features like true multi-tasking, Wi-Fi hotspot, Internet Calling, NFC support, and full web browsing. With this device, users will also be the first to receive software upgrades and new Google mobile apps as soon as they become available. For more details, visit http://www.google.com/nexus.", + "display": { + "screenResolution": "WVGA (800 x 480)", + "screenSize": "4.0 inches", + "touchScreen": true + }, + "hardware": { + "accelerometer": true, + "audioJack": "3.5mm", + "cpu": "1GHz Cortex A8 (Hummingbird) processor", + "fmRadio": false, + "physicalKeyboard": false, + "usb": "USB 2.0" + }, + "id": "nexus-s", + "images": [ + "img/phones/nexus-s.0.jpg", + "img/phones/nexus-s.1.jpg", + "img/phones/nexus-s.2.jpg", + "img/phones/nexus-s.3.jpg" + ], + "name": "Nexus S", + "sizeAndWeight": { + "dimensions": [ + "63.0 mm (w)", + "123.9 mm (h)", + "10.88 mm (d)" + ], + "weight": "129.0 grams" + }, + "storage": { + "flash": "16384MB", + "ram": "512MB" + } +} diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phones/phones.json b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phones/phones.json new file mode 100644 index 0000000000..339b94fbb5 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/app/phones/phones.json @@ -0,0 +1,155 @@ +[ + { + "age": 0, + "id": "motorola-xoom-with-wi-fi", + "imageUrl": "img/phones/motorola-xoom-with-wi-fi.0.jpg", + "name": "Motorola XOOM\u2122 with Wi-Fi", + "snippet": "The Next, Next Generation\r\n\r\nExperience the future with Motorola XOOM with Wi-Fi, the world's first tablet powered by Android 3.0 (Honeycomb)." + }, + { + "age": 1, + "id": "motorola-xoom", + "imageUrl": "img/phones/motorola-xoom.0.jpg", + "name": "MOTOROLA XOOM\u2122", + "snippet": "The Next, Next Generation\n\nExperience the future with MOTOROLA XOOM, the world's first tablet powered by Android 3.0 (Honeycomb)." + }, + { + "age": 2, + "carrier": "AT&T", + "id": "motorola-atrix-4g", + "imageUrl": "img/phones/motorola-atrix-4g.0.jpg", + "name": "MOTOROLA ATRIX\u2122 4G", + "snippet": "MOTOROLA ATRIX 4G the world's most powerful smartphone." + }, + { + "age": 3, + "id": "dell-streak-7", + "imageUrl": "img/phones/dell-streak-7.0.jpg", + "name": "Dell Streak 7", + "snippet": "Introducing Dell\u2122 Streak 7. Share photos, videos and movies together. It\u2019s small enough to carry around, big enough to gather around." + }, + { + "age": 4, + "carrier": "Cellular South", + "id": "samsung-gem", + "imageUrl": "img/phones/samsung-gem.0.jpg", + "name": "Samsung Gem\u2122", + "snippet": "The Samsung Gem\u2122 brings you everything that you would expect and more from a touch display smart phone \u2013 more apps, more features and a more affordable price." + }, + { + "age": 5, + "carrier": "Dell", + "id": "dell-venue", + "imageUrl": "img/phones/dell-venue.0.jpg", + "name": "Dell Venue", + "snippet": "The Dell Venue; Your Personal Express Lane to Everything" + }, + { + "age": 6, + "carrier": "Best Buy", + "id": "nexus-s", + "imageUrl": "img/phones/nexus-s.0.jpg", + "name": "Nexus S", + "snippet": "Fast just got faster with Nexus S. A pure Google experience, Nexus S is the first phone to run Gingerbread (Android 2.3), the fastest version of Android yet." + }, + { + "age": 7, + "carrier": "Cellular South", + "id": "lg-axis", + "imageUrl": "img/phones/lg-axis.0.jpg", + "name": "LG Axis", + "snippet": "Android Powered, Google Maps Navigation, 5 Customizable Home Screens" + }, + { + "age": 8, + "id": "samsung-galaxy-tab", + "imageUrl": "img/phones/samsung-galaxy-tab.0.jpg", + "name": "Samsung Galaxy Tab\u2122", + "snippet": "Feel Free to Tab\u2122. The Samsung Galaxy Tab\u2122 brings you an ultra-mobile entertainment experience through its 7\u201d display, high-power processor and Adobe\u00ae Flash\u00ae Player compatibility." + }, + { + "age": 9, + "carrier": "Cellular South", + "id": "samsung-showcase-a-galaxy-s-phone", + "imageUrl": "img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg", + "name": "Samsung Showcase\u2122 a Galaxy S\u2122 phone", + "snippet": "The Samsung Showcase\u2122 delivers a cinema quality experience like you\u2019ve never seen before. Its innovative 4\u201d touch display technology provides rich picture brilliance, even outdoors" + }, + { + "age": 10, + "carrier": "Verizon", + "id": "droid-2-global-by-motorola", + "imageUrl": "img/phones/droid-2-global-by-motorola.0.jpg", + "name": "DROID\u2122 2 Global by Motorola", + "snippet": "The first smartphone with a 1.2 GHz processor and global capabilities." + }, + { + "age": 11, + "carrier": "Verizon", + "id": "droid-pro-by-motorola", + "imageUrl": "img/phones/droid-pro-by-motorola.0.jpg", + "name": "DROID\u2122 Pro by Motorola", + "snippet": "The next generation of DOES." + }, + { + "age": 12, + "carrier": "AT&T", + "id": "motorola-bravo-with-motoblur", + "imageUrl": "img/phones/motorola-bravo-with-motoblur.0.jpg", + "name": "MOTOROLA BRAVO\u2122 with MOTOBLUR\u2122", + "snippet": "An experience to cheer about." + }, + { + "age": 13, + "carrier": "T-Mobile", + "id": "motorola-defy-with-motoblur", + "imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg", + "name": "Motorola DEFY\u2122 with MOTOBLUR\u2122", + "snippet": "Are you ready for everything life throws your way?" + }, + { + "age": 14, + "carrier": "T-Mobile", + "id": "t-mobile-mytouch-4g", + "imageUrl": "img/phones/t-mobile-mytouch-4g.0.jpg", + "name": "T-Mobile myTouch 4G", + "snippet": "The T-Mobile myTouch 4G is a premium smartphone designed to deliver blazing fast 4G speeds so that you can video chat from practically anywhere, with or without Wi-Fi." + }, + { + "age": 15, + "carrier": "US Cellular", + "id": "samsung-mesmerize-a-galaxy-s-phone", + "imageUrl": "img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg", + "name": "Samsung Mesmerize\u2122 a Galaxy S\u2122 phone", + "snippet": "The Samsung Mesmerize\u2122 delivers a cinema quality experience like you\u2019ve never seen before. Its innovative 4\u201d touch display technology provides rich picture brilliance,even outdoors" + }, + { + "age": 16, + "carrier": "Sprint", + "id": "sanyo-zio", + "imageUrl": "img/phones/sanyo-zio.0.jpg", + "name": "SANYO ZIO", + "snippet": "The Sanyo Zio by Kyocera is an Android smartphone with a combination of ultra-sleek styling, strong performance and unprecedented value." + }, + { + "age": 17, + "id": "samsung-transform", + "imageUrl": "img/phones/samsung-transform.0.jpg", + "name": "Samsung Transform\u2122", + "snippet": "The Samsung Transform\u2122 brings you a fun way to customize your Android powered touch screen phone to just the way you like it through your favorite themed \u201cSprint ID Service Pack\u201d." + }, + { + "age": 18, + "id": "t-mobile-g2", + "imageUrl": "img/phones/t-mobile-g2.0.jpg", + "name": "T-Mobile G2", + "snippet": "The T-Mobile G2 with Google is the first smartphone built for 4G speeds on T-Mobile's new network. Get the information you need, faster than you ever thought possible." + }, + { + "age": 19, + "id": "motorola-charm-with-motoblur", + "imageUrl": "img/phones/motorola-charm-with-motoblur.0.jpg", + "name": "Motorola CHARM\u2122 with MOTOBLUR\u2122", + "snippet": "Motorola CHARM fits easily in your pocket or palm. Includes MOTOBLUR service." + } +] diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/example-config.json b/aio/content/examples/upgrade-phonecat-1-typescript/ts/example-config.json new file mode 100644 index 0000000000..a8781b9a31 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/example-config.json @@ -0,0 +1,4 @@ +{ + "build": "build:upgrade", + "run": "serve:upgrade" +} diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/karma.conf.ajs.js b/aio/content/examples/upgrade-phonecat-1-typescript/ts/karma.conf.ajs.js new file mode 100644 index 0000000000..dc829d1983 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/karma.conf.ajs.js @@ -0,0 +1,32 @@ +//jshint strict: false +module.exports = function(config) { + config.set({ + + basePath: './app', + + files: [ + 'https://code.angularjs.org/1.5.5/angular.js', + 'https://code.angularjs.org/1.5.5/angular-animate.js', + 'https://code.angularjs.org/1.5.5/angular-resource.js', + 'https://code.angularjs.org/1.5.5/angular-route.js', + 'https://code.angularjs.org/1.5.5/angular-mocks.js', + '**/*.module.js', + '*!(.module|.spec).js', + '!(bower_components)/**/*!(.module|.spec).js', + '**/*.spec.js' + ], + + autoWatch: true, + + frameworks: ['jasmine'], + + browsers: ['Chrome', 'Firefox'], + + plugins: [ + 'karma-chrome-launcher', + 'karma-firefox-launcher', + 'karma-jasmine' + ] + + }); +}; diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/run-unit-tests.sh b/aio/content/examples/upgrade-phonecat-1-typescript/ts/run-unit-tests.sh new file mode 100755 index 0000000000..239e5ff7d7 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/run-unit-tests.sh @@ -0,0 +1,7 @@ +## The boilerplate Karma configuration won't work with AngularJS tests since +## a specific loading configuration is needed for them. +## We keep one in karma.conf.ajs.js. This scripts runs the AngularJS tests with +## that config. + +PATH=$(npm bin):$PATH +tsc && karma start karma.conf.ajs.js diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/tsconfig.ajs.json b/aio/content/examples/upgrade-phonecat-1-typescript/ts/tsconfig.ajs.json new file mode 100644 index 0000000000..53da36ca95 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/tsconfig.ajs.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "removeComments": false, + "noImplicitAny": false, + "suppressImplicitAnyIndexErrors": true + } +} diff --git a/aio/content/examples/upgrade-phonecat-1-typescript/ts/tsconfig.json b/aio/content/examples/upgrade-phonecat-1-typescript/ts/tsconfig.json new file mode 100644 index 0000000000..be11d1eb49 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-1-typescript/ts/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ "es2015", "dom" ], + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true, + "typeRoots": [ + "../../node_modules/@types/" + ] + }, + "compileOnSave": true, + "exclude": [ + "node_modules/*", + "**/*-aot.ts", + "aot/**/*" + ] +} diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/README.md b/aio/content/examples/upgrade-phonecat-2-hybrid/README.md new file mode 100644 index 0000000000..b005739148 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/README.md @@ -0,0 +1,34 @@ +This is the Angular Phonecat application adjusted to fit our boilerplate project +structure. + +The following changes from vanilla Phonecat are applied: + +* Karma config for unit tests is in karma.conf.ajs.js because the boilerplate + Karma config is not compatible with the way AngularJS tests need to be run. + The shell script run-unit-tests.sh can be used to run the unit tests. +* There's a `package.ajs.json`, which is not used to run anything but only to + show an example of changing the PhoneCat http-server root path. +* Also for the Karma shim, there is a `karma-test-shim.1.js` file which isn't + used but is shown in the test appendix. +* Instead of using Bower, AngularJS and its dependencies are fetched from a CDN + in index.html and karma.conf.ajs.js. +* E2E tests have been moved to the parent directory, where `run-e2e-tests` can + discover and run them along with all the other examples. +* Most of the phone JSON and image data removed in the interest of keeping + repo weight down. Keeping enough to retain testability of the app. + +## Running the app + +Start like any example + + npm run start + +## Running unit tests + + ./run-unit-tests.sh + +## Running E2E tests + +Like for any example (at the project root): + + gulp run-e2e-tests --filter=phonecat-2 diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/e2e-spec.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/e2e-spec.ts new file mode 100644 index 0000000000..2ec8e37977 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/e2e-spec.ts @@ -0,0 +1,107 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; +import { setProtractorToHybridMode } from '../protractor-helpers'; + +// Angular E2E Testing Guide: +// https://docs.angularjs.org/guide/e2e-testing + +describe('PhoneCat Application', function() { + + beforeAll(function () { + setProtractorToHybridMode(); + }); + + it('should redirect `index.html` to `index.html#!/phones', function() { + browser.get('index.html'); + expect(browser.getLocationAbsUrl()).toBe('/phones'); + }); + + describe('View: Phone list', function() { + + beforeEach(function() { + browser.get('index.html#!/phones'); + }); + + it('should filter the phone list as a user types into the search box', function() { + let phoneList = element.all(by.css('.phones li')); + let query = element(by.css('input')); + + expect(phoneList.count()).toBe(20); + + query.sendKeys('nexus'); + expect(phoneList.count()).toBe(1); + + query.clear(); + query.sendKeys('motorola'); + expect(phoneList.count()).toBe(8); + }); + + it('should be possible to control phone order via the drop-down menu', function() { + let queryField = element(by.css('input')); + let orderSelect = element(by.css('select')); + let nameOption = orderSelect.element(by.css('option[value="name"]')); + let phoneNameColumn = element.all(by.css('.phones .name')); + + function getNames() { + return phoneNameColumn.map(function(elem) { + return elem.getText(); + }); + } + + queryField.sendKeys('tablet'); // Let's narrow the dataset to make the assertions shorter + + expect(getNames()).toEqual([ + 'Motorola XOOM\u2122 with Wi-Fi', + 'MOTOROLA XOOM\u2122' + ]); + + nameOption.click(); + + expect(getNames()).toEqual([ + 'MOTOROLA XOOM\u2122', + 'Motorola XOOM\u2122 with Wi-Fi' + ]); + }); + + it('should render phone specific links', function() { + let query = element(by.css('input')); + query.sendKeys('nexus'); + + element.all(by.css('.phones li a')).first().click(); + browser.sleep(200); // Not sure why this is needed but it is. The route change works fine. + expect(browser.getLocationAbsUrl()).toBe('/phones/nexus-s'); + }); + + }); + + describe('View: Phone detail', function() { + + beforeEach(function() { + browser.get('index.html#!/phones/nexus-s'); + }); + + it('should display the `nexus-s` page', function() { + expect(element(by.css('h1')).getText()).toBe('Nexus S'); + }); + + it('should display the first phone image as the main phone image', function() { + let mainImage = element(by.css('img.phone.selected')); + + expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/); + }); + + it('should swap the main image when clicking on a thumbnail image', function() { + let mainImage = element(by.css('img.phone.selected')); + let thumbnails = element.all(by.css('.phone-thumbs img')); + + thumbnails.get(2).click(); + expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.2.jpg/); + + thumbnails.get(0).click(); + expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/); + }); + + }); + +}); diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/.gitignore b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/.gitignore new file mode 100644 index 0000000000..927d62d641 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/.gitignore @@ -0,0 +1,8 @@ +aot/**/* +!aot/index.html +dist +!app/tsconfig.json +!rollup-config.js +!karma.conf.ajs.js +!copy-dist-files.js +!systemjs.config.1.js diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/aot/index.html b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/aot/index.html new file mode 100644 index 0000000000..fdc9584928 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/aot/index.html @@ -0,0 +1,40 @@ + + + + + + + + + Google Phone Gallery + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/ajs-upgraded-providers.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/ajs-upgraded-providers.ts new file mode 100644 index 0000000000..f6e1654d74 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/ajs-upgraded-providers.ts @@ -0,0 +1,14 @@ +// #docregion +export abstract class RouteParams { + [key: string]: string; +} + +export function routeParamsFactory(i: any) { + return i.get('$routeParams'); +} + +export const routeParamsProvider = { + provide: RouteParams, + useFactory: routeParamsFactory, + deps: ['$injector'] +}; diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/app.animations.css b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/app.animations.css new file mode 100644 index 0000000000..175320b509 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/app.animations.css @@ -0,0 +1,67 @@ +/* Animate `ngRepeat` in `phoneList` component */ +.phone-list-item.ng-enter, +.phone-list-item.ng-leave, +.phone-list-item.ng-move { + overflow: hidden; + transition: 0.5s linear all; +} + +.phone-list-item.ng-enter, +.phone-list-item.ng-leave.ng-leave-active, +.phone-list-item.ng-move { + height: 0; + margin-bottom: 0; + opacity: 0; + padding-bottom: 0; + padding-top: 0; +} + +.phone-list-item.ng-enter.ng-enter-active, +.phone-list-item.ng-leave, +.phone-list-item.ng-move.ng-move-active { + height: 120px; + margin-bottom: 20px; + opacity: 1; + padding-bottom: 4px; + padding-top: 15px; +} + +/* Animate view transitions with `ngView` */ +.view-container { + position: relative; +} + +.view-frame { + margin-top: 20px; +} + +.view-frame.ng-enter, +.view-frame.ng-leave { + background: white; + left: 0; + position: absolute; + right: 0; + top: 0; +} + +.view-frame.ng-enter { + animation: 1s fade-in; + z-index: 100; +} + +.view-frame.ng-leave { + animation: 1s fade-out; + z-index: 99; +} + +@keyframes fade-in { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes fade-out { + from { opacity: 1; } + to { opacity: 0; } +} + +/* Older browsers might need vendor-prefixes for keyframes and animation! */ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/app.animations.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/app.animations.ts new file mode 100644 index 0000000000..f0739b6405 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/app.animations.ts @@ -0,0 +1,43 @@ +'use strict'; + +angular. + module('phonecatApp'). + animation('.phone', function phoneAnimationFactory() { + return { + addClass: animateIn, + removeClass: animateOut + }; + + function animateIn(element: JQuery, className: string, done: () => void) { + if (className !== 'selected') { return; } + + element.css({ + display: 'block', + position: 'absolute', + top: 500, + left: 0 + }).animate({ + top: 0 + }, done); + + return function animateInEnd(wasCanceled: boolean) { + if (wasCanceled) { element.stop(); } + }; + } + + function animateOut(element: JQuery, className: string, done: () => void) { + if (className !== 'selected') { return; } + + element.css({ + position: 'absolute', + top: 0, + left: 0 + }).animate({ + top: -500 + }, done); + + return function animateOutEnd(wasCanceled: boolean) { + if (wasCanceled) { element.stop(); } + }; + } + }); diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/app.config.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/app.config.ts new file mode 100644 index 0000000000..458ed94250 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/app.config.ts @@ -0,0 +1,19 @@ +'use strict'; + +angular. + module('phonecatApp'). + config(['$locationProvider', '$routeProvider', + function config($locationProvider: angular.ILocationProvider, + $routeProvider: angular.route.IRouteProvider) { + $locationProvider.hashPrefix('!'); + + $routeProvider. + when('/phones', { + template: '' + }). + when('/phones/:phoneId', { + template: '' + }). + otherwise('/phones'); + } + ]); diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/app.css b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/app.css new file mode 100644 index 0000000000..f4b45b02a5 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/app.css @@ -0,0 +1,93 @@ +body { + padding: 20px; +} + +h1 { + border-bottom: 1px solid gray; + margin-top: 0; +} + +/* View: Phone list */ +.phones { + list-style: none; +} + +.phones li { + clear: both; + height: 115px; + padding-top: 15px; +} + +.thumb { + float: left; + height: 100px; + margin: -0.5em 1em 1.5em 0; + padding-bottom: 1em; + width: 100px; +} + +/* View: Phone detail */ +.phone { + background-color: white; + display: none; + float: left; + height: 400px; + margin-bottom: 2em; + margin-right: 3em; + padding: 2em; + width: 400px; +} + +.phone:first-child { + display: block; +} + +.phone-images { + background-color: white; + float: left; + height: 450px; + overflow: hidden; + position: relative; + width: 450px; +} + +.phone-thumbs { + list-style: none; + margin: 0; +} + +.phone-thumbs img { + height: 100px; + padding: 1em; + width: 100px; +} + +.phone-thumbs li { + background-color: white; + border: 1px solid black; + cursor: pointer; + display: inline-block; + margin: 1em; +} + +.specs { + clear: both; + list-style: none; + margin: 0; + padding: 0; +} + +.specs dt { + font-weight: bold; +} + +.specs > li { + display: inline-block; + vertical-align: top; + width: 200px; +} + +.specs > li > span { + font-size: 1.2em; + font-weight: bold; +} diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/app.module.ajs.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/app.module.ajs.ts new file mode 100644 index 0000000000..089c3c7d85 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/app.module.ajs.ts @@ -0,0 +1,11 @@ +// #docregion +'use strict'; + +// Define the `phonecatApp` AngularJS module +angular.module('phonecatApp', [ + 'ngAnimate', + 'ngRoute', + 'core', + 'phoneDetail', + 'phoneList', +]); diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/app.module.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/app.module.ts new file mode 100644 index 0000000000..ea570e3cbf --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/app.module.ts @@ -0,0 +1,73 @@ +// #docplaster +// #docregion bare +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +// #enddocregion bare +// #docregion upgrademodule +import { UpgradeModule } from '@angular/upgrade/static'; +// #enddocregion upgrademodule +// #docregion httpmodule +import { HttpModule } from '@angular/http'; +// #enddocregion httpmodule +// #docregion phonelist +import { FormsModule } from '@angular/forms'; +// #enddocregion phonelist +// #docregion phone +import { Phone } from './core/phone/phone.service'; +// #enddocregion phone +// #docregion checkmarkpipe +import { CheckmarkPipe } from './core/checkmark/checkmark.pipe'; +// #enddocregion checkmarkpipe +// #docregion phonelist +import { PhoneListComponent } from './phone-list/phone-list.component'; +// #enddocregion phonelist +// #docregion routeparams +import { routeParamsProvider } from './ajs-upgraded-providers'; +// #enddocregion routeparams +// #docregion phonedetail +import { PhoneDetailComponent } from './phone-detail/phone-detail.component'; +// #enddocregion phonedetail + +// #docregion bare, upgrademodule, httpmodule, phone, phonelist, phonedetail, checkmarkpipe + +@NgModule({ + imports: [ + BrowserModule, + // #enddocregion bare + UpgradeModule, + // #enddocregion upgrademodule + HttpModule, + // #enddocregion httpmodule, phone + FormsModule, + // #docregion bare, upgrademodule, httpmodule, phone + ], + // #enddocregion bare, upgrademodule, httpmodule, phone + declarations: [ + PhoneListComponent, + // #enddocregion phonelist + PhoneDetailComponent, + // #enddocregion phonedetail + CheckmarkPipe + // #docregion phonelist, phonedetail + ], + entryComponents: [ + PhoneListComponent, + // #enddocregion phonelist + PhoneDetailComponent + ], + // #docregion phone, routeparams + providers: [ + Phone, + // #enddocregion phone + routeParamsProvider + // #docregion phone + ] + // #enddocregion routeparams +// #docregion bare, upgrademodule, httpmodule, phonelist +}) +export class AppModule { + // #enddocregion bare + ngDoBootstrap() {} + // #docregion bare +} +// #enddocregion bare, upgrademodule, httpmodule, phone, phonelist, phonedetail, checkmarkpipe diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/core/checkmark/checkmark.pipe.spec.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/core/checkmark/checkmark.pipe.spec.ts new file mode 100644 index 0000000000..f7485ec2ba --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/core/checkmark/checkmark.pipe.spec.ts @@ -0,0 +1,11 @@ +// #docregion +import { CheckmarkPipe } from './checkmark.pipe'; + +describe('CheckmarkPipe', function() { + + it('should convert boolean values to unicode checkmark or cross', function () { + const checkmarkPipe = new CheckmarkPipe(); + expect(checkmarkPipe.transform(true)).toBe('\u2713'); + expect(checkmarkPipe.transform(false)).toBe('\u2718'); + }); +}); diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/core/checkmark/checkmark.pipe.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/core/checkmark/checkmark.pipe.ts new file mode 100644 index 0000000000..888017e15c --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/core/checkmark/checkmark.pipe.ts @@ -0,0 +1,9 @@ +// #docregion +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({name: 'checkmark'}) +export class CheckmarkPipe implements PipeTransform { + transform(input: boolean) { + return input ? '\u2713' : '\u2718'; + } +} diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/core/core.module.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/core/core.module.ts new file mode 100644 index 0000000000..84a91dc7a6 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/core/core.module.ts @@ -0,0 +1,4 @@ +'use strict'; + +// Define the `core` module +angular.module('core', ['core.phone']); diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/core/phone/phone.module.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/core/phone/phone.module.ts new file mode 100644 index 0000000000..0b6b348899 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/core/phone/phone.module.ts @@ -0,0 +1,4 @@ +'use strict'; + +// Define the `core.phone` module +angular.module('core.phone', ['ngResource']); diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/core/phone/phone.service.spec.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/core/phone/phone.service.spec.ts new file mode 100644 index 0000000000..a0c1655c20 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/core/phone/phone.service.spec.ts @@ -0,0 +1,51 @@ +// #docregion +import { inject, TestBed } from '@angular/core/testing'; +import { + Http, + BaseRequestOptions, + ResponseOptions, + Response +} from '@angular/http'; +import { MockBackend, MockConnection } from '@angular/http/testing'; +import { Phone, PhoneData } from './phone.service'; + +describe('Phone', function() { + let phone: Phone; + let phonesData: PhoneData[] = [ + {name: 'Phone X', snippet: '', images: []}, + {name: 'Phone Y', snippet: '', images: []}, + {name: 'Phone Z', snippet: '', images: []} + ]; + let mockBackend: MockBackend; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + Phone, + MockBackend, + BaseRequestOptions, + { provide: Http, + useFactory: (backend: MockBackend, options: BaseRequestOptions) => new Http(backend, options), + deps: [MockBackend, BaseRequestOptions] + } + ] + }); + }); + + beforeEach(inject([MockBackend, Phone], (_mockBackend_: MockBackend, _phone_: Phone) => { + mockBackend = _mockBackend_; + phone = _phone_; + })); + + it('should fetch the phones data from `/phones/phones.json`', (done: () => void) => { + mockBackend.connections.subscribe((conn: MockConnection) => { + conn.mockRespond(new Response(new ResponseOptions({body: JSON.stringify(phonesData)}))); + }); + phone.query().subscribe(result => { + expect(result).toEqual(phonesData); + done(); + }); + }); + +}); + diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/core/phone/phone.service.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/core/phone/phone.service.ts new file mode 100644 index 0000000000..c4673475fb --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/core/phone/phone.service.ts @@ -0,0 +1,42 @@ +// #docregion +import { Injectable } from '@angular/core'; +import { Http, Response } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; + +// #docregion downgrade-injectable +declare var angular: angular.IAngularStatic; +import { downgradeInjectable } from '@angular/upgrade/static'; +// #enddocregion downgrade-injectable + +import 'rxjs/add/operator/map'; + +// #docregion phonedata-interface +export interface PhoneData { + name: string; + snippet: string; + images: string[]; +} +// #enddocregion phonedata-interface + +// #docregion fullclass +// #docregion classdef, downgrade-injectable +@Injectable() +export class Phone { +// #enddocregion classdef, downgrade-injectable + constructor(private http: Http) { } + query(): Observable { + return this.http.get(`phones/phones.json`) + .map((res: Response) => res.json()); + } + get(id: string): Observable { + return this.http.get(`phones/${id}.json`) + .map((res: Response) => res.json()); + } +// #docregion classdef, downgrade-injectable +} +// #enddocregion classdef +// #enddocregion fullclass + +angular.module('core.phone') + .factory('phone', downgradeInjectable(Phone)); +// #enddocregion downgrade-injectable diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/.gitkeep b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.0.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.0.jpg new file mode 100644 index 0000000000..7ce0dce4ee Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.1.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.1.jpg new file mode 100644 index 0000000000..ed8cad89fb Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.1.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.2.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.2.jpg new file mode 100644 index 0000000000..c83529e0f9 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.2.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.3.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.3.jpg new file mode 100644 index 0000000000..cd2c30280d Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.3.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.4.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.4.jpg new file mode 100644 index 0000000000..b4d8472da8 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.4.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-venue.0.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-venue.0.jpg new file mode 100644 index 0000000000..b4cb4eb25f Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-venue.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/droid-2-global-by-motorola.0.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/droid-2-global-by-motorola.0.jpg new file mode 100644 index 0000000000..60700a2ab3 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/droid-2-global-by-motorola.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/droid-pro-by-motorola.0.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/droid-pro-by-motorola.0.jpg new file mode 100644 index 0000000000..c7710de986 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/droid-pro-by-motorola.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/lg-axis.0.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/lg-axis.0.jpg new file mode 100644 index 0000000000..55e5a23bb2 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/lg-axis.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-atrix-4g.0.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-atrix-4g.0.jpg new file mode 100644 index 0000000000..2446159e93 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-atrix-4g.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-atrix-4g.1.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-atrix-4g.1.jpg new file mode 100644 index 0000000000..867f207459 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-atrix-4g.1.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-atrix-4g.2.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-atrix-4g.2.jpg new file mode 100644 index 0000000000..27d78338c4 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-atrix-4g.2.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-atrix-4g.3.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-atrix-4g.3.jpg new file mode 100644 index 0000000000..29459a68a4 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-atrix-4g.3.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-bravo-with-motoblur.0.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-bravo-with-motoblur.0.jpg new file mode 100644 index 0000000000..e452ae7e7c Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-bravo-with-motoblur.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-charm-with-motoblur.0.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-charm-with-motoblur.0.jpg new file mode 100644 index 0000000000..21e4b8d741 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-charm-with-motoblur.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-defy-with-motoblur.0.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-defy-with-motoblur.0.jpg new file mode 100644 index 0000000000..c7c5e3ba0c Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-defy-with-motoblur.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.0.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.0.jpg new file mode 100644 index 0000000000..a6c993291e Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.1.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.1.jpg new file mode 100644 index 0000000000..400b89e2df Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.1.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.2.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.2.jpg new file mode 100644 index 0000000000..86b55d28dd Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.2.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.3.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.3.jpg new file mode 100644 index 0000000000..85ec293ae5 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.3.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.4.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.4.jpg new file mode 100644 index 0000000000..75ef1464cb Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.4.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.5.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.5.jpg new file mode 100644 index 0000000000..4d42db4330 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.5.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom.0.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom.0.jpg new file mode 100644 index 0000000000..bf6954bbd5 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom.1.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom.1.jpg new file mode 100644 index 0000000000..659688a47d Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom.1.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom.2.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom.2.jpg new file mode 100644 index 0000000000..ce0ff1002e Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom.2.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/nexus-s.0.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/nexus-s.0.jpg new file mode 100644 index 0000000000..0952bc79c2 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/nexus-s.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/nexus-s.1.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/nexus-s.1.jpg new file mode 100644 index 0000000000..f33004dd7f Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/nexus-s.1.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/nexus-s.2.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/nexus-s.2.jpg new file mode 100644 index 0000000000..c5c63621f1 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/nexus-s.2.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/nexus-s.3.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/nexus-s.3.jpg new file mode 100644 index 0000000000..e51f75b0ed Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/nexus-s.3.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-galaxy-tab.0.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-galaxy-tab.0.jpg new file mode 100644 index 0000000000..3750377ad9 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-galaxy-tab.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-gem.0.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-gem.0.jpg new file mode 100644 index 0000000000..0d5024a026 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-gem.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg new file mode 100644 index 0000000000..11b8f860cb Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg new file mode 100644 index 0000000000..11b8f860cb Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-transform.0.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-transform.0.jpg new file mode 100644 index 0000000000..0e3107caf5 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-transform.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/sanyo-zio.0.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/sanyo-zio.0.jpg new file mode 100644 index 0000000000..9eeb9b96ed Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/sanyo-zio.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/t-mobile-g2.0.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/t-mobile-g2.0.jpg new file mode 100644 index 0000000000..6b6c09e058 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/t-mobile-g2.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/t-mobile-mytouch-4g.0.jpg b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/t-mobile-mytouch-4g.0.jpg new file mode 100644 index 0000000000..beba1f6827 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/t-mobile-mytouch-4g.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/main-aot.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/main-aot.ts new file mode 100644 index 0000000000..23a741c684 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/main-aot.ts @@ -0,0 +1,10 @@ +// #docregion +import { platformBrowser } from '@angular/platform-browser'; +import { UpgradeModule } from '@angular/upgrade/static'; + +import { AppModuleNgFactory } from '../aot/app/app.module.ngfactory'; + +platformBrowser().bootstrapModuleFactory(AppModuleNgFactory).then(platformRef => { + const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule; + upgrade.bootstrap(document.documentElement, ['phonecatApp']); +}); diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/main.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/main.ts new file mode 100644 index 0000000000..886e8ffac8 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/main.ts @@ -0,0 +1,11 @@ +// #docregion bootstrap +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { UpgradeModule } from '@angular/upgrade/static'; + +import { AppModule } from './app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => { + const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule; + upgrade.bootstrap(document.documentElement, ['phonecatApp']); +}); +// #enddocregion bootstrap diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.component.ajs.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.component.ajs.ts new file mode 100644 index 0000000000..80282858c4 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.component.ajs.ts @@ -0,0 +1,28 @@ +// #docregion +declare var angular: angular.IAngularStatic; +import { Phone, PhoneData } from '../core/phone/phone.service'; + +class PhoneDetailController { + phone: PhoneData; + mainImageUrl: string; + + static $inject = ['$routeParams', 'phone']; + constructor($routeParams: angular.route.IRouteParamsService, phone: Phone) { + let phoneId = $routeParams['phoneId']; + phone.get(phoneId).subscribe(data => { + this.phone = data; + this.setImage(data.images[0]); + }); + } + + setImage(imageUrl: string) { + this.mainImageUrl = imageUrl; + } +} + +angular. + module('phoneDetail'). + component('phoneDetail', { + templateUrl: 'phone-detail/phone-detail.template.html', + controller: PhoneDetailController + }); diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.component.spec.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.component.spec.ts new file mode 100644 index 0000000000..e3b9143a94 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.component.spec.ts @@ -0,0 +1,59 @@ +// #docregion +// #docregion activatedroute +import { ActivatedRoute } from '@angular/router'; + +// #enddocregion activatedroute +import { Observable } from 'rxjs/Rx'; + +import { async, TestBed } from '@angular/core/testing'; + +import { PhoneDetailComponent } from './phone-detail.component'; +import { Phone, PhoneData } from '../core/phone/phone.service'; +import { CheckmarkPipe } from '../core/checkmark/checkmark.pipe'; + +function xyzPhoneData(): PhoneData { + return { + name: 'phone xyz', + snippet: '', + images: ['image/url1.png', 'image/url2.png'] + }; +} + +class MockPhone { + get(id: string): Observable { + return Observable.of(xyzPhoneData()); + } +} + +// #docregion activatedroute + +class ActivatedRouteMock { + constructor(public snapshot: any) {} +} + +// #enddocregion activatedroute + +describe('PhoneDetailComponent', () => { + + // #docregion activatedroute + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CheckmarkPipe, PhoneDetailComponent ], + providers: [ + { provide: Phone, useClass: MockPhone }, + { provide: ActivatedRoute, useValue: new ActivatedRouteMock({ params: { 'phoneId': 1 } }) } + ] + }) + .compileComponents(); + })); + // #enddocregion activatedroute + + it('should fetch phone detail', () => { + const fixture = TestBed.createComponent(PhoneDetailComponent); + fixture.detectChanges(); + let compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain(xyzPhoneData().name); + }); + +}); diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.component.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.component.ts new file mode 100644 index 0000000000..e486ab3e6d --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.component.ts @@ -0,0 +1,42 @@ +// #docplaster +// #docregion +declare var angular: angular.IAngularStatic; +import { downgradeComponent } from '@angular/upgrade/static'; + +// #docregion initialclass +import { Component } from '@angular/core'; + +import { Phone, PhoneData } from '../core/phone/phone.service'; +// #enddocregion initialclass +import { RouteParams } from '../ajs-upgraded-providers'; + +// #docregion initialclass +@Component({ + moduleId: module.id, + selector: 'phone-detail', + templateUrl: './phone-detail.template.html', + // #enddocregion initialclass + // #docregion initialclass +}) +export class PhoneDetailComponent { + phone: PhoneData; + mainImageUrl: string; + + constructor(routeParams: RouteParams, phone: Phone) { + phone.get(routeParams['phoneId']).subscribe(phone => { + this.phone = phone; + this.setImage(phone.images[0]); + }); + } + + setImage(imageUrl: string) { + this.mainImageUrl = imageUrl; + } +} +// #enddocregion initialclass + +angular.module('phoneDetail') + .directive( + 'phoneDetail', + downgradeComponent({component: PhoneDetailComponent}) as angular.IDirectiveFactory + ); diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.module.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.module.ts new file mode 100644 index 0000000000..fd7cb3b920 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.module.ts @@ -0,0 +1,7 @@ +'use strict'; + +// Define the `phoneDetail` module +angular.module('phoneDetail', [ + 'ngRoute', + 'core.phone' +]); diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.template.html b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.template.html new file mode 100644 index 0000000000..46a96d66c3 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.template.html @@ -0,0 +1,120 @@ + +
    +
    + +
    + +

    {{phone.name}}

    + +

    {{phone.description}}

    + +
      +
    • + +
    • +
    + +
      +
    • + Availability and Networks +
      +
      Availability
      +
      {{availability}}
      +
      +
    • +
    • + Battery +
      +
      Type
      +
      {{phone.battery?.type}}
      +
      Talk Time
      +
      {{phone.battery?.talkTime}}
      +
      Standby time (max)
      +
      {{phone.battery?.standbyTime}}
      +
      +
    • +
    • + Storage and Memory +
      +
      RAM
      +
      {{phone.storage?.ram}}
      +
      Internal Storage
      +
      {{phone.storage?.flash}}
      +
      +
    • +
    • + Connectivity +
      +
      Network Support
      +
      {{phone.connectivity?.cell}}
      +
      WiFi
      +
      {{phone.connectivity?.wifi}}
      +
      Bluetooth
      +
      {{phone.connectivity?.bluetooth}}
      +
      Infrared
      +
      {{phone.connectivity?.infrared | checkmark}}
      +
      GPS
      +
      {{phone.connectivity?.gps | checkmark}}
      +
      +
    • +
    • + Android +
      +
      OS Version
      +
      {{phone.android?.os}}
      +
      UI
      +
      {{phone.android?.ui}}
      +
      +
    • +
    • + Size and Weight +
      +
      Dimensions
      +
      {{dim}}
      +
      Weight
      +
      {{phone.sizeAndWeight?.weight}}
      +
      +
    • +
    • + Display +
      +
      Screen size
      +
      {{phone.display?.screenSize}}
      +
      Screen resolution
      +
      {{phone.display?.screenResolution}}
      +
      Touch screen
      +
      {{phone.display?.touchScreen | checkmark}}
      +
      +
    • +
    • + Hardware +
      +
      CPU
      +
      {{phone.hardware?.cpu}}
      +
      USB
      +
      {{phone.hardware?.usb}}
      +
      Audio / headphone jack
      +
      {{phone.hardware?.audioJack}}
      +
      FM Radio
      +
      {{phone.hardware?.fmRadio | checkmark}}
      +
      Accelerometer
      +
      {{phone.hardware?.accelerometer | checkmark}}
      +
      +
    • +
    • + Camera +
      +
      Primary
      +
      {{phone.camera?.primary}}
      +
      Features
      +
      {{phone.camera?.features?.join(', ')}}
      +
      +
    • +
    • + Additional Features +
      {{phone.additionalFeatures}}
      +
    • +
    +
    diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.component.ajs.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.component.ajs.ts new file mode 100644 index 0000000000..81eac1cd81 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.component.ajs.ts @@ -0,0 +1,24 @@ +// #docregion +declare var angular: angular.IAngularStatic; +import { Phone, PhoneData } from '../core/phone/phone.service'; + +class PhoneListController { + phones: PhoneData[]; + orderProp: string; + + static $inject = ['phone']; + constructor(phone: Phone) { + phone.query().subscribe(phones => { + this.phones = phones; + }); + this.orderProp = 'age'; + } + +} + +angular. + module('phoneList'). + component('phoneList', { + templateUrl: 'app/phone-list/phone-list.template.html', + controller: PhoneListController + }); diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.component.spec.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.component.spec.ts new file mode 100644 index 0000000000..2bb9d2b62f --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.component.spec.ts @@ -0,0 +1,66 @@ +/* tslint:disable */ +// #docregion +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs/Rx'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { SpyLocation } from '@angular/common/testing'; + +import { PhoneListComponent } from './phone-list.component'; +import { Phone, PhoneData } from '../core/phone/phone.service'; + +class ActivatedRouteMock { + constructor(public snapshot: any) {} +} + +class MockPhone { + query(): Observable { + return Observable.of([ + {name: 'Nexus S', snippet: '', images: []}, + {name: 'Motorola DROID', snippet: '', images: []} + ]); + } +} + +let fixture: ComponentFixture; + +describe('PhoneList', () => { + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PhoneListComponent ], + providers: [ + { provide: ActivatedRoute, useValue: new ActivatedRouteMock({ params: { 'phoneId': 1 } }) }, + { provide: Location, useClass: SpyLocation }, + { provide: Phone, useClass: MockPhone }, + ], + schemas: [ NO_ERRORS_SCHEMA ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PhoneListComponent); + }); + + it('should create "phones" model with 2 phones fetched from xhr', () => { + fixture.detectChanges(); + let compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelectorAll('.phone-list-item').length).toBe(2); + expect( + compiled.querySelector('.phone-list-item:nth-child(1)').textContent + ).toContain('Motorola DROID'); + expect( + compiled.querySelector('.phone-list-item:nth-child(2)').textContent + ).toContain('Nexus S'); + }); + + xit('should set the default value of orderProp model', () => { + fixture.detectChanges(); + let compiled = fixture.debugElement.nativeElement; + expect( + compiled.querySelector('select option:last-child').selected + ).toBe(true); + }); + +}); diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.component.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.component.ts new file mode 100644 index 0000000000..bdd5930b79 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.component.ts @@ -0,0 +1,73 @@ +// #docregion downgrade-component +declare var angular: angular.IAngularStatic; +import { downgradeComponent } from '@angular/upgrade/static'; + +// #enddocregion downgrade-component + +// #docregion initialclass +import { Component } from '@angular/core'; +import { Phone, PhoneData } from '../core/phone/phone.service'; + +// #docregion downgrade-component +@Component({ + moduleId: module.id, + selector: 'phone-list', + templateUrl: 'phone-list.template.html' +}) +export class PhoneListComponent { + // #enddocregion downgrade-component + phones: PhoneData[]; + query: string; + orderProp: string; + + constructor(phone: Phone) { + phone.query().subscribe(phones => { + this.phones = phones; + }); + this.orderProp = 'age'; + } + // #enddocregion initialclass + + // #docregion getphones + getPhones(): PhoneData[] { + return this.sortPhones(this.filterPhones(this.phones)); + } + + private filterPhones(phones: PhoneData[]) { + if (phones && this.query) { + return phones.filter(phone => { + let name = phone.name.toLowerCase(); + let snippet = phone.snippet.toLowerCase(); + return name.indexOf(this.query) >= 0 || snippet.indexOf(this.query) >= 0; + }); + } + return phones; + } + + private sortPhones(phones: PhoneData[]) { + if (phones && this.orderProp) { + return phones + .slice(0) // Make a copy + .sort((a, b) => { + if (a[this.orderProp] < b[this.orderProp]) { + return -1; + } else if ([b[this.orderProp] < a[this.orderProp]]) { + return 1; + } else { + return 0; + } + }); + } + return phones; + } + // #enddocregion getphones + // #docregion initialclass, downgrade-component +} +// #enddocregion initialclass + +angular.module('phoneList') + .directive( + 'phoneList', + downgradeComponent({component: PhoneListComponent}) as angular.IDirectiveFactory + ); +// #enddocregion downgrade-component diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.module.ts b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.module.ts new file mode 100644 index 0000000000..8ade7c5b88 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.module.ts @@ -0,0 +1,4 @@ +'use strict'; + +// Define the `phoneList` module +angular.module('phoneList', ['core.phone']); diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.template.html b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.template.html new file mode 100644 index 0000000000..8f02113985 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.template.html @@ -0,0 +1,40 @@ +
    +
    +
    + + + +

    + Search: + +

    + +

    + Sort by: + +

    + + +
    +
    + + + + + + +
    +
    +
    diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phones/dell-streak-7.json b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phones/dell-streak-7.json new file mode 100644 index 0000000000..a32eb6ff98 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phones/dell-streak-7.json @@ -0,0 +1,64 @@ +{ + "additionalFeatures": "Front Facing 1.3MP Camera", + "android": { + "os": "Android 2.2", + "ui": "Dell Stage" + }, + "availability": [ + "T-Mobile" + ], + "battery": { + "standbyTime": "", + "talkTime": "", + "type": "Lithium Ion (Li-Ion) (2780 mAH)" + }, + "camera": { + "features": [ + "Flash", + "Video" + ], + "primary": "5.0 megapixels" + }, + "connectivity": { + "bluetooth": "Bluetooth 2.1", + "cell": "T-mobile HSPA+ @ 2100/1900/AWS/850 MHz", + "gps": true, + "infrared": false, + "wifi": "802.11 b/g" + }, + "description": "Introducing Dell\u2122 Streak 7. Share photos, videos and movies together. It\u2019s small enough to carry around, big enough to gather around. Android\u2122 2.2-based tablet with over-the-air upgrade capability for future OS releases. A vibrant 7-inch, multitouch display with full Adobe\u00ae Flash 10.1 pre-installed. Includes a 1.3 MP front-facing camera for face-to-face chats on popular services such as Qik or Skype. 16 GB of internal storage, plus Wi-Fi, Bluetooth and built-in GPS keeps you in touch with the world around you. Connect on your terms. Save with 2-year contract or flexibility with prepaid pay-as-you-go plans", + "display": { + "screenResolution": "WVGA (800 x 480)", + "screenSize": "7.0 inches", + "touchScreen": true + }, + "hardware": { + "accelerometer": true, + "audioJack": "3.5mm", + "cpu": "nVidia Tegra T20", + "fmRadio": false, + "physicalKeyboard": false, + "usb": "USB 2.0" + }, + "id": "dell-streak-7", + "images": [ + "img/phones/dell-streak-7.0.jpg", + "img/phones/dell-streak-7.1.jpg", + "img/phones/dell-streak-7.2.jpg", + "img/phones/dell-streak-7.3.jpg", + "img/phones/dell-streak-7.4.jpg" + ], + "name": "Dell Streak 7", + "sizeAndWeight": { + "dimensions": [ + "199.9 mm (w)", + "119.8 mm (h)", + "12.4 mm (d)" + ], + "weight": "450.0 grams" + }, + "storage": { + "flash": "16000MB", + "ram": "512MB" + } +} diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phones/motorola-atrix-4g.json b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phones/motorola-atrix-4g.json new file mode 100644 index 0000000000..ccca00e3b2 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phones/motorola-atrix-4g.json @@ -0,0 +1,62 @@ +{ + "additionalFeatures": "", + "android": { + "os": "Android 2.2", + "ui": "MOTOBLUR" + }, + "availability": [ + "AT&T" + ], + "battery": { + "standbyTime": "400 hours", + "talkTime": "5 hours", + "type": "Lithium Ion (Li-Ion) (1930 mAH)" + }, + "camera": { + "features": [ + "" + ], + "primary": "" + }, + "connectivity": { + "bluetooth": "Bluetooth 2.1", + "cell": "WCDMA 850/1900/2100, GSM 850/900/1800/1900, HSDPA 14Mbps (Category 10) Edge Class 12, GPRS Class 12, eCompass, AGPS", + "gps": true, + "infrared": false, + "wifi": "802.11 a/b/g/n" + }, + "description": "MOTOROLA ATRIX 4G gives you dual-core processing power and the revolutionary webtop application. With webtop and a compatible Motorola docking station, sold separately, you can surf the Internet with a full Firefox browser, create and edit docs, or access multimedia on a large screen almost anywhere.", + "display": { + "screenResolution": "QHD (960 x 540)", + "screenSize": "4.0 inches", + "touchScreen": true + }, + "hardware": { + "accelerometer": true, + "audioJack": "3.5mm", + "cpu": "1 GHz Dual Core", + "fmRadio": false, + "physicalKeyboard": false, + "usb": "USB 2.0" + }, + "id": "motorola-atrix-4g", + "images": [ + "img/phones/motorola-atrix-4g.0.jpg", + "img/phones/motorola-atrix-4g.1.jpg", + "img/phones/motorola-atrix-4g.2.jpg", + "img/phones/motorola-atrix-4g.3.jpg" + ], + "name": "MOTOROLA ATRIX\u2122 4G", + "sizeAndWeight": { + "dimensions": [ + "63.5 mm (w)", + "117.75 mm (h)", + "10.95 mm (d)" + ], + "weight": "135.0 grams" + }, + "storage": { + "flash": "", + "ram": "" + } +} diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phones/motorola-xoom-with-wi-fi.json b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phones/motorola-xoom-with-wi-fi.json new file mode 100644 index 0000000000..4ba9c8d5b5 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phones/motorola-xoom-with-wi-fi.json @@ -0,0 +1,65 @@ +{ + "additionalFeatures": "Sensors: proximity, ambient light, barometer, gyroscope", + "android": { + "os": "Android 3.0", + "ui": "Honeycomb" + }, + "availability": [ + "" + ], + "battery": { + "standbyTime": "336 hours", + "talkTime": "24 hours", + "type": "Other ( mAH)" + }, + "camera": { + "features": [ + "Flash", + "Video" + ], + "primary": "5.0 megapixels" + }, + "connectivity": { + "bluetooth": "Bluetooth 2.1", + "cell": "", + "gps": true, + "infrared": false, + "wifi": "802.11 b/g/n" + }, + "description": "Motorola XOOM with Wi-Fi has a super-powerful dual-core processor and Android\u2122 3.0 (Honeycomb) \u2014 the Android platform designed specifically for tablets. With its 10.1-inch HD widescreen display, you\u2019ll enjoy HD video in a thin, light, powerful and upgradeable tablet.", + "display": { + "screenResolution": "WXGA (1200 x 800)", + "screenSize": "10.1 inches", + "touchScreen": true + }, + "hardware": { + "accelerometer": true, + "audioJack": "3.5mm", + "cpu": "1 GHz Dual Core Tegra 2", + "fmRadio": false, + "physicalKeyboard": false, + "usb": "USB 2.0" + }, + "id": "motorola-xoom-with-wi-fi", + "images": [ + "img/phones/motorola-xoom-with-wi-fi.0.jpg", + "img/phones/motorola-xoom-with-wi-fi.1.jpg", + "img/phones/motorola-xoom-with-wi-fi.2.jpg", + "img/phones/motorola-xoom-with-wi-fi.3.jpg", + "img/phones/motorola-xoom-with-wi-fi.4.jpg", + "img/phones/motorola-xoom-with-wi-fi.5.jpg" + ], + "name": "Motorola XOOM\u2122 with Wi-Fi", + "sizeAndWeight": { + "dimensions": [ + "249.1 mm (w)", + "167.8 mm (h)", + "12.9 mm (d)" + ], + "weight": "708.0 grams" + }, + "storage": { + "flash": "32000MB", + "ram": "1000MB" + } +} diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phones/motorola-xoom.json b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phones/motorola-xoom.json new file mode 100644 index 0000000000..f0f0c8711d --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phones/motorola-xoom.json @@ -0,0 +1,62 @@ +{ + "additionalFeatures": "Front-facing camera. Sensors: proximity, ambient light, barometer, gyroscope.", + "android": { + "os": "Android 3.0", + "ui": "Android" + }, + "availability": [ + "Verizon" + ], + "battery": { + "standbyTime": "336 hours", + "talkTime": "24 hours", + "type": "Other (3250 mAH)" + }, + "camera": { + "features": [ + "Flash", + "Video" + ], + "primary": "5.0 megapixels" + }, + "connectivity": { + "bluetooth": "Bluetooth 2.1", + "cell": "CDMA 800 /1900 LTE 700, Rx diversity in all bands", + "gps": true, + "infrared": false, + "wifi": "802.11 a/b/g/n" + }, + "description": "MOTOROLA XOOM has a super-powerful dual-core processor and Android\u2122 3.0 (Honeycomb) \u2014 the Android platform designed specifically for tablets. With its 10.1-inch HD widescreen display, you\u2019ll enjoy HD video in a thin, light, powerful and upgradeable tablet.", + "display": { + "screenResolution": "WXGA (1200 x 800)", + "screenSize": "10.1 inches", + "touchScreen": true + }, + "hardware": { + "accelerometer": true, + "audioJack": "3.5mm", + "cpu": "1 GHz Dual Core Tegra 2", + "fmRadio": false, + "physicalKeyboard": false, + "usb": "USB 2.0" + }, + "id": "motorola-xoom", + "images": [ + "img/phones/motorola-xoom.0.jpg", + "img/phones/motorola-xoom.1.jpg", + "img/phones/motorola-xoom.2.jpg" + ], + "name": "MOTOROLA XOOM\u2122", + "sizeAndWeight": { + "dimensions": [ + "249.0 mm (w)", + "168.0 mm (h)", + "12.7 mm (d)" + ], + "weight": "726.0 grams" + }, + "storage": { + "flash": "32000MB", + "ram": "1000MB" + } +} diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phones/nexus-s.json b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phones/nexus-s.json new file mode 100644 index 0000000000..5e712e2ff8 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phones/nexus-s.json @@ -0,0 +1,69 @@ +{ + "additionalFeatures": "Contour Display, Near Field Communications (NFC), Three-axis gyroscope, Anti-fingerprint display coating, Internet Calling support (VoIP/SIP)", + "android": { + "os": "Android 2.3", + "ui": "Android" + }, + "availability": [ + "M1,", + "O2,", + "Orange,", + "Singtel,", + "StarHub,", + "T-Mobile,", + "Vodafone" + ], + "battery": { + "standbyTime": "428 hours", + "talkTime": "6 hours", + "type": "Lithium Ion (Li-Ion) (1500 mAH)" + }, + "camera": { + "features": [ + "Flash", + "Video" + ], + "primary": "5.0 megapixels" + }, + "connectivity": { + "bluetooth": "Bluetooth 2.1", + "cell": "Quad-band GSM: 850, 900, 1800, 1900\r\nTri-band HSPA: 900, 2100, 1700\r\nHSPA type: HSDPA (7.2Mbps) HSUPA (5.76Mbps)", + "gps": true, + "infrared": false, + "wifi": "802.11 b/g/n" + }, + "description": "Nexus S is the next generation of Nexus devices, co-developed by Google and Samsung. The latest Android platform (Gingerbread), paired with a 1 GHz Hummingbird processor and 16GB of memory, makes Nexus S one of the fastest phones on the market. It comes pre-installed with the best of Google apps and enabled with new and popular features like true multi-tasking, Wi-Fi hotspot, Internet Calling, NFC support, and full web browsing. With this device, users will also be the first to receive software upgrades and new Google mobile apps as soon as they become available. For more details, visit http://www.google.com/nexus.", + "display": { + "screenResolution": "WVGA (800 x 480)", + "screenSize": "4.0 inches", + "touchScreen": true + }, + "hardware": { + "accelerometer": true, + "audioJack": "3.5mm", + "cpu": "1GHz Cortex A8 (Hummingbird) processor", + "fmRadio": false, + "physicalKeyboard": false, + "usb": "USB 2.0" + }, + "id": "nexus-s", + "images": [ + "img/phones/nexus-s.0.jpg", + "img/phones/nexus-s.1.jpg", + "img/phones/nexus-s.2.jpg", + "img/phones/nexus-s.3.jpg" + ], + "name": "Nexus S", + "sizeAndWeight": { + "dimensions": [ + "63.0 mm (w)", + "123.9 mm (h)", + "10.88 mm (d)" + ], + "weight": "129.0 grams" + }, + "storage": { + "flash": "16384MB", + "ram": "512MB" + } +} diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phones/phones.json b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phones/phones.json new file mode 100644 index 0000000000..339b94fbb5 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/app/phones/phones.json @@ -0,0 +1,155 @@ +[ + { + "age": 0, + "id": "motorola-xoom-with-wi-fi", + "imageUrl": "img/phones/motorola-xoom-with-wi-fi.0.jpg", + "name": "Motorola XOOM\u2122 with Wi-Fi", + "snippet": "The Next, Next Generation\r\n\r\nExperience the future with Motorola XOOM with Wi-Fi, the world's first tablet powered by Android 3.0 (Honeycomb)." + }, + { + "age": 1, + "id": "motorola-xoom", + "imageUrl": "img/phones/motorola-xoom.0.jpg", + "name": "MOTOROLA XOOM\u2122", + "snippet": "The Next, Next Generation\n\nExperience the future with MOTOROLA XOOM, the world's first tablet powered by Android 3.0 (Honeycomb)." + }, + { + "age": 2, + "carrier": "AT&T", + "id": "motorola-atrix-4g", + "imageUrl": "img/phones/motorola-atrix-4g.0.jpg", + "name": "MOTOROLA ATRIX\u2122 4G", + "snippet": "MOTOROLA ATRIX 4G the world's most powerful smartphone." + }, + { + "age": 3, + "id": "dell-streak-7", + "imageUrl": "img/phones/dell-streak-7.0.jpg", + "name": "Dell Streak 7", + "snippet": "Introducing Dell\u2122 Streak 7. Share photos, videos and movies together. It\u2019s small enough to carry around, big enough to gather around." + }, + { + "age": 4, + "carrier": "Cellular South", + "id": "samsung-gem", + "imageUrl": "img/phones/samsung-gem.0.jpg", + "name": "Samsung Gem\u2122", + "snippet": "The Samsung Gem\u2122 brings you everything that you would expect and more from a touch display smart phone \u2013 more apps, more features and a more affordable price." + }, + { + "age": 5, + "carrier": "Dell", + "id": "dell-venue", + "imageUrl": "img/phones/dell-venue.0.jpg", + "name": "Dell Venue", + "snippet": "The Dell Venue; Your Personal Express Lane to Everything" + }, + { + "age": 6, + "carrier": "Best Buy", + "id": "nexus-s", + "imageUrl": "img/phones/nexus-s.0.jpg", + "name": "Nexus S", + "snippet": "Fast just got faster with Nexus S. A pure Google experience, Nexus S is the first phone to run Gingerbread (Android 2.3), the fastest version of Android yet." + }, + { + "age": 7, + "carrier": "Cellular South", + "id": "lg-axis", + "imageUrl": "img/phones/lg-axis.0.jpg", + "name": "LG Axis", + "snippet": "Android Powered, Google Maps Navigation, 5 Customizable Home Screens" + }, + { + "age": 8, + "id": "samsung-galaxy-tab", + "imageUrl": "img/phones/samsung-galaxy-tab.0.jpg", + "name": "Samsung Galaxy Tab\u2122", + "snippet": "Feel Free to Tab\u2122. The Samsung Galaxy Tab\u2122 brings you an ultra-mobile entertainment experience through its 7\u201d display, high-power processor and Adobe\u00ae Flash\u00ae Player compatibility." + }, + { + "age": 9, + "carrier": "Cellular South", + "id": "samsung-showcase-a-galaxy-s-phone", + "imageUrl": "img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg", + "name": "Samsung Showcase\u2122 a Galaxy S\u2122 phone", + "snippet": "The Samsung Showcase\u2122 delivers a cinema quality experience like you\u2019ve never seen before. Its innovative 4\u201d touch display technology provides rich picture brilliance, even outdoors" + }, + { + "age": 10, + "carrier": "Verizon", + "id": "droid-2-global-by-motorola", + "imageUrl": "img/phones/droid-2-global-by-motorola.0.jpg", + "name": "DROID\u2122 2 Global by Motorola", + "snippet": "The first smartphone with a 1.2 GHz processor and global capabilities." + }, + { + "age": 11, + "carrier": "Verizon", + "id": "droid-pro-by-motorola", + "imageUrl": "img/phones/droid-pro-by-motorola.0.jpg", + "name": "DROID\u2122 Pro by Motorola", + "snippet": "The next generation of DOES." + }, + { + "age": 12, + "carrier": "AT&T", + "id": "motorola-bravo-with-motoblur", + "imageUrl": "img/phones/motorola-bravo-with-motoblur.0.jpg", + "name": "MOTOROLA BRAVO\u2122 with MOTOBLUR\u2122", + "snippet": "An experience to cheer about." + }, + { + "age": 13, + "carrier": "T-Mobile", + "id": "motorola-defy-with-motoblur", + "imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg", + "name": "Motorola DEFY\u2122 with MOTOBLUR\u2122", + "snippet": "Are you ready for everything life throws your way?" + }, + { + "age": 14, + "carrier": "T-Mobile", + "id": "t-mobile-mytouch-4g", + "imageUrl": "img/phones/t-mobile-mytouch-4g.0.jpg", + "name": "T-Mobile myTouch 4G", + "snippet": "The T-Mobile myTouch 4G is a premium smartphone designed to deliver blazing fast 4G speeds so that you can video chat from practically anywhere, with or without Wi-Fi." + }, + { + "age": 15, + "carrier": "US Cellular", + "id": "samsung-mesmerize-a-galaxy-s-phone", + "imageUrl": "img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg", + "name": "Samsung Mesmerize\u2122 a Galaxy S\u2122 phone", + "snippet": "The Samsung Mesmerize\u2122 delivers a cinema quality experience like you\u2019ve never seen before. Its innovative 4\u201d touch display technology provides rich picture brilliance,even outdoors" + }, + { + "age": 16, + "carrier": "Sprint", + "id": "sanyo-zio", + "imageUrl": "img/phones/sanyo-zio.0.jpg", + "name": "SANYO ZIO", + "snippet": "The Sanyo Zio by Kyocera is an Android smartphone with a combination of ultra-sleek styling, strong performance and unprecedented value." + }, + { + "age": 17, + "id": "samsung-transform", + "imageUrl": "img/phones/samsung-transform.0.jpg", + "name": "Samsung Transform\u2122", + "snippet": "The Samsung Transform\u2122 brings you a fun way to customize your Android powered touch screen phone to just the way you like it through your favorite themed \u201cSprint ID Service Pack\u201d." + }, + { + "age": 18, + "id": "t-mobile-g2", + "imageUrl": "img/phones/t-mobile-g2.0.jpg", + "name": "T-Mobile G2", + "snippet": "The T-Mobile G2 with Google is the first smartphone built for 4G speeds on T-Mobile's new network. Get the information you need, faster than you ever thought possible." + }, + { + "age": 19, + "id": "motorola-charm-with-motoblur", + "imageUrl": "img/phones/motorola-charm-with-motoblur.0.jpg", + "name": "Motorola CHARM\u2122 with MOTOBLUR\u2122", + "snippet": "Motorola CHARM fits easily in your pocket or palm. Includes MOTOBLUR service." + } +] diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/bs-config.aot.json b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/bs-config.aot.json new file mode 100644 index 0000000000..e59a7403a0 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/bs-config.aot.json @@ -0,0 +1,14 @@ +{ + "open": false, + "logLevel": "silent", + "port": 8080, + "server": { + "baseDir": "aot", + "routes": { + "/node_modules": "node_modules" + }, + "middleware": { + "0": null + } + } +} diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/copy-dist-files.js b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/copy-dist-files.js new file mode 100644 index 0000000000..a857af085c --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/copy-dist-files.js @@ -0,0 +1,25 @@ +// #docregion +var fsExtra = require('fs-extra'); +var resources = [ + // polyfills + 'node_modules/core-js/client/shim.min.js', + 'node_modules/zone.js/dist/zone.min.js', + // css + 'app/app.css', + 'app/app.animations.css', + // images and json files + 'app/img/', + 'app/phones/', + // app files + 'app/app.module.ajs.js', + 'app/app.config.js', + 'app/app.animations.js', + 'app/core/core.module.js', + 'app/core/phone/phone.module.js', + 'app/phone-list/phone-list.module.js', + 'app/phone-detail/phone-detail.module.js' +]; +resources.map(function(sourcePath) { + var destPath = `aot/${sourcePath}`; + fsExtra.copySync(sourcePath, destPath); +}); diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/example-config.json b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/example-config.json new file mode 100644 index 0000000000..401c14f835 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/example-config.json @@ -0,0 +1,5 @@ +{ + "build": "build:upgrade", + "run": "serve:upgrade", + "unittesting": true +} diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/index.html b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/index.html new file mode 100644 index 0000000000..f747e641e0 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/index.html @@ -0,0 +1,51 @@ + + + + + + + + + Google Phone Gallery + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/karma.conf.ajs.js b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/karma.conf.ajs.js new file mode 100644 index 0000000000..a52abf73ce --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/karma.conf.ajs.js @@ -0,0 +1,73 @@ +//jshint strict: false +module.exports = function(config) { + config.set({ + + // #docregion basepath + basePath: './', + // #enddocregion basepath + + files: [ + 'https://code.angularjs.org/1.5.5/angular.js', + 'https://code.angularjs.org/1.5.5/angular-animate.js', + 'https://code.angularjs.org/1.5.5/angular-resource.js', + 'https://code.angularjs.org/1.5.5/angular-route.js', + 'https://code.angularjs.org/1.5.5/angular-mocks.js', + + // #docregion files + // System.js for module loading + 'node_modules/systemjs/dist/system.src.js', + + // Polyfills + 'node_modules/core-js/client/shim.js', + + // zone.js + 'node_modules/zone.js/dist/zone.js', + 'node_modules/zone.js/dist/long-stack-trace-zone.js', + 'node_modules/zone.js/dist/proxy.js', + 'node_modules/zone.js/dist/sync-test.js', + 'node_modules/zone.js/dist/jasmine-patch.js', + 'node_modules/zone.js/dist/async-test.js', + 'node_modules/zone.js/dist/fake-async-test.js', + + // RxJs. + { pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false }, + { pattern: 'node_modules/rxjs/**/*.js.map', included: false, watched: false }, + + // Angular itself and the testing library + {pattern: 'node_modules/@angular/**/*.js', included: false, watched: false}, + {pattern: 'node_modules/@angular/**/*.js.map', included: false, watched: false}, + + {pattern: 'systemjs.config.js', included: false, watched: false}, + 'karma-test-shim.js', + + {pattern: 'app/**/*.module.js', included: false, watched: true}, + {pattern: 'app/*!(.module|.spec).js', included: false, watched: true}, + {pattern: 'app/!(bower_components)/**/*!(.module|.spec).js', included: false, watched: true}, + {pattern: 'app/**/*.spec.js', included: false, watched: true}, + + {pattern: '**/*.html', included: false, watched: true}, + // #enddocregion files + ], + + // #docregion html + // proxied base paths for loading assets + proxies: { + // required for component assets fetched by Angular's compiler + "/phone-detail": '/base/app/phone-detail', + "/phone-list": '/base/app/phone-list' + }, + // #enddocregion html + + autoWatch: true, + + frameworks: ['jasmine'], + + browsers: ['Chrome'], + + plugins: [ + 'karma-chrome-launcher', + 'karma-jasmine' + ] + + }); +}; diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/package.ajs.json b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/package.ajs.json new file mode 100644 index 0000000000..54f73776dd --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/package.ajs.json @@ -0,0 +1,37 @@ +{ + "name": "angular-phonecat", + "private": true, + "version": "0.0.0", + "description": "A tutorial application for AngularJS", + "repository": "https://github.com/angular/angular-phonecat", + "license": "MIT", + "devDependencies": { + "bower": "^1.7.7", + "http-server": "^0.9.0", + "jasmine-core": "^2.4.1", + "karma": "^0.13.22", + "karma-chrome-launcher": "^0.2.3", + "karma-firefox-launcher": "^0.1.7", + "karma-jasmine": "^0.3.8", + "protractor": "^3.2.2", + "shelljs": "^0.6.0" + }, + "scripts": { + "postinstall": "bower install", + + "prestart": "npm install", + "start": "http-server -a localhost -p 8000 -c-1 ./", + + "pretest": "npm install", + "test": "karma start karma.conf.js", + "test-single-run": "karma start karma.conf.js --single-run", + + "preupdate-webdriver": "npm install", + "update-webdriver": "webdriver-manager update", + + "preprotractor": "npm run update-webdriver", + "protractor": "protractor e2e-tests/protractor.conf.js", + + "update-index-async": "node -e \"require('shelljs/global'); sed('-i', /\\/\\/@@NG_LOADER_START@@[\\s\\S]*\\/\\/@@NG_LOADER_END@@/, '//@@NG_LOADER_START@@\\n' + sed(/sourceMappingURL=angular-loader.min.js.map/,'sourceMappingURL=bower_components/angular-loader/angular-loader.min.js.map','app/bower_components/angular-loader/angular-loader.min.js') + '\\n//@@NG_LOADER_END@@', 'app/index-async.html');\"" + } +} diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/rollup-config.js b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/rollup-config.js new file mode 100644 index 0000000000..aeb227689c --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/rollup-config.js @@ -0,0 +1,21 @@ +// #docregion +import rollup from 'rollup' +import nodeResolve from 'rollup-plugin-node-resolve' +import commonjs from 'rollup-plugin-commonjs'; +import uglify from 'rollup-plugin-uglify' + +//paths are relative to the execution path +export default { + entry: 'app/main-aot.js', + dest: 'aot/dist/build.js', // output a single application bundle + sourceMap: true, + sourceMapFile: 'aot/dist/build.js.map', + format: 'iife', + plugins: [ + nodeResolve({jsnext: true, module: true}), + commonjs({ + include: ['node_modules/rxjs/**'] + }), + uglify() + ] +} diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/run-unit-tests.sh b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/run-unit-tests.sh new file mode 100755 index 0000000000..239e5ff7d7 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/run-unit-tests.sh @@ -0,0 +1,7 @@ +## The boilerplate Karma configuration won't work with AngularJS tests since +## a specific loading configuration is needed for them. +## We keep one in karma.conf.ajs.js. This scripts runs the AngularJS tests with +## that config. + +PATH=$(npm bin):$PATH +tsc && karma start karma.conf.ajs.js diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/systemjs.config.1.js b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/systemjs.config.1.js new file mode 100644 index 0000000000..8197c44a89 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/systemjs.config.1.js @@ -0,0 +1,50 @@ +/** + * System configuration for Angular samples + * Adjust as necessary for your application needs. + */ +(function (global) { + // #docregion paths + System.config({ + paths: { + // paths serve as alias + 'npm:': '/node_modules/' + }, + map: { + app: '/app', + // #enddocregion paths + // angular bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.js', + '@angular/router/upgrade': 'npm:@angular/router/bundles/router-upgrade.umd.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', + // #docregion paths + '@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js', + // #enddocregion paths + + // other libraries + 'rxjs': 'npm:rxjs', + 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api', + // #docregion paths + }, + // #enddocregion paths + // packages tells the System loader how to load when no filename and/or no extension + packages: { + 'app': { + main: './main.js', + defaultExtension: 'js' + }, + rxjs: { + defaultExtension: 'js' + }, + 'angular-in-memory-web-api': { + main: './index.js', + defaultExtension: 'js' + } + } + }); +})(this); diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/tsconfig-aot.json b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/tsconfig-aot.json new file mode 100644 index 0000000000..58f9de3309 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/tsconfig-aot.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "es2015", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": ["es2015", "dom"], + "removeComments": false, + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true, + "typeRoots": [ + "../../node_modules/@types/" + ] + }, + + "files": [ + "app/app.module.ts", + "app/main-aot.ts" + ], + + "angularCompilerOptions": { + "genDir": "aot", + "skipMetadataEmit" : true + } +} diff --git a/aio/content/examples/upgrade-phonecat-2-hybrid/ts/tsconfig.json b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/tsconfig.json new file mode 100644 index 0000000000..f267800f14 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-2-hybrid/ts/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ "es2015", "dom" ], + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true, + "typeRoots": [ + "../../node_modules/@types/" + ] + }, + "compileOnSave": true, + "exclude": [ + "node_modules/*", + "**/*-aot.ts" + ] +} diff --git a/aio/content/examples/upgrade-phonecat-3-router/README.md b/aio/content/examples/upgrade-phonecat-3-router/README.md new file mode 100644 index 0000000000..4f8e4928af --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/README.md @@ -0,0 +1,34 @@ +This is the Angular Phonecat application adjusted to fit our boilerplate project +structure. + +The following changes from vanilla Phonecat are applied: + +* Karma config for unit tests is in karma.conf.ng1.js because the boilerplate + Karma config is not compatible with the way Angular 1 tests need to be run. + The shell script run-unit-tests.sh can be used to run the unit tests. +* There's a `package.ng1.json`, which is not used to run anything but only to + show an example of changing the PhoneCat http-server root path. +* Also for the Karma shim, there is a `karma-test-shim.1.js` file which isn't + used but is shown in the test appendix. +* Instead of using Bower, Angular 1 and its dependencies are fetched from a CDN + in index.html and karma.conf.ng1.js. +* E2E tests have been moved to the parent directory, where `run-e2e-tests` can + discover and run them along with all the other examples. +* Most of the phone JSON and image data removed in the interest of keeping + repo weight down. Keeping enough to retain testability of the app. + +## Running the app + +Start like any example + + npm run start + +## Running unit tests + + ./run-unit-tests.sh + +## Running E2E tests + +Like for any example (at the project root): + + gulp run-e2e-tests --filter=phonecat-2 diff --git a/aio/content/examples/upgrade-phonecat-3-router/e2e-spec.ts b/aio/content/examples/upgrade-phonecat-3-router/e2e-spec.ts new file mode 100644 index 0000000000..2ec8e37977 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/e2e-spec.ts @@ -0,0 +1,107 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; +import { setProtractorToHybridMode } from '../protractor-helpers'; + +// Angular E2E Testing Guide: +// https://docs.angularjs.org/guide/e2e-testing + +describe('PhoneCat Application', function() { + + beforeAll(function () { + setProtractorToHybridMode(); + }); + + it('should redirect `index.html` to `index.html#!/phones', function() { + browser.get('index.html'); + expect(browser.getLocationAbsUrl()).toBe('/phones'); + }); + + describe('View: Phone list', function() { + + beforeEach(function() { + browser.get('index.html#!/phones'); + }); + + it('should filter the phone list as a user types into the search box', function() { + let phoneList = element.all(by.css('.phones li')); + let query = element(by.css('input')); + + expect(phoneList.count()).toBe(20); + + query.sendKeys('nexus'); + expect(phoneList.count()).toBe(1); + + query.clear(); + query.sendKeys('motorola'); + expect(phoneList.count()).toBe(8); + }); + + it('should be possible to control phone order via the drop-down menu', function() { + let queryField = element(by.css('input')); + let orderSelect = element(by.css('select')); + let nameOption = orderSelect.element(by.css('option[value="name"]')); + let phoneNameColumn = element.all(by.css('.phones .name')); + + function getNames() { + return phoneNameColumn.map(function(elem) { + return elem.getText(); + }); + } + + queryField.sendKeys('tablet'); // Let's narrow the dataset to make the assertions shorter + + expect(getNames()).toEqual([ + 'Motorola XOOM\u2122 with Wi-Fi', + 'MOTOROLA XOOM\u2122' + ]); + + nameOption.click(); + + expect(getNames()).toEqual([ + 'MOTOROLA XOOM\u2122', + 'Motorola XOOM\u2122 with Wi-Fi' + ]); + }); + + it('should render phone specific links', function() { + let query = element(by.css('input')); + query.sendKeys('nexus'); + + element.all(by.css('.phones li a')).first().click(); + browser.sleep(200); // Not sure why this is needed but it is. The route change works fine. + expect(browser.getLocationAbsUrl()).toBe('/phones/nexus-s'); + }); + + }); + + describe('View: Phone detail', function() { + + beforeEach(function() { + browser.get('index.html#!/phones/nexus-s'); + }); + + it('should display the `nexus-s` page', function() { + expect(element(by.css('h1')).getText()).toBe('Nexus S'); + }); + + it('should display the first phone image as the main phone image', function() { + let mainImage = element(by.css('img.phone.selected')); + + expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/); + }); + + it('should swap the main image when clicking on a thumbnail image', function() { + let mainImage = element(by.css('img.phone.selected')); + let thumbnails = element.all(by.css('.phone-thumbs img')); + + thumbnails.get(2).click(); + expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.2.jpg/); + + thumbnails.get(0).click(); + expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/); + }); + + }); + +}); diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/.gitignore b/aio/content/examples/upgrade-phonecat-3-router/ts/.gitignore new file mode 100644 index 0000000000..7f5c313a3e --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/.gitignore @@ -0,0 +1,7 @@ +**/*.js +aot/**/* +!aot/bs-config.json +!aot/index.html +!copy-dist-files.js +!rollup-config.js +!systemjs.config.1.js diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/aot/bs-config.json b/aio/content/examples/upgrade-phonecat-3-router/ts/aot/bs-config.json new file mode 100644 index 0000000000..7c85d6eddd --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/aot/bs-config.json @@ -0,0 +1,5 @@ +{ + "port": 8000, + "files": ["./aot/**/*.{html,htm,css,js}"], + "server": { "baseDir": "./aot" } +} diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/aot/index.html b/aio/content/examples/upgrade-phonecat-3-router/ts/aot/index.html new file mode 100644 index 0000000000..0d6cf5946e --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/aot/index.html @@ -0,0 +1,38 @@ + + + + + + + + + Google Phone Gallery + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/ajs-upgraded-providers.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/ajs-upgraded-providers.ts new file mode 100644 index 0000000000..f6e1654d74 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/ajs-upgraded-providers.ts @@ -0,0 +1,14 @@ +// #docregion +export abstract class RouteParams { + [key: string]: string; +} + +export function routeParamsFactory(i: any) { + return i.get('$routeParams'); +} + +export const routeParamsProvider = { + provide: RouteParams, + useFactory: routeParamsFactory, + deps: ['$injector'] +}; diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/app-routing.module.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/app-routing.module.ts new file mode 100644 index 0000000000..0485b1848d --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/app-routing.module.ts @@ -0,0 +1,30 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { Routes, RouterModule, UrlHandlingStrategy, UrlTree } from '@angular/router'; +import { APP_BASE_HREF, HashLocationStrategy, LocationStrategy } from '@angular/common'; + +import { PhoneListComponent } from './phone-list/phone-list.component'; + +export class Ng1Ng2UrlHandlingStrategy implements UrlHandlingStrategy { + shouldProcessUrl(url: UrlTree) { + return url.toString() === '/' || url.toString() === '/phones'; + } + extract(url: UrlTree) { return url; } + merge(url: UrlTree, whole: UrlTree) { return url; } +} + +const routes: Routes = [ + { path: '', redirectTo: 'phones', pathMatch: 'full' }, + { path: 'phones', component: PhoneListComponent } +]; + +@NgModule({ + imports: [ RouterModule.forRoot(routes) ], + exports: [ RouterModule ], + providers: [ + { provide: APP_BASE_HREF, useValue: '!' }, + { provide: LocationStrategy, useClass: HashLocationStrategy }, + { provide: UrlHandlingStrategy, useClass: Ng1Ng2UrlHandlingStrategy } + ] +}) +export class AppRoutingModule { } diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.animations.css b/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.animations.css new file mode 100644 index 0000000000..175320b509 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.animations.css @@ -0,0 +1,67 @@ +/* Animate `ngRepeat` in `phoneList` component */ +.phone-list-item.ng-enter, +.phone-list-item.ng-leave, +.phone-list-item.ng-move { + overflow: hidden; + transition: 0.5s linear all; +} + +.phone-list-item.ng-enter, +.phone-list-item.ng-leave.ng-leave-active, +.phone-list-item.ng-move { + height: 0; + margin-bottom: 0; + opacity: 0; + padding-bottom: 0; + padding-top: 0; +} + +.phone-list-item.ng-enter.ng-enter-active, +.phone-list-item.ng-leave, +.phone-list-item.ng-move.ng-move-active { + height: 120px; + margin-bottom: 20px; + opacity: 1; + padding-bottom: 4px; + padding-top: 15px; +} + +/* Animate view transitions with `ngView` */ +.view-container { + position: relative; +} + +.view-frame { + margin-top: 20px; +} + +.view-frame.ng-enter, +.view-frame.ng-leave { + background: white; + left: 0; + position: absolute; + right: 0; + top: 0; +} + +.view-frame.ng-enter { + animation: 1s fade-in; + z-index: 100; +} + +.view-frame.ng-leave { + animation: 1s fade-out; + z-index: 99; +} + +@keyframes fade-in { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes fade-out { + from { opacity: 1; } + to { opacity: 0; } +} + +/* Older browsers might need vendor-prefixes for keyframes and animation! */ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.animations.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.animations.ts new file mode 100644 index 0000000000..f0739b6405 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.animations.ts @@ -0,0 +1,43 @@ +'use strict'; + +angular. + module('phonecatApp'). + animation('.phone', function phoneAnimationFactory() { + return { + addClass: animateIn, + removeClass: animateOut + }; + + function animateIn(element: JQuery, className: string, done: () => void) { + if (className !== 'selected') { return; } + + element.css({ + display: 'block', + position: 'absolute', + top: 500, + left: 0 + }).animate({ + top: 0 + }, done); + + return function animateInEnd(wasCanceled: boolean) { + if (wasCanceled) { element.stop(); } + }; + } + + function animateOut(element: JQuery, className: string, done: () => void) { + if (className !== 'selected') { return; } + + element.css({ + position: 'absolute', + top: 0, + left: 0 + }).animate({ + top: -500 + }, done); + + return function animateOutEnd(wasCanceled: boolean) { + if (wasCanceled) { element.stop(); } + }; + } + }); diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.component.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.component.ts new file mode 100644 index 0000000000..6ecd19ab80 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.component.ts @@ -0,0 +1,13 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'phonecat-app', + template: ` + +
    +
    +
    + ` +}) +export class AppComponent { } diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.config.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.config.ts new file mode 100644 index 0000000000..51a5d82422 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.config.ts @@ -0,0 +1,16 @@ +'use strict'; + +angular. + module('phonecatApp'). + config(['$locationProvider', '$routeProvider', + function config($locationProvider: angular.ILocationProvider, + $routeProvider: angular.route.IRouteProvider) { + $locationProvider.hashPrefix('!'); + // #docregion ajs-routes + $routeProvider + .when('/phones/:phoneId', { + template: '' + }); + // #enddocregion ajs-routes + } + ]); diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.css b/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.css new file mode 100644 index 0000000000..f4b45b02a5 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.css @@ -0,0 +1,93 @@ +body { + padding: 20px; +} + +h1 { + border-bottom: 1px solid gray; + margin-top: 0; +} + +/* View: Phone list */ +.phones { + list-style: none; +} + +.phones li { + clear: both; + height: 115px; + padding-top: 15px; +} + +.thumb { + float: left; + height: 100px; + margin: -0.5em 1em 1.5em 0; + padding-bottom: 1em; + width: 100px; +} + +/* View: Phone detail */ +.phone { + background-color: white; + display: none; + float: left; + height: 400px; + margin-bottom: 2em; + margin-right: 3em; + padding: 2em; + width: 400px; +} + +.phone:first-child { + display: block; +} + +.phone-images { + background-color: white; + float: left; + height: 450px; + overflow: hidden; + position: relative; + width: 450px; +} + +.phone-thumbs { + list-style: none; + margin: 0; +} + +.phone-thumbs img { + height: 100px; + padding: 1em; + width: 100px; +} + +.phone-thumbs li { + background-color: white; + border: 1px solid black; + cursor: pointer; + display: inline-block; + margin: 1em; +} + +.specs { + clear: both; + list-style: none; + margin: 0; + padding: 0; +} + +.specs dt { + font-weight: bold; +} + +.specs > li { + display: inline-block; + vertical-align: top; + width: 200px; +} + +.specs > li > span { + font-size: 1.2em; + font-weight: bold; +} diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.module.ajs.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.module.ajs.ts new file mode 100644 index 0000000000..e493137966 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.module.ajs.ts @@ -0,0 +1,11 @@ +// #docregion +'use strict'; + +// Define the `phonecatApp` Angular 1 module +angular.module('phonecatApp', [ + 'ngAnimate', + 'ngRoute', + 'core', + 'phoneDetail', + 'phoneList', +]); diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.module.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.module.ts new file mode 100644 index 0000000000..e0bb64f4e4 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/app.module.ts @@ -0,0 +1,42 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { UpgradeModule } from '@angular/upgrade/static'; +import { HttpModule } from '@angular/http'; +import { FormsModule } from '@angular/forms'; + +import { AppRoutingModule } from './app-routing.module'; +import { AppComponent } from './app.component'; +import { Phone } from './core/phone/phone.service'; +import { CheckmarkPipe } from './core/checkmark/checkmark.pipe'; +import { PhoneListComponent } from './phone-list/phone-list.component'; +import { PhoneDetailComponent } from './phone-detail/phone-detail.component'; +import { routeParamsProvider } from './ajs-upgraded-providers'; + +@NgModule({ + imports: [ + BrowserModule, + UpgradeModule, + HttpModule, + FormsModule, + AppRoutingModule + ], + declarations: [ + AppComponent, + PhoneListComponent, + PhoneDetailComponent, + CheckmarkPipe + ], + entryComponents: [ + PhoneListComponent, + PhoneDetailComponent + ], + providers: [ + Phone, + routeParamsProvider + ], + // #docregion bootstrap + bootstrap: [ AppComponent ] +}) +export class AppModule { } +// #enddocregion bootstrap diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/core/checkmark/checkmark.pipe.spec.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/core/checkmark/checkmark.pipe.spec.ts new file mode 100644 index 0000000000..f7485ec2ba --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/core/checkmark/checkmark.pipe.spec.ts @@ -0,0 +1,11 @@ +// #docregion +import { CheckmarkPipe } from './checkmark.pipe'; + +describe('CheckmarkPipe', function() { + + it('should convert boolean values to unicode checkmark or cross', function () { + const checkmarkPipe = new CheckmarkPipe(); + expect(checkmarkPipe.transform(true)).toBe('\u2713'); + expect(checkmarkPipe.transform(false)).toBe('\u2718'); + }); +}); diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/core/checkmark/checkmark.pipe.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/core/checkmark/checkmark.pipe.ts new file mode 100644 index 0000000000..888017e15c --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/core/checkmark/checkmark.pipe.ts @@ -0,0 +1,9 @@ +// #docregion +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({name: 'checkmark'}) +export class CheckmarkPipe implements PipeTransform { + transform(input: boolean) { + return input ? '\u2713' : '\u2718'; + } +} diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/core/core.module.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/core/core.module.ts new file mode 100644 index 0000000000..84a91dc7a6 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/core/core.module.ts @@ -0,0 +1,4 @@ +'use strict'; + +// Define the `core` module +angular.module('core', ['core.phone']); diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/core/phone/phone.module.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/core/phone/phone.module.ts new file mode 100644 index 0000000000..0b6b348899 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/core/phone/phone.module.ts @@ -0,0 +1,4 @@ +'use strict'; + +// Define the `core.phone` module +angular.module('core.phone', ['ngResource']); diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/core/phone/phone.service.spec.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/core/phone/phone.service.spec.ts new file mode 100644 index 0000000000..a0c1655c20 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/core/phone/phone.service.spec.ts @@ -0,0 +1,51 @@ +// #docregion +import { inject, TestBed } from '@angular/core/testing'; +import { + Http, + BaseRequestOptions, + ResponseOptions, + Response +} from '@angular/http'; +import { MockBackend, MockConnection } from '@angular/http/testing'; +import { Phone, PhoneData } from './phone.service'; + +describe('Phone', function() { + let phone: Phone; + let phonesData: PhoneData[] = [ + {name: 'Phone X', snippet: '', images: []}, + {name: 'Phone Y', snippet: '', images: []}, + {name: 'Phone Z', snippet: '', images: []} + ]; + let mockBackend: MockBackend; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + Phone, + MockBackend, + BaseRequestOptions, + { provide: Http, + useFactory: (backend: MockBackend, options: BaseRequestOptions) => new Http(backend, options), + deps: [MockBackend, BaseRequestOptions] + } + ] + }); + }); + + beforeEach(inject([MockBackend, Phone], (_mockBackend_: MockBackend, _phone_: Phone) => { + mockBackend = _mockBackend_; + phone = _phone_; + })); + + it('should fetch the phones data from `/phones/phones.json`', (done: () => void) => { + mockBackend.connections.subscribe((conn: MockConnection) => { + conn.mockRespond(new Response(new ResponseOptions({body: JSON.stringify(phonesData)}))); + }); + phone.query().subscribe(result => { + expect(result).toEqual(phonesData); + done(); + }); + }); + +}); + diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/core/phone/phone.service.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/core/phone/phone.service.ts new file mode 100644 index 0000000000..ccbd1fdd72 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/core/phone/phone.service.ts @@ -0,0 +1,32 @@ +// #docregion +import { Injectable } from '@angular/core'; +import { Http, Response } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; + +declare var angular: angular.IAngularStatic; +import { downgradeInjectable } from '@angular/upgrade/static'; + +import 'rxjs/add/operator/map'; + +export interface PhoneData { + name: string; + snippet: string; + images: string[]; +} + +@Injectable() +export class Phone { + constructor(private http: Http) { } + query(): Observable { + return this.http.get(`phones/phones.json`) + .map((res: Response) => res.json()); + } + get(id: string): Observable { + return this.http.get(`phones/${id}.json`) + .map((res: Response) => res.json()); + } +} + +angular.module('core.phone') + .factory('phone', downgradeInjectable(Phone)); + diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/.gitkeep b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.0.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.0.jpg new file mode 100644 index 0000000000..7ce0dce4ee Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.1.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.1.jpg new file mode 100644 index 0000000000..ed8cad89fb Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.1.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.2.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.2.jpg new file mode 100644 index 0000000000..c83529e0f9 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.2.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.3.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.3.jpg new file mode 100644 index 0000000000..cd2c30280d Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.3.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.4.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.4.jpg new file mode 100644 index 0000000000..b4d8472da8 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.4.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-venue.0.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-venue.0.jpg new file mode 100644 index 0000000000..b4cb4eb25f Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-venue.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/droid-2-global-by-motorola.0.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/droid-2-global-by-motorola.0.jpg new file mode 100644 index 0000000000..60700a2ab3 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/droid-2-global-by-motorola.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/droid-pro-by-motorola.0.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/droid-pro-by-motorola.0.jpg new file mode 100644 index 0000000000..c7710de986 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/droid-pro-by-motorola.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/lg-axis.0.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/lg-axis.0.jpg new file mode 100644 index 0000000000..55e5a23bb2 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/lg-axis.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-atrix-4g.0.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-atrix-4g.0.jpg new file mode 100644 index 0000000000..2446159e93 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-atrix-4g.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-atrix-4g.1.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-atrix-4g.1.jpg new file mode 100644 index 0000000000..867f207459 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-atrix-4g.1.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-atrix-4g.2.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-atrix-4g.2.jpg new file mode 100644 index 0000000000..27d78338c4 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-atrix-4g.2.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-atrix-4g.3.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-atrix-4g.3.jpg new file mode 100644 index 0000000000..29459a68a4 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-atrix-4g.3.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-bravo-with-motoblur.0.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-bravo-with-motoblur.0.jpg new file mode 100644 index 0000000000..e452ae7e7c Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-bravo-with-motoblur.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-charm-with-motoblur.0.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-charm-with-motoblur.0.jpg new file mode 100644 index 0000000000..21e4b8d741 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-charm-with-motoblur.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-defy-with-motoblur.0.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-defy-with-motoblur.0.jpg new file mode 100644 index 0000000000..c7c5e3ba0c Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-defy-with-motoblur.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.0.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.0.jpg new file mode 100644 index 0000000000..a6c993291e Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.1.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.1.jpg new file mode 100644 index 0000000000..400b89e2df Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.1.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.2.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.2.jpg new file mode 100644 index 0000000000..86b55d28dd Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.2.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.3.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.3.jpg new file mode 100644 index 0000000000..85ec293ae5 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.3.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.4.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.4.jpg new file mode 100644 index 0000000000..75ef1464cb Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.4.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.5.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.5.jpg new file mode 100644 index 0000000000..4d42db4330 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.5.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom.0.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom.0.jpg new file mode 100644 index 0000000000..bf6954bbd5 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom.1.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom.1.jpg new file mode 100644 index 0000000000..659688a47d Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom.1.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom.2.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom.2.jpg new file mode 100644 index 0000000000..ce0ff1002e Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom.2.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/nexus-s.0.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/nexus-s.0.jpg new file mode 100644 index 0000000000..0952bc79c2 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/nexus-s.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/nexus-s.1.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/nexus-s.1.jpg new file mode 100644 index 0000000000..f33004dd7f Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/nexus-s.1.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/nexus-s.2.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/nexus-s.2.jpg new file mode 100644 index 0000000000..c5c63621f1 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/nexus-s.2.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/nexus-s.3.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/nexus-s.3.jpg new file mode 100644 index 0000000000..e51f75b0ed Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/nexus-s.3.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-galaxy-tab.0.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-galaxy-tab.0.jpg new file mode 100644 index 0000000000..3750377ad9 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-galaxy-tab.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-gem.0.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-gem.0.jpg new file mode 100644 index 0000000000..0d5024a026 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-gem.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg new file mode 100644 index 0000000000..11b8f860cb Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg new file mode 100644 index 0000000000..11b8f860cb Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-transform.0.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-transform.0.jpg new file mode 100644 index 0000000000..0e3107caf5 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-transform.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/sanyo-zio.0.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/sanyo-zio.0.jpg new file mode 100644 index 0000000000..9eeb9b96ed Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/sanyo-zio.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/t-mobile-g2.0.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/t-mobile-g2.0.jpg new file mode 100644 index 0000000000..6b6c09e058 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/t-mobile-g2.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/t-mobile-mytouch-4g.0.jpg b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/t-mobile-mytouch-4g.0.jpg new file mode 100644 index 0000000000..beba1f6827 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-3-router/ts/app/img/phones/t-mobile-mytouch-4g.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/main-aot.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/main-aot.ts new file mode 100644 index 0000000000..23a741c684 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/main-aot.ts @@ -0,0 +1,10 @@ +// #docregion +import { platformBrowser } from '@angular/platform-browser'; +import { UpgradeModule } from '@angular/upgrade/static'; + +import { AppModuleNgFactory } from '../aot/app/app.module.ngfactory'; + +platformBrowser().bootstrapModuleFactory(AppModuleNgFactory).then(platformRef => { + const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule; + upgrade.bootstrap(document.documentElement, ['phonecatApp']); +}); diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/main.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/main.ts new file mode 100644 index 0000000000..51b8e4d2a8 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/main.ts @@ -0,0 +1,10 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { UpgradeModule } from '@angular/upgrade/static'; + +import { AppModule } from './app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => { + const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule; + upgrade.bootstrap(document.documentElement, ['phonecatApp']); +}); diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.component.spec.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.component.spec.ts new file mode 100644 index 0000000000..e3b9143a94 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.component.spec.ts @@ -0,0 +1,59 @@ +// #docregion +// #docregion activatedroute +import { ActivatedRoute } from '@angular/router'; + +// #enddocregion activatedroute +import { Observable } from 'rxjs/Rx'; + +import { async, TestBed } from '@angular/core/testing'; + +import { PhoneDetailComponent } from './phone-detail.component'; +import { Phone, PhoneData } from '../core/phone/phone.service'; +import { CheckmarkPipe } from '../core/checkmark/checkmark.pipe'; + +function xyzPhoneData(): PhoneData { + return { + name: 'phone xyz', + snippet: '', + images: ['image/url1.png', 'image/url2.png'] + }; +} + +class MockPhone { + get(id: string): Observable { + return Observable.of(xyzPhoneData()); + } +} + +// #docregion activatedroute + +class ActivatedRouteMock { + constructor(public snapshot: any) {} +} + +// #enddocregion activatedroute + +describe('PhoneDetailComponent', () => { + + // #docregion activatedroute + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CheckmarkPipe, PhoneDetailComponent ], + providers: [ + { provide: Phone, useClass: MockPhone }, + { provide: ActivatedRoute, useValue: new ActivatedRouteMock({ params: { 'phoneId': 1 } }) } + ] + }) + .compileComponents(); + })); + // #enddocregion activatedroute + + it('should fetch phone detail', () => { + const fixture = TestBed.createComponent(PhoneDetailComponent); + fixture.detectChanges(); + let compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain(xyzPhoneData().name); + }); + +}); diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.component.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.component.ts new file mode 100644 index 0000000000..048a4104c8 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.component.ts @@ -0,0 +1,34 @@ +// #docplaster +// #docregion +declare var angular: angular.IAngularStatic; +import { downgradeComponent } from '@angular/upgrade/static'; +import { Component } from '@angular/core'; + +import { Phone, PhoneData } from '../core/phone/phone.service'; +import { RouteParams } from '../ajs-upgraded-providers'; + +@Component({ + moduleId: module.id, + templateUrl: 'phone-detail.template.html', +}) +export class PhoneDetailComponent { + phone: PhoneData; + mainImageUrl: string; + + constructor(routeParams: RouteParams, phone: Phone) { + phone.get(routeParams['phoneId']).subscribe(phone => { + this.phone = phone; + this.setImage(phone.images[0]); + }); + } + + setImage(imageUrl: string) { + this.mainImageUrl = imageUrl; + } +} + +angular.module('phoneDetail') + .directive( + 'phoneDetail', + downgradeComponent({component: PhoneDetailComponent}) as angular.IDirectiveFactory + ); diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.module.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.module.ts new file mode 100644 index 0000000000..fd7cb3b920 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.module.ts @@ -0,0 +1,7 @@ +'use strict'; + +// Define the `phoneDetail` module +angular.module('phoneDetail', [ + 'ngRoute', + 'core.phone' +]); diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.template.html b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.template.html new file mode 100644 index 0000000000..46a96d66c3 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.template.html @@ -0,0 +1,120 @@ + +
    +
    + +
    + +

    {{phone.name}}

    + +

    {{phone.description}}

    + +
      +
    • + +
    • +
    + +
      +
    • + Availability and Networks +
      +
      Availability
      +
      {{availability}}
      +
      +
    • +
    • + Battery +
      +
      Type
      +
      {{phone.battery?.type}}
      +
      Talk Time
      +
      {{phone.battery?.talkTime}}
      +
      Standby time (max)
      +
      {{phone.battery?.standbyTime}}
      +
      +
    • +
    • + Storage and Memory +
      +
      RAM
      +
      {{phone.storage?.ram}}
      +
      Internal Storage
      +
      {{phone.storage?.flash}}
      +
      +
    • +
    • + Connectivity +
      +
      Network Support
      +
      {{phone.connectivity?.cell}}
      +
      WiFi
      +
      {{phone.connectivity?.wifi}}
      +
      Bluetooth
      +
      {{phone.connectivity?.bluetooth}}
      +
      Infrared
      +
      {{phone.connectivity?.infrared | checkmark}}
      +
      GPS
      +
      {{phone.connectivity?.gps | checkmark}}
      +
      +
    • +
    • + Android +
      +
      OS Version
      +
      {{phone.android?.os}}
      +
      UI
      +
      {{phone.android?.ui}}
      +
      +
    • +
    • + Size and Weight +
      +
      Dimensions
      +
      {{dim}}
      +
      Weight
      +
      {{phone.sizeAndWeight?.weight}}
      +
      +
    • +
    • + Display +
      +
      Screen size
      +
      {{phone.display?.screenSize}}
      +
      Screen resolution
      +
      {{phone.display?.screenResolution}}
      +
      Touch screen
      +
      {{phone.display?.touchScreen | checkmark}}
      +
      +
    • +
    • + Hardware +
      +
      CPU
      +
      {{phone.hardware?.cpu}}
      +
      USB
      +
      {{phone.hardware?.usb}}
      +
      Audio / headphone jack
      +
      {{phone.hardware?.audioJack}}
      +
      FM Radio
      +
      {{phone.hardware?.fmRadio | checkmark}}
      +
      Accelerometer
      +
      {{phone.hardware?.accelerometer | checkmark}}
      +
      +
    • +
    • + Camera +
      +
      Primary
      +
      {{phone.camera?.primary}}
      +
      Features
      +
      {{phone.camera?.features?.join(', ')}}
      +
      +
    • +
    • + Additional Features +
      {{phone.additionalFeatures}}
      +
    • +
    +
    diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.component.spec.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.component.spec.ts new file mode 100644 index 0000000000..2bb9d2b62f --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.component.spec.ts @@ -0,0 +1,66 @@ +/* tslint:disable */ +// #docregion +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs/Rx'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { SpyLocation } from '@angular/common/testing'; + +import { PhoneListComponent } from './phone-list.component'; +import { Phone, PhoneData } from '../core/phone/phone.service'; + +class ActivatedRouteMock { + constructor(public snapshot: any) {} +} + +class MockPhone { + query(): Observable { + return Observable.of([ + {name: 'Nexus S', snippet: '', images: []}, + {name: 'Motorola DROID', snippet: '', images: []} + ]); + } +} + +let fixture: ComponentFixture; + +describe('PhoneList', () => { + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PhoneListComponent ], + providers: [ + { provide: ActivatedRoute, useValue: new ActivatedRouteMock({ params: { 'phoneId': 1 } }) }, + { provide: Location, useClass: SpyLocation }, + { provide: Phone, useClass: MockPhone }, + ], + schemas: [ NO_ERRORS_SCHEMA ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PhoneListComponent); + }); + + it('should create "phones" model with 2 phones fetched from xhr', () => { + fixture.detectChanges(); + let compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelectorAll('.phone-list-item').length).toBe(2); + expect( + compiled.querySelector('.phone-list-item:nth-child(1)').textContent + ).toContain('Motorola DROID'); + expect( + compiled.querySelector('.phone-list-item:nth-child(2)').textContent + ).toContain('Nexus S'); + }); + + xit('should set the default value of orderProp model', () => { + fixture.detectChanges(); + let compiled = fixture.debugElement.nativeElement; + expect( + compiled.querySelector('select option:last-child').selected + ).toBe(true); + }); + +}); diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.component.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.component.ts new file mode 100644 index 0000000000..d198774deb --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.component.ts @@ -0,0 +1,61 @@ +// #docregion +declare var angular: angular.IAngularStatic; +import { downgradeComponent } from '@angular/upgrade/static'; + +import { Component } from '@angular/core'; +import { Phone, PhoneData } from '../core/phone/phone.service'; + +@Component({ + moduleId: module.id, + templateUrl: 'phone-list.template.html' +}) +export class PhoneListComponent { + phones: PhoneData[]; + query: string; + orderProp: string; + + constructor(phone: Phone) { + phone.query().subscribe(phones => { + this.phones = phones; + }); + this.orderProp = 'age'; + } + + getPhones(): PhoneData[] { + return this.sortPhones(this.filterPhones(this.phones)); + } + + private filterPhones(phones: PhoneData[]) { + if (phones && this.query) { + return phones.filter(phone => { + let name = phone.name.toLowerCase(); + let snippet = phone.snippet.toLowerCase(); + return name.indexOf(this.query) >= 0 || snippet.indexOf(this.query) >= 0; + }); + } + return phones; + } + + private sortPhones(phones: PhoneData[]) { + if (phones && this.orderProp) { + return phones + .slice(0) // Make a copy + .sort((a, b) => { + if (a[this.orderProp] < b[this.orderProp]) { + return -1; + } else if ([b[this.orderProp] < a[this.orderProp]]) { + return 1; + } else { + return 0; + } + }); + } + return phones; + } +} + +angular.module('phoneList') + .directive( + 'phoneList', + downgradeComponent({component: PhoneListComponent}) as angular.IDirectiveFactory + ); diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.module.ts b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.module.ts new file mode 100644 index 0000000000..8ade7c5b88 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.module.ts @@ -0,0 +1,4 @@ +'use strict'; + +// Define the `phoneList` module +angular.module('phoneList', ['core.phone']); diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.template.html b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.template.html new file mode 100644 index 0000000000..2678d384c2 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.template.html @@ -0,0 +1,38 @@ +
    +
    +
    + + +

    + Search: + +

    + +

    + Sort by: + +

    + +
    +
    + + + + + + +
    +
    +
    diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/phones/dell-streak-7.json b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phones/dell-streak-7.json new file mode 100644 index 0000000000..a32eb6ff98 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phones/dell-streak-7.json @@ -0,0 +1,64 @@ +{ + "additionalFeatures": "Front Facing 1.3MP Camera", + "android": { + "os": "Android 2.2", + "ui": "Dell Stage" + }, + "availability": [ + "T-Mobile" + ], + "battery": { + "standbyTime": "", + "talkTime": "", + "type": "Lithium Ion (Li-Ion) (2780 mAH)" + }, + "camera": { + "features": [ + "Flash", + "Video" + ], + "primary": "5.0 megapixels" + }, + "connectivity": { + "bluetooth": "Bluetooth 2.1", + "cell": "T-mobile HSPA+ @ 2100/1900/AWS/850 MHz", + "gps": true, + "infrared": false, + "wifi": "802.11 b/g" + }, + "description": "Introducing Dell\u2122 Streak 7. Share photos, videos and movies together. It\u2019s small enough to carry around, big enough to gather around. Android\u2122 2.2-based tablet with over-the-air upgrade capability for future OS releases. A vibrant 7-inch, multitouch display with full Adobe\u00ae Flash 10.1 pre-installed. Includes a 1.3 MP front-facing camera for face-to-face chats on popular services such as Qik or Skype. 16 GB of internal storage, plus Wi-Fi, Bluetooth and built-in GPS keeps you in touch with the world around you. Connect on your terms. Save with 2-year contract or flexibility with prepaid pay-as-you-go plans", + "display": { + "screenResolution": "WVGA (800 x 480)", + "screenSize": "7.0 inches", + "touchScreen": true + }, + "hardware": { + "accelerometer": true, + "audioJack": "3.5mm", + "cpu": "nVidia Tegra T20", + "fmRadio": false, + "physicalKeyboard": false, + "usb": "USB 2.0" + }, + "id": "dell-streak-7", + "images": [ + "img/phones/dell-streak-7.0.jpg", + "img/phones/dell-streak-7.1.jpg", + "img/phones/dell-streak-7.2.jpg", + "img/phones/dell-streak-7.3.jpg", + "img/phones/dell-streak-7.4.jpg" + ], + "name": "Dell Streak 7", + "sizeAndWeight": { + "dimensions": [ + "199.9 mm (w)", + "119.8 mm (h)", + "12.4 mm (d)" + ], + "weight": "450.0 grams" + }, + "storage": { + "flash": "16000MB", + "ram": "512MB" + } +} diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/phones/motorola-atrix-4g.json b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phones/motorola-atrix-4g.json new file mode 100644 index 0000000000..ccca00e3b2 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phones/motorola-atrix-4g.json @@ -0,0 +1,62 @@ +{ + "additionalFeatures": "", + "android": { + "os": "Android 2.2", + "ui": "MOTOBLUR" + }, + "availability": [ + "AT&T" + ], + "battery": { + "standbyTime": "400 hours", + "talkTime": "5 hours", + "type": "Lithium Ion (Li-Ion) (1930 mAH)" + }, + "camera": { + "features": [ + "" + ], + "primary": "" + }, + "connectivity": { + "bluetooth": "Bluetooth 2.1", + "cell": "WCDMA 850/1900/2100, GSM 850/900/1800/1900, HSDPA 14Mbps (Category 10) Edge Class 12, GPRS Class 12, eCompass, AGPS", + "gps": true, + "infrared": false, + "wifi": "802.11 a/b/g/n" + }, + "description": "MOTOROLA ATRIX 4G gives you dual-core processing power and the revolutionary webtop application. With webtop and a compatible Motorola docking station, sold separately, you can surf the Internet with a full Firefox browser, create and edit docs, or access multimedia on a large screen almost anywhere.", + "display": { + "screenResolution": "QHD (960 x 540)", + "screenSize": "4.0 inches", + "touchScreen": true + }, + "hardware": { + "accelerometer": true, + "audioJack": "3.5mm", + "cpu": "1 GHz Dual Core", + "fmRadio": false, + "physicalKeyboard": false, + "usb": "USB 2.0" + }, + "id": "motorola-atrix-4g", + "images": [ + "img/phones/motorola-atrix-4g.0.jpg", + "img/phones/motorola-atrix-4g.1.jpg", + "img/phones/motorola-atrix-4g.2.jpg", + "img/phones/motorola-atrix-4g.3.jpg" + ], + "name": "MOTOROLA ATRIX\u2122 4G", + "sizeAndWeight": { + "dimensions": [ + "63.5 mm (w)", + "117.75 mm (h)", + "10.95 mm (d)" + ], + "weight": "135.0 grams" + }, + "storage": { + "flash": "", + "ram": "" + } +} diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/phones/motorola-xoom-with-wi-fi.json b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phones/motorola-xoom-with-wi-fi.json new file mode 100644 index 0000000000..4ba9c8d5b5 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phones/motorola-xoom-with-wi-fi.json @@ -0,0 +1,65 @@ +{ + "additionalFeatures": "Sensors: proximity, ambient light, barometer, gyroscope", + "android": { + "os": "Android 3.0", + "ui": "Honeycomb" + }, + "availability": [ + "" + ], + "battery": { + "standbyTime": "336 hours", + "talkTime": "24 hours", + "type": "Other ( mAH)" + }, + "camera": { + "features": [ + "Flash", + "Video" + ], + "primary": "5.0 megapixels" + }, + "connectivity": { + "bluetooth": "Bluetooth 2.1", + "cell": "", + "gps": true, + "infrared": false, + "wifi": "802.11 b/g/n" + }, + "description": "Motorola XOOM with Wi-Fi has a super-powerful dual-core processor and Android\u2122 3.0 (Honeycomb) \u2014 the Android platform designed specifically for tablets. With its 10.1-inch HD widescreen display, you\u2019ll enjoy HD video in a thin, light, powerful and upgradeable tablet.", + "display": { + "screenResolution": "WXGA (1200 x 800)", + "screenSize": "10.1 inches", + "touchScreen": true + }, + "hardware": { + "accelerometer": true, + "audioJack": "3.5mm", + "cpu": "1 GHz Dual Core Tegra 2", + "fmRadio": false, + "physicalKeyboard": false, + "usb": "USB 2.0" + }, + "id": "motorola-xoom-with-wi-fi", + "images": [ + "img/phones/motorola-xoom-with-wi-fi.0.jpg", + "img/phones/motorola-xoom-with-wi-fi.1.jpg", + "img/phones/motorola-xoom-with-wi-fi.2.jpg", + "img/phones/motorola-xoom-with-wi-fi.3.jpg", + "img/phones/motorola-xoom-with-wi-fi.4.jpg", + "img/phones/motorola-xoom-with-wi-fi.5.jpg" + ], + "name": "Motorola XOOM\u2122 with Wi-Fi", + "sizeAndWeight": { + "dimensions": [ + "249.1 mm (w)", + "167.8 mm (h)", + "12.9 mm (d)" + ], + "weight": "708.0 grams" + }, + "storage": { + "flash": "32000MB", + "ram": "1000MB" + } +} diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/phones/motorola-xoom.json b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phones/motorola-xoom.json new file mode 100644 index 0000000000..f0f0c8711d --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phones/motorola-xoom.json @@ -0,0 +1,62 @@ +{ + "additionalFeatures": "Front-facing camera. Sensors: proximity, ambient light, barometer, gyroscope.", + "android": { + "os": "Android 3.0", + "ui": "Android" + }, + "availability": [ + "Verizon" + ], + "battery": { + "standbyTime": "336 hours", + "talkTime": "24 hours", + "type": "Other (3250 mAH)" + }, + "camera": { + "features": [ + "Flash", + "Video" + ], + "primary": "5.0 megapixels" + }, + "connectivity": { + "bluetooth": "Bluetooth 2.1", + "cell": "CDMA 800 /1900 LTE 700, Rx diversity in all bands", + "gps": true, + "infrared": false, + "wifi": "802.11 a/b/g/n" + }, + "description": "MOTOROLA XOOM has a super-powerful dual-core processor and Android\u2122 3.0 (Honeycomb) \u2014 the Android platform designed specifically for tablets. With its 10.1-inch HD widescreen display, you\u2019ll enjoy HD video in a thin, light, powerful and upgradeable tablet.", + "display": { + "screenResolution": "WXGA (1200 x 800)", + "screenSize": "10.1 inches", + "touchScreen": true + }, + "hardware": { + "accelerometer": true, + "audioJack": "3.5mm", + "cpu": "1 GHz Dual Core Tegra 2", + "fmRadio": false, + "physicalKeyboard": false, + "usb": "USB 2.0" + }, + "id": "motorola-xoom", + "images": [ + "img/phones/motorola-xoom.0.jpg", + "img/phones/motorola-xoom.1.jpg", + "img/phones/motorola-xoom.2.jpg" + ], + "name": "MOTOROLA XOOM\u2122", + "sizeAndWeight": { + "dimensions": [ + "249.0 mm (w)", + "168.0 mm (h)", + "12.7 mm (d)" + ], + "weight": "726.0 grams" + }, + "storage": { + "flash": "32000MB", + "ram": "1000MB" + } +} diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/phones/nexus-s.json b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phones/nexus-s.json new file mode 100644 index 0000000000..5e712e2ff8 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phones/nexus-s.json @@ -0,0 +1,69 @@ +{ + "additionalFeatures": "Contour Display, Near Field Communications (NFC), Three-axis gyroscope, Anti-fingerprint display coating, Internet Calling support (VoIP/SIP)", + "android": { + "os": "Android 2.3", + "ui": "Android" + }, + "availability": [ + "M1,", + "O2,", + "Orange,", + "Singtel,", + "StarHub,", + "T-Mobile,", + "Vodafone" + ], + "battery": { + "standbyTime": "428 hours", + "talkTime": "6 hours", + "type": "Lithium Ion (Li-Ion) (1500 mAH)" + }, + "camera": { + "features": [ + "Flash", + "Video" + ], + "primary": "5.0 megapixels" + }, + "connectivity": { + "bluetooth": "Bluetooth 2.1", + "cell": "Quad-band GSM: 850, 900, 1800, 1900\r\nTri-band HSPA: 900, 2100, 1700\r\nHSPA type: HSDPA (7.2Mbps) HSUPA (5.76Mbps)", + "gps": true, + "infrared": false, + "wifi": "802.11 b/g/n" + }, + "description": "Nexus S is the next generation of Nexus devices, co-developed by Google and Samsung. The latest Android platform (Gingerbread), paired with a 1 GHz Hummingbird processor and 16GB of memory, makes Nexus S one of the fastest phones on the market. It comes pre-installed with the best of Google apps and enabled with new and popular features like true multi-tasking, Wi-Fi hotspot, Internet Calling, NFC support, and full web browsing. With this device, users will also be the first to receive software upgrades and new Google mobile apps as soon as they become available. For more details, visit http://www.google.com/nexus.", + "display": { + "screenResolution": "WVGA (800 x 480)", + "screenSize": "4.0 inches", + "touchScreen": true + }, + "hardware": { + "accelerometer": true, + "audioJack": "3.5mm", + "cpu": "1GHz Cortex A8 (Hummingbird) processor", + "fmRadio": false, + "physicalKeyboard": false, + "usb": "USB 2.0" + }, + "id": "nexus-s", + "images": [ + "img/phones/nexus-s.0.jpg", + "img/phones/nexus-s.1.jpg", + "img/phones/nexus-s.2.jpg", + "img/phones/nexus-s.3.jpg" + ], + "name": "Nexus S", + "sizeAndWeight": { + "dimensions": [ + "63.0 mm (w)", + "123.9 mm (h)", + "10.88 mm (d)" + ], + "weight": "129.0 grams" + }, + "storage": { + "flash": "16384MB", + "ram": "512MB" + } +} diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/app/phones/phones.json b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phones/phones.json new file mode 100644 index 0000000000..339b94fbb5 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/app/phones/phones.json @@ -0,0 +1,155 @@ +[ + { + "age": 0, + "id": "motorola-xoom-with-wi-fi", + "imageUrl": "img/phones/motorola-xoom-with-wi-fi.0.jpg", + "name": "Motorola XOOM\u2122 with Wi-Fi", + "snippet": "The Next, Next Generation\r\n\r\nExperience the future with Motorola XOOM with Wi-Fi, the world's first tablet powered by Android 3.0 (Honeycomb)." + }, + { + "age": 1, + "id": "motorola-xoom", + "imageUrl": "img/phones/motorola-xoom.0.jpg", + "name": "MOTOROLA XOOM\u2122", + "snippet": "The Next, Next Generation\n\nExperience the future with MOTOROLA XOOM, the world's first tablet powered by Android 3.0 (Honeycomb)." + }, + { + "age": 2, + "carrier": "AT&T", + "id": "motorola-atrix-4g", + "imageUrl": "img/phones/motorola-atrix-4g.0.jpg", + "name": "MOTOROLA ATRIX\u2122 4G", + "snippet": "MOTOROLA ATRIX 4G the world's most powerful smartphone." + }, + { + "age": 3, + "id": "dell-streak-7", + "imageUrl": "img/phones/dell-streak-7.0.jpg", + "name": "Dell Streak 7", + "snippet": "Introducing Dell\u2122 Streak 7. Share photos, videos and movies together. It\u2019s small enough to carry around, big enough to gather around." + }, + { + "age": 4, + "carrier": "Cellular South", + "id": "samsung-gem", + "imageUrl": "img/phones/samsung-gem.0.jpg", + "name": "Samsung Gem\u2122", + "snippet": "The Samsung Gem\u2122 brings you everything that you would expect and more from a touch display smart phone \u2013 more apps, more features and a more affordable price." + }, + { + "age": 5, + "carrier": "Dell", + "id": "dell-venue", + "imageUrl": "img/phones/dell-venue.0.jpg", + "name": "Dell Venue", + "snippet": "The Dell Venue; Your Personal Express Lane to Everything" + }, + { + "age": 6, + "carrier": "Best Buy", + "id": "nexus-s", + "imageUrl": "img/phones/nexus-s.0.jpg", + "name": "Nexus S", + "snippet": "Fast just got faster with Nexus S. A pure Google experience, Nexus S is the first phone to run Gingerbread (Android 2.3), the fastest version of Android yet." + }, + { + "age": 7, + "carrier": "Cellular South", + "id": "lg-axis", + "imageUrl": "img/phones/lg-axis.0.jpg", + "name": "LG Axis", + "snippet": "Android Powered, Google Maps Navigation, 5 Customizable Home Screens" + }, + { + "age": 8, + "id": "samsung-galaxy-tab", + "imageUrl": "img/phones/samsung-galaxy-tab.0.jpg", + "name": "Samsung Galaxy Tab\u2122", + "snippet": "Feel Free to Tab\u2122. The Samsung Galaxy Tab\u2122 brings you an ultra-mobile entertainment experience through its 7\u201d display, high-power processor and Adobe\u00ae Flash\u00ae Player compatibility." + }, + { + "age": 9, + "carrier": "Cellular South", + "id": "samsung-showcase-a-galaxy-s-phone", + "imageUrl": "img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg", + "name": "Samsung Showcase\u2122 a Galaxy S\u2122 phone", + "snippet": "The Samsung Showcase\u2122 delivers a cinema quality experience like you\u2019ve never seen before. Its innovative 4\u201d touch display technology provides rich picture brilliance, even outdoors" + }, + { + "age": 10, + "carrier": "Verizon", + "id": "droid-2-global-by-motorola", + "imageUrl": "img/phones/droid-2-global-by-motorola.0.jpg", + "name": "DROID\u2122 2 Global by Motorola", + "snippet": "The first smartphone with a 1.2 GHz processor and global capabilities." + }, + { + "age": 11, + "carrier": "Verizon", + "id": "droid-pro-by-motorola", + "imageUrl": "img/phones/droid-pro-by-motorola.0.jpg", + "name": "DROID\u2122 Pro by Motorola", + "snippet": "The next generation of DOES." + }, + { + "age": 12, + "carrier": "AT&T", + "id": "motorola-bravo-with-motoblur", + "imageUrl": "img/phones/motorola-bravo-with-motoblur.0.jpg", + "name": "MOTOROLA BRAVO\u2122 with MOTOBLUR\u2122", + "snippet": "An experience to cheer about." + }, + { + "age": 13, + "carrier": "T-Mobile", + "id": "motorola-defy-with-motoblur", + "imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg", + "name": "Motorola DEFY\u2122 with MOTOBLUR\u2122", + "snippet": "Are you ready for everything life throws your way?" + }, + { + "age": 14, + "carrier": "T-Mobile", + "id": "t-mobile-mytouch-4g", + "imageUrl": "img/phones/t-mobile-mytouch-4g.0.jpg", + "name": "T-Mobile myTouch 4G", + "snippet": "The T-Mobile myTouch 4G is a premium smartphone designed to deliver blazing fast 4G speeds so that you can video chat from practically anywhere, with or without Wi-Fi." + }, + { + "age": 15, + "carrier": "US Cellular", + "id": "samsung-mesmerize-a-galaxy-s-phone", + "imageUrl": "img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg", + "name": "Samsung Mesmerize\u2122 a Galaxy S\u2122 phone", + "snippet": "The Samsung Mesmerize\u2122 delivers a cinema quality experience like you\u2019ve never seen before. Its innovative 4\u201d touch display technology provides rich picture brilliance,even outdoors" + }, + { + "age": 16, + "carrier": "Sprint", + "id": "sanyo-zio", + "imageUrl": "img/phones/sanyo-zio.0.jpg", + "name": "SANYO ZIO", + "snippet": "The Sanyo Zio by Kyocera is an Android smartphone with a combination of ultra-sleek styling, strong performance and unprecedented value." + }, + { + "age": 17, + "id": "samsung-transform", + "imageUrl": "img/phones/samsung-transform.0.jpg", + "name": "Samsung Transform\u2122", + "snippet": "The Samsung Transform\u2122 brings you a fun way to customize your Android powered touch screen phone to just the way you like it through your favorite themed \u201cSprint ID Service Pack\u201d." + }, + { + "age": 18, + "id": "t-mobile-g2", + "imageUrl": "img/phones/t-mobile-g2.0.jpg", + "name": "T-Mobile G2", + "snippet": "The T-Mobile G2 with Google is the first smartphone built for 4G speeds on T-Mobile's new network. Get the information you need, faster than you ever thought possible." + }, + { + "age": 19, + "id": "motorola-charm-with-motoblur", + "imageUrl": "img/phones/motorola-charm-with-motoblur.0.jpg", + "name": "Motorola CHARM\u2122 with MOTOBLUR\u2122", + "snippet": "Motorola CHARM fits easily in your pocket or palm. Includes MOTOBLUR service." + } +] diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/bs-config.aot.json b/aio/content/examples/upgrade-phonecat-3-router/ts/bs-config.aot.json new file mode 100644 index 0000000000..e59a7403a0 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/bs-config.aot.json @@ -0,0 +1,14 @@ +{ + "open": false, + "logLevel": "silent", + "port": 8080, + "server": { + "baseDir": "aot", + "routes": { + "/node_modules": "node_modules" + }, + "middleware": { + "0": null + } + } +} diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/copy-dist-files.js b/aio/content/examples/upgrade-phonecat-3-router/ts/copy-dist-files.js new file mode 100644 index 0000000000..a857af085c --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/copy-dist-files.js @@ -0,0 +1,25 @@ +// #docregion +var fsExtra = require('fs-extra'); +var resources = [ + // polyfills + 'node_modules/core-js/client/shim.min.js', + 'node_modules/zone.js/dist/zone.min.js', + // css + 'app/app.css', + 'app/app.animations.css', + // images and json files + 'app/img/', + 'app/phones/', + // app files + 'app/app.module.ajs.js', + 'app/app.config.js', + 'app/app.animations.js', + 'app/core/core.module.js', + 'app/core/phone/phone.module.js', + 'app/phone-list/phone-list.module.js', + 'app/phone-detail/phone-detail.module.js' +]; +resources.map(function(sourcePath) { + var destPath = `aot/${sourcePath}`; + fsExtra.copySync(sourcePath, destPath); +}); diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/example-config.json b/aio/content/examples/upgrade-phonecat-3-router/ts/example-config.json new file mode 100644 index 0000000000..401c14f835 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/example-config.json @@ -0,0 +1,5 @@ +{ + "build": "build:upgrade", + "run": "serve:upgrade", + "unittesting": true +} diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/index.html b/aio/content/examples/upgrade-phonecat-3-router/ts/index.html new file mode 100644 index 0000000000..572c80d315 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/index.html @@ -0,0 +1,44 @@ + + + + + + + + + Google Phone Gallery + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/rollup-config.js b/aio/content/examples/upgrade-phonecat-3-router/ts/rollup-config.js new file mode 100644 index 0000000000..aeb227689c --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/rollup-config.js @@ -0,0 +1,21 @@ +// #docregion +import rollup from 'rollup' +import nodeResolve from 'rollup-plugin-node-resolve' +import commonjs from 'rollup-plugin-commonjs'; +import uglify from 'rollup-plugin-uglify' + +//paths are relative to the execution path +export default { + entry: 'app/main-aot.js', + dest: 'aot/dist/build.js', // output a single application bundle + sourceMap: true, + sourceMapFile: 'aot/dist/build.js.map', + format: 'iife', + plugins: [ + nodeResolve({jsnext: true, module: true}), + commonjs({ + include: ['node_modules/rxjs/**'] + }), + uglify() + ] +} diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/systemjs.config.1.js b/aio/content/examples/upgrade-phonecat-3-router/ts/systemjs.config.1.js new file mode 100644 index 0000000000..b801d42bad --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/systemjs.config.1.js @@ -0,0 +1,48 @@ +/** + * System configuration for Angular samples + * Adjust as necessary for your application needs. + */ +(function (global) { + // #docregion paths + System.config({ + paths: { + // paths serve as alias + 'npm:': '/node_modules/' + }, + map: { + app: '/app', + // #enddocregion paths + // angular bundles + '@angular/core': 'npm:@angular/core/bundles/core.umd.js', + '@angular/common': 'npm:@angular/common/bundles/common.umd.js', + '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'npm:@angular/http/bundles/http.umd.js', + '@angular/router': 'npm:@angular/router/bundles/router.umd.js', + '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', + '@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js', + '@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js', + + // other libraries + 'rxjs': 'npm:rxjs', + 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api', + // #docregion paths + }, + // #enddocregion paths + // packages tells the System loader how to load when no filename and/or no extension + packages: { + 'app': { + main: './main.js', + defaultExtension: 'js' + }, + rxjs: { + defaultExtension: 'js' + }, + 'angular-in-memory-web-api': { + main: './index.js', + defaultExtension: 'js' + } + } + }); +})(this); diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/tsconfig-aot.json b/aio/content/examples/upgrade-phonecat-3-router/ts/tsconfig-aot.json new file mode 100644 index 0000000000..58f9de3309 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/tsconfig-aot.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "es2015", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": ["es2015", "dom"], + "removeComments": false, + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true, + "typeRoots": [ + "../../node_modules/@types/" + ] + }, + + "files": [ + "app/app.module.ts", + "app/main-aot.ts" + ], + + "angularCompilerOptions": { + "genDir": "aot", + "skipMetadataEmit" : true + } +} diff --git a/aio/content/examples/upgrade-phonecat-3-router/ts/tsconfig.json b/aio/content/examples/upgrade-phonecat-3-router/ts/tsconfig.json new file mode 100644 index 0000000000..f267800f14 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-3-router/ts/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ "es2015", "dom" ], + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true, + "typeRoots": [ + "../../node_modules/@types/" + ] + }, + "compileOnSave": true, + "exclude": [ + "node_modules/*", + "**/*-aot.ts" + ] +} diff --git a/aio/content/examples/upgrade-phonecat-4-final/README.md b/aio/content/examples/upgrade-phonecat-4-final/README.md new file mode 100644 index 0000000000..7448da44e6 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/README.md @@ -0,0 +1,24 @@ +This is the Angular Phonecat application adjusted to fit our boilerplate project +structure. + +The following changes from vanilla Phonecat are applied: + +* Karma config for unit tests is in karma.conf.ng1.js because the boilerplate + Karma config is not compatible with the way tests in this project need to be run. + The shell script run-unit-tests.sh can be used to run the unit tests. +* E2E tests have been moved to the parent directory, where `run-e2e-tests` can + discover and run them along with all the other examples. +* Most of the phone JSON and image data removed in the interest of keeping + repo weight down. Keeping enough to retain testability of the app. + +## Running the app + +Start like any example + + npm run start + +## Running E2E tests + +Like for any example (at the project root): + + gulp run-e2e-tests --filter=phonecat-3 diff --git a/aio/content/examples/upgrade-phonecat-4-final/e2e-spec.ts b/aio/content/examples/upgrade-phonecat-4-final/e2e-spec.ts new file mode 100644 index 0000000000..6f47c54d02 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/e2e-spec.ts @@ -0,0 +1,109 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +// Angular E2E Testing Guide: +// https://docs.angularjs.org/guide/e2e-testing + +describe('PhoneCat Application', function() { + + // #docregion redirect + it('should redirect `index.html` to `index.html#!/phones', function() { + browser.get('index.html'); + browser.waitForAngular(); + browser.getCurrentUrl().then(function(url: string) { + expect(url.endsWith('/phones')).toBe(true); + }); + }); + // #enddocregion redirect + + describe('View: Phone list', function() { + + beforeEach(function() { + browser.get('index.html#!/phones'); + }); + + it('should filter the phone list as a user types into the search box', function() { + let phoneList = element.all(by.css('.phones li')); + let query = element(by.css('input')); + + expect(phoneList.count()).toBe(20); + + query.sendKeys('nexus'); + expect(phoneList.count()).toBe(1); + + query.clear(); + query.sendKeys('motorola'); + expect(phoneList.count()).toBe(8); + }); + + it('should be possible to control phone order via the drop-down menu', function() { + let queryField = element(by.css('input')); + let orderSelect = element(by.css('select')); + let nameOption = orderSelect.element(by.css('option[value="name"]')); + let phoneNameColumn = element.all(by.css('.phones .name')); + + function getNames() { + return phoneNameColumn.map(function(elem) { + return elem.getText(); + }); + } + + queryField.sendKeys('tablet'); // Let's narrow the dataset to make the assertions shorter + + expect(getNames()).toEqual([ + 'Motorola XOOM\u2122 with Wi-Fi', + 'MOTOROLA XOOM\u2122' + ]); + + nameOption.click(); + + expect(getNames()).toEqual([ + 'MOTOROLA XOOM\u2122', + 'Motorola XOOM\u2122 with Wi-Fi' + ]); + }); + + // #docregion links + it('should render phone specific links', function() { + let query = element(by.css('input')); + query.sendKeys('nexus'); + element.all(by.css('.phones li a')).first().click(); + browser.getCurrentUrl().then(function(url: string) { + expect(url.endsWith('/phones/nexus-s')).toBe(true); + }); + }); + // #enddocregion links + + }); + + describe('View: Phone detail', function() { + + beforeEach(function() { + browser.get('index.html#!/phones/nexus-s'); + }); + + it('should display the `nexus-s` page', function() { + expect(element(by.css('h1')).getText()).toBe('Nexus S'); + }); + + it('should display the first phone image as the main phone image', function() { + let mainImage = element(by.css('img.phone.selected')); + + expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/); + }); + + it('should swap the main image when clicking on a thumbnail image', function() { + let mainImage = element(by.css('img.phone.selected')); + let thumbnails = element.all(by.css('.phone-thumbs img')); + + thumbnails.get(2).click(); + expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.2.jpg/); + + thumbnails.get(0).click(); + expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/); + }); + + }); + +}); diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/app-routing.module.ts b/aio/content/examples/upgrade-phonecat-4-final/ts/app/app-routing.module.ts new file mode 100644 index 0000000000..f64d82e253 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/app-routing.module.ts @@ -0,0 +1,23 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { APP_BASE_HREF, HashLocationStrategy, LocationStrategy } from '@angular/common'; + +import { PhoneDetailComponent } from './phone-detail/phone-detail.component'; +import { PhoneListComponent } from './phone-list/phone-list.component'; + +const routes: Routes = [ + { path: '', redirectTo: 'phones', pathMatch: 'full' }, + { path: 'phones', component: PhoneListComponent }, + { path: 'phones/:phoneId', component: PhoneDetailComponent } +]; + +@NgModule({ + imports: [ RouterModule.forRoot(routes) ], + exports: [ RouterModule ], + providers: [ + { provide: APP_BASE_HREF, useValue: '!' }, + { provide: LocationStrategy, useClass: HashLocationStrategy }, + ] +}) +export class AppRoutingModule { } diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/app.component.ts b/aio/content/examples/upgrade-phonecat-4-final/ts/app/app.component.ts new file mode 100644 index 0000000000..c476614121 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/app.component.ts @@ -0,0 +1,8 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'phonecat-app', + template: '' +}) +export class AppComponent { } diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/app.css b/aio/content/examples/upgrade-phonecat-4-final/ts/app/app.css new file mode 100644 index 0000000000..f4b45b02a5 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/app.css @@ -0,0 +1,93 @@ +body { + padding: 20px; +} + +h1 { + border-bottom: 1px solid gray; + margin-top: 0; +} + +/* View: Phone list */ +.phones { + list-style: none; +} + +.phones li { + clear: both; + height: 115px; + padding-top: 15px; +} + +.thumb { + float: left; + height: 100px; + margin: -0.5em 1em 1.5em 0; + padding-bottom: 1em; + width: 100px; +} + +/* View: Phone detail */ +.phone { + background-color: white; + display: none; + float: left; + height: 400px; + margin-bottom: 2em; + margin-right: 3em; + padding: 2em; + width: 400px; +} + +.phone:first-child { + display: block; +} + +.phone-images { + background-color: white; + float: left; + height: 450px; + overflow: hidden; + position: relative; + width: 450px; +} + +.phone-thumbs { + list-style: none; + margin: 0; +} + +.phone-thumbs img { + height: 100px; + padding: 1em; + width: 100px; +} + +.phone-thumbs li { + background-color: white; + border: 1px solid black; + cursor: pointer; + display: inline-block; + margin: 1em; +} + +.specs { + clear: both; + list-style: none; + margin: 0; + padding: 0; +} + +.specs dt { + font-weight: bold; +} + +.specs > li { + display: inline-block; + vertical-align: top; + width: 200px; +} + +.specs > li > span { + font-size: 1.2em; + font-weight: bold; +} diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/app.module.ts b/aio/content/examples/upgrade-phonecat-4-final/ts/app/app.module.ts new file mode 100644 index 0000000000..607ecab8c9 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/app.module.ts @@ -0,0 +1,34 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; +import { HttpModule } from '@angular/http'; + +import { AppRoutingModule } from './app-routing.module'; +import { AppComponent } from './app.component'; +import { CheckmarkPipe } from './core/checkmark/checkmark.pipe'; +import { Phone } from './core/phone/phone.service'; +import { PhoneDetailComponent } from './phone-detail/phone-detail.component'; +import { PhoneListComponent } from './phone-list/phone-list.component'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule, + HttpModule, + AppRoutingModule + ], + declarations: [ + AppComponent, + PhoneListComponent, + CheckmarkPipe, + PhoneDetailComponent + ], + providers: [ + Phone + ], + // #docregion bootstrap + bootstrap: [ AppComponent ] + // #enddocregion bootstrap +}) +export class AppModule {} diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/core/checkmark/checkmark.pipe.spec.ts b/aio/content/examples/upgrade-phonecat-4-final/ts/app/core/checkmark/checkmark.pipe.spec.ts new file mode 100644 index 0000000000..75150500a6 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/core/checkmark/checkmark.pipe.spec.ts @@ -0,0 +1,10 @@ +import { CheckmarkPipe } from './checkmark.pipe'; + +describe('CheckmarkPipe', function() { + + it('should convert boolean values to unicode checkmark or cross', function () { + const checkmarkPipe = new CheckmarkPipe(); + expect(checkmarkPipe.transform(true)).toBe('\u2713'); + expect(checkmarkPipe.transform(false)).toBe('\u2718'); + }); +}); diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/core/checkmark/checkmark.pipe.ts b/aio/content/examples/upgrade-phonecat-4-final/ts/app/core/checkmark/checkmark.pipe.ts new file mode 100644 index 0000000000..888017e15c --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/core/checkmark/checkmark.pipe.ts @@ -0,0 +1,9 @@ +// #docregion +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({name: 'checkmark'}) +export class CheckmarkPipe implements PipeTransform { + transform(input: boolean) { + return input ? '\u2713' : '\u2718'; + } +} diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/core/phone/phone.service.spec.ts b/aio/content/examples/upgrade-phonecat-4-final/ts/app/core/phone/phone.service.spec.ts new file mode 100644 index 0000000000..e3a422965b --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/core/phone/phone.service.spec.ts @@ -0,0 +1,49 @@ +import { inject, TestBed } from '@angular/core/testing'; +import { + Http, + BaseRequestOptions, + ResponseOptions, + Response +} from '@angular/http'; +import { MockBackend, MockConnection } from '@angular/http/testing'; +import { Phone, PhoneData } from './phone.service'; + +describe('Phone', function() { + let phone: Phone; + let phonesData: PhoneData[] = [ + {name: 'Phone X', snippet: '', images: []}, + {name: 'Phone Y', snippet: '', images: []}, + {name: 'Phone Z', snippet: '', images: []} + ]; + let mockBackend: MockBackend; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + Phone, + MockBackend, + BaseRequestOptions, + { provide: Http, + useFactory: (backend: MockBackend, options: BaseRequestOptions) => new Http(backend, options), + deps: [MockBackend, BaseRequestOptions] + } + ] + }); + }); + + beforeEach(inject([MockBackend, Phone], (_mockBackend_: MockBackend, _phone_: Phone) => { + mockBackend = _mockBackend_; + phone = _phone_; + })); + + it('should fetch the phones data from `/phones/phones.json`', (done: () => void) => { + mockBackend.connections.subscribe((conn: MockConnection) => { + conn.mockRespond(new Response(new ResponseOptions({body: JSON.stringify(phonesData)}))); + }); + phone.query().subscribe(result => { + expect(result).toEqual(phonesData); + done(); + }); + }); + +}); diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/core/phone/phone.service.ts b/aio/content/examples/upgrade-phonecat-4-final/ts/app/core/phone/phone.service.ts new file mode 100644 index 0000000000..83a837afb7 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/core/phone/phone.service.ts @@ -0,0 +1,25 @@ +// #docregion +import { Injectable } from '@angular/core'; +import { Http, Response } from '@angular/http'; +import { Observable } from 'rxjs/Rx'; + +import 'rxjs/add/operator/map'; + +export interface PhoneData { + name: string; + snippet: string; + images: string[]; +} + +@Injectable() +export class Phone { + constructor(private http: Http) { } + query(): Observable { + return this.http.get(`phones/phones.json`) + .map((res: Response) => res.json()); + } + get(id: string): Observable { + return this.http.get(`phones/${id}.json`) + .map((res: Response) => res.json()); + } +} diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/.gitkeep b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.0.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.0.jpg new file mode 100644 index 0000000000..7ce0dce4ee Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.1.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.1.jpg new file mode 100644 index 0000000000..ed8cad89fb Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.1.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.2.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.2.jpg new file mode 100644 index 0000000000..c83529e0f9 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.2.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.3.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.3.jpg new file mode 100644 index 0000000000..cd2c30280d Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.3.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.4.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.4.jpg new file mode 100644 index 0000000000..b4d8472da8 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.4.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-venue.0.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-venue.0.jpg new file mode 100644 index 0000000000..b4cb4eb25f Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-venue.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/droid-2-global-by-motorola.0.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/droid-2-global-by-motorola.0.jpg new file mode 100644 index 0000000000..60700a2ab3 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/droid-2-global-by-motorola.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/droid-pro-by-motorola.0.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/droid-pro-by-motorola.0.jpg new file mode 100644 index 0000000000..c7710de986 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/droid-pro-by-motorola.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/lg-axis.0.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/lg-axis.0.jpg new file mode 100644 index 0000000000..55e5a23bb2 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/lg-axis.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-atrix-4g.0.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-atrix-4g.0.jpg new file mode 100644 index 0000000000..2446159e93 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-atrix-4g.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-atrix-4g.1.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-atrix-4g.1.jpg new file mode 100644 index 0000000000..867f207459 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-atrix-4g.1.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-atrix-4g.2.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-atrix-4g.2.jpg new file mode 100644 index 0000000000..27d78338c4 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-atrix-4g.2.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-atrix-4g.3.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-atrix-4g.3.jpg new file mode 100644 index 0000000000..29459a68a4 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-atrix-4g.3.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-bravo-with-motoblur.0.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-bravo-with-motoblur.0.jpg new file mode 100644 index 0000000000..e452ae7e7c Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-bravo-with-motoblur.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-charm-with-motoblur.0.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-charm-with-motoblur.0.jpg new file mode 100644 index 0000000000..21e4b8d741 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-charm-with-motoblur.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-defy-with-motoblur.0.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-defy-with-motoblur.0.jpg new file mode 100644 index 0000000000..c7c5e3ba0c Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-defy-with-motoblur.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.0.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.0.jpg new file mode 100644 index 0000000000..a6c993291e Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.1.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.1.jpg new file mode 100644 index 0000000000..400b89e2df Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.1.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.2.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.2.jpg new file mode 100644 index 0000000000..86b55d28dd Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.2.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.3.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.3.jpg new file mode 100644 index 0000000000..85ec293ae5 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.3.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.4.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.4.jpg new file mode 100644 index 0000000000..75ef1464cb Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.4.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.5.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.5.jpg new file mode 100644 index 0000000000..4d42db4330 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.5.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom.0.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom.0.jpg new file mode 100644 index 0000000000..bf6954bbd5 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom.1.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom.1.jpg new file mode 100644 index 0000000000..659688a47d Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom.1.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom.2.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom.2.jpg new file mode 100644 index 0000000000..ce0ff1002e Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom.2.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/nexus-s.0.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/nexus-s.0.jpg new file mode 100644 index 0000000000..0952bc79c2 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/nexus-s.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/nexus-s.1.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/nexus-s.1.jpg new file mode 100644 index 0000000000..f33004dd7f Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/nexus-s.1.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/nexus-s.2.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/nexus-s.2.jpg new file mode 100644 index 0000000000..c5c63621f1 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/nexus-s.2.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/nexus-s.3.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/nexus-s.3.jpg new file mode 100644 index 0000000000..e51f75b0ed Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/nexus-s.3.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-galaxy-tab.0.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-galaxy-tab.0.jpg new file mode 100644 index 0000000000..3750377ad9 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-galaxy-tab.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-gem.0.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-gem.0.jpg new file mode 100644 index 0000000000..0d5024a026 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-gem.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg new file mode 100644 index 0000000000..11b8f860cb Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg new file mode 100644 index 0000000000..11b8f860cb Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-transform.0.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-transform.0.jpg new file mode 100644 index 0000000000..0e3107caf5 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-transform.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/sanyo-zio.0.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/sanyo-zio.0.jpg new file mode 100644 index 0000000000..9eeb9b96ed Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/sanyo-zio.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/t-mobile-g2.0.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/t-mobile-g2.0.jpg new file mode 100644 index 0000000000..6b6c09e058 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/t-mobile-g2.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/t-mobile-mytouch-4g.0.jpg b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/t-mobile-mytouch-4g.0.jpg new file mode 100644 index 0000000000..beba1f6827 Binary files /dev/null and b/aio/content/examples/upgrade-phonecat-4-final/ts/app/img/phones/t-mobile-mytouch-4g.0.jpg differ diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/main.ts b/aio/content/examples/upgrade-phonecat-4-final/ts/app/main.ts new file mode 100644 index 0000000000..08be7a99ba --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/main.ts @@ -0,0 +1,10 @@ +// #docregion +// #docregion imports +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app.module'; +// #enddocregion imports + +// #docregion bootstrap +platformBrowserDynamic().bootstrapModule(AppModule); +// #enddocregion bootstrap diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/phone-detail/phone-detail.component.spec.ts b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phone-detail/phone-detail.component.spec.ts new file mode 100644 index 0000000000..e3b9143a94 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phone-detail/phone-detail.component.spec.ts @@ -0,0 +1,59 @@ +// #docregion +// #docregion activatedroute +import { ActivatedRoute } from '@angular/router'; + +// #enddocregion activatedroute +import { Observable } from 'rxjs/Rx'; + +import { async, TestBed } from '@angular/core/testing'; + +import { PhoneDetailComponent } from './phone-detail.component'; +import { Phone, PhoneData } from '../core/phone/phone.service'; +import { CheckmarkPipe } from '../core/checkmark/checkmark.pipe'; + +function xyzPhoneData(): PhoneData { + return { + name: 'phone xyz', + snippet: '', + images: ['image/url1.png', 'image/url2.png'] + }; +} + +class MockPhone { + get(id: string): Observable { + return Observable.of(xyzPhoneData()); + } +} + +// #docregion activatedroute + +class ActivatedRouteMock { + constructor(public snapshot: any) {} +} + +// #enddocregion activatedroute + +describe('PhoneDetailComponent', () => { + + // #docregion activatedroute + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CheckmarkPipe, PhoneDetailComponent ], + providers: [ + { provide: Phone, useClass: MockPhone }, + { provide: ActivatedRoute, useValue: new ActivatedRouteMock({ params: { 'phoneId': 1 } }) } + ] + }) + .compileComponents(); + })); + // #enddocregion activatedroute + + it('should fetch phone detail', () => { + const fixture = TestBed.createComponent(PhoneDetailComponent); + fixture.detectChanges(); + let compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('h1').textContent).toContain(xyzPhoneData().name); + }); + +}); diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/phone-detail/phone-detail.component.ts b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phone-detail/phone-detail.component.ts new file mode 100644 index 0000000000..579dd5601e --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phone-detail/phone-detail.component.ts @@ -0,0 +1,28 @@ +// #docplaster +// #docregion +import { Component } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +import { Phone, PhoneData } from '../core/phone/phone.service'; + +@Component({ + moduleId: module.id, + selector: 'phone-detail', + templateUrl: './phone-detail.template.html' +}) +export class PhoneDetailComponent { + phone: PhoneData; + mainImageUrl: string; + + constructor(activatedRoute: ActivatedRoute, phone: Phone) { + phone.get(activatedRoute.snapshot.params['phoneId']) + .subscribe((p: PhoneData) => { + this.phone = p; + this.setImage(p.images[0]); + }); + } + + setImage(imageUrl: string) { + this.mainImageUrl = imageUrl; + } +} diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/phone-detail/phone-detail.template.html b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phone-detail/phone-detail.template.html new file mode 100644 index 0000000000..46a96d66c3 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phone-detail/phone-detail.template.html @@ -0,0 +1,120 @@ + +
    +
    + +
    + +

    {{phone.name}}

    + +

    {{phone.description}}

    + +
      +
    • + +
    • +
    + +
      +
    • + Availability and Networks +
      +
      Availability
      +
      {{availability}}
      +
      +
    • +
    • + Battery +
      +
      Type
      +
      {{phone.battery?.type}}
      +
      Talk Time
      +
      {{phone.battery?.talkTime}}
      +
      Standby time (max)
      +
      {{phone.battery?.standbyTime}}
      +
      +
    • +
    • + Storage and Memory +
      +
      RAM
      +
      {{phone.storage?.ram}}
      +
      Internal Storage
      +
      {{phone.storage?.flash}}
      +
      +
    • +
    • + Connectivity +
      +
      Network Support
      +
      {{phone.connectivity?.cell}}
      +
      WiFi
      +
      {{phone.connectivity?.wifi}}
      +
      Bluetooth
      +
      {{phone.connectivity?.bluetooth}}
      +
      Infrared
      +
      {{phone.connectivity?.infrared | checkmark}}
      +
      GPS
      +
      {{phone.connectivity?.gps | checkmark}}
      +
      +
    • +
    • + Android +
      +
      OS Version
      +
      {{phone.android?.os}}
      +
      UI
      +
      {{phone.android?.ui}}
      +
      +
    • +
    • + Size and Weight +
      +
      Dimensions
      +
      {{dim}}
      +
      Weight
      +
      {{phone.sizeAndWeight?.weight}}
      +
      +
    • +
    • + Display +
      +
      Screen size
      +
      {{phone.display?.screenSize}}
      +
      Screen resolution
      +
      {{phone.display?.screenResolution}}
      +
      Touch screen
      +
      {{phone.display?.touchScreen | checkmark}}
      +
      +
    • +
    • + Hardware +
      +
      CPU
      +
      {{phone.hardware?.cpu}}
      +
      USB
      +
      {{phone.hardware?.usb}}
      +
      Audio / headphone jack
      +
      {{phone.hardware?.audioJack}}
      +
      FM Radio
      +
      {{phone.hardware?.fmRadio | checkmark}}
      +
      Accelerometer
      +
      {{phone.hardware?.accelerometer | checkmark}}
      +
      +
    • +
    • + Camera +
      +
      Primary
      +
      {{phone.camera?.primary}}
      +
      Features
      +
      {{phone.camera?.features?.join(', ')}}
      +
      +
    • +
    • + Additional Features +
      {{phone.additionalFeatures}}
      +
    • +
    +
    diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/phone-list/phone-list.component.spec.ts b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phone-list/phone-list.component.spec.ts new file mode 100644 index 0000000000..834c93df8f --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phone-list/phone-list.component.spec.ts @@ -0,0 +1,71 @@ +/* tslint:disable */ +// #docregion routestuff +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs/Rx'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { SpyLocation } from '@angular/common/testing'; + +import { PhoneListComponent } from './phone-list.component'; +import { Phone, PhoneData } from '../core/phone/phone.service'; + +// #enddocregion routestuff + +class ActivatedRouteMock { + constructor(public snapshot: any) {} +} + +class MockPhone { + query(): Observable { + return Observable.of([ + {name: 'Nexus S', snippet: '', images: []}, + {name: 'Motorola DROID', snippet: '', images: []} + ]); + } +} + +let fixture: ComponentFixture; + +describe('PhoneList', () => { + + // #docregion routestuff + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ PhoneListComponent ], + providers: [ + { provide: ActivatedRoute, useValue: new ActivatedRouteMock({ params: { 'phoneId': 1 } }) }, + { provide: Location, useClass: SpyLocation }, + { provide: Phone, useClass: MockPhone }, + ], + schemas: [ NO_ERRORS_SCHEMA ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PhoneListComponent); + }); + // #enddocregion routestuff + + it('should create "phones" model with 2 phones fetched from xhr', () => { + fixture.detectChanges(); + let compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelectorAll('.phone-list-item').length).toBe(2); + expect( + compiled.querySelector('.phone-list-item:nth-child(1)').textContent + ).toContain('Motorola DROID'); + expect( + compiled.querySelector('.phone-list-item:nth-child(2)').textContent + ).toContain('Nexus S'); + }); + + xit('should set the default value of orderProp model', () => { + fixture.detectChanges(); + let compiled = fixture.debugElement.nativeElement; + expect( + compiled.querySelector('select option:last-child').selected + ).toBe(true); + }); + +}); diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/phone-list/phone-list.component.ts b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phone-list/phone-list.component.ts new file mode 100644 index 0000000000..0d9eb07e21 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phone-list/phone-list.component.ts @@ -0,0 +1,54 @@ +// #docregion +import { Component } from '@angular/core'; + +import { Phone, PhoneData } from '../core/phone/phone.service'; + +@Component({ + moduleId: module.id, + selector: 'phone-list', + templateUrl: './phone-list.template.html', +}) +export class PhoneListComponent { + phones: PhoneData[]; + query: string; + orderProp: string; + + constructor(phone: Phone) { + phone.query().subscribe(phones => { + this.phones = phones; + }); + this.orderProp = 'age'; + } + + getPhones(): PhoneData[] { + return this.sortPhones(this.filterPhones(this.phones)); + } + + private filterPhones(phones: PhoneData[]) { + if (phones && this.query) { + return phones.filter(phone => { + let name = phone.name.toLowerCase(); + let snippet = phone.snippet.toLowerCase(); + return name.indexOf(this.query) >= 0 || snippet.indexOf(this.query) >= 0; + }); + } + return phones; + } + + private sortPhones(phones: PhoneData[]) { + if (phones && this.orderProp) { + return phones + .slice(0) // Make a copy + .sort((a, b) => { + if (a[this.orderProp] < b[this.orderProp]) { + return -1; + } else if ([b[this.orderProp] < a[this.orderProp]]) { + return 1; + } else { + return 0; + } + }); + } + return phones; + } +} diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/phone-list/phone-list.template.html b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phone-list/phone-list.template.html new file mode 100644 index 0000000000..b4a994b297 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phone-list/phone-list.template.html @@ -0,0 +1,40 @@ +
    +
    +
    + + + +

    + Search: + +

    + +

    + Sort by: + +

    + + +
    +
    + + + + + + +
    +
    +
    diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/phones/dell-streak-7.json b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phones/dell-streak-7.json new file mode 100644 index 0000000000..a32eb6ff98 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phones/dell-streak-7.json @@ -0,0 +1,64 @@ +{ + "additionalFeatures": "Front Facing 1.3MP Camera", + "android": { + "os": "Android 2.2", + "ui": "Dell Stage" + }, + "availability": [ + "T-Mobile" + ], + "battery": { + "standbyTime": "", + "talkTime": "", + "type": "Lithium Ion (Li-Ion) (2780 mAH)" + }, + "camera": { + "features": [ + "Flash", + "Video" + ], + "primary": "5.0 megapixels" + }, + "connectivity": { + "bluetooth": "Bluetooth 2.1", + "cell": "T-mobile HSPA+ @ 2100/1900/AWS/850 MHz", + "gps": true, + "infrared": false, + "wifi": "802.11 b/g" + }, + "description": "Introducing Dell\u2122 Streak 7. Share photos, videos and movies together. It\u2019s small enough to carry around, big enough to gather around. Android\u2122 2.2-based tablet with over-the-air upgrade capability for future OS releases. A vibrant 7-inch, multitouch display with full Adobe\u00ae Flash 10.1 pre-installed. Includes a 1.3 MP front-facing camera for face-to-face chats on popular services such as Qik or Skype. 16 GB of internal storage, plus Wi-Fi, Bluetooth and built-in GPS keeps you in touch with the world around you. Connect on your terms. Save with 2-year contract or flexibility with prepaid pay-as-you-go plans", + "display": { + "screenResolution": "WVGA (800 x 480)", + "screenSize": "7.0 inches", + "touchScreen": true + }, + "hardware": { + "accelerometer": true, + "audioJack": "3.5mm", + "cpu": "nVidia Tegra T20", + "fmRadio": false, + "physicalKeyboard": false, + "usb": "USB 2.0" + }, + "id": "dell-streak-7", + "images": [ + "img/phones/dell-streak-7.0.jpg", + "img/phones/dell-streak-7.1.jpg", + "img/phones/dell-streak-7.2.jpg", + "img/phones/dell-streak-7.3.jpg", + "img/phones/dell-streak-7.4.jpg" + ], + "name": "Dell Streak 7", + "sizeAndWeight": { + "dimensions": [ + "199.9 mm (w)", + "119.8 mm (h)", + "12.4 mm (d)" + ], + "weight": "450.0 grams" + }, + "storage": { + "flash": "16000MB", + "ram": "512MB" + } +} diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/phones/motorola-atrix-4g.json b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phones/motorola-atrix-4g.json new file mode 100644 index 0000000000..ccca00e3b2 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phones/motorola-atrix-4g.json @@ -0,0 +1,62 @@ +{ + "additionalFeatures": "", + "android": { + "os": "Android 2.2", + "ui": "MOTOBLUR" + }, + "availability": [ + "AT&T" + ], + "battery": { + "standbyTime": "400 hours", + "talkTime": "5 hours", + "type": "Lithium Ion (Li-Ion) (1930 mAH)" + }, + "camera": { + "features": [ + "" + ], + "primary": "" + }, + "connectivity": { + "bluetooth": "Bluetooth 2.1", + "cell": "WCDMA 850/1900/2100, GSM 850/900/1800/1900, HSDPA 14Mbps (Category 10) Edge Class 12, GPRS Class 12, eCompass, AGPS", + "gps": true, + "infrared": false, + "wifi": "802.11 a/b/g/n" + }, + "description": "MOTOROLA ATRIX 4G gives you dual-core processing power and the revolutionary webtop application. With webtop and a compatible Motorola docking station, sold separately, you can surf the Internet with a full Firefox browser, create and edit docs, or access multimedia on a large screen almost anywhere.", + "display": { + "screenResolution": "QHD (960 x 540)", + "screenSize": "4.0 inches", + "touchScreen": true + }, + "hardware": { + "accelerometer": true, + "audioJack": "3.5mm", + "cpu": "1 GHz Dual Core", + "fmRadio": false, + "physicalKeyboard": false, + "usb": "USB 2.0" + }, + "id": "motorola-atrix-4g", + "images": [ + "img/phones/motorola-atrix-4g.0.jpg", + "img/phones/motorola-atrix-4g.1.jpg", + "img/phones/motorola-atrix-4g.2.jpg", + "img/phones/motorola-atrix-4g.3.jpg" + ], + "name": "MOTOROLA ATRIX\u2122 4G", + "sizeAndWeight": { + "dimensions": [ + "63.5 mm (w)", + "117.75 mm (h)", + "10.95 mm (d)" + ], + "weight": "135.0 grams" + }, + "storage": { + "flash": "", + "ram": "" + } +} diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/phones/motorola-xoom-with-wi-fi.json b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phones/motorola-xoom-with-wi-fi.json new file mode 100644 index 0000000000..4ba9c8d5b5 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phones/motorola-xoom-with-wi-fi.json @@ -0,0 +1,65 @@ +{ + "additionalFeatures": "Sensors: proximity, ambient light, barometer, gyroscope", + "android": { + "os": "Android 3.0", + "ui": "Honeycomb" + }, + "availability": [ + "" + ], + "battery": { + "standbyTime": "336 hours", + "talkTime": "24 hours", + "type": "Other ( mAH)" + }, + "camera": { + "features": [ + "Flash", + "Video" + ], + "primary": "5.0 megapixels" + }, + "connectivity": { + "bluetooth": "Bluetooth 2.1", + "cell": "", + "gps": true, + "infrared": false, + "wifi": "802.11 b/g/n" + }, + "description": "Motorola XOOM with Wi-Fi has a super-powerful dual-core processor and Android\u2122 3.0 (Honeycomb) \u2014 the Android platform designed specifically for tablets. With its 10.1-inch HD widescreen display, you\u2019ll enjoy HD video in a thin, light, powerful and upgradeable tablet.", + "display": { + "screenResolution": "WXGA (1200 x 800)", + "screenSize": "10.1 inches", + "touchScreen": true + }, + "hardware": { + "accelerometer": true, + "audioJack": "3.5mm", + "cpu": "1 GHz Dual Core Tegra 2", + "fmRadio": false, + "physicalKeyboard": false, + "usb": "USB 2.0" + }, + "id": "motorola-xoom-with-wi-fi", + "images": [ + "img/phones/motorola-xoom-with-wi-fi.0.jpg", + "img/phones/motorola-xoom-with-wi-fi.1.jpg", + "img/phones/motorola-xoom-with-wi-fi.2.jpg", + "img/phones/motorola-xoom-with-wi-fi.3.jpg", + "img/phones/motorola-xoom-with-wi-fi.4.jpg", + "img/phones/motorola-xoom-with-wi-fi.5.jpg" + ], + "name": "Motorola XOOM\u2122 with Wi-Fi", + "sizeAndWeight": { + "dimensions": [ + "249.1 mm (w)", + "167.8 mm (h)", + "12.9 mm (d)" + ], + "weight": "708.0 grams" + }, + "storage": { + "flash": "32000MB", + "ram": "1000MB" + } +} diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/phones/motorola-xoom.json b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phones/motorola-xoom.json new file mode 100644 index 0000000000..f0f0c8711d --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phones/motorola-xoom.json @@ -0,0 +1,62 @@ +{ + "additionalFeatures": "Front-facing camera. Sensors: proximity, ambient light, barometer, gyroscope.", + "android": { + "os": "Android 3.0", + "ui": "Android" + }, + "availability": [ + "Verizon" + ], + "battery": { + "standbyTime": "336 hours", + "talkTime": "24 hours", + "type": "Other (3250 mAH)" + }, + "camera": { + "features": [ + "Flash", + "Video" + ], + "primary": "5.0 megapixels" + }, + "connectivity": { + "bluetooth": "Bluetooth 2.1", + "cell": "CDMA 800 /1900 LTE 700, Rx diversity in all bands", + "gps": true, + "infrared": false, + "wifi": "802.11 a/b/g/n" + }, + "description": "MOTOROLA XOOM has a super-powerful dual-core processor and Android\u2122 3.0 (Honeycomb) \u2014 the Android platform designed specifically for tablets. With its 10.1-inch HD widescreen display, you\u2019ll enjoy HD video in a thin, light, powerful and upgradeable tablet.", + "display": { + "screenResolution": "WXGA (1200 x 800)", + "screenSize": "10.1 inches", + "touchScreen": true + }, + "hardware": { + "accelerometer": true, + "audioJack": "3.5mm", + "cpu": "1 GHz Dual Core Tegra 2", + "fmRadio": false, + "physicalKeyboard": false, + "usb": "USB 2.0" + }, + "id": "motorola-xoom", + "images": [ + "img/phones/motorola-xoom.0.jpg", + "img/phones/motorola-xoom.1.jpg", + "img/phones/motorola-xoom.2.jpg" + ], + "name": "MOTOROLA XOOM\u2122", + "sizeAndWeight": { + "dimensions": [ + "249.0 mm (w)", + "168.0 mm (h)", + "12.7 mm (d)" + ], + "weight": "726.0 grams" + }, + "storage": { + "flash": "32000MB", + "ram": "1000MB" + } +} diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/phones/nexus-s.json b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phones/nexus-s.json new file mode 100644 index 0000000000..5e712e2ff8 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phones/nexus-s.json @@ -0,0 +1,69 @@ +{ + "additionalFeatures": "Contour Display, Near Field Communications (NFC), Three-axis gyroscope, Anti-fingerprint display coating, Internet Calling support (VoIP/SIP)", + "android": { + "os": "Android 2.3", + "ui": "Android" + }, + "availability": [ + "M1,", + "O2,", + "Orange,", + "Singtel,", + "StarHub,", + "T-Mobile,", + "Vodafone" + ], + "battery": { + "standbyTime": "428 hours", + "talkTime": "6 hours", + "type": "Lithium Ion (Li-Ion) (1500 mAH)" + }, + "camera": { + "features": [ + "Flash", + "Video" + ], + "primary": "5.0 megapixels" + }, + "connectivity": { + "bluetooth": "Bluetooth 2.1", + "cell": "Quad-band GSM: 850, 900, 1800, 1900\r\nTri-band HSPA: 900, 2100, 1700\r\nHSPA type: HSDPA (7.2Mbps) HSUPA (5.76Mbps)", + "gps": true, + "infrared": false, + "wifi": "802.11 b/g/n" + }, + "description": "Nexus S is the next generation of Nexus devices, co-developed by Google and Samsung. The latest Android platform (Gingerbread), paired with a 1 GHz Hummingbird processor and 16GB of memory, makes Nexus S one of the fastest phones on the market. It comes pre-installed with the best of Google apps and enabled with new and popular features like true multi-tasking, Wi-Fi hotspot, Internet Calling, NFC support, and full web browsing. With this device, users will also be the first to receive software upgrades and new Google mobile apps as soon as they become available. For more details, visit http://www.google.com/nexus.", + "display": { + "screenResolution": "WVGA (800 x 480)", + "screenSize": "4.0 inches", + "touchScreen": true + }, + "hardware": { + "accelerometer": true, + "audioJack": "3.5mm", + "cpu": "1GHz Cortex A8 (Hummingbird) processor", + "fmRadio": false, + "physicalKeyboard": false, + "usb": "USB 2.0" + }, + "id": "nexus-s", + "images": [ + "img/phones/nexus-s.0.jpg", + "img/phones/nexus-s.1.jpg", + "img/phones/nexus-s.2.jpg", + "img/phones/nexus-s.3.jpg" + ], + "name": "Nexus S", + "sizeAndWeight": { + "dimensions": [ + "63.0 mm (w)", + "123.9 mm (h)", + "10.88 mm (d)" + ], + "weight": "129.0 grams" + }, + "storage": { + "flash": "16384MB", + "ram": "512MB" + } +} diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/app/phones/phones.json b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phones/phones.json new file mode 100644 index 0000000000..339b94fbb5 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/app/phones/phones.json @@ -0,0 +1,155 @@ +[ + { + "age": 0, + "id": "motorola-xoom-with-wi-fi", + "imageUrl": "img/phones/motorola-xoom-with-wi-fi.0.jpg", + "name": "Motorola XOOM\u2122 with Wi-Fi", + "snippet": "The Next, Next Generation\r\n\r\nExperience the future with Motorola XOOM with Wi-Fi, the world's first tablet powered by Android 3.0 (Honeycomb)." + }, + { + "age": 1, + "id": "motorola-xoom", + "imageUrl": "img/phones/motorola-xoom.0.jpg", + "name": "MOTOROLA XOOM\u2122", + "snippet": "The Next, Next Generation\n\nExperience the future with MOTOROLA XOOM, the world's first tablet powered by Android 3.0 (Honeycomb)." + }, + { + "age": 2, + "carrier": "AT&T", + "id": "motorola-atrix-4g", + "imageUrl": "img/phones/motorola-atrix-4g.0.jpg", + "name": "MOTOROLA ATRIX\u2122 4G", + "snippet": "MOTOROLA ATRIX 4G the world's most powerful smartphone." + }, + { + "age": 3, + "id": "dell-streak-7", + "imageUrl": "img/phones/dell-streak-7.0.jpg", + "name": "Dell Streak 7", + "snippet": "Introducing Dell\u2122 Streak 7. Share photos, videos and movies together. It\u2019s small enough to carry around, big enough to gather around." + }, + { + "age": 4, + "carrier": "Cellular South", + "id": "samsung-gem", + "imageUrl": "img/phones/samsung-gem.0.jpg", + "name": "Samsung Gem\u2122", + "snippet": "The Samsung Gem\u2122 brings you everything that you would expect and more from a touch display smart phone \u2013 more apps, more features and a more affordable price." + }, + { + "age": 5, + "carrier": "Dell", + "id": "dell-venue", + "imageUrl": "img/phones/dell-venue.0.jpg", + "name": "Dell Venue", + "snippet": "The Dell Venue; Your Personal Express Lane to Everything" + }, + { + "age": 6, + "carrier": "Best Buy", + "id": "nexus-s", + "imageUrl": "img/phones/nexus-s.0.jpg", + "name": "Nexus S", + "snippet": "Fast just got faster with Nexus S. A pure Google experience, Nexus S is the first phone to run Gingerbread (Android 2.3), the fastest version of Android yet." + }, + { + "age": 7, + "carrier": "Cellular South", + "id": "lg-axis", + "imageUrl": "img/phones/lg-axis.0.jpg", + "name": "LG Axis", + "snippet": "Android Powered, Google Maps Navigation, 5 Customizable Home Screens" + }, + { + "age": 8, + "id": "samsung-galaxy-tab", + "imageUrl": "img/phones/samsung-galaxy-tab.0.jpg", + "name": "Samsung Galaxy Tab\u2122", + "snippet": "Feel Free to Tab\u2122. The Samsung Galaxy Tab\u2122 brings you an ultra-mobile entertainment experience through its 7\u201d display, high-power processor and Adobe\u00ae Flash\u00ae Player compatibility." + }, + { + "age": 9, + "carrier": "Cellular South", + "id": "samsung-showcase-a-galaxy-s-phone", + "imageUrl": "img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg", + "name": "Samsung Showcase\u2122 a Galaxy S\u2122 phone", + "snippet": "The Samsung Showcase\u2122 delivers a cinema quality experience like you\u2019ve never seen before. Its innovative 4\u201d touch display technology provides rich picture brilliance, even outdoors" + }, + { + "age": 10, + "carrier": "Verizon", + "id": "droid-2-global-by-motorola", + "imageUrl": "img/phones/droid-2-global-by-motorola.0.jpg", + "name": "DROID\u2122 2 Global by Motorola", + "snippet": "The first smartphone with a 1.2 GHz processor and global capabilities." + }, + { + "age": 11, + "carrier": "Verizon", + "id": "droid-pro-by-motorola", + "imageUrl": "img/phones/droid-pro-by-motorola.0.jpg", + "name": "DROID\u2122 Pro by Motorola", + "snippet": "The next generation of DOES." + }, + { + "age": 12, + "carrier": "AT&T", + "id": "motorola-bravo-with-motoblur", + "imageUrl": "img/phones/motorola-bravo-with-motoblur.0.jpg", + "name": "MOTOROLA BRAVO\u2122 with MOTOBLUR\u2122", + "snippet": "An experience to cheer about." + }, + { + "age": 13, + "carrier": "T-Mobile", + "id": "motorola-defy-with-motoblur", + "imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg", + "name": "Motorola DEFY\u2122 with MOTOBLUR\u2122", + "snippet": "Are you ready for everything life throws your way?" + }, + { + "age": 14, + "carrier": "T-Mobile", + "id": "t-mobile-mytouch-4g", + "imageUrl": "img/phones/t-mobile-mytouch-4g.0.jpg", + "name": "T-Mobile myTouch 4G", + "snippet": "The T-Mobile myTouch 4G is a premium smartphone designed to deliver blazing fast 4G speeds so that you can video chat from practically anywhere, with or without Wi-Fi." + }, + { + "age": 15, + "carrier": "US Cellular", + "id": "samsung-mesmerize-a-galaxy-s-phone", + "imageUrl": "img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg", + "name": "Samsung Mesmerize\u2122 a Galaxy S\u2122 phone", + "snippet": "The Samsung Mesmerize\u2122 delivers a cinema quality experience like you\u2019ve never seen before. Its innovative 4\u201d touch display technology provides rich picture brilliance,even outdoors" + }, + { + "age": 16, + "carrier": "Sprint", + "id": "sanyo-zio", + "imageUrl": "img/phones/sanyo-zio.0.jpg", + "name": "SANYO ZIO", + "snippet": "The Sanyo Zio by Kyocera is an Android smartphone with a combination of ultra-sleek styling, strong performance and unprecedented value." + }, + { + "age": 17, + "id": "samsung-transform", + "imageUrl": "img/phones/samsung-transform.0.jpg", + "name": "Samsung Transform\u2122", + "snippet": "The Samsung Transform\u2122 brings you a fun way to customize your Android powered touch screen phone to just the way you like it through your favorite themed \u201cSprint ID Service Pack\u201d." + }, + { + "age": 18, + "id": "t-mobile-g2", + "imageUrl": "img/phones/t-mobile-g2.0.jpg", + "name": "T-Mobile G2", + "snippet": "The T-Mobile G2 with Google is the first smartphone built for 4G speeds on T-Mobile's new network. Get the information you need, faster than you ever thought possible." + }, + { + "age": 19, + "id": "motorola-charm-with-motoblur", + "imageUrl": "img/phones/motorola-charm-with-motoblur.0.jpg", + "name": "Motorola CHARM\u2122 with MOTOBLUR\u2122", + "snippet": "Motorola CHARM fits easily in your pocket or palm. Includes MOTOBLUR service." + } +] diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/example-config.json b/aio/content/examples/upgrade-phonecat-4-final/ts/example-config.json new file mode 100644 index 0000000000..401c14f835 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/example-config.json @@ -0,0 +1,5 @@ +{ + "build": "build:upgrade", + "run": "serve:upgrade", + "unittesting": true +} diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/index.html b/aio/content/examples/upgrade-phonecat-4-final/ts/index.html new file mode 100644 index 0000000000..fee59370e6 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/index.html @@ -0,0 +1,36 @@ + + + + + + + + + + Google Phone Gallery + + + + + + + + + + + + + + + + + + + diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/run-unit-tests.sh b/aio/content/examples/upgrade-phonecat-4-final/ts/run-unit-tests.sh new file mode 100644 index 0000000000..00a5abb7bc --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/run-unit-tests.sh @@ -0,0 +1,7 @@ +## The boilerplate Karma configuration won't work with Angular 1 tests since +## a specific loading configuration is needed for them. +## We keep one in karma.conf.ng1.js. This scripts runs the ng1 tests with +## that config. + +PATH=$(npm bin):$PATH +tsc && karma start karma.conf.ng1.js diff --git a/aio/content/examples/upgrade-phonecat-4-final/ts/tsconfig.json b/aio/content/examples/upgrade-phonecat-4-final/ts/tsconfig.json new file mode 100644 index 0000000000..f267800f14 --- /dev/null +++ b/aio/content/examples/upgrade-phonecat-4-final/ts/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ "es2015", "dom" ], + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true, + "typeRoots": [ + "../../node_modules/@types/" + ] + }, + "compileOnSave": true, + "exclude": [ + "node_modules/*", + "**/*-aot.ts" + ] +} diff --git a/aio/content/examples/user-input/e2e-spec.ts b/aio/content/examples/user-input/e2e-spec.ts new file mode 100644 index 0000000000..c0bb770848 --- /dev/null +++ b/aio/content/examples/user-input/e2e-spec.ts @@ -0,0 +1,99 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by, protractor } from 'protractor'; + +describe('User Input Tests', function () { + + beforeAll(function () { + browser.get(''); + }); + + it('should support the click event', function () { + let mainEle = element(by.css('click-me')); + let buttonEle = element(by.css('click-me button')); + expect(mainEle.getText()).not.toContain('You are my hero!'); + buttonEle.click().then(function() { + expect(mainEle.getText()).toContain('You are my hero!'); + }); + }); + + it('should support the click event with an event payload', function () { + let mainEle = element(by.css('click-me2')); + let buttonEle = element(by.css('click-me2 button')); + expect(mainEle.getText()).not.toContain('Event target is '); + buttonEle.click().then(function() { + expect(mainEle.getText()).toContain('Event target is BUTTON'); + }); + }); + + it('should support the keyup event ', function () { + let mainEle = element(by.css('key-up1')); + let inputEle = mainEle.element(by.css('input')); + let outputTextEle = mainEle.element(by.css('p')); + expect(outputTextEle.getText()).toEqual(''); + inputEle.sendKeys('abc'); + expect(outputTextEle.getText()).toEqual('a | ab | abc |'); + }); + + it('should support user input from a local template let (loopback)', function () { + let mainEle = element(by.css('loop-back')); + let inputEle = mainEle.element(by.css('input')); + let outputTextEle = mainEle.element(by.css('p')); + expect(outputTextEle.getText()).toEqual(''); + inputEle.sendKeys('abc'); + expect(outputTextEle.getText()).toEqual('abc'); + }); + + it('should be able to combine click event with a local template var', function () { + let mainEle = element(by.css('key-up2')); + let inputEle = mainEle.element(by.css('input')); + let outputTextEle = mainEle.element(by.css('p')); + expect(outputTextEle.getText()).toEqual(''); + inputEle.sendKeys('abc'); + expect(outputTextEle.getText()).toEqual('a | ab | abc |'); + }); + + xit('should be able to filter key events', () => { + let mainEle = element(by.css('key-up3')); + let inputEle = mainEle.element(by.css('input')); + let outputTextEle = mainEle.element(by.css('p')); + expect(outputTextEle.getText()).toEqual(''); + inputEle.sendKeys('abc'); + expect(outputTextEle.getText()).toEqual('', 'should be blank - have not sent enter yet'); + // broken atm, see https://github.com/angular/angular/issues/9419 + inputEle.sendKeys(protractor.Key.ENTER); + expect(outputTextEle.getText()).toEqual('abc'); + }); + + it('should be able to filter blur events', function () { + let prevInputEle = element(by.css('key-up3 input')); + let mainEle = element(by.css('key-up4')); + let inputEle = mainEle.element(by.css('input')); + let outputTextEle = mainEle.element(by.css('p')); + expect(outputTextEle.getText()).toEqual(''); + inputEle.sendKeys('abc'); + expect(outputTextEle.getText()).toEqual('', 'should be blank - have not sent enter yet'); + // change the focus + prevInputEle.click().then(function() { + expect(outputTextEle.getText()).toEqual('abc'); + }); + }); + + it('should be able to compose little tour of heroes', function () { + let mainEle = element(by.css('little-tour')); + let inputEle = mainEle.element(by.css('input')); + let addButtonEle = mainEle.element(by.css('button')); + let heroEles = mainEle.all(by.css('li')); + let numHeroes: number; + expect(heroEles.count()).toBeGreaterThan(0); + heroEles.count().then(function(count: number) { + numHeroes = count; + inputEle.sendKeys('abc'); + return addButtonEle.click(); + }).then(function() { + expect(heroEles.count()).toEqual(numHeroes + 1, 'should be one more hero added'); + expect(heroEles.get(numHeroes).getText()).toContain('abc'); + }); + }); +}); + diff --git a/aio/content/examples/user-input/ts/example-config.json b/aio/content/examples/user-input/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aio/content/examples/user-input/ts/plnkr.json b/aio/content/examples/user-input/ts/plnkr.json new file mode 100644 index 0000000000..dd8f063d37 --- /dev/null +++ b/aio/content/examples/user-input/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "User Input", + "basePath": "src/", + "files": [ + "!**/*.d.ts", + "!**/*.js" + ], + "tags": ["input"] +} diff --git a/aio/content/examples/user-input/ts/plnkr.no-link.html b/aio/content/examples/user-input/ts/plnkr.no-link.html new file mode 100644 index 0000000000..d3d8c0aa26 --- /dev/null +++ b/aio/content/examples/user-input/ts/plnkr.no-link.html @@ -0,0 +1,433 @@ +
    \ No newline at end of file diff --git a/aio/content/examples/user-input/ts/src/app/app.component.html b/aio/content/examples/user-input/ts/src/app/app.component.html new file mode 100644 index 0000000000..036b04c510 --- /dev/null +++ b/aio/content/examples/user-input/ts/src/app/app.component.html @@ -0,0 +1,27 @@ +

    + +

    + +

    + +

    + +

    Give me some keys!

    +
    + +

    keyup loop-back component

    +
    +

    + +

    Give me some more keys!

    +
    + +

    Type away! Press [enter] when done.

    +
    + +

    Type away! Press [enter] or click elsewhere when done.

    +
    + +

    Little Tour of Heroes

    +

    Add a new hero

    +
    \ No newline at end of file diff --git a/aio/content/examples/user-input/ts/src/app/app.component.ts b/aio/content/examples/user-input/ts/src/app/app.component.ts new file mode 100644 index 0000000000..56906ef048 --- /dev/null +++ b/aio/content/examples/user-input/ts/src/app/app.component.ts @@ -0,0 +1,9 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + moduleId: module.id, + selector: 'my-app', + templateUrl: './app.component.html' +}) +export class AppComponent { } diff --git a/aio/content/examples/user-input/ts/src/app/app.module.ts b/aio/content/examples/user-input/ts/src/app/app.module.ts new file mode 100644 index 0000000000..41f13f9f11 --- /dev/null +++ b/aio/content/examples/user-input/ts/src/app/app.module.ts @@ -0,0 +1,37 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppComponent } from './app.component'; +import { ClickMeComponent } from './click-me.component'; +import { ClickMe2Component } from './click-me2.component'; +import { + KeyUpComponent_v1, + KeyUpComponent_v2, + KeyUpComponent_v3, + KeyUpComponent_v4 +} from './keyup.components'; +import { LittleTourComponent } from './little-tour.component'; +import { LoopbackComponent } from './loop-back.component'; + + +@NgModule({ + imports: [ + BrowserModule + ], + declarations: [ + AppComponent, + ClickMeComponent, + ClickMe2Component, + KeyUpComponent_v1, + KeyUpComponent_v2, + KeyUpComponent_v3, + KeyUpComponent_v4, + LittleTourComponent, + LoopbackComponent + ], + providers: [ + + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/aio/content/examples/user-input/ts/src/app/click-me.component.ts b/aio/content/examples/user-input/ts/src/app/click-me.component.ts new file mode 100644 index 0000000000..45a4ca7e70 --- /dev/null +++ b/aio/content/examples/user-input/ts/src/app/click-me.component.ts @@ -0,0 +1,24 @@ +/* FOR DOCS ... MUST MATCH ClickMeComponent template +// #docregion click-me-button + +// #enddocregion click-me-button +*/ + +// #docregion +import { Component } from '@angular/core'; + +// #docregion click-me-component +@Component({ + selector: 'click-me', + template: ` + + {{clickMessage}}` +}) +export class ClickMeComponent { + clickMessage = ''; + + onClickMe() { + this.clickMessage = 'You are my hero!'; + } +} +// #enddocregion click-me-component diff --git a/aio/content/examples/user-input/ts/src/app/click-me2.component.ts b/aio/content/examples/user-input/ts/src/app/click-me2.component.ts new file mode 100644 index 0000000000..1e35731a82 --- /dev/null +++ b/aio/content/examples/user-input/ts/src/app/click-me2.component.ts @@ -0,0 +1,18 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'click-me2', + template: ` + + {{clickMessage}}` +}) +export class ClickMe2Component { + clickMessage = ''; + clicks = 1; + + onClickMe2(event: any) { + let evtMsg = event ? ' Event target is ' + event.target.tagName : ''; + this.clickMessage = (`Click #${this.clicks++}. ${evtMsg}`); + } +} diff --git a/aio/content/examples/user-input/ts/src/app/keyup.components.ts b/aio/content/examples/user-input/ts/src/app/keyup.components.ts new file mode 100644 index 0000000000..94ed1ae423 --- /dev/null +++ b/aio/content/examples/user-input/ts/src/app/keyup.components.ts @@ -0,0 +1,88 @@ +/* tslint:disable:class-name component-class-suffix */ +// #docplaster +// #docregion +import { Component } from '@angular/core'; + +// #docregion key-up-component-1 +@Component({ + selector: 'key-up1', +// #docregion key-up-component-1-template + template: ` + +

    {{values}}

    + ` +// #enddocregion key-up-component-1-template +}) +// #docregion key-up-component-1-class, key-up-component-1-class-no-type +export class KeyUpComponent_v1 { + values = ''; + +// #enddocregion key-up-component-1-class, key-up-component-1-class-no-type + /* + // #docregion key-up-component-1-class-no-type + onKey(event: any) { // without type info + this.values += event.target.value + ' | '; + } + // #enddocregion key-up-component-1-class-no-type + */ + // #docregion key-up-component-1-class + + onKey(event: KeyboardEvent) { // with type info + this.values += (event.target).value + ' | '; + } +// #docregion key-up-component-1-class-no-type +} +// #enddocregion key-up-component-1,key-up-component-1-class, key-up-component-1-class-no-type + +////////////////////////////////////////// + +// #docregion key-up-component-2 +@Component({ + selector: 'key-up2', + template: ` + +

    {{values}}

    + ` +}) +export class KeyUpComponent_v2 { + values = ''; + onKey(value: string) { + this.values += value + ' | '; + } +} +// #enddocregion key-up-component-2 + +////////////////////////////////////////// + +// #docregion key-up-component-3 +@Component({ + selector: 'key-up3', + template: ` + +

    {{value}}

    + ` +}) +export class KeyUpComponent_v3 { + value = ''; + onEnter(value: string) { this.value = value; } +} +// #enddocregion key-up-component-3 + +////////////////////////////////////////// + +// #docregion key-up-component-4 +@Component({ + selector: 'key-up4', + template: ` + + +

    {{value}}

    + ` +}) +export class KeyUpComponent_v4 { + value = ''; + update(value: string) { this.value = value; } +} +// #enddocregion key-up-component-4 diff --git a/aio/content/examples/user-input/ts/src/app/little-tour.component.ts b/aio/content/examples/user-input/ts/src/app/little-tour.component.ts new file mode 100644 index 0000000000..5862f033d6 --- /dev/null +++ b/aio/content/examples/user-input/ts/src/app/little-tour.component.ts @@ -0,0 +1,25 @@ +// #docregion +import { Component } from '@angular/core'; + +// #docregion little-tour +@Component({ + selector: 'little-tour', + template: ` + + + + +
    • {{hero}}
    + ` +}) +export class LittleTourComponent { + heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado']; + addHero(newHero: string) { + if (newHero) { + this.heroes.push(newHero); + } + } +} +// #enddocregion little-tour diff --git a/aio/content/examples/user-input/ts/src/app/loop-back.component.ts b/aio/content/examples/user-input/ts/src/app/loop-back.component.ts new file mode 100644 index 0000000000..6c9dad1da8 --- /dev/null +++ b/aio/content/examples/user-input/ts/src/app/loop-back.component.ts @@ -0,0 +1,12 @@ +// #docregion +import { Component } from '@angular/core'; +// #docregion loop-back-component +@Component({ + selector: 'loop-back', + template: ` + +

    {{box.value}}

    + ` +}) +export class LoopbackComponent { } +// #enddocregion loop-back-component diff --git a/aio/content/examples/user-input/ts/src/index.html b/aio/content/examples/user-input/ts/src/index.html new file mode 100644 index 0000000000..9728814107 --- /dev/null +++ b/aio/content/examples/user-input/ts/src/index.html @@ -0,0 +1,27 @@ + + + + User Input + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/aio/content/examples/user-input/ts/src/main.ts b/aio/content/examples/user-input/ts/src/main.ts new file mode 100644 index 0000000000..311c44b76d --- /dev/null +++ b/aio/content/examples/user-input/ts/src/main.ts @@ -0,0 +1,5 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/aio/content/examples/user-input/ts/src/user-input-styles.css b/aio/content/examples/user-input/ts/src/user-input-styles.css new file mode 100644 index 0000000000..b2133e5103 --- /dev/null +++ b/aio/content/examples/user-input/ts/src/user-input-styles.css @@ -0,0 +1,9 @@ +fieldset {border-style:none} +img {height: 100px;} +.box {border: 1px solid black; padding:3px} +.child-div {margin-left: 1em; font-weight: normal} +.hidden {display: none} +.parent-div {margin-top: 1em; font-weight: bold} +.special {font-weight:bold;} +.toe {margin-left: 1em; font-style: italic;} +little-hero {color:blue; font-size: smaller; background-color: Turquoise } \ No newline at end of file diff --git a/aio/content/examples/webpack/ts-snippets/webpack.config.snippets.ts b/aio/content/examples/webpack/ts-snippets/webpack.config.snippets.ts index d1e8f7de42..2a3c596359 100644 --- a/aio/content/examples/webpack/ts-snippets/webpack.config.snippets.ts +++ b/aio/content/examples/webpack/ts-snippets/webpack.config.snippets.ts @@ -34,11 +34,11 @@ output: { // #docregion loaders rules: [ { - test: /\.ts$/ + test: /\.ts$/, loader: 'awesome-typescript-loader' }, { - test: /\.css$/ + test: /\.css$/, loaders: 'style-loader!css-loader' } ] diff --git a/aio/content/examples/webpack/ts/config/webpack.common.js b/aio/content/examples/webpack/ts/config/webpack.common.js index 5f83bfdde5..49dd91e334 100644 --- a/aio/content/examples/webpack/ts/config/webpack.common.js +++ b/aio/content/examples/webpack/ts/config/webpack.common.js @@ -24,7 +24,10 @@ module.exports = { rules: [ { test: /\.ts$/, - loaders: ['awesome-typescript-loader', 'angular2-template-loader'] + loaders: [{ + loader: 'awesome-typescript-loader', + options: { configFileName: helpers.root('src', 'tsconfig.json') } + } , 'angular2-template-loader'] }, { test: /\.html$/, diff --git a/aio/content/examples/webpack/ts/example-config.json b/aio/content/examples/webpack/ts/example-config.json index 9e587f892f..e405d0c01a 100644 --- a/aio/content/examples/webpack/ts/example-config.json +++ b/aio/content/examples/webpack/ts/example-config.json @@ -1,4 +1,4 @@ { "build": "build:webpack", - "run": "http-server:cli" + "run": "serve:cli" } diff --git a/aio/content/examples/webpack/ts/webpack.config.js b/aio/content/examples/webpack/ts/webpack.config.js new file mode 100644 index 0000000000..66141706fe --- /dev/null +++ b/aio/content/examples/webpack/ts/webpack.config.js @@ -0,0 +1,3 @@ +// #docregion +module.exports = require('./config/webpack.dev.js'); +// #enddocregion \ No newline at end of file