diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..1cf867f786 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +# http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + + +[*.md] +max_line_length = 0 +trim_trailing_whitespace = false + +[*.jade] +max_line_length = 0 +trim_trailing_whitespace = false + +# Indentation override +#[lib/**.js] +#[{package.json,.travis.yml}] +#[**/**.js] diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000000..d1c2c2b349 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,30 @@ +module.exports = { + "globals": { + "describe": true, + "beforeEach": true, + "it": true, + "expect": true + }, + "env": { + "node": true + }, + "extends": "eslint:recommended", + "rules": { + "indent": [ + "error", + 2 + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "single" + ], + "semi": [ + "error", + "always" + ] + } +}; \ No newline at end of file diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 0000000000..77c09fb883 --- /dev/null +++ b/.firebaserc @@ -0,0 +1,8 @@ +{ + "projects": { + "live": "angular-io", + "ngdocsdev": "ngdocsdev", + "kw-dev": "kw-angular-io", + "dev": "angular-io-dev" + } +} \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..fa749aa3fd --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,7 @@ +**Please do not add issues or pull requests to this repo.** +We are no longer making changes to documentation in this repository. +We will no longer process new issues or PRs and we will close them automatically. + +**Please post new [issues](https://github.com/angular/angular/issues) and [pull requests](https://github.com/angular/angular/pulls) to the content folder in [https://github.com/angular/angular/tree/master/aio/content](https://github.com/angular/angular/tree/master/aio/content)**. + +Be sure to prefix your issue/PR title with "**docs(aio):**" diff --git a/.github/PULL_REQUEST_TEMPLATE.MD b/.github/PULL_REQUEST_TEMPLATE.MD new file mode 100644 index 0000000000..c4e15a1d58 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.MD @@ -0,0 +1,7 @@ +**Please do not add issues or pull requests to this repo.** +We are no longer making changes to documentation in this repository. +We will no longer process new issues or PRs and we will close them automatically. + +**Please post new [issues](https://github.com/angular/angular/issues) and [pull requests](https://github.com/angular/angular/pulls) to the content folder in [https://github.com/angular/angular/tree/master/aio/content](https://github.com/angular/angular/tree/master/aio/content)**. + +Be sure to prefix your issue/PR title with "**docs(aio):**" diff --git a/.gitignore b/.gitignore index 4f68bf98cf..c327f2acf5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,37 @@ node_modules +_temp bower_components jspm_packages -typings -packages +**/packages build pubspec.lock .pub .packages *.map .DS_Store +**/*.iml .idea +.vscode **/js/latest/api **/ts/latest/api -**/docs/_fragments +**/dart/latest/api +**/docs/**/_fragments +_.* **/resources/zips public/docs/xref-*.* _zip-output - - - +www* +npm-debug*.log* +**/debug.log +*.plnkr.html +plnkr.html +*.eplnkr.html +eplnkr.html +*plnkr.no-link.html +public/docs/*/latest/guide/cheatsheet.json +protractor-results.txt +link-checker-results.txt +/dist +/public/docs/dart +/public/docs/ts/_cache +/public/docs/_examples/*/dart diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000000..1e8b314962 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +6 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..edba3b37d8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,47 @@ +dist: trusty +sudo: required +language: node_js +node_js: + - "6" +os: + - linux +env: + global: + - DBUS_SESSION_BUS_ADDRESS=/dev/null + - DISPLAY=:99.0 + - CHROME_BIN=chromium-browser + - LATEST_RELEASE=4.0.0 + # Temporarily disabled until there is a new release branch for 4.0.0 + # - LATEST_RELEASE_BRANCH=2.4.x + - TASK_FLAGS="--dgeni-log=warn" + matrix: + # current angular release jobs + - TASK=lint + - TASK="run-e2e-tests --fast" SCRIPT=examples-install.sh + - TASK=build-compile SCRIPT=deploy-install.sh WAIT="travis_wait 50" POST_SCRIPT="check-docs.sh -v" + # current angular release branch jobs + # - TASK="run-e2e-tests --fast" SCRIPT=examples-install-preview.sh PREVIEW_BRANCH=$LATEST_RELEASE_BRANCH + # - TASK=build-compile SCRIPT=deploy-install-preview.sh PREVIEW_BRANCH=$LATEST_RELEASE_BRANCH WAIT="travis_wait 50" POST_SCRIPT="check-docs.sh -v" + # angular master jobs + - TASK="run-e2e-tests --fast" SCRIPT=examples-install-preview.sh PREVIEW_BRANCH=master + - TASK=build-compile SCRIPT=deploy-install-preview.sh PREVIEW_BRANCH=master WAIT="travis_wait 50" POST_SCRIPT="check-docs.sh -v" +matrix: + fast_finish: true + allow_failures: + # allow current angular release branch and master to fail + # these should be moved to a daily task instead of being ran on every PR + # - env: TASK="run-e2e-tests --fast" SCRIPT=examples-install-preview.sh PREVIEW_BRANCH=$LATEST_RELEASE_BRANCH + # - env: TASK=build-compile SCRIPT=deploy-install-preview.sh PREVIEW_BRANCH=$LATEST_RELEASE_BRANCH WAIT="travis_wait 50" POST_SCRIPT="check-docs.sh -v" + - env: TASK="run-e2e-tests --fast" SCRIPT=examples-install-preview.sh PREVIEW_BRANCH=master + - env: TASK=build-compile SCRIPT=deploy-install-preview.sh PREVIEW_BRANCH=master WAIT="travis_wait 50" POST_SCRIPT="check-docs.sh -v" +before_install: + - source ./scripts/env-set.sh + - ./scripts/before-install.sh +install: + - npm install --no-optional + - if [[ -n "$SCRIPT" ]]; then echo "EXTRA INSTALL $SCRIPT"; ./scripts/$SCRIPT; fi +before_script: + - sh -e /etc/init.d/xvfb start +script: + - $WAIT gulp $TASK $TASK_FLAGS + - if [[ -n "$POST_SCRIPT" ]]; then ./scripts/$POST_SCRIPT; fi diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 7fe30fc88d..0000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,9 +0,0 @@ -// Place your settings in this file to overwrite default and user settings. -{ - // Controls the rendering size of tabs in characters. Accepted values: "auto", 2, 4, 6, etc. If set to "auto", the value will be guessed when a file is opened. - "editor.tabSize": 2, - // Controls if the editor will insert spaces for tabs. Accepted values: "auto", true, false. If set to "auto", the value will be guessed when a file is opened. - "editor.insertSpaces": true, - // When enabled, will trim trailing whitespace when you save a file. - "files.trimTrailingWhitespace": false -} \ No newline at end of file diff --git a/LICENSE b/LICENSE index 48d0699dc3..47bfda24ad 100644 --- a/LICENSE +++ b/LICENSE @@ -1,13 +1,21 @@ -Copyright 2010-2015 Google, Inc. http://angularjs.org +The MIT License -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at +Copyright (c) 2017 Google, Inc. - http://www.apache.org/licenses/LICENSE-2.0 +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index f0f8a0346b..5a68c5c1b7 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,157 @@ +# This angular.io documentation repository is closed and preserved just for archival purposes + +**Please post new [issues](https://github.com/angular/angular/issues) and [pull requests](https://github.com/angular/angular/pulls) to the content folder in [https://github.com/angular/angular/tree/master/aio/content](https://github.com/angular/angular/tree/master/aio/content)**. + +
+ # Angular.io -Angular.io is currently the preview site for Angular 2. This site also includes links to other helpful angular resources including Angular 1, Angular Material, and AngularFire. +---------- + +[![Build Status][travis-badge]][travis-badge-url] + +Angular.io is site for Angular **documentation** . + +This site also includes links to other helpful angular resources including +AngularJS, Angular Material, and AngularFire. + +## Issues + +Please file **Developer Guide, Cookbook, and code sample issues _only_** in this +[Angular.io](https://github.com/angular/angular.io/issues) github repo. + +**Angular API issues, cheatsheet corrections, feature requests, defect reports, and technical questions** concerning Angular itself +belong in the [**angular source code**](https://github.com/angular/angular/issues) github repo. +We can't handle those topics here and will ask you to re-post them on the angular repo. ## How you can help -- [File an issue on github](https://github.com/angular/angular.io/issues) -- [Contribute to Angular.io](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md) +Filing issues is helpful but **pull requests** that improve the docs are even better! + +Learn how to [contribute to Angular.io](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md). ## Development Setup -1. install [nvm](https://www.npmjs.com/package/nvm) -2. cd into root directory `angular.io/` -3. make sure you are using the latest node and npm by running `nvm use 4`. -3. install local packages by running `npm install` +This site relies heavily on node and npm. -## Development setup with watches - 1. cd into root directory `angular.io/` - 2. run `gulp serve-and-watch` - 3. open this url in the browser: [http://localhost:9000/](http://localhost:9000/) - 4. refresh your browser to see any changes. +1. Make sure you are using at least node v.5+ and latest npm; +if not install [nvm](https://github.com/creationix/nvm) to get node going on your machine. -## Development setup with watches and browser reload +1. Install global npm packages by running `./scripts/before-install.sh` + +1. Clone + + - this repo + - [angular/angular source code repo](https://github.com/angular/angular) + + to the same parent directory. The **cloned repo directories must be siblings**, with the latter named **angular**. + +1. cd into root directory `angular.io/` + +1. Install local npm packages by running `./scripts/install.sh` + +1. See [below](#code-sample-development) for code sample development preparation. + +## Content Development +All documentation content is written in Jade which has [its own syntax](http://jade-lang.com/reference/). +Be aware of the strict demands imposed by this significant-whitespace language. +We strongly recommend running one of the gulp `serve-and-sync` commands [described below](#serve-and-sync) +while editing content so you can see the effect of your changes *as you type*. + +The documentation relies on specific styles and mixins. +Learn about those in the [documentation styleguide](https://angular.io/docs/ts/latest/styleguide.html). + +The jade documentation files are language-specific directories under either `public/docs/`. +For example, all of the TypeScript docs are in `public/docs/ts/latest`, e.g. +- `public/docs/ts/latest/quickstart.jade` +- `public/docs/ts/latest/guide/architecture.jade` +- `public/docs/ts/latest/cookbook/component-communication.jade` +- `public/docs/ts/latest/tutorial/toh-pt5.jade` + +### Local server with watches and browser reload 1. cd into root directory `angular.io/` - 2. run `gulp serve-and-sync` - 3. browser will launch ( on localhost:3000 instead of localhost:9000) and stay refreshed automatically. + 1. run `gulp serve-and-sync` + 1. browser will launch on localhost:3000 and stay refreshed automatically. + + +If you are only going to work on a specific part of the docs, such as the dev guide, then you can use one of the more specific gulp tasks to only watch those parts of the file system: + +* `gulp serve-and-sync` : watch all the local Jade/Sass files, the API source and examples, and the dev guide files +* `gulp serve-and-sync-api` : watch only the API source and example files +* `gulp serve-and-sync-devguide` : watch only the dev guide files +* `gulp build-and-serve` : watch only the local Jade/Sass files + +## Code Sample Development + +All documentation is supported by sample code and plunkers. +Such code resides in the `public/docs/_examples` directory, under page-specific directories, further divided by language track. + +For example, the TypeScript QuickStart sample is in `public/docs/_examples/quickstart/ts`. + +All samples are in a consistent directory structure using the same styles and the same npm packages, including the latest release of Angular. +This consistency is possible in part thanks to gulp-driven tooling. +To run the samples locally and confirm that they work properly, +take the following extra steps to prepare the environment: + +1. cd to `public/docs/_examples` + +1. install the canonical node packages for all samples by running `npm install` + +1. cd back up to `angular.io` root: `cd ../../..` + +1. run `gulp add-example-boilerplate` (elevate to admin on Windows) +to copy canonical files to the sample directories and create symlinks there for node_modules. + +Now cd into any particular sample's language directory (e.g., `public/docs/_examples/quickstart/ts`) and try: +- `npm start` to simultaneously compile-with-watch and serve-in-browser-with-watch +- `npm run tsc` to compile only +- `npm run lite` to serve-and-watch in browser + +Look at the scripts in `package.json` for other options. +Also, open any `plunkr.no-link.html` to see the code execute in plunker +(you may have to run `gulp build-plunkers` first to create/update). + +You must check that your example is free of lint errors. +- `gulp lint` + +### Sample end-to-end tests + +All samples should be covered to some degree by end-to-end tests: +- `gulp run-e2e-tests` to run all TypeScript and JavaScript tests +- `gulp run-e2e-tests --lang=all` to run TypeScript and JavaScript tests +- `gulp run-e2e-tests --filter=quickstart` to filter the examples to run, by name +- `gulp run-e2e-tests --fast` to ignore npm install, webdriver update and boilerplate copy + +Any combination of options is possible. + +### Resetting the project +This project generates a lot of untracked files, if you wish to reset it to a mint state, you can run: + +- `git clean -xdf` + +Also, there is a script available for Linux, OSX and Windows Gitbash users that will setup the project using the steps shown in this section: + +- `./scripts/install.sh` + +### Run with current build instead of release packages +Can switch the `@angular` packages in `~/public/docs/_examples/node_modules` to the current build packages with +``` +gulp install-example-angular --build +``` +Restore to RELEASE packages with +``` +gulp install-example-angular +``` +>These commands will fail if something is locking any of the packages ... as an IDE often does. +> +>The symptom typically is an error trying to `rm -rf node_modules/@angular`. +> +>_Solution_: unlock the hold on the package(s). In VS Code, re-load the window (`cmd-P` then enter `>relow`). + ## Technology Used -- Angular 1.x: The production ready version of Angular +- Angular: Current Angular +- AngularJS: A v.1.x version of Angular - Angular Material: An implementation of Material Design in Angular.js +- Gulp: node-based tooling - Harp: The static web server with built-in preprocessing. - Sass: A professional grade CSS extension language - Normalize: A modern, HTML5-ready alternative to CSS resets @@ -35,4 +161,7 @@ Angular.io is currently the preview site for Angular 2. This site also includes ## License -Powered by Google ©2010-2015. Code licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). Documentation licensed under [CC BY 3.0](http://creativecommons.org/licenses/by/3.0/). +Powered by Google ©2010-2017. Code licensed under an [MIT-style License](https://github.com/angular.io/blob/master/LICENSE). Documentation licensed under [CC BY 4.0](http://creativecommons.org/licenses/by/4.0/). + +[travis-badge]: https://travis-ci.org/angular/angular.io.svg?branch=master +[travis-badge-url]: https://travis-ci.org/angular/angular.io diff --git a/firebase.json b/firebase.json index 166124991f..4f6e69080f 100644 --- a/firebase.json +++ b/firebase.json @@ -1,55 +1,121 @@ { - "firebase": "angular-io", - "public": "www", - "rewrites": [ - { - "source": "/docs/dart/latest/testing", - "destination": "/docs/dart/latest/index.html" - }, - { - "source": "/docs/dart/latest/tutorial", - "destination": "/docs/dart/latest/index.html" - }, - { - "source": "/docs/js/latest/testing", - "destination": "/docs/js/latest/index.html" - }, - { - "source": "/docs/js/latest/tutorial", - "destination": "/docs/js/latest/index.html" - }, - { - "source": "/docs/ts/latest/guide/setup.html", - "destination": "/docs/ts/latest/index.html" - }, - { - "source": "/cheatsheet", - "destination": "/docs/ts/latest/guide/cheatsheet.html" - }, - { - "source": "/docs/js/latest/guide/cheatsheet.html", - "destination": "/docs/js/latest/index.html" - }, - { - "source": "/docs/dart/latest/guide/cheatsheet.html", - "destination": "/docs/dart/latest/index.html" - }, - { - "source": "/AngularCheatSheet_Letter.pdf", - "destination": "/docs/ts/latest/guide/AngularCheatSheet_Letter.pdf" - }, - { - "source": "/AngularCheatSheet_Poster.pdf", - "destination": "/docs/ts/latest/guide/AngularCheatSheet_Poster.pdf" - }, - { - "source": "/cardboard", - "destination": "/cardboard/index.html" - } - ], - "ignore": [ - "firebase.json", - "**/.*", - "**/node_modules/**" - ] + "hosting": { + "public": "www", + "redirects": [ + { + "source": "/docs/dart/latest/quickstart.html", + "destination": "https://webdev.dartlang.org/angular/quickstart?utm_campaign=dart_migration&utm_medium=redirect&utm_source=angular.io", + "type": 301 + }, + { + "source": "/docs/dart/latest/tutorial/toh-pt5.html", + "destination": "https://webdev.dartlang.org/angular/tutorial/toh-pt5?utm_campaign=dart_migration&utm_medium=redirect&utm_source=angular.io", + "type": 301 + }, + { + "source": "/docs/dart/latest/tutorial", + "destination": "https://webdev.dartlang.org/angular/tutorial?utm_campaign=dart_migration&utm_medium=redirect&utm_source=angular.io", + "type": 301 + }, + { + "source": "/docs/dart/latest/tutorial/**", + "destination": "https://webdev.dartlang.org/angular/tutorial?utm_campaign=dart_migration&utm_medium=redirect&utm_source=angular.io", + "type": 301 + }, + { + "source": "/docs/dart/latest/api", + "destination": "https://webdev.dartlang.org/angular/api?utm_campaign=dart_migration&utm_medium=redirect&utm_source=angular.io", + "type": 301 + }, + { + "source": "/docs/dart/latest/api/**", + "destination": "https://webdev.dartlang.org/angular/api?utm_campaign=dart_migration&utm_medium=redirect&utm_source=angular.io", + "type": 301 + }, + { + "source": "/docs/dart/latest/guide/**", + "destination": "https://webdev.dartlang.org/angular/guide?utm_campaign=dart_migration&utm_medium=redirect&utm_source=angular.io", + "type": 301 + }, + { + "source": "/**/dart", + "destination": "https://webdev.dartlang.org/angular?utm_campaign=dart_migration&utm_medium=redirect&utm_source=angular.io", + "type": 301 + }, + { + "source": "/**/dart/**", + "destination": "https://webdev.dartlang.org/angular?utm_campaign=dart_migration&utm_medium=redirect&utm_source=angular.io", + "type": 301 + }, + { + "source": "/docs", + "destination": "https://angular.io/docs/ts/latest/", + "type": 301 + } + ], + "rewrites": [ + { + "source": "/docs/js/latest/testing", + "destination": "/docs/js/latest/guide/testing.html" + }, + { + "source": "/docs/js/latest/tutorial", + "destination": "/docs/js/latest/index.html" + }, + { + "source": "/docs/ts/latest/cookbook/a1-a2-quick-reference.html", + "destination": "/docs/ts/latest/cookbook/ajs-quick-reference.html" + }, + { + "source": "/docs/ts/latest/guide/setup.html", + "destination": "/docs/ts/latest/index.html" + }, + { + "source": "/docs/ts/latest/testing", + "destination": "/docs/ts/latest/guide/testing.html" + }, + { + "source": "/cheatsheet", + "destination": "/docs/ts/latest/guide/cheatsheet.html" + }, + { + "source": "/cheatsheet.json", + "destination": "/docs/ts/latest/guide/cheatsheet.json" + }, + { + "source": "/AngularCheatSheet_Letter.pdf", + "destination": "/docs/ts/latest/guide/AngularCheatSheet_Letter.pdf" + }, + { + "source": "/AngularCheatSheet_Poster.pdf", + "destination": "/docs/ts/latest/guide/AngularCheatSheet_Poster.pdf" + }, + { + "source": "/cardboard", + "destination": "/cardboard/index.html" + }, + { + "source": "/license", + "destination": "/license.txt" + }, + { + "source": "/events", + "destination": "/events.html" + }, + { + "source": "/survey", + "destination": "/survey.html" + }, + { + "source": "/styleguide", + "destination": "/docs/ts/latest/guide/style-guide.html" + } + ], + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**", + "docs/dart/**" + ] + } } diff --git a/gulpfile.js b/gulpfile.js index 8a45f3d144..b6a3900b24 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,55 +1,432 @@ var gulp = require('gulp'); -var watch = require('gulp-watch'); var gutil = require('gulp-util'); var taskListing = require('gulp-task-listing'); var path = require('canonical-path'); var del = require('del'); var _ = require('lodash'); var argv = require('yargs').argv; +var env = require('gulp-env'); var Q = require("q"); -// delPromise is a 'promise' version of del -var delPromise = Q.denodeify(del); var Minimatch = require("minimatch").Minimatch; var Dgeni = require('dgeni'); +var Package = require('dgeni').Package; var fsExtra = require('fs-extra'); var fs = fsExtra; var exec = require('child_process').exec; var execPromise = Q.denodeify(exec); +var execSync = require('child_process').execSync; +// cross platform version of spawn that also works on windows. +var xSpawn = require('cross-spawn'); var prompt = require('prompt'); +var globby = require("globby"); +// Ugh... replacement needed to kill processes on any OS +// - because childProcess.kill does not work properly on windows +var treeKill = require("tree-kill"); +var blc = require("broken-link-checker"); +var less = require('gulp-less'); +var tslint = require('gulp-tslint'); // TODO: // 1. Think about using runSequence // 2. Think about using spawn instead of exec in case of long error messages. var TOOLS_PATH = './tools'; +var ANGULAR_IO_PROJECT_PATH = path.resolve('.'); var ANGULAR_PROJECT_PATH = '../angular'; var PUBLIC_PATH = './public'; +var TEMP_PATH = './_temp'; var DOCS_PATH = path.join(PUBLIC_PATH, 'docs'); + +var EXAMPLES_PATH = path.join(DOCS_PATH, '_examples'); +var BOILERPLATE_PATH = path.join(EXAMPLES_PATH, '_boilerplate'); +var EXAMPLES_TESTING_PATH = path.join(EXAMPLES_PATH, 'testing/ts'); +var NOT_API_DOCS_GLOB = path.join(PUBLIC_PATH, './{docs/*/latest/!(api),!(docs)}/**/*.*'); var RESOURCES_PATH = path.join(PUBLIC_PATH, 'resources'); +var LIVE_EXAMPLES_PATH = path.join(RESOURCES_PATH, 'live-examples'); +var STYLES_SOURCE_PATH = path.join(TOOLS_PATH, 'styles-builder/less'); var docShredder = require(path.resolve(TOOLS_PATH, 'doc-shredder/doc-shredder')); -var exampleZipper = require(path.resolve(TOOLS_PATH, '_example-zipper/exampleZipper')); +var ExampleZipper = require(path.resolve(TOOLS_PATH, 'example-zipper/exampleZipper')); +var regularPlunker = require(path.resolve(TOOLS_PATH, 'plunker-builder/regularPlunker')); +var embeddedPlunker = require(path.resolve(TOOLS_PATH, 'plunker-builder/embeddedPlunker')); +var fsUtils = require(path.resolve(TOOLS_PATH, 'fs-utils/fsUtils')); + +const WWW = argv.page ? 'www-pages' : 'www' + +const isSilent = !!argv.silent; +if (isSilent) gutil.log = gutil.noop; +const _dgeniLogLevel = argv.dgeniLog || (isSilent ? 'error' : 'info'); var _devguideShredOptions = { examplesDir: path.join(DOCS_PATH, '_examples'), fragmentsDir: path.join(DOCS_PATH, '_fragments'), - zipDir: path.join(RESOURCES_PATH, 'zips') + zipDir: path.join(RESOURCES_PATH, 'zips'), + logLevel: _dgeniLogLevel +}; + +var _devguideShredJadeOptions = { + jadeDir: DOCS_PATH, + logLevel: _dgeniLogLevel }; var _apiShredOptions = { - examplesDir: path.join(ANGULAR_PROJECT_PATH, 'modules/angular2/examples'), + lang: 'ts', + examplesDir: path.join(ANGULAR_PROJECT_PATH, 'packages/examples'), fragmentsDir: path.join(DOCS_PATH, '_fragments/_api'), - zipDir: path.join(RESOURCES_PATH, 'zips/api') + zipDir: path.join(RESOURCES_PATH, 'zips/api'), + logLevel: _dgeniLogLevel }; - - -var _excludePatterns = ['**/node_modules/**', '**/typings/**', '**/packages/**']; +var _excludePatterns = ['**/node_modules/**']; var _excludeMatchers = _excludePatterns.map(function(excludePattern){ return new Minimatch(excludePattern) }); +var _exampleBoilerplateFiles = [ + 'src/styles.css', + 'src/systemjs.config.js', + 'src/systemjs-angular-loader.js', + 'src/tsconfig.json', + 'bs-config.json', + 'bs-config.e2e.json', + 'package.json', + 'tslint.json' +]; + +var _exampleUnitTestingBoilerplateFiles = [ + 'src/browser-test-shim.js', + 'karma-test-shim.js', + 'karma.conf.js' +]; + +var _exampleConfigFilename = 'example-config.json'; + +// Gulp flags: +// +// --lang=[all | ts | js | dart | 'ts|js' | 'ts|js|dart' | ...] +// +// This affects which language API docs and E2E tests are run. Can be 'all', +// or a regex pattern to match any one of 'ts', 'js', or 'dart'. +// Default: 'ts|js' except for the "full site build" tasks (see below), +// for which it is 'all'. + +// langs and skipLangs partition ['ts', 'js', 'dart']. +var lang, langs, skipLangs, buildDartApiDocs = false; +function configLangs(langOption) { + const fullSiteBuildTasks = ['build-compile', 'check-deploy', 'harp-compile']; + const buildAllDocs = argv['_'] && + fullSiteBuildTasks.some((task) => argv['_'].indexOf(task) >= 0); + const langDefault = /*buildAllDocs ? 'all' :*/ 'ts|js'; + if (langOption === '') { + lang = ''; + langs = []; + } else { + lang = (langOption || langDefault).toLowerCase(); + if (lang === 'all') lang = 'ts|js|dart'; + langs = lang.match(/\w+/g); // the languages in `lang` as an array + } + gutil.log(`Building docs for: [${langs}]`); + if (langs.indexOf('dart') >= 0) { + buildDartApiDocs = true; + // For Dart, be proactive about checking for the repo + checkAngularProjectPath(ngPathFor('dart')); + } else { + argv.pub = false; + } + skipLangs = []; + ['ts', 'js', 'dart'].forEach(lang => { + if (langs.indexOf(lang) < 0) skipLangs.push(lang); + }); + gutil.log(`Skipped languages: [${skipLangs}]`); +} +configLangs(argv.lang); + +function isDartPath(path) { + // Testing via indexOf() for now. If we need to match only paths with folders + // named 'dart' vs 'dart*' then try: path.match('/dart(/|$)') != null; + return path.indexOf('/dart') > -1; +} + +function excludeDartPaths(paths) { + return paths.filter(function (p) { return !isDartPath(p); }); +} + +/** + * Run Protractor End-to-End Specs for Doc Samples + * Alias for 'run-e2e-tests' + */ +gulp.task('e2e', runE2e); + +gulp.task('run-e2e-tests', runE2e); + +/** + * Run Protractor End-to-End Tests for Doc Samples + * + * Flags + * --filter to filter/select _example app subdir names + * e.g. gulp e2e --filter=foo // all example apps with 'foo' in their folder names. + * + * --fast by-passes the npm install and webdriver update + * Use it for repeated test runs (but not the FIRST run) + * e.g. gulp e2e --fast + * + * --lang to filter by code language (see above for details) + * e.g. gulp e2e --lang=ts // only TypeScript apps + */ +function runE2e() { + var promise; + if (argv.fast) { + // fast; skip all setup + promise = Promise.resolve(true); + } else { + // Not 'fast'; do full setup + gutil.log('runE2e: install _examples stuff'); + var spawnInfo = spawnExt('npm', ['install'], { cwd: EXAMPLES_PATH}); + promise = spawnInfo.promise + .then(copyExampleBoilerplate) + .then(function() { + gutil.log('runE2e: update webdriver'); + spawnInfo = spawnExt('npm', ['run', 'webdriver:update'], {cwd: EXAMPLES_PATH}); + return spawnInfo.promise; + }); + }; + + var outputFile = path.join(process.cwd(), 'protractor-results.txt'); + + promise.then(function() { + return findAndRunE2eTests(argv.filter, outputFile); + }).then(function(status) { + reportStatus(status, outputFile); + if (status.failed.length > 0){ + return Promise.reject('Some test suites failed'); + } + }).catch(function(e) { + gutil.log(e); + process.exitCode = 1; + }); + return promise; +} + +// finds all of the *e2e-spec.tests under the _examples folder along +// with the corresponding apps that they should run under. Then run +// each app/spec collection sequentially. +function findAndRunE2eTests(filter, outputFile) { + // create an output file with header. + var startTime = new Date().getTime(); + var header = `Doc Sample Protractor Results for ${lang} on ${new Date().toLocaleString()}\n`; + header += argv.fast ? + ' Fast Mode (--fast): no npm install, webdriver update, or boilerplate copy\n' : + ' Slow Mode: npm install, webdriver update, and boilerplate copy\n'; + header += ` Filter: ${filter ? filter : 'All tests'}\n\n`; + fs.writeFileSync(outputFile, header); + + // create an array of combos where each + // combo consists of { examplePath: ... } + var examplePaths = []; + var e2eSpecPaths = getE2eSpecPaths(EXAMPLES_PATH); + e2eSpecPaths.forEach(function(specPath) { + // get all of the examples under each dir where a pcFilename is found + localExamplePaths = getExamplePaths(specPath, true); + // Filter by example name + if (filter) { + localExamplePaths = localExamplePaths.filter(function (fn) { + return fn.match(filter) != null; + }) + } + // Filter by language, also supports variations like js-es6 + localExamplePaths = localExamplePaths.filter(function (fn) { + return fn.match('/'+lang+'(?:-[^/]*)?$') != null; + }); + localExamplePaths.forEach(function(examplePath) { + examplePaths.push(examplePath); + }) + }); + + // run the tests sequentially + var status = { passed: [], failed: [] }; + return examplePaths.reduce(function (promise, examplePath) { + return promise.then(function () { + var runTests = isDartPath(examplePath) ? runE2eDartTests : runE2eTsTests; + return runTests(examplePath, outputFile).then(function(ok) { + var arr = ok ? status.passed : status.failed; + arr.push(examplePath); + }) + }); + }, Q.resolve()).then(function() { + var stopTime = new Date().getTime(); + status.elapsedTime = (stopTime - startTime)/1000; + return status; + }); +} + +// start the example in appDir; then run protractor with the specified +// fileName; then shut down the example. All protractor output is appended +// to the outputFile. +function runE2eTsTests(appDir, outputFile) { + // Grab protractor configuration or defaults to systemjs config. + try { + var exampleConfig = fs.readJsonSync(`${appDir}/${_exampleConfigFilename}`); + } catch (e) { + exampleConfig = {}; + } + + var config = { + build: exampleConfig.build || 'build', + run: exampleConfig.run || 'serve:e2e' + }; + + var appBuildSpawnInfo = spawnExt('npm', ['run', config.build], { cwd: appDir }); + var appRunSpawnInfo = spawnExt('npm', ['run', config.run, '--', '-s'], { cwd: appDir }); + + var run = runProtractor(appBuildSpawnInfo.promise, appDir, appRunSpawnInfo, outputFile); + + if (fs.existsSync(appDir + '/aot/index.html')) { + run = run.then(() => runProtractorAoT(appDir, outputFile)); + } + return run; +} + +function runProtractor(prepPromise, appDir, appRunSpawnInfo, outputFile) { + var specFilename = path.resolve(`${appDir}/../e2e-spec.ts`); + return prepPromise + .catch(function(){ + var emsg = `Application at ${appDir} failed to transpile.\n\n`; + gutil.log(emsg); + fs.appendFileSync(outputFile, emsg); + return Promise.reject(emsg); + }) + .then(function (data) { + var transpileError = false; + + // start protractor + + var spawnInfo = spawnExt('npm', [ 'run', 'protractor', '--', 'protractor.config.js', + `--specs=${specFilename}`, '--params.appDir=' + appDir, '--params.outputFile=' + outputFile], { cwd: EXAMPLES_PATH }); + + spawnInfo.proc.stderr.on('data', function (data) { + transpileError = transpileError || /npm ERR! Exit status 100/.test(data.toString()); + }); + return spawnInfo.promise.catch(function(err) { + if (transpileError) { + var emsg = `${specFilename} failed to transpile.\n\n`; + gutil.log(emsg); + fs.appendFileSync(outputFile, emsg); + } + return Promise.reject(emsg); + }); + }) + .then( + function() { return finish(true);}, + function() { return finish(false);} + ) + + function finish(ok){ + // Ugh... proc.kill does not work properly on windows with child processes. + // appRun.proc.kill(); + treeKill(appRunSpawnInfo.proc.pid); + return ok; + } +} + +function runProtractorAoT(appDir, outputFile) { + fs.appendFileSync(outputFile, '++ AoT version ++\n'); + var aotBuildSpawnInfo = spawnExt('npm', ['run', 'build:aot'], { cwd: appDir }); + var promise = aotBuildSpawnInfo.promise; + + var copyFileCmd = 'copy-dist-files.js'; + if (fs.existsSync(appDir + '/' + copyFileCmd)) { + promise = promise.then(() => + spawnExt('node', [copyFileCmd], { cwd: appDir }).promise ); + } + var aotRunSpawnInfo = spawnExt('npm', ['run', 'serve:aot'], { cwd: appDir }); + return runProtractor(promise, appDir, aotRunSpawnInfo, outputFile); +} + +// start the server in appDir/build/web; then run protractor with the specified +// fileName; then shut down the example. All protractor output is appended +// to the outputFile. +function runE2eDartTests(appDir, outputFile) { + // Launch http server out of ts directory because all the config files are there. + var httpLaunchDir = path.resolve(appDir, '../ts'); + var deployDir = path.resolve(appDir, 'build/web'); + gutil.log('AppDir for Dart e2e: ' + appDir); + gutil.log('Deploying from: ' + deployDir); + + var appRunSpawnInfo = spawnExt('npm', ['run', 'serve:e2e', '--', deployDir, '-s'], { cwd: httpLaunchDir }); + if (!appRunSpawnInfo.proc.pid) { + gutil.log('http-server failed to launch over ' + deployDir); + return false; + } + if (argv.pub === false) { + var prepPromise = Promise.resolve(true); + gutil.log('Skipping pub upgrade and pub build (--no-pub flag present)'); + } else { + var pubUpgradeSpawnInfo = spawnExt('pub', ['upgrade'], { cwd: appDir }); + var prepPromise = pubUpgradeSpawnInfo.promise.then(function (data) { + return spawnExt('pub', ['build'], { cwd: appDir }).promise; + }); + } + return runProtractor(prepPromise, appDir, appRunSpawnInfo, outputFile); +} + +function reportStatus(status, outputFile) { + var log = ['']; + log.push('Suites passed:'); + status.passed.forEach(function(val) { + log.push(' ' + val); + }); + + if (status.failed.length == 0) { + log.push('All tests passed'); + } else { + log.push('Suites failed:'); + status.failed.forEach(function (val) { + log.push(' ' + val); + }); + } + log.push('\nElapsed time: ' + status.elapsedTime + ' seconds'); + var log = log.join('\n'); + gutil.log(log); + fs.appendFileSync(outputFile, log); +} + +// returns both a promise and the spawned process so that it can be killed if needed. +function spawnExt(command, args, options) { + var deferred = Q.defer(); + var descr = command + " " + args.join(' '); + var proc; + gutil.log('running: ' + descr); + try { + proc = xSpawn.spawn(command, args, options); + } catch(e) { + gutil.log(e); + deferred.reject(e); + return { proc: null, promise: deferred.promise }; + } + proc.stdout.on('data', function (data) { + gutil.log(data.toString()); + }); + proc.stderr.on('data', function (data) { + gutil.log(data.toString()); + }); + proc.on('close', function (returnCode) { + gutil.log('completed: ' + descr); + // Many tasks (e.g., tsc) complete but are actually errors; + // Confirm return code is zero. + returnCode === 0 ? deferred.resolve(0) : deferred.reject(returnCode); + }); + proc.on('error', function (data) { + gutil.log('completed with error:' + descr); + gutil.log(data.toString()); + deferred.reject(data); + }); + return { proc: proc, promise: deferred.promise }; +} + +// Public tasks + +gulp.task('default', ['help']); gulp.task('help', taskListing.withFilters(function(taskName) { var isSubTask = taskName.substr(0,1) == "_"; @@ -59,81 +436,166 @@ gulp.task('help', taskListing.withFilters(function(taskName) { return shouldRemove; })); -gulp.task('serve-and-sync', ['build-docs'], function (cb) { +// requires admin access because it adds symlinks +gulp.task('add-example-boilerplate', function() { + var realPath = path.join(EXAMPLES_PATH, '/node_modules'); + var nodeModulesPaths = excludeDartPaths(getNodeModulesPaths(EXAMPLES_PATH)); - // execCommands(['harp server'], {}, cb); - execCommands(['npm run harp -- server .'], {}, cb); - - var browserSync = require('browser-sync').create(); - browserSync.init({ - proxy: 'localhost:9000', - files: [path.join(DOCS_PATH, '**/*/**/*')], - logFileChanges: true, - reloadDelay: 500 + nodeModulesPaths.forEach(function(linkPath) { + gutil.log("symlinking " + linkPath + ' -> ' + realPath) + fsUtils.addSymlink(realPath, linkPath); }); - devGuideExamplesWatch(_devguideShredOptions, function() { - browserSync.reload(); - }); + return copyExampleBoilerplate(); +}); - apiSourceWatch(function() { - browserSync.reload(); - }); +// copies boilerplate files to locations +// where an example app is found +gulp.task('_copy-example-boilerplate', function (done) { + return argv.fast ? done() : copyExampleBoilerplate(); }); -gulp.task('build-and-serve', ['build-docs'], function (cb) { - execCommands(['npm run harp -- server .'], {}, cb); +// copies boilerplate files to locations +// where an example app is found +// also copies certain web files (e.g., styles.css) to ~/_examples/**/dart/**/web +function copyExampleBoilerplate() { + gutil.log('Copying example boilerplate files'); + var examplePaths = excludeDartPaths(getExamplePaths(EXAMPLES_PATH)); - var browserSync = require('browser-sync').create(); - browserSync.init({ - proxy: 'localhost:9000', - files: [path.join(DOCS_PATH, '**/*/**/*')], - logFileChanges: true, - reloadDelay: 500 + // Make boilerplate files read-only to avoid that they be edited by mistake. + var destFileMode = '444'; + return copyFiles(_exampleBoilerplateFiles, BOILERPLATE_PATH, examplePaths, destFileMode) + // copy the unit test boilerplate + .then(function() { + var unittestPaths = getUnitTestingPaths(EXAMPLES_PATH); + return copyFiles(_exampleUnitTestingBoilerplateFiles, + EXAMPLES_TESTING_PATH, unittestPaths, destFileMode); + }) + .catch(function(err) { + gutil.log(err); + throw err; + }); +} + +gulp.task('remove-example-boilerplate', function() { + var nodeModulesPaths = getNodeModulesPaths(EXAMPLES_PATH); + nodeModulesPaths.forEach(function(linkPath) { + fsUtils.removeSymlink(linkPath); }); + + deleteExampleBoilerPlate(); }); -gulp.task('build-docs', ['_shred-devguide-examples', 'build-api-docs', '_zip-examples'], function() { - return buildShredMaps(true); +// Npm install Angular libraries into examples/node_modules, +// either release or current build packages +// Examples: +// gulp install-example-angular --build // use current build packages +// gulp install-example-angular --build=2.0.0-b43f954 // use tagged packages +// gulp install-example-angular // restore release packages +// +// Find the tags here: https://github.com/angular/core-builds/releases +gulp.task('install-example-angular', installExampleAngular); + +function installExampleAngular() { + var sources; + var template; + var libs = [ + 'core', 'common', 'compiler', 'compiler-cli', + 'platform-browser', 'platform-browser-dynamic', 'platform-server', + 'forms', 'http', 'router', 'upgrade']; + + var build = argv.build; + if (build) { + if (typeof build === 'string') { + build = (build[0]==='#' ? '' : '#') + build; + } else { + build = ''; + } + } else{ + build = 'npm'; + } + // Like: "angular/core-builds" or "@angular/core" + sources = libs.map( lib => { + return build === 'npm' + ? `@angular/${lib}` + : `git+https://github.com/angular/${lib}-builds${build}`; + }); + + if (argv.build) { + sources.push('@angular/tsc-wrapped'); // tsc-wrapped needed for builds + sources.push('typescript@2.2.1'); // recent builds need recent TypeScript + } + + sources.push('@angular/router-deprecated'); + + gutil.log(`Installing Angular packages from ${build === 'npm' ? 'NPM' : 'BUILD ' + build}`); + + var spawnInfo = spawnExt('node', ['node_modules/rimraf/bin.js', 'node_modules/@angular'], { cwd: EXAMPLES_PATH}); + return spawnInfo.promise + .then(() => { + spawnInfo = spawnExt('npm', ['install', ...sources], {cwd: EXAMPLES_PATH}); + return spawnInfo.promise + }); +} + +// deletes boilerplate files that were added by copyExampleBoilerplate +// from locations where an example app is found +gulp.task('_delete-example-boilerplate', deleteExampleBoilerPlate); + +function deleteExampleBoilerPlate() { + gutil.log('Deleting example boilerplate files'); + var examplePaths = getExamplePaths(EXAMPLES_PATH); + var dartExampleWebPaths = getDartExampleWebPaths(EXAMPLES_PATH); + var unittestPaths = getUnitTestingPaths(EXAMPLES_PATH); + + return deleteFiles(_exampleBoilerplateFiles, examplePaths) + .then(function() { + return deleteFiles(_exampleUnitTestingBoilerplateFiles, unittestPaths); + }); +} + +gulp.task('serve-and-sync', ['build-docs'], function (cb) { + // watchAndSync({devGuide: true, apiDocs: true, apiExamples: true, localFiles: true}, cb); + watchAndSync({devGuide: true, devGuideJade: true, apiDocs: true, apiExamples: true, localFiles: true}, cb); }); -gulp.task('build-devguide-docs', ['_shred-devguide-examples'], function() { - return buildShredMaps(true); +gulp.task('serve-and-sync-api', ['build-docs'], function (cb) { + watchAndSync({apiDocs: true, apiExamples: true}, cb); }); -gulp.task('build-api-docs', ['_shred-api-examples'], function() { - if (!fs.existsSync(ANGULAR_PROJECT_PATH)) { - throw new Error('build-api-docs task requires the angular2 repo to be at ' + path.resolve(ANGULAR_PROJECT_PATH)); - } - return buildApiDocs(); +gulp.task('serve-and-sync-devguide', ['build-devguide-docs', 'build-plunkers' ], function (cb) { + watchAndSync({devGuide: true, devGuideJade: true, localFiles: true}, cb); }); -gulp.task('_shred-devguide-examples', ['_shred-clean-devguide'], function() { - return docShredder.shred( _devguideShredOptions); +gulp.task('_serve-and-sync-jade', function (cb) { + watchAndSync({devGuideJade: true, localFiles: true}, cb); }); -gulp.task('_shred-clean-devguide', function(cb) { - var cleanPath = path.join(_devguideShredOptions.fragmentsDir, '**/*.*') - return delPromise([ cleanPath, '!**/*.ovr.*', '!**/_api/**']); +gulp.task('build-and-serve', ['build-docs'], function (cb) { + watchAndSync({localFiles: true}, cb); }); -gulp.task('_shred-api-examples', ['_shred-clean-api'], function() { - return docShredder.shred( _apiShredOptions); +gulp.task('build-docs', ['build-devguide-docs', 'build-api-docs', 'build-plunkers', '_zip-examples']); + +gulp.task('build-api-docs', ['build-js-api-docs', 'build-ts-api-docs']); + +gulp.task('build-devguide-docs', ['_shred-devguide-examples', '_shred-devguide-shared-jade'], function() { + return buildShredMaps(true); }); -gulp.task('_shred-clean-api', function(cb) { - var cleanPath = path.join(_apiShredOptions.fragmentsDir, '**/*.*') - return delPromise([ cleanPath, '!**/*.ovr.*' ]); +gulp.task('build-ts-api-docs', ['_shred-api-examples'], function() { + return buildApiDocs('ts'); }); -gulp.task('_build-shred-maps', function() { - return build-shred-maps(true); +gulp.task('build-js-api-docs', ['_shred-api-examples'], function() { + return buildApiDocs('js'); }); -gulp.task('_zip-examples', function() { - exampleZipper.zipExamples(_devguideShredOptions.examplesDir, _devguideShredOptions.zipDir); - exampleZipper.zipExamples(_apiShredOptions.examplesDir, _apiShredOptions.zipDir); +// Using the --build flag will use systemjs.config.web.build.js (for preview builds) +gulp.task('build-plunkers', ['_copy-example-boilerplate'], function() { + regularPlunker.buildPlunkers(EXAMPLES_PATH, LIVE_EXAMPLES_PATH, { errFn: gutil.log, build: argv.build }); + return embeddedPlunker.buildPlunkers(EXAMPLES_PATH, LIVE_EXAMPLES_PATH, { errFn: gutil.log, build: argv.build, targetSelf: argv.targetSelf }); }); gulp.task('git-changed-examples', ['_shred-devguide-examples'], function(){ @@ -149,10 +611,10 @@ gulp.task('git-changed-examples', ['_shred-devguide-examples'], function(){ sha = argv.sha; messageSuffix = ' on commit: ' + (argv.sha.length ? argv.sha : '[last commit]'); } else { - console.log('git-changed-examples may be called with either an "--sha" argument like this:'); - console.log(' gulp git-changed-examples --sha=4d2ac96fa247306ddd2d4c4e0c8dee2223502eb2'); - console.log('or with an "--after" argument like this') - console.log(' gulp git-changed-examples --after="August 1, 2015"'); + gutil.log('git-changed-examples may be called with either an "--sha" argument like this:'); + gutil.log(' gulp git-changed-examples --sha=4d2ac96fa247306ddd2d4c4e0c8dee2223502eb2'); + gutil.log('or with an "--after" argument like this') + gutil.log(' gulp git-changed-examples --after="August 1, 2015"'); return; } var jadeShredMap; @@ -163,49 +625,526 @@ gulp.task('git-changed-examples', ['_shred-devguide-examples'], function(){ } else if (sha) { return getChangedExamples(sha); } else { - console.log('git-changed-examples may be called with either an "--sha" argument like this:'); - console.log(' gulp git-changed-examples --sha=4d2ac96fa247306ddd2d4c4e0c8dee2223502eb2'); - console.log('or with an "--after" argument like this') - console.log(' gulp git-changed-examples --after="August 1, 2015"'); + gutil.log('git-changed-examples may be called with either an "--sha" argument like this:'); + gutil.log(' gulp git-changed-examples --sha=4d2ac96fa247306ddd2d4c4e0c8dee2223502eb2'); + gutil.log('or with an "--after" argument like this') + gutil.log(' gulp git-changed-examples --after="August 1, 2015"'); } }).then(function(examplePaths) { examplePaths = filterOutExcludedPatterns(examplePaths, _excludeMatchers); - console.log('\nExamples changed ' + messageSuffix); - console.log(examplePaths) - console.log("\nJade files affected by changed example files " + messageSuffix); + gutil.log('\nExamples changed ' + messageSuffix); + gutil.log(examplePaths) + gutil.log("\nJade files affected by changed example files " + messageSuffix); var jadeExampleMap = jadeShredMapToJadeExampleMap(jadeShredMap, examplePaths); - console.log(JSON.stringify(jadeExampleMap, null, " ")); - console.log("-----"); + gutil.log(JSON.stringify(jadeExampleMap, null, " ")); + gutil.log("-----"); }).catch(function(err) { - console.log(err); + gutil.log(err); throw err; }); }); -gulp.task('check-deploy', ['build-docs'], function() { - console.log('running harp compile...'); - return execPromise('npm run harp -- compile . ./www', {}).then(function() { - execPromise('npm run live-server ./www'); +gulp.task('harp-compile', () => { + return harpCompile() +}); + +gulp.task('harp-serve', () => { + // Harp will watch and serve workspace files. + const cmd = 'npm run harp -- server .'; + gutil.log('Launching harp server (over project files)'); + gutil.log(` > ${cmd}`); + gutil.log('Note: issuing this command directly from the command line will show harp comiple warnings.'); + return execPromise(cmd); +}); + +gulp.task('serve-www', () => { + // Serve generated site. + return execPromise(`npm run live-server ${WWW}`); +}); + +gulp.task('build-compile', ['build-docs'], function() { + return harpCompile(); +}); + +gulp.task('check-deploy', ['firebase-use-proj-check', 'build-docs'], () => { + return harpCompile().then(function() { + gutil.log('compile ok'); + gutil.log('running live server ...'); + execPromise(`npm run live-server ${WWW}`); return askDeploy(); }).then(function(shouldDeploy) { if (shouldDeploy) { - console.log('deploying...'); + gutil.log('deploying...'); return execPromise('firebase deploy'); } else { return ['Not deploying']; } }).then(function(s) { - console.log(s.join('')); + gutil.log(s.join('')); + }).catch(function(e) { + gutil.log(e); }); }); +gulp.task('firebase-use-proj-check', cb => { + try { + execSync('firebase use'); + } catch (e) { + // Rerun command so user gets project + alias info + execSync('firebase use', {stdio:[0,1,2]}); + throw `\nAborting: no firebase project selected. Run:\n\n firebase use \n\n`; + } + return cb(); +}); + +gulp.task('test-api-builder', function (cb) { + execCommands(['npm run test-api-builder'], {}, cb); +}); + +// Usage: +// angular.io: gulp link-checker +// local site: gulp link-checker --url=http://localhost:3000 +gulp.task('link-checker', function(done) { + var method = 'get'; // the default 'head' fails for some sites + var exclude = [ + // Dart API docs aren't working yet; ignore them + '*/dart/latest/api/*', + // Somehow the link checker sees ng1 {{...}} in the resource page; ignore it + 'resources/%7B%7Bresource.url%7D%7D', + // API docs have links directly into GitHub repo sources; these can + // quickly become invalid, so ignore them for now: + '*/angular/tree/*', + // harp.json "bios" for "Ryan Schmukler", URL isn't valid: + 'http://slingingcode.com/' + ]; + var blcOptions = { requestMethod: method, excludedKeywords: exclude}; + return linkChecker({ blcOptions: blcOptions }); +}); + + +// Internal tasks +gulp.task('set-prod-env', function () { + // Supposedly running in production makes harp faster + // and less likely to drown in node_modules. + env({ + vars: { NODE_ENV: "production" } + }); + gutil.log("NODE_ENV: " + process.env.NODE_ENV); +}); + +// used to test just harpCompile without a build step +gulp.task('_harp-compile', function() { + return harpCompile().then(function() { + gutil.log('compile ok'); + }).catch(function(e) { + gutil.log('compile failed'); + }); +}); + +gulp.task('_shred-devguide-examples', ['_shred-clean-devguide', '_copy-example-boilerplate'], function() { + // Split big shredding task into partials 2016-06-14 + const exPath = path.join(EXAMPLES_PATH, (argv.filter || '') + '*'); + var examplePaths = globby.sync(exPath, {ignore: ['**/node_modules', '**/_boilerplate']}); + var promise = Promise.resolve(true); + examplePaths.forEach(function (examplePath) { + promise = promise.then(() => docShredder.shredSingleExampleDir(_devguideShredOptions, examplePath)); + }); + return promise; +}); + +gulp.task('_shred-devguide-shared-jade', ['_shred-clean-devguide-shared-jade', '_copy-example-boilerplate'], function() { + return docShredder.shred(_devguideShredJadeOptions); +}); + +gulp.task('_shred-clean-devguide-shared-jade', function(cb) { + // oldCleanPath is only needed to cleanup any jade fragments still sitting in the old location + var oldCleanPath = path.join(DOCS_PATH, '**/_.*.jade'); + // jade fragments now all go into _fragments subdirs under their source. + var newCleanPath = path.join(DOCS_PATH, '**/_fragments/*.jade'); + // Much slower 8-9x then using globby first ... ??? + // return del([ newCleanPath, oldCleanPath]); + var files = globby.sync( [newCleanPath, oldCleanPath]); + return del(files); +}); + +gulp.task('_shred-clean-devguide', function(cb) { + var cleanPath = path.join(_devguideShredOptions.fragmentsDir, (argv.filter || '*') + '*/*.*') + return del([ cleanPath, '!**/*.ovr.*', '!**/_api/**']); +}); + +gulp.task('_shred-api-examples', ['_shred-clean-api'], function() { + const promises = []; + gutil.log('Shredding API examples for languages: ' + langs.join(', ')); + langs.forEach(lang => { + if (lang === 'js') return; // JS is handled via TS. + checkAngularProjectPath(ngPathFor(lang)); + promises.push(docShredder.shred(_apiShredOptions)); + }); + return Q.all(promises); +}); + +gulp.task('_shred-clean-api', function(cb) { + var cleanPath = path.join(_apiShredOptions.fragmentsDir, '**/*.*') + return del([ cleanPath, '!**/*.ovr.*' ]); +}); + +gulp.task('_zip-examples', function() { + new ExampleZipper(_devguideShredOptions.examplesDir, _devguideShredOptions.zipDir); + // exampleZipper.zipExamples(_apiShredOptions.examplesDir, _apiShredOptions.zipDir); +}); + + +// Linting + +gulp.task('lint', function() { + return gulp.src([ + './public/docs/_examples/**/*.ts', + '!./public/docs/_examples/**/ts-snippets/*.ts', + '!./public/docs/_examples/style-guide/ts/**/*.avoid.ts', + '!./public/docs/_examples/**/node_modules/**/*', + '!./public/docs/_examples/**/build/**/*', + // temporary until codelyzer is fixed mgechev/codelyzer#60 + '!./public/docs/_examples/animations/ts/app/hero.service.ts' + ]) + .pipe(tslint({ + rulesDirectory: ['node_modules/codelyzer'], + configuration: require('./tslint.json') + })) + .pipe(tslint.report('prose', { + summarizeFailureOutput: true + })); +}); + + +// Helper functions + +function harpCompile() { + // Supposedly running in production makes harp faster + // and less likely to drown in node_modules. + env({ vars: { NODE_ENV: "production" } }); + gutil.log("NODE_ENV: " + process.env.NODE_ENV); + + if(argv.page) harpJsonSetJade2NgTo(true); + + if(skipLangs && fs.existsSync(WWW) && backupApiHtmlFilesExist(WWW)) { + gutil.log(`Harp site recompile: skipping recompilation of API docs for [${skipLangs}]`); + gutil.log(`API docs will be copied from existing ${WWW} folder (if they exist).`) + del.sync(`${WWW}-backup`); // remove existing backup if it exists + renameIfExistsSync(WWW, `${WWW}-backup`); + } else { + gutil.log(`Harp full site compile, including API docs for all languages.`); + if (skipLangs) + gutil.log(`Ignoring API docs skip set (${skipLangs}) because full ` + + `site has not been built yet or some API HTML files are missing.`); + } + + var deferred = Q.defer(); + gutil.log('running harp compile...'); + showHideExampleNodeModules('hide'); + showHideApiDir('hide'); + var spawnInfo = spawnExt('npm',['run','harp', '--', 'compile', '.', WWW ]); + spawnInfo.promise.then(function(x) { + gutil.log("NODE_ENV: " + process.env.NODE_ENV); + showHideExampleNodeModules('show'); + showHideApiDir('show'); + harpJsonSetJade2NgTo(false); + if (x !== 0) { + deferred.reject(x) + } else { + restoreApiHtml(); + deferred.resolve(x); + } + }).catch(function(e) { + gutil.log("NODE_ENV: " + process.env.NODE_ENV); + showHideExampleNodeModules('show'); + showHideApiDir('show'); + harpJsonSetJade2NgTo(false); + deferred.reject(e); + }); + return deferred.promise; +} + +function linkChecker(options) { + var deferred = Q.defer(); + var options = options || {}; + + var blcOptions = options.blcOptions || {}; + var customData = options.customData || {}; + + // don't bother reporting bad links matching this RegExp + var excludeBad = argv.excludeBad ? new RegExp(argv.excludeBad) : (options.excludeBad || ''); + + var previousPage; + var siteUrl = argv.url || options.url || 'https://angular.io/'; + + // See https://github.com/stevenvachon/broken-link-checker#blcsitecheckeroptions-handlers + var handlers = { + robots: function(robots, customData){}, + html: function(tree, robots, response, pageUrl, customData){ + // gutil.log('Scanning ' + pageUrl); + }, + junk: function(result, customData){}, + + // Analyze links + link: function(result, customData){ + if (!result.broken) { return; } + if (excludeBad && excludeBad.test(result.url.resolved)) { return; } + + var currentPage = result.base.resolved + if (previousPage !== currentPage) { + previousPage = currentPage; + fs.appendFileSync(outputFile, '\n' + currentPage); + gutil.log('broken: ' + currentPage); + } + var msg = '\n [' + result.html.location.line + ', ' + result.brokenReason + '] ' + result.url.resolved; + fs.appendFileSync(outputFile, msg); + // gutil.log(msg); + // gutil.log(result); + }, + + page: function(error, pageUrl, customData){}, + site: function(error, siteUrl, customData){}, + + end: function(){ + var stopTime = new Date().getTime(); + var elapsed = 'Elapsed link-checking time: ' + ((stopTime - startTime)/1000) + ' seconds'; + gutil.log(elapsed); + fs.appendFileSync(outputFile, '\n'+elapsed); + gutil.log('Output in file: ' + outputFile); + deferred.resolve(true); + } + }; + + // create an output file with header. + var outputFile = path.join(process.cwd(), 'link-checker-results.txt'); + var header = 'Link checker results for: ' + siteUrl + + '\nStarted: ' + (new Date()).toLocaleString() + + '\nExcluded links (blc file globs): ' + blcOptions.excludedKeywords + + '\nExcluded links (custom --exclude-bad regex): ' + excludeBad.toString() + '\n\n'; + gutil.log(header); + fs.writeFileSync(outputFile, header); + + var siteChecker = new blc.SiteChecker(blcOptions, handlers); + var startTime = new Date().getTime(); + + try { + gutil.log('link checker started'); + siteChecker.enqueue(siteUrl, customData); + } catch (err) { + gutil.log('link checker died'); + console.error('link checker died', err); + deferred.reject(err); + } + return deferred.promise; +} + +// harp has issues with node_modules under the public dir +// but we need them there for example testing and development +// this method allows the node modules folder under '_examples' +// to be temporarily moved out from under 'public' while harp +// compilation is occurring. +function showHideExampleNodeModules(showOrHide) { + var nmPath = path.join(EXAMPLES_PATH, "/node_modules"); + var nmHiddenPath = path.join(TEMP_PATH, "/node_modules"); + if (showOrHide == 'hide' && fs.existsSync(nmPath)) { + if (!fs.existsSync(TEMP_PATH)) { + fs.mkdirSync(TEMP_PATH); + } + fs.renameSync(nmPath, nmHiddenPath); + } else if (showOrHide == 'show' && fs.existsSync(nmHiddenPath)) { + fs.renameSync(nmHiddenPath, nmPath); + fs.rmdirSync(TEMP_PATH); + } +} + +// Show/hide the API docs harp source folder for every lang in skipLangs. +function showHideApiDir(showOrHide) { + skipLangs.forEach(lang => { + _showHideApiDir(lang, showOrHide); + }); +} + +// Rename the API docs harp source folder for lang to/from 'api' to '_api-tmp-foo'. +function _showHideApiDir(lang, showOrHide) { + const vers = 'latest'; + const basePath = path.join(DOCS_PATH, lang, vers); + const apiDirPath = path.join(basePath, 'api'); + const disabledApiDirPath = path.join(basePath, '_api-tmp-hide-from-jade'); + const args = showOrHide == 'hide' + ? [apiDirPath, disabledApiDirPath] + : [disabledApiDirPath, apiDirPath]; + renameIfExistsSync(...args); +} + +// For each lang in skipLangs, copy the API dir from ${WWW}-backup to WWW. +function restoreApiHtml() { + const vers = 'latest'; + skipLangs.forEach(lang => { + const relApiDir = path.join('docs', lang, vers, 'api'); + const apiSubdir = path.join(WWW, relApiDir); + const backupApiSubdir = path.join(`${WWW}-backup`, relApiDir); + if (fs.existsSync(backupApiSubdir)) { + gutil.log(`cp ${backupApiSubdir} ${apiSubdir}`) + fs.copySync(backupApiSubdir, apiSubdir); + } + }); +} + +// For each lang in skipLangs, ensure API dir exists in folderName +function backupApiHtmlFilesExist(folderName) { + const vers = 'latest'; + var result = 1; + skipLangs.forEach(lang => { + if (lang === 'dart') return true; + const relApiDir = path.join('docs', lang, vers, 'api'); + const backupApiSubdir = path.join(folderName, relApiDir); + if (!fs.existsSync(backupApiSubdir)) { + gutil.log(`WARNING: API docs HTML folder doesn't exist: ${backupApiSubdir}`); + result = 0; + } + }); + return result; +} + +function harpJsonSetJade2NgTo(v) { + const harpJsonPath = path.join(ANGULAR_IO_PROJECT_PATH, 'harp.json'); + execSync(`perl -pi -e 's/("jade2ng": *)\\w+/$1${v}/' ${harpJsonPath}`); + const harpJson = require(harpJsonPath); + gutil.log(`jade2ng: ${harpJson.globals.jade2ng}`); +} + +// Copies fileNames into destPaths, setting the mode of the +// files at the destination as optional_destFileMode if given. +// returns a promise +function copyFiles(fileNames, originPath, destPaths, optional_destFileMode) { + var copy = Q.denodeify(fsExtra.copy); + var chmod = Q.denodeify(fsExtra.chmod); + var copyPromises = []; + destPaths.forEach(function(destPath) { + fileNames.forEach(function(fileName) { + var originName = path.join(originPath, fileName); + var destName = path.join(destPath, fileName); + var p = copy(originName, destName, { clobber: true}); + if(optional_destFileMode !== undefined) { + p = p.then(function () { + return chmod(destName, optional_destFileMode); + }); + } + copyPromises.push(p); + }); + }); + return Q.all(copyPromises); +} + +function deleteFiles(baseFileNames, destPaths) { + var remove = Q.denodeify(fsExtra.remove); + var delPromises = []; + destPaths.forEach(function(destPath) { + baseFileNames.forEach(function(baseFileName) { + var destFileName = path.join(destPath, baseFileName); + var p = remove(destFileName); + delPromises.push(p); + }); + }); + return Q.all(delPromises); +} + +// TODO: filter out all paths that are subdirs of another +// path in the result. +function getE2eSpecPaths(basePath) { + var paths = getPaths(basePath, '*e2e-spec.+(js|ts)', true); + return _.uniq(paths); +} + +function getNodeModulesPaths(basePath) { + var paths = getExamplePaths(basePath).map(function(examplePath) { + return path.join(examplePath, "/node_modules"); + }); + return paths; +} + +function getExamplePaths(basePath, includeBase) { + // includeBase defaults to false + return getPaths(basePath, _exampleConfigFilename, includeBase); +} + +function getDartExampleWebPaths(basePath) { + var paths = globby.sync([path.join(basePath,"**/dart/**/web")]) + return paths; +} + +function getUnitTestingPaths(basePath) { + var examples = getPaths(basePath, _exampleConfigFilename, true); + return examples.filter((example) => { + var exampleConfig = fs.readJsonSync(`${example}/${_exampleConfigFilename}`, {throws: false}); + return exampleConfig && !!exampleConfig.unittesting; + }); +} + +function getPaths(basePath, filename, includeBase) { + var filenames = getFilenames(basePath, filename, includeBase); + var paths = filenames.map(function(fileName) { + return path.dirname(fileName); + }); + return paths; +} + +function getFilenames(basePath, filename, includeBase) { + // includeBase defaults to false + var includePatterns = [path.join(basePath, "**/" + filename)]; + if (!includeBase) { + // ignore (skip) the top level version. + includePatterns.push("!" + path.join(basePath, "/" + filename)); + } + // ignore (skip) the files in BOILERPLATE_PATH. + includePatterns.push("!" + path.join(BOILERPLATE_PATH, "/" + filename)); + var nmPattern = path.join(basePath, "**/node_modules/**"); + var filenames = globby.sync(includePatterns, {ignore: [nmPattern]}); + return filenames; +} + +function watchAndSync(options, cb) { + // Supposedly running in production makes harp faster + // and less likely to drown in node_modules. + env({ + vars: { NODE_ENV: "production" } + }); + + execCommands(['npm run harp -- server .'], {}, cb); + + var browserSync = require('browser-sync').create(); + browserSync.init({proxy: 'localhost:9000'}); + + // When using the --focus=name flag, only **/name/**/*.* example files and + // **/name.jade files are watched. This is useful for performance reasons. + // Example: gulp serve-and-sync --focus=architecture + var focus = argv.focus; + + if (options.devGuide) { + devGuideExamplesWatch(_devguideShredOptions, browserSync.reload, focus); + } + if (options.devGuideJade) { + devGuideSharedJadeWatch( { jadeDir: DOCS_PATH}, browserSync.reload, focus); + } + if (options.apiDocs) { + apiSourceWatch(browserSync.reload); + } + if (options.apiExamples) { + apiExamplesWatch(browserSync.reload); + } + if (options.localFiles) { + gulp.watch(NOT_API_DOCS_GLOB, browserSync.reload); + } +} + // returns a promise; function askDeploy() { - + // Show user what the currently active firebase project is: + execSync('firebase use', {stdio:[0,1,2]}); prompt.start(); var schema = { name: 'shouldDeploy', - description: 'Deploy to Firebase? (y/n): ', + description: `Deploy ${WWW} to firebase? (y/n)`, type: 'string', pattern: /Y|N|y|n/, message: "Respond with either a 'y' or 'n'", @@ -218,10 +1157,6 @@ function askDeploy() { } -gulp.task('test-api-builder', function (cb) { - execCommands(['npm run test-api-builder'], {}, cb); -}); - function filterOutExcludedPatterns(fileNames, excludeMatchers) { return fileNames.filter(function(fileName) { return !excludeMatchers.some(function(excludeMatcher) { @@ -230,56 +1165,102 @@ function filterOutExcludedPatterns(fileNames, excludeMatchers) { }); } -function apiSourceWatch(postShredAction) { - var srcPattern = [path.join(ANGULAR_PROJECT_PATH, 'modules/angular2/src/**/*.*')]; - watch(srcPattern, function (event, done) { - console.log('Event type: ' + event.event); // added, changed, or deleted - console.log('Event path: ' + event.path); // The path of the modified file - // need to run just build - buildApiDocs().then(done); +function apiSourceWatch(postBuildAction) { + var srcPattern = [path.join(ANGULAR_PROJECT_PATH, 'modules/@angular/**/*.*')]; + gulp.watch(srcPattern, {readDelay: 500}, function (event, done) { + gutil.log('API source changed'); + gutil.log('Event type: ' + event.event); // added, changed, or deleted + gutil.log('Event path: ' + event.path); // The path of the modified file + + return Q.all([buildApiDocs('ts'), buildApiDocs('js')]).then(postBuildAction); }); - var examplesPattern = [path.join(ANGULAR_PROJECT_PATH, 'modules/angular2/examples/**/*.*')]; - watch(examplesPattern, function (event, done) { - console.log('Event type: ' + event.event); // added, changed, or deleted - console.log('Event path: ' + event.path); // The path of the modified file - // need to run shredder - var cleanPath = path.join(_apiShredOptions.fragmentsDir, '**/*.*'); - return delPromise([ cleanPath, '!**/*.ovr.*' ]).then(function() { +} + +function apiExamplesWatch(postShredAction) { + var examplesPath = path.join(ANGULAR_PROJECT_PATH, 'modules/@angular/examples/**'); + var includePattern = path.join(examplesPath, '**/*.*'); + var excludePattern = '!' + path.join(examplesPath, '**/node_modules/**/*.*'); + var cleanPath = [path.join(_apiShredOptions.fragmentsDir, '**/*.*'), '!**/*.ovr.*']; + + gulp.watch([includePattern, excludePattern], {readDelay: 500}, function (event, done) { + gutil.log('API example changed'); + gutil.log('Event type: ' + event.type); // added, changed, or deleted + gutil.log('Event path: ' + event.path); // The path of the modified file + + return del(cleanPath).then(function() { return docShredder.shred(_apiShredOptions); - }).then(function() { - postShredAction && postShredAction(); - }); + }).then(postShredAction); + }); +} + +function devGuideExamplesWatch(shredOptions, postShredAction, focus) { + var watchPattern = focus ? '{' + focus + ',cb-' + focus+ '}/**/*.*' : '**/*.*'; + var includePattern = path.join(shredOptions.examplesDir, watchPattern); + // removed this version because gulp.watch has the same glob issue that dgeni has. + // var excludePattern = '!' + path.join(shredOptions.examplesDir, '**/node_modules/**/*.*'); + // gulp.watch([includePattern, excludePattern], {readDelay: 500}, function (event, done) { + var ignoreThese = [ '**/node_modules/**', '**/_fragments/**', '**/dist/**', + '**/dart/.pub/**', '**/dart/build/**']; + ignoreThese = ignoreThese.concat(_exampleBoilerplateFiles.map((file) => `public/docs/_examples/*/*/${file}`)); + var files = globby.sync( [includePattern], { ignore: ignoreThese }); + gulp.watch([files], {readDelay: 500}, function (event, done) { + gutil.log('Dev Guide example changed') + gutil.log('Event type: ' + event.type); // added, changed, or deleted + gutil.log('Event path: ' + event.path); // The path of the modified file + return docShredder.shredSingleDir(shredOptions, event.path).then(postShredAction); }); +} +function devGuideSharedJadeWatch(shredOptions, postShredAction, focus) { + var watchPattern = focus ? '**/' + focus + '.jade' : '**/*.jade'; + var includePattern = path.join(DOCS_PATH, watchPattern); + // removed this version because gulp.watch has the same glob issue that dgeni has. + // var excludePattern = '!' + path.join(shredOptions.jadeDir, '**/node_modules/**/*.*'); + // gulp.watch([includePattern, excludePattern], {readDelay: 500}, function (event, done) { + var ignoreThese = [ '**/node_modules/**', '**/_examples/**', '**/_fragments/**', '**/latest/api/**' ]; + var files = globby.sync( [includePattern], { ignore: ignoreThese}); + gulp.watch([files], {readDelay: 500}, function (event, done) { + gutil.log('Dev Guide jade file changed') + gutil.log('Event type: ' + event.type); // added, changed, or deleted + gutil.log('Event path: ' + event.path); // The path of the modified file + return docShredder.shredSingleJadeDir(shredOptions, event.path).then(postShredAction); + }); } -function buildApiDocs() { + +// Generate the API docs for the specified language, if not specified then it defaults to ts +function buildApiDocs(targetLanguage) { + var ALLOWED_LANGUAGES = ['ts', 'js', 'dart']; + var GENERATE_API_LANGUAGES = ['ts', 'js']; + checkAngularProjectPath(); try { - var dgeni = new Dgeni([require(path.resolve(TOOLS_PATH, 'api-builder/angular.io-package'))]); - return dgeni.generate().then(function() { - // Make a copy of the JS API docs to the TS folder - return gulp.src([path.join(DOCS_PATH, 'js/latest/api/**/*.*'), '!' + path.join(DOCS_PATH, 'js/latest/api/index.jade')]) - .pipe(gulp.dest('./public/docs/ts/latest/api')); + // Build a specialized package to generate different versions of the API docs + var package = new Package('apiDocs', [require(path.resolve(TOOLS_PATH, 'api-builder/angular.io-package'))]); + package.config(function(log, targetEnvironments, writeFilesProcessor, readTypeScriptModules, linkDocsInlineTagDef) { + log.level = _dgeniLogLevel; + ALLOWED_LANGUAGES.forEach(function(target) { targetEnvironments.addAllowed(target); }); + if (targetLanguage) { + targetEnvironments.activate(targetLanguage); + + if (GENERATE_API_LANGUAGES.indexOf(targetLanguage) === -1) { + // Don't read TypeScript modules if we are not generating API docs - Dart I am looking at you! + readTypeScriptModules.$enabled = false; + } + linkDocsInlineTagDef.lang = targetLanguage; + linkDocsInlineTagDef.vers = 'latest'; + writeFilesProcessor.outputFolder = path.join(targetLanguage, linkDocsInlineTagDef.vers, 'api'); + } }); + + var dgeni = new Dgeni([package]); + return dgeni.generate(); } catch(err) { - console.log(err); - console.log(err.stack); + console.error(err); + console.error(err.stack); throw err; } } -function devGuideExamplesWatch(shredOptions, postShredAction) { - var pattern = path.join(shredOptions.examplesDir, "**/*.*"); - watch([pattern], function (event, done) { - console.log('Event type: ' + event.event); // added, changed, or deleted - console.log('Event path: ' + event.path); // The path of the modified file - docShredder.shredSingleDir(shredOptions, event.path).then(function () { - postShredAction && postShredAction(); - }); - }); -} - - function buildShredMaps(shouldWrite) { var options = { devguideExamplesDir: _devguideShredOptions.examplesDir, @@ -287,7 +1268,8 @@ function buildShredMaps(shouldWrite) { fragmentsDir: _devguideShredOptions.fragmentsDir, jadeDir: './public/docs', outputDir: './public/docs', - writeFilesEnabled: shouldWrite + writeFilesEnabled: shouldWrite, + logLevel: _dgeniLogLevel }; return docShredder.buildShredMap(options).then(function(docs) { return docs; @@ -349,11 +1331,11 @@ function getChangedExamplesForCommit(commit, relativePath) { return commit.getDiff().then(function(diffList) { var filePaths = []; diffList.forEach(function (diff) { - diff.patches().forEach(function (patch) { + diff.patches().then(function (patch) { if (patch.isAdded() || patch.isModified) { var filePath = path.normalize(patch.newFile().path()); var isExample = filePath.indexOf(relativePath) >= 0; - // console.log(filePath + " isExample: " + isExample); + // gutil.log(filePath + " isExample: " + isExample); if (isExample) { filePaths.push(filePath); } @@ -413,6 +1395,7 @@ function addKeyValue(map, key, value) { } } + // Synchronously execute a chain of commands. // cmds: an array of commands // options: { shouldLog: true, shouldThrow: true } @@ -423,6 +1406,8 @@ function execCommands(cmds, options, cb) { options.shouldLog = options.shouldLog == null ? true : options.shouldLog; if (!cmds || cmds.length == 0) cb(null, null, null); var exec = require('child_process').exec; // just to make it more portable. + gutil.log("NODE_ENV: " + process.env.NODE_ENV); + exec(cmds[0], options, function(err, stdout, stderr) { if (err == null) { if (options.shouldLog) { @@ -447,6 +1432,21 @@ function execCommands(cmds, options, cb) { }); } +function ngPathFor(lang) { + return ANGULAR_PROJECT_PATH + (lang === 'dart' ? '-dart' : ''); +} +function checkAngularProjectPath(_ngPath) { + var ngPath = path.resolve(_ngPath || ngPathFor('ts')); + if (fs.existsSync(ngPath)) return; + throw new Error('API related tasks require the angular2 repo to be at ' + ngPath); +} -gulp.task('default', ['help']); \ No newline at end of file +function renameIfExistsSync(oldPath, newPath) { + if (fs.existsSync(oldPath)) { + gutil.log(`Rename: mv ${oldPath} ${newPath}`); + fs.renameSync(oldPath, newPath); + } else { + gutil.log(`renameIfExistsSync cannot rename, path not found: ${oldPath}`); + } +} diff --git a/harp.json b/harp.json index a05aae5945..82e5ca762f 100644 --- a/harp.json +++ b/harp.json @@ -4,9 +4,7 @@ "description": "Angular is a development platform for building mobile and desktop web applications", "keywords": "Angular, AngularJS, AngularDart, Javscript, Dart, Framework, JavaScript MVC, Google", "siteURL": "http://angular.io/", - "jsLatest": "2.0.0-alpha.12", - "dartLatest": "2.0.0-alpha.6", - + "jade2ng": false, "bios": { "misko": { "name": "Miško Hevery", @@ -31,7 +29,7 @@ "picture": "/resources/images/bios/naomi.jpg", "twitter": "naomitraveller", "website": "http://google.com/+NaomiBlack", - "bio": "Naomi is the Technical Program Manager for Angular, including Angular Material and AngularDart. An unrepentant generalist, she's been at Google since 2006, leading projects ranging from Accessibility to Google Transit. She fights daleks in her spare time.", + "bio": "Naomi is Angular's TPM generalist and jack-of-all-trades. She supports Angular's internal Google users and solves hard-to-define problems. She's been at Google since 2006, as a technical program manager on projects ranging from Accessibility to Google Transit. She fights daleks in her spare time.", "type": "Lead" }, @@ -40,14 +38,25 @@ "picture": "/resources/images/bios/brad-green.jpg", "twitter": "bradlygreen", "website": "https://plus.google.com/+BradGreen", - "bio": "Brad Green works at Google as an engineering director. Brad manages the Google Sales Platform suite of projects as well as the AngularJS framework. Prior to Google, Brad worked on the early mobile web at AvantGo, founded and sold startups, and spent a few hard years toiling as a caterer. Brad's first job out of school was as lackey to Steve Jobs at NeXT Computer writing demo software and designing his slide presentations. Brad lives in Mountain View, CA with his wife and two children.", + "bio": "Brad Green works at Google as an engineering director. Brad manages the Google Sales Platform suite of projects as well as the AngularJS framework. Prior to Google, Brad worked on the early mobile web at AvantGo, founded and sold startups, and spent a few hard years toiling as a caterer. Brad's first job out of school was as lackey to Steve Jobs at NeXT Computer writing demo software and designing his slide presentations. Brad enjoys throwing dinner parties with his wife Heather and putting on plays with his children.", "type": "Lead" }, "juleskremer": { "name": "Jules Kremer", "picture": "/resources/images/bios/juleskremer.jpg", - "bio": "Jules is a TPM on the Angular team. When not working with developers, Jules is often bending into pretzel-like shapes, climbing mountains or drinking really awesome beer.", + "twitter": "jules_kremer", + "website": "https://plus.google.com/+JulesKremer", + "bio": "Jules is Head of Angular Developer Relations at Google. When not working with developers, Jules is often bending into pretzel-like shapes, climbing mountains or drinking really awesome beer.", + "type": "Lead" + }, + + "jelbourn": { + "name": "Jeremy Elbourn", + "picture": "/resources/images/bios/jelbourn.jpg", + "twitter": "jelbourn", + "website": "https://plus.google.com/+JeremyElbourn/", + "bio": "Angular Material Team Lead. FE Engineer @ Google specializing in AngularJS, component design, and the cleanest of code.", "type": "Lead" }, @@ -56,7 +65,7 @@ "picture": "/resources/images/bios/pete.jpg", "twitter": "petebd", "website": "http://www.bacondarwin.com/", - "bio": "Angular 1 for JS Team Lead. Pete has been working on the core team since 2012 and became the team lead for the Angular 1 for JS branch in November 2014. He has co-authored a book on AngularJS and regularly talks about and teaches Angular.", + "bio": "AngularJS for JS Team Lead. Pete has been working on the core team since 2012 and became the team lead for the AngularJS for JS branch in November 2014. He has co-authored a book on AngularJS and regularly talks about and teaches Angular.", "type": "Lead" }, @@ -65,32 +74,39 @@ "picture": "/resources/images/bios/thomas.jpg", "twitter": "ThomasBurleson", "website": "http://www.solutionoptimist.com/", - "bio": "Thomas is a software architect for commercial & open-source web solutions. With over 15 years working on thin-client RUX [using Flex and Angular], Thomas is passionate about software excellence, and distributed team-development. Thomas is the Team Lead for Google's Angular Material OSS. His mission is to work with a fantastic team and help deliver a framework of UX components for the AngularJS frameworks (v1.x and v2). He previously worked at SiriusXM as a UX and Principal Architect for their Internet Radio applications.", + "bio": "AngularJS Material and @angular/flex-layout Team Lead. Thomas joined the core team in 2014. He leads a team of developers working on UX components for AngularJS.", "type": "Lead" }, - - "victorsavkin": { - "name": "Victor Savkin", - "picture": "/resources/images/bios/victor.jpg", - "twitter": "victorsavkin", - "bio": "Victor works on Angular at Google. He is interested in functional programming and client-side applications. Being a language nerd he spends a lot of his time playing with TypeScript, Dart, Elm, Haskell, and Clojure.", + "stephenfluin": { + "name": "Stephen Fluin", + "picture": "/resources/images/bios/stephenfluin.jpg", + "twitter": "stephenfluin", + "website": "https://plus.google.com/+stephenfluin", + "bio": "Stephen is a Developer Advocate working on the Angular team. Before joining Google, he was a Google Expert. Stephen loves to help enterprises use technology more effectively.", + "type": "Google" + }, + "robwormald": { + "name": "Rob Wormald", + "picture": "/resources/images/bios/rob-wormald.jpg", + "twitter": "robwormald", + "website": "http://github.com/robwormald", + "bio": "Rob is a Developer Advocate on the Angular team at Google. He's the Angular team's resident reactive programming geek and founded the Reactive Extensions for Angular project, ngrx.", + "type": "Google" + }, + "aaronzhang": { + "name": "Aaron Zhang (章小飞)", + "picture": "/resources/images/bios/xiaofei.jpg", + "twitter": "", + "website": "http://github.com/damoqiongqiu", + "bio": "Aaron is Angular's developer PM in China. He is the lead for angular.cn and social channels in China, and helps developers in China's enterprise and open source communities to be successful with Angular. One of the earliest Angular developers in China since Angular 2012, he translated the first books on Angular into Chinese. Aaron joined the Google team in 2016.", "type": "Google" }, - "tobias": { "name": "Tobias Bosch", "picture": "/resources/images/bios/tobias.jpg", "twitter": "tbosch1009", "website": "https://plus.google.com/+TobiasBosch", - "bio": "Tobias Bosch is a software engineer at Google. He is part of the Angular core team and works on Angular 2.", - "type": "Google" - }, - - "brianford": { - "name": "Brian Ford", - "picture": "/resources/images/bios/brian-ford.jpg", - "twitter": "briantford", - "bio": "Brian works on the AngularJS core team at Google where he tries his very best to make computers do the right thing.", + "bio": "Tobias Bosch is a software engineer at Google. He is part of the Angular core team and works on Angular.", "type": "Google" }, @@ -107,19 +123,10 @@ "name": "David East", "picture": "/resources/images/bios/david-east.jpg", "twitter": "_davideast", + "website":"https://github.com/davideast", "bio": "David East is a Developer Programs Engineer at Google. He works full-time on the Firebase team and part-time on the Angular core team.", "type": "Google" }, - - "jeffcross": { - "name": "Jeff Cross", - "picture": "/resources/images/bios/jeff-cross.jpg", - "twitter": "jeffbcross", - "website": "https://twitter.com/jeffbcross", - "bio": "Jeff is a member of the Angular core team at Google, focusing on data access and application performance. Jeff has an extensive background in open source software, marketing, and user experience design. When not in front of a computer, he spends his time doing whatever his kids tell him to do, which usually involves playing music or making gadgets.", - "type": "Google" - }, - "alexeagle": { "name": "Alex Eagle", "picture": "/resources/images/bios/alex-eagle.jpg", @@ -138,14 +145,6 @@ "type": "Google" }, - "yegor": { - "name": "Yegor Jbanov", - "picture": "/resources/images/bios/yegor.jpg", - "website": "http://google.com/+YegorJbanov", - "bio": "I'm Yegor and I work on Angular 2 for Dart and performance.", - "type": "Google" - }, - "julieralph": { "name": "Julie Ralph", "picture": "/resources/images/bios/julie-ralph.jpg", @@ -160,25 +159,7 @@ "picture": "/resources/images/bios/alex-rickabaugh.jpg", "twitter": "synalx", "website": "https://plus.google.com/+AlexRickabaugh/about", - "bio": "I am a new member of the Angular team, solving challenges of data access and RPC for applications of any scale.", - "type": "Google" - }, - - "jelbourn": { - "name": "Jeremy Elbourn", - "picture": "/resources/images/bios/jelbourn.jpg", - "twitter": "jelbourn", - "website": "https://plus.google.com/+JeremyElbourn/", - "bio": "FE Engineer @ Google specializing in AngularJS, component design, and the cleanest of code.", - "type": "Google" - }, - - "alexwolfe": { - "name": "Alex Wolfe", - "picture": "/resources/images/bios/alex-wolfe.jpg", - "twitter": "alexwolfe", - "website": "https://github.com/alexwolfe", - "bio": "Alex is the Head of UX for Firebase at Google and leads the design and development for the website, dashboard, and docs. Alex helps lead the design and development for the Angular.io website. He has been designing and building products for over 15 years and has helped grow over 10 startups in the valley. Prior to joining Firebase, he was the Head of UX/UI for AdRoll. Alex is an avid tennis player and a former Street Fighter 2 World Champion.", + "bio": "Core team member working to optimize the Angular platform for the next generation of applications, including mobile. Before joining the Angular team, Alex worked in the Google sales organization where he helped build the first large Angular application within Google.", "type": "Google" }, @@ -200,12 +181,46 @@ "type": "Google" }, + "hansl": { + "name": "Hans Larsen", + "picture": "/resources/images/bios/hansl.jpg", + "twitter": "hanslatwork", + "website": "http://www.codingatwork.com/", + "bio": "Hans is a software engineer at Google on the Angular team and was previously at Slack. He works everyday to help make it easier for everyone to create beautiful, consistent web applications using Angular, using Material Design components and the CLI tool.", + "type": "Google" + }, + + "victorsavkin": { + "name": "Victor Savkin", + "picture": "/resources/images/bios/victor.jpg", + "twitter": "victorsavkin", + "website": "http://victorsavkin.com/", + "bio": "Victor has been on the Angular team since the inception of Angular. While at Google, Victor developed dependency injection, change detection, forms, and the router. Today he is a co-founder at nrwl.io.", + "type": "Community" + }, + "jeffcross": { + "name": "Jeff Cross", + "picture": "/resources/images/bios/jeff-cross.jpg", + "twitter": "jeffbcross", + "website": "https://twitter.com/jeffbcross", + "bio": "Jeff was one of the earliest core team members on AngularJS. He developed the Angular http and AngularFire modules, contributed to RxJS 5, and was most recently the Tech Lead of the Angular Mobile team at Google. Jeff is a former Googler and co-founder at nrwl.io.", + "type": "Community" + }, + "alexwolfe": { + "name": "Alex Wolfe", + "picture": "/resources/images/bios/alex-wolfe.jpg", + "twitter": "alexwolfe", + "website": "https://github.com/alexwolfe", + "bio": "Alex built and designed the original angular.io website, and the reboot of the Angular logo. An X-Googler, Alex has been designing and building products for over 15 years and helped grow over 10 startups in the valley. Alex is an avid tennis player and a former Street Fighter 2 World Champion.", + "type": "Community" + }, + "marcy": { "name": "Marcy Sutton", "picture": "/resources/images/bios/marcy.jpg", "twitter": "marcysutton", "website": "http://marcysutton.com/", - "bio": "Marcy Sutton is a developer at Adobe in Seattle working on Angular accessibility and Material Design. She is a primary contributor to ngAria, the accessibility module, as well as the author of a new accessibility plug-in for Protractor. She's also in love with riding bicycles.", + "bio": "Marcy Sutton is a senior front-end engineer at Deque Systems, where she works on the axe-core team focusing on accessibility test integrations. Marcy is passionate about making the web accessible for everyone. She is a core team member to Angular Material, where she regularly brings her accessibility expertise to the table–she is also a primary contributor to the ngAria module as well as an accessibility plugin for Protractor. She's in love with riding bicycles and snowboards and can often be found outside.", "type": "Community" }, @@ -222,50 +237,59 @@ "name": "Lucas Mirelmann", "picture": "/resources/images/bios/lucas.jpg", "twitter": "lgalfaso", + "website": "https://github.com/lgalfaso", "bio": "Lucas works as a Software Engineer at Google and is a core Angular contributor.", "type": "Google" }, - "tonyc": { - "name": "Tony Childs", - "picture": "/resources/images/bios/tonyc.jpg", - "twitter": "javatricks", - "website": "http://www.stupidjavatricks.com/", - "bio": "Tony Childs is a consultant working for Google. He is a contributor to the Angular Material project and is responsible for the md-icon component.", + "kathy": { + "name": "Kathy Walrath", + "picture": "/resources/images/bios/kathy.jpg", + "twitter": "kwalrath", + "website": "https://plus.google.com/+KathyWalrath", + "bio": "Kathy writes and edits docs about Dart and related technologies. Before Google, she worked at Sun, NeXT, and HP. Long ago, Kathy co-created and maintained The Java Tutorial.", "type": "Google" }, - "ryan": { - "name": "Ryan Schmukler", - "picture": "/resources/images/bios/ryan.jpg", - "twitter": "rschmukler", - "website": "http://slingingcode.com/", - "bio": "Developer on ngMaterial. Full-stack JavaScript hacker. Open-source contributor with libraries totaling over 225K downloads.", - "type": "Community" + "kara": { + "name": "Kara Erickson", + "picture": "/resources/images/bios/kara-erickson.jpg", + "twitter": "karaforthewin", + "website": "https://github.com/kara", + "bio": "Kara is a software engineer on the Angular team at Google and a co-organizer of the Angular-SF Meetup. Prior to Google, she helped build UI components in Angular for guest management systems at OpenTable. She enjoys snacking indiscriminately and probably other things too.", + "type": "Google" }, - "rmesserle": { - "name": "Robert Messerle", - "picture": "/resources/images/bios/rmesserle.jpg", - "twitter": "Bobbo_O", - "bio": "Robert is a software engineer on the Angular team at Google, working primarily on the Angular Material project.", + "chuckj": { + "name": "Chuck Jazdzewski", + "picture": "/resources/images/bios/chuckj.jpg", + "twitter": "chuckjaz", + "website": "http://removingalldoubt.com/", + "bio": "Chuck is a Software Engineer on the Angular team at Google. He is a programming language geek, UI framework and component library veteran, and has a passion for simplifying the task of programming. Before Google, he worked at Microsoft and Borland.", "type": "Google" }, - - "kathy": { - "name": "Kathy Walrath", - "picture": "/resources/images/bios/kathy.jpg", - "twitter": "kwalrath", - "website": "https://plus.google.com/+KathyWalrath", - "bio": "Kathy writes and edits docs about Dart and related technologies. Before Google, she worked at Sun, NeXT, and HP. Long ago, Kathy co-created and maintained The Java Tutorial.", + "vikram": { + "name": "Vikram Subramanian", + "picture": "/resources/images/bios/vikram.jpg", + "twitter": "vikerman", + "bio": "Vikram is a Software Engineer on the Angular team focused on Engineering Productivity. That means he makes sure people on the team can move fast and not break things. Vikram enjoys doing Yoga and going on walks with his daughter.", "type": "Google" }, - "scott": { - "name": "Scott Hyndman", - "picture": "/resources/images/bios/scott.jpg", - "bio": "Scott works for Google on the Material Design team, where he brings designers' dreams to life on the web.", + "maxsills": { + "name": "Max Sills", + "picture": "/resources/images/bios/max-sills.jpg", + "twitter": "angularjs", + "website": "http://google-opensource.blogspot.com/", + "bio": "Max Sills is Angular's Open Source lawyer.", "type": "Google" }, + "shannon": { + "name": "Shannon Ayres", + "picture": "/resources/images/bios/shannon.jpg", + "bio": "Shannon is a technical editor in Developer Relations at Google. She loves movies, especially Sunset Boulevard, and her favorite TV show is The Walking Dead. Her mission: Righting wrong writing!", + "type": "Google" + }, + "pawel": { "name": "Pawel Kozlowski", "picture": "/resources/images/bios/pawel.jpg", @@ -279,7 +303,7 @@ "picture": "/resources/images/bios/michal.jpg", "twitter": "m_gol", "website": "https://plus.google.com/u/0/103101124310040612163/", - "bio": "Front-end developer at Laboratorium EE, core contributor to Angular & jQuery. Makes sure Angular 1 & jQuery work fine together. Interested in new JavaScript standards.", + "bio": "Front-end developer at Laboratorium EE, core contributor to Angular & jQuery. Makes sure AngularJS & jQuery work fine together. Interested in new JavaScript standards.", "type": "Community" }, @@ -293,9 +317,9 @@ "elad": { "name": "Elad Bezalel", "picture": "/resources/images/bios/eladbezalel.jpg", - "website": "https://github.com/EladBezalel", - "bio": "Elad is a fullstack developer with a very storng love for design. Since 8 years old, he's been designing in Photoshop and later on fell in love with programing. This strong bond between design and computer programming gave birth to a new kind of love. And he is currently doing the combination of both, as a core member of the ngMaterial project.", - "type": "Community" + "website": "https://github.com/EladBezalel", + "bio": "Elad is a fullstack developer with a very strong love for design. Since 8 years old, he's been designing in Photoshop and later on fell in love with programing. This strong bond between design and computer programming gave birth to a new kind of love. And he is currently doing the combination of both, as a core member of the ngMaterial project.", + "type": "Community" }, "marclaval": { @@ -303,7 +327,7 @@ "picture": "/resources/images/bios/marclaval.jpg", "twitter": "marclaval", "website": "https://github.com/mlaval", - "bio": "Marc is a manager at Amadeus where he leads the team in charge of developing and recommending UI frameworks for the company. He is also an open source developer and a contributor to Angular 2.", + "bio": "Marc is a manager at Amadeus where he leads the team in charge of developing and recommending UI frameworks for the company. He is also an open source developer and a contributor to Angular.", "type": "Community" }, @@ -321,15 +345,7 @@ "picture": "/resources/images/bios/patrick-stapleton.jpg", "twitter": "gdi2290", "website": "https://angularclass.com/", - "bio": "Also know as PatrickJS where JS stands for his middle and last names. Patrick is very active in Open-Source with over 4,300+ contributions in the last year alone on projects such as Angular2, AngularJS, FalcorJS, Docker, Bootstrap, gulp, and redis to name a few. He is also working on the development of Angular 2 server-side rendering as Universal Angular 2 and teaching Modern Web Development at AngularClass. He was previously the CTO of Keychain Logistics, a HackReactor Instructor and Alum.", - "type": "Community" - }, - - "cburgdorf": { - "name": "Christoph Burgdorf", - "picture": "/resources/images/bios/cburgdorf.jpg", - "website": "https://github.com/cburgdorf", - "bio": "Christoph began with programming at the age of 10 with BASIC but has since moved on to become proficient in various different programming languages and technologies. Christoph has contributed to many projects, including AngularJS, jquery-ui, TodoMVC and the banshee media player and is also the creator of the nickel.rs framework. When he's not evangelizing Git, he likes to travel the world with his bicycle.", + "bio": "Also know as PatrickJS where JS stands for his middle and last names. Patrick is very active in Open-Source with over 4,300+ contributions in the last year alone on projects such as Angular, AngularJS, FalcorJS, Docker, Bootstrap, gulp, and redis to name a few. He is also working on the development of Angular server-side rendering as Universal Angular and teaching Modern Web Development at AngularClass. He was previously the CTO of Keychain Logistics, a HackReactor Instructor and Alum.", "type": "Community" }, @@ -345,9 +361,11 @@ "name": "Ward Bell", "picture": "/resources/images/bios/wardbell.jpg", "website": "https://github.com/wardbell", + "twitter": "wardbell", "bio": "Ward is an all-around developer with JavaScript, node, and .net chops. He's a frequent conference speaker and podcaster, trainer, Google Developer Expert for Angular, Microsoft MVP, and PluralSight author. He is also president of IdeaBlade, an enterprise software consulting firm and the makers of breeze.js. He would like to get more sleep and spend more time in the mountains.", "type": "Community" }, + "johnpapa": { "name": "John Papa", "picture": "/resources/images/bios/john-papa.jpg", @@ -356,14 +374,169 @@ "bio": "John is a Google Developer Expert, Microsoft Regional Director and MVP, frequent author of courses for Pluralsight, a former technology Evangelist for Microsoft front end teams, and author of the popular Angular Style Guide. He can often be found speaking around the world at keynotes and sessions for many conferences. You can always find John at johnpapa.net or on twitter at @john_papa.", "type": "Community" }, + "martinstaffa": { "name": "Martin Staffa", "picture": "/resources/images/bios/martinstaffa.jpg", "twitter": "Narretz", - "bio": "Martin is an English major turned web developer who loves frontend stuff. He's been part of the Angular 1 team since 2014. If you can't find him roaming the Github issue queues, he's probably out with his camera somewhere.", + "bio": "Martin is an English major turned web developer who loves frontend stuff. He's been part of the AngularJS team since 2014. If you can't find him roaming the Github issue queues, he's probably out with his camera somewhere.", "type": "Community" - } + }, + + "topherfangio": { + "name": "Topher Fangio", + "picture": "/resources/images/bios/topherfangio.jpg", + "twitter": "topherfangio", + "website": "http://github.com/topherfangio", + "bio": "Topher loves the web and how it empowers new forms of creativity, connection and business. He is currently a core contributor on the Angular Material project and sometimes blogs about random things.", + "type": "Community" + }, + + "filipesilva": { + "name": "Filipe Silva", + "picture": "/resources/images/bios/filipe-silva.jpg", + "twitter": "filipematossilv", + "website": "http://github.com/filipesilva", + "bio": "Filipe is a passion-driven developer that always strives for the most elegant solution for each problem. He is currently an author for Angular.io, a core contributor for Angular-CLI and senior front end engineer at KonnectAgain. When not busy going through PRs, you can find him scouring reddit for new dinner recipes to cook or enjoying a craft beer in Dublin.", + "type": "Community" + }, + + "teropa": { + "name": "Tero Parviainen", + "picture": "/resources/images/bios/teropa.jpg", + "twitter": "teropa", + "website": "http://teropa.info/", + "bio": "Tero is an independent software developer and writer. He's been building web applications for his whole professional career, and has almost figured out how to do vertical centering in CSS.", + "type": "Community" + }, + + "deborah": { + "name": "Deborah Kurata", + "picture": "/resources/images/bios/deborah.jpg", + "twitter": "deborahkurata", + "website": "http://blogs.msmvps.com/deborahk/", + "bio": "Deborah is an independent software developer and author. She is author of several Pluralsight courses including: 'Angular 2: Getting Started'", + "type": "Community" + }, + + "jesusrodriguez": { + "name": "Jesús Rodríguez", + "picture": "/resources/images/bios/jesus-rodriguez.jpg", + "twitter": "foxandxss", + "website": "http://angular-tips.com/", + "bio": "Jesus is an open source lover, a book author and editor, and AngularUI lead developer. He is currently a core contributor to the UI Bootstrap project.", + "type": "Community" + }, + "torgeirhelgevold": { + "name": "Torgeir Helgevold", + "picture": "/resources/images/bios/torgeirhelgevold.jpg", + "twitter": "helgevold", + "website": "http://www.syntaxsuccess.com/", + "bio": "Torgeir (Tor) is a front-end architect with a passion for JavaScript development. He is also an author for angular.io and an active tech blogger.", + "type": "Community" + }, + + "fatimaremtullah": { + "name": "Fatima Remtullah", + "picture": "/resources/images/bios/fatima.jpg", + "twitter": "amitafr", + "website": "http://www.amitafremtullah.com/", + "bio": "Fatima is a Product Designer and Front-End Developer. When she is not nerding out she is probably eating an abundance of cookies.", + "type": "Community" + }, + + "eric": { + "name": "Eric Jimenez", + "picture": "/resources/images/bios/eric.jpg", + "twitter": "_ericjim", + "website": "http://eric.to/", + "bio": "Eric is a gamer, writer, and programmer.", + "type": "Community" + }, + + "mikeryan": { + "name": "Mike Ryan", + "picture": "/resources/images/bios/mikeryan.jpg", + "twitter": "mikeryan52", + "website": "https://medium.com/@MikeRyan52", + "bio": "Mike Ryan is a Software Engineer at Synapse Wireless, working on solving challenging problems in the internet-of-things space. He is an advocate of reactive programming and a core contributor to the ngrx project.", + "type": "Community" + }, + + "rex": { + "name": "Rex Ye", + "picture": "/resources/images/bios/rex.jpg", + "twitter": "rexebin", + "bio": "Rex is a full-stack developer. He maintains the Angular.cn website with his old pal Ralph Wang and he plays a key role in bridging between the Chinese Angular community and the world-wide community. He loves playing with flashy new technologies and enjoys the challenge of mastering new skills. His biggest challenge to date is figuring out how to sooth a crying 4-month-old baby.", + "type": "Community" + }, + + "ralph": { + "name": "Ralph Wang", + "picture": "/resources/images/bios/ralph.jpg", + "twitter": "ralph_wang_gde", + "bio": "Ralph(Zhicheng Wang) is a senior consultant at ThoughWorks and also a GDE. He is a technology enthusiast and he is a passionate advocate of “Simplicity, Professionalism and Sharing”. In his eighteen years of R&D career, he worked as tester, R&D engineer, project manager, product manager and CTO. He is looking forward to the birth of his baby.", + "type": "Community" + }, + + "brandonroberts": { + "name": "Brandon Roberts", + "picture": "/resources/images/bios/brandonroberts.jpg", + "twitter": "brandontroberts", + "website": "https://github.com/brandonroberts", + "bio": "Brandon is a front-end developer for a game studio developing web applications for STEM-based learning games. He is also a natural born troubleshooter who helps solve Angular issues on Github and Gitter support channels, particularly dealing with routing. He is also a member of the Angular docs team.", + "type": "Community" + }, + + "crisbeto": { + "name": "Kristiyan Kostadinov", + "picture": "/resources/images/bios/crisbeto.jpg", + "website": "http://crisbeto.com/", + "bio": "Kristiyan is a front-end developer, passionate open-source contributor and a core team member on Angular Material.", + "type": "Community" + }, + + "gkalpak": { + "name": "Georgios Kalpakas", + "picture": "/resources/images/bios/gkalpak.jpg", + "twitter": "gkalpakas", + "website": "https://github.com/gkalpak", + "bio": "George is a Software Engineer with a passion for chess, robotics and automating stuff. He has a strong need to know how things work (so if you already know, he'd love to have a talk with you). He has been a member of the AngularJS team since 2014. When not doing geeky stuff, he is probably trying to convince his wife and kids to apply programming principles in real life. (Or is it the other way around?)", + "type": "Community" + }, + + "kapunahelewong": { + "name": "Kapunahele Wong", + "picture": "/resources/images/bios/kapunahelewong.jpg", + "website": " https://github.com/kapunahelewong", + "twitter": "kapunahele", + "bio": "Kapunahele is a front-end developer and contributor to angular.io. She loves just about anything to do with JavaScript, Angular and electronics. She enjoys mapping Hawaiian star names and constellations to Western ones and loves dancing native Hawaiian hula.", + "type": "Community" + }, + + "devversion": { + "name": "Paul Gschwendtner", + "picture": "/resources/images/bios/devversion.jpg", + "website": "http://github.com/DevVersion/", + "twitter": "DevVersion", + "bio": "Paul is a 16-year-old developer living in Germany. While he attends school, Paul works as a core team member on Angular Material. Paul focuses on tooling and building components for Angular.", + "type": "Community" + }, + + "mmalerba": { + "name": "Miles Malerba", + "picture": "/resources/images/bios/mmalerba.jpg", + "bio": "Miles is a software engineer on the Angular Material team at Google. In addition to Javascripting he enjoys eating food and ogling cute puppies.", + "type": "Google" + }, + + "jasonaden": { + "name": "Jason Aden", + "picture": "/resources/images/bios/jasonaden.jpg", + "bio": "Jason is a software engineer at Google on the Angular Core team. He is enthusiastic about Angular and application development in the modern age. In his free time Jason enjoys spending time with his wife and four children and doing outdoor activities (hiking, fishing, snowboarding, etc.).", + "type": "Google" + } } } } diff --git a/package.json b/package.json index 71d1e12eaf..2879b1483d 100644 --- a/package.json +++ b/package.json @@ -2,63 +2,75 @@ "name": "angular.io", "version": "0.0.0", "private": true, - "description": "Angular 2 documentation", + "description": "Angular documentation", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "harp": "harp", "live-server": "live-server", - "test-api-builder": "jasmine-node tools/api-builder" + "test-api-builder": "jasmine-node tools/api-builder", + "protractor": "protractor" }, "repository": { "type": "git", "url": "https://github.com/angular/angular.io.git" }, - "licenses": [ - { - "type": "Apache", - "url": "http://www.apache.org/licenses/LICENSE-2.0.html" - } - ], + "license": "MIT", "bugs": { "url": "" }, "devDependencies": { - "archiver": "^0.16.0", - "assert-plus": "^0.1.5", + "archiver": "^1.0.0", + "assert-plus": "^1.0.0", + "bootstrap": "3.3.6", + "broken-link-checker": "0.7.1", "browser-sync": "^2.9.3", "canonical-path": "0.0.2", - "del": "^1.2.0", + "cheerio": "^0.20.0", + "codelyzer": "0.0.26", + "cross-spawn": "^4.0.0", + "del": "^2.2.0", "dgeni": "^0.4.0", - "dgeni-packages": "^0.11.0", + "dgeni-packages": "^0.16.0", "diff": "^2.1.3", - "fs-extra": "^0.24.0", - "glob": "^5.0.14", - "globule": "^0.2.0", + "fs-extra": "^0.30.0", + "globby": "^4.0.0", "gulp": "^3.5.6", + "gulp-env": "0.4.0", + "gulp-less": "^3.1.0", + "gulp-sass": "^2.3.2", "gulp-task-listing": "^1.0.1", + "gulp-tslint": "^5.0.0", "gulp-util": "^3.0.6", "gulp-watch": "^4.3.4", - "harp": "^0.19.0", + "harp": "^0.21.0", "html2jade": "^0.8.4", "indent-string": "^2.1.0", "jasmine-core": "^2.3.4", "jasmine-node": "^1.14.5", + "jsdom": "^9.2.1", "jsonfile": "^2.2.2", "karma": "^0.13.10", - "karma-chrome-launcher": "^0.2.0", - "karma-jasmine": "^0.3.6", - "live-server": "^0.8.1", - "lodash": "^3.10.1", + "karma-chrome-launcher": "^1.0.1", + "karma-jasmine": "^1.0.2", + "live-server": "^1.0.0", + "lodash": "^4.13.1", "marked": "^0.3.5", - "minimatch": "^2.0.10", + "minimatch": "^3.0.0", "mkdirp": "^0.5.1", "node-html-encoder": "0.0.2", - "nodegit": "0.5.0", - "path": "^0.11.14", - "prompt": "^0.2.14", + "nodegit": "0.13.0", + "path": "^0.12.7", + "prompt": "^1.0.0", + "protractor": "^3.0.0", "q": "^1.4.1", - "typescript": "~1.5.3", - "yargs": "^3.23.0" - } + "tree-kill": "^1.0.0", + "tslint": "^3.15.1", + "typescript": "~2.0.10", + "yargs": "^4.7.1" + }, + "dependencies": { + "jstransformer-marked": "^1.0.1" + }, + "homepage": "http://angular.io/" } diff --git a/public/_data.json b/public/_data.json index 261b31c3db..3b224f69eb 100644 --- a/public/_data.json +++ b/public/_data.json @@ -1,12 +1,12 @@ { "index": { "hero": "home", - "title": "Angular is a development platform for building mobile and desktop applications" + "title": "One framework.", + "subtitle": "Mobile & desktop." }, "features": { - "title": "Features & Benefits", - "subtitle": "Powerful Features for Developing Apps" + "title": "Features & Benefits" }, "contribute": { @@ -15,8 +15,17 @@ "autoformat": "true" }, - "download": { - "title": "Download Angular", - "subtitle": "Setup & Installation" + "events": { + "title": "Events", + "subtitle": "Where we'll be presenting" + }, + + "support": { + "title": "Support", + "subtitle": "Get help from the Angular Community" + }, + + "presskit": { + "title": "Press Kit" } -} \ No newline at end of file +} diff --git a/public/_includes/_cta-bar.jade b/public/_includes/_cta-bar.jade index 54a7016965..029bc7e2f9 100644 --- a/public/_includes/_cta-bar.jade +++ b/public/_includes/_cta-bar.jade @@ -1,3 +1,2 @@ .cta-bar - a(href="https://github.com/docs/ts/latest/quickstart.html" class="button button-large button-shield md-raised md-primary" md-button) Learn in 5 Min - a(href="https://github.com/download/" class="button button-large button-secondary" md-button) Download + a(href="https://github.com/docs/ts/latest/quickstart.html" class="button button-large button-shield md-raised " + "md-primary" md-button) Get Started diff --git a/public/_includes/_footer.jade b/public/_includes/_footer.jade index ec56e847d8..421c208aa1 100644 --- a/public/_includes/_footer.jade +++ b/public/_includes/_footer.jade @@ -1,48 +1,57 @@ -.main-footer - nav.background-silver.grid-fluid +if current.path[1] + - var language = current.path[1] +if current.path[2] + - var version = current.path[2].replace(/\_+/gm, ".") +if version && language + - var styleguide = "/docs/"+language+"/"+ version +"/styleguide.html" +else + - var styleguide = "/docs/ts/latest/styleguide.html" + +div(class="main-footer" data-swiftype-index="false") + nav.background-midnight.grid-fluid .c3.main-footer-branding .logo-inverse-large .c2 - h3.text-headline LIBRARIES + h3.text-headline RESOURCES ul.text-body - li Angular 2.0 - li Angular 1 for JS - li Angular 1 for Dart - li Angular Material - li AngularFire - - .c2 - h3.text-headline LEARN - - ul.text-body - li 5 Min Quickstart - li Step by Step Guide - li Full API - li Resources - li Design Docs & Notes + // TODO: (ericjim) make a libraries page to showcase all angular libraries + //li Libraries + li About + li Books & Training + li Tools & Libraries + li Community + li Press Kit .c2 h3.text-headline HELP ul.text-body + li Stack Overflow + li Gitter li Google Group - li Chat Room - li Report an Issue + li Report Issues + li Site Feedback - .c3 + .c2 h3.text-headline COMMUNITY ul.text-body - li Blog - li Google+ - li Twitter + li Events + li Meetups + li Twitter li GitHub + li Contribute + + .c2 + h3.text-headline LANGUAGES + ul.text-body + li 中文版 - footer(class="background-steel") - small.text-caption Powered by Google ©2010-2015. Code licensed under the Apache License, Version 2.0. Documentation licensed under CC BY 3.0. - a(aria-label="View Style Guide" href="https://github.com/docs/styleguide.html" title="Style Guide" class="styleguide-trigger text-snow" md-button) - span.icon-favorite \ No newline at end of file + footer(class="background-midnight") + small.text-caption Powered by Google ©2010-2017. Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0. + a(aria-label="View Style Guide" href=styleguide title="Style Guide" class="styleguide-trigger") + span.icon-favorite diff --git a/public/_includes/_head-include.jade b/public/_includes/_head-include.jade index 1ed9a2b002..489ba51bd2 100644 --- a/public/_includes/_head-include.jade +++ b/public/_includes/_head-include.jade @@ -1,14 +1,21 @@ - var language = current.path[1] - var version = '' +- var section = '' if current.path[2] - var version = current.path[2].replace(/\_+/gm, ".") +if current.path[3] + - var section = current.path[3].toUpperCase() + + if language == 'js' if language == 'dart' if title == "Angular" title #{title} +else if section + title #{title} - #{language} - #{section} else if language title #{title} - #{language} else @@ -34,8 +41,9 @@ meta(itemprop="description" content="#{description}") meta(itemprop="image" content="https://angular.io/resources/images/logos/standard/shield-large.png") link(rel="icon" type="image/x-icon" href="https://github.com/resources/images/icons/favicon.ico") -link(rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/angular_material/0.8.3/angular-material.min.css") -link(href='https://fonts.googleapis.com/css?family=Roboto:400,300,500,400italic,700' rel='stylesheet' type='text/css') +link(rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/angular_material/1.0.0/angular-material.min.css") +link(href="https://fonts.googleapis.com/css?family=Roboto:400,300,500,400italic,700" rel='stylesheet' type='text/css') +link(href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet") link(rel="stylesheet" href="https://github.com/resources/css/vendor/icomoon/style.css") link(rel="stylesheet" href="https://github.com/resources/css/vendor/animate.css") link(rel="stylesheet" href="https://github.com/resources/css/main.css") @@ -56,4 +64,4 @@ link(rel="stylesheet" href="https://github.com/resources/css/main.css") - \ No newline at end of file + diff --git a/public/_includes/_hero-home.jade b/public/_includes/_hero-home.jade index 16612ca475..b413803e3b 100644 --- a/public/_includes/_hero-home.jade +++ b/public/_includes/_hero-home.jade @@ -1,21 +1,13 @@ -header(class="background-sky") - .hero.background-lon-paper.is-large - h1.text-headline.hero-logo #{title} - - .hero-cta - a(href="https://github.com/docs/ts/latest/quickstart.html" class="md-raised button button-large button-plain" - md-button) Learn in 5 mins - a(href="https://github.com/download/" class="button button-large button-navy" md-button) Download - - != partial("_social-icons") - - -.banner.is-centered - - a(href="http://angularconnect.com/?utm_source=angular-io&utm_medium=angular-io&utm_campaign=angular-io" class="button button-large" md-button) - .banner-ng-annoucement.angularconnect-logo - h3.text-display-1 AngularConnect 2015 - h4.text-subhead The European Angular Conference - div - a(href="https://www.youtube.com/channel/UCzrskTiT_ObAk3xBkVxMz5g" class="button button-xlarge button-navy" md-button) Watch Talk Videos - +header(class="background-sky l-relative") + + .hero.background-superhero-paper.is-large + img(class="hero-logo" src='https://github.com/resources/images/logos/angular/angular.svg') + h1.text-headline #{title}
#{subtitle} + a(href="https://github.com/docs/ts/latest/quickstart.html" class="hero-cta md-raised button button-large button-plain" md-button) Get Started + + announcement-bar + .announcement-bar-slide.clearfix + img(src="https://github.com/resources/images/logos/angular/angular-banner-logo-grey.png" width="64") + p Angular v4.0 is out! Smaller, faster, no biggie... + a(href="http://angularjs.blogspot.com/2017/03/angular-400-now-available.html" target="_blank" class="button md-button") Learn more + diff --git a/public/_includes/_hero.jade b/public/_includes/_hero.jade index 308f02df21..bf346a6a3a 100644 --- a/public/_includes/_hero.jade +++ b/public/_includes/_hero.jade @@ -1,16 +1,41 @@ -- var textFormat = '' +//- template: public/_includes/_hero +//- Refer to jade.template.html and addJadeDataDocsProcessor to figure out where the context of this jade file originates +- var textFormat = ''; - var headerTitle = title + (typeof varType !== 'undefined' ? (': ' + varType) : ''); +- var capitalize = function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } +- var useBadges = docType || stability; +//- renamer :: String -> String +//- Renames `Let` and `Var` into `Const` +- var renamer = function renamer(docType) { +- return (docType === 'Let' || docType === 'Var') ? 'Const' : docType +- } if current.path[4] && current.path[3] == 'api' - var textFormat = 'is-standard-case' -header(class="hero background-sky") - h1(class="hero-title text-display-1 #{textFormat}") #{headerTitle} +if current.path.indexOf('cheatsheet') > 0 || current.path[current.path.length-2] === 'api' + - var base = current.path[4] ? '../guide' : './guide'; + - var ngVersion = '(v4.X.Y)'; + +header.hero.background-sky + h1(class="hero-title #{textFormat}") #{headerTitle} !{ngVersion} + + if useBadges + if stability + span(class="badge is-#{stability}"). + #{capitalize(stability)} + if security + span(class="badge is-deprecated"). + Security Risk + + //- CLEAR FLOAT ELEMENTS + .clear if subtitle - h2.hero-subtitle.text-subhead #{subtitle} + h2.hero-subtitle #{subtitle} + else if docType + h2.hero-subtitle #{renamer(capitalize(docType))} - else if current.path[0] == "docs" - != partial("_version-dropdown") + if current.path[3] == 'api' && current.path[1] == 'dart' + block breadcrumbs - != partial("_social-icons") \ No newline at end of file diff --git a/public/_includes/_main-nav.jade b/public/_includes/_main-nav.jade index 7925ad6d48..5ecfcd7108 100644 --- a/public/_includes/_main-nav.jade +++ b/public/_includes/_main-nav.jade @@ -1,12 +1,13 @@ -md-toolbar(class="main-nav background-regal l-pinned-top l-layer-5") - nav - h1 Angular by Google +- var language = current.path[1] || 'ts' +- if (language !== 'ts' || language !== 'js' || language !== 'dart') { language = 'ts'; } - button(class="main-nav-button main-nav-mobile-trigger l-right" aria-label="View Menu" ng-click="toggleMainMenu($event)" md-button) Site Menu +nav(data-swiftype-index="false" class="main-nav l-pinned-top l-layer-5", scroll-y-offset-element) + h1 Angular by Google - ul(ng-class="showMainNav ? 'is-visible' : ''") - li.l-left Features - li.l-left Docs - li.l-left About - li.l-left Contribute - li.l-right Install \ No newline at end of file + button(class="main-nav-button main-nav-mobile-trigger l-right" aria-label="View Menu" ng-click="appCtrl.toggleMainMenu($event)" md-button) Site Menu + + ul(ng-class="appCtrl.showMainNav ? 'is-visible' : ''") + li.l-left Features + li.l-left Docs + li.l-left Events + li.l-right Get Started diff --git a/public/_includes/_next-item.jade b/public/_includes/_next-item.jade index 0152402f0b..df379265ae 100644 --- a/public/_includes/_next-item.jade +++ b/public/_includes/_next-item.jade @@ -1,20 +1,24 @@ - var currentPage = false - var nextPage = false +- var hideNextPage = false; + - var data = public.docs[current.path[1]][current.path[2]][current.path[3]]._data for page, slug in data - if slug != "index" - // CHECK IF CURRENT PAGE IS SET, THEN SET NEXT PAGE - if currentPage - if !nextPage + // CHECK IF CURRENT PAGE IS SET, THEN SET NEXT PAGE + if currentPage + if !nextPage && page.nextable && !page.hide + if !hideNextPage .l-sub-section h3 Next Step a(href="https://github.com/docs/#{current.path[1]}/#{current.path[2]}/#{current.path[3]}/#{slug}.html") #{page.title} - //NEXT PAGE HAS NOW BEEN SET - - var nextPage = true + //NEXT PAGE HAS NOW BEEN SET + - var nextPage = true + + - hideNextPage = page.hideNextPage - // SET CURRENT PAGE FLAG WHEN YOU PASS IT - if current.path[4] == slug - - var currentPage = true \ No newline at end of file + // SET CURRENT PAGE FLAG WHEN YOU PASS IT + if current.path[4] == slug + - var currentPage = true diff --git a/public/_includes/_scripts-include.jade b/public/_includes/_scripts-include.jade index e32fe80975..aee83952ab 100644 --- a/public/_includes/_scripts-include.jade +++ b/public/_includes/_scripts-include.jade @@ -3,42 +3,37 @@ script(src="https://github.com/resources/js/vendor/prettify.js") script(src="https://github.com/resources/js/vendor/lang-basic.js") script(src="https://github.com/resources/js/vendor/lang-dart.js") script(src="https://github.com/resources/js/vendor/lodash.js") +script(src="https://github.com/resources/js/vendor/clipboard.min.js") -script(src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js") -script(src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-animate.min.js") -script(src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-aria.min.js") -script(src="https://ajax.googleapis.com/ajax/libs/angular_material/0.8.3/angular-material.min.js") +script(src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular.min.js") +script(src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular-animate.min.js") +script(src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.0/angular-aria.min.js") +script(src="https://ajax.googleapis.com/ajax/libs/angular_material/1.0.0/angular-material.min.js") + + + + + script(src="https://github.com/resources/js/site.js") +script(src="https://github.com/resources/js/util.js") script(src="https://github.com/resources/js/controllers/app-controller.js") +script(src="https://github.com/resources/js/controllers/resources-controller.js") +script(src="https://github.com/resources/js/directives/cheatsheet.js") +script(src="https://github.com/resources/js/directives/api-list.js") script(src="https://github.com/resources/js/directives/bio.js") +script(src="https://github.com/resources/js/directives/bold.js") +script(src="https://github.com/resources/js/directives/announcement-bar.js") +script(src="https://github.com/resources/js/directives/code.js") +script(src="https://github.com/resources/js/directives/copy.js") script(src="https://github.com/resources/js/directives/code-tabs.js") script(src="https://github.com/resources/js/directives/code-pane.js") script(src="https://github.com/resources/js/directives/code-example.js") - - - -script. - (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ - (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) - })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); - - ga('create', 'UA-8594346-15', 'auto'); - ga('send', 'pageview') - - -if current.path[0] == "docs" - - script. - (function(w,d,t,u,n,s,e){w['SwiftypeObject']=n;w[n]=w[n]||function(){ - (w[n].q=w[n].q||[]).push(arguments);};s=d.createElement(t); - e=d.getElementsByTagName(t)[0];s.async=1;s.src=u;e.parentNode.insertBefore(s,e); - })(window,document,'script','//s.swiftypecdn.com/install/v1/st.js','_st'); - - _st('install','VsuU7kH5Hnnj9tfyNvfK'); \ No newline at end of file +script(src="https://github.com/resources/js/directives/if-docs.js") +script(src="https://github.com/resources/js/directives/live-example.js") +script(src="https://github.com/resources/js/directives/scroll-y-offset-element.js") diff --git a/public/_includes/_scripts-minimum.jade b/public/_includes/_scripts-minimum.jade new file mode 100644 index 0000000000..a568f88d15 --- /dev/null +++ b/public/_includes/_scripts-minimum.jade @@ -0,0 +1,27 @@ + +script. + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); + + ga('create', 'UA-8594346-15', 'auto'); + ga('send', 'pageview') + + +if current.path[0] == "docs" || current.path[0] == "search" +script. + (function(w,d,t,u,n,s,e){w['SwiftypeObject']=n;w[n]=w[n]||function(){ + (w[n].q=w[n].q||[]).push(arguments);};s=d.createElement(t); + e=d.getElementsByTagName(t)[0];s.async=1;s.src=u;e.parentNode.insertBefore(s,e); + })(window,document,'script','//s.swiftypecdn.com/install/v2/st.js','_st'); + + _st('install','VsuU7kH5Hnnj9tfyNvfK','2.0.0'); + + + +script(src="https://www.gstatic.com/feedback/api.js" type="text/javascript") + + +script. + (function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+"://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}})(document,"script","twitter-wjs"); diff --git a/public/_includes/_social-icons.jade b/public/_includes/_social-icons.jade deleted file mode 100644 index 6e5d636514..0000000000 --- a/public/_includes/_social-icons.jade +++ /dev/null @@ -1,7 +0,0 @@ -.social-icons - button(aria-label="Share on G+" onClick="window.open('https://plus.google.com/share?url=#{siteURL}', '', 'height=500, width=600')" class="googleplus") - button(aria-label="Share on Twitter" onClick="window.open('https://twitter.com/share?text=Angular%20-%20#{description}', '', 'height=250, width=600')") - button(aria-label="Share on Facebook" onClick="window.open('https://www.facebook.com/sharer/sharer.php?u=#{siteURL}', '', 'height=500, width=600')" data-href="#{siteURL}" data-colorscheme="dark" data-layout="button_count" data-action="https://github.com/like" data-show-faces="false") - button(aria-label="Share on Linkedin" onClick="window.open('http://www.linkedin.com/shareArticle?mini=true&url=#{siteURL}&title=Angular&summary=#{description}.', '', 'height=625, width=500')") - -p(style="color: black;") #{url} \ No newline at end of file diff --git a/public/_includes/_util-fns.jade b/public/_includes/_util-fns.jade index b66ceb801a..7a755027c8 100644 --- a/public/_includes/_util-fns.jade +++ b/public/_includes/_util-fns.jade @@ -1,18 +1,134 @@ //- Mixins and associated functions -mixin makeExample(filePath, region, title, stylePatterns) +//- _docsFor: used to identify the language this version of the docs if for; +//- Should be one of: 'ts', 'dart' or 'js'. Set in lang specific _util-fns file. +- var _docsFor = ''; + +//- Should match `_docsFor`, but in this case provides the full capitalized +//- name of the language. +- var _Lang = 'TypeScript'; + +//- Simple "macros" used via interpolation in text: +//- e.g., the #{_priv}el variable has an `@Input` #{_decorator}. + +//- Use #{_decorator} whereever the word "decorator" is expected, provided it is not +//- preceded by the article "a". (E.g., will be "annotation" for Dart) +- var _decorator = 'decorator'; + +//- Articles (which toggle between 'a' and 'an'). Used for, e.g., +//- array vs. list; decorator vs. annotation. +- var _a = 'a'; +- var _an = 'an'; + +//- TS arrays vs. Dart lists +- var _Array = 'Array'; +- var _array = 'array'; + +//- Promise vs. Future, etc +- var _Promise = 'Promise'; +- var _PromiseUrl = 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'; +- var _PromiseLinked = '' + _Promise + ''; +- var _Observable = 'Observable'; + +//- Directories & folders +- var _appDir = 'app'; +- var _indexHtmlDir = 'project root'; +- var _mainDir = _appDir; + +//- Location of sample code +- var _liveLink = 'live link'; +- var _ngRepoURL = 'https://github.com/angular/angular'; +- var _ngDocRepoURL = 'https://github.com/angular/angular.io'; +- var _qsRepo = 'https://github.com/angular/quickstart'; +- var _qsRepoZip = _qsRepo + '/archive/master.zip'; + +- var _npm = 'npm'; + +//- NgModule related +- var _AppModuleVsAppComp = 'AppModule' +- var _appModuleTsVsAppCompTs = 'src/app/app.module.ts' +- var _appModuleTsVsMainTs = 'src/app/app.module.ts' +- var _bootstrapModule = 'bootstrapModule' +- var _declsVsDirectives = 'declarations' +- var _moduleVsComp = 'module' +- var _moduleVsRootComp = 'module' +- var _platformBrowserDynamicVsBootStrap = 'platformBrowserDynamic' + +//- Other +- var _truthy = 'truthy'; +- var _falsy = 'falsy'; + +//- Used to prefix identifiers that are private. In Dart this will be '_'. +- var _priv = ''; + +//- Use to conditionally include the block that follows +ifDocsFor(...). +//- Generally favor use of Jade named blocks instead. ifDocsFor is convenient +//- for prose that should appear only in one language version. +mixin ifDocsFor(langPattern) + if _docsFor.toLowerCase().match(langPattern.toLowerCase()) + block + +//- Use to map inlined (prose) TS paths into, say, Dart paths via the +//- adjustTsExamplePathForDart transformer function. +mixin adjExPath(path) + if adjustTsExamplePathForDart + | #{adjustTsExamplePathForDart(path)} + else + | #{path} + +mixin includeShared(filePath, region) + - var newPath = translatePath(filePath, region); + !=partial(newPath) + +mixin makeExample(_filePath, region, _title, stylePatterns) + - var adjustments = adjustExamplePathAndTitle({filePath:_filePath, title:_title}); + - var filePath = adjustments.filePath; + - var title = adjustments.title; - var language = attributes.language || getExtn(filePath); - - var format = attributes.format || "linenums"; - var frag = getFrag(filePath, region); - if (title) - .example-title #{title} - code-example(language="#{language}" format="#{format}") - != styleString(frag, stylePatterns) + - var defaultFormat = frag.split('\n').length > 2 ? "linenums" : ""; + - var format = attributes.format || defaultFormat; + - if (attributes.format === '.') format = ''; + - var avoid = !!attributes.avoid; + - var avoidClass = avoid ? 'is-anti-pattern' : ''; + + div(class="code-example #{avoidClass}") + if (title) + header + h4 #{title} + code-example(language="#{language}" format="#{format}") + != styleString(frag, stylePatterns) + +//- Like makeExample, but: (1) doesn't show line numbers. (2) If region +//- is omitted and title is 'foo (r)' then region is taken as 'r'. +//- (3) Title will always end with a phrase in parentheses; if no such +//- ending is given or is just (), then the title will be suffixed with +//- either "(excerpt)", or "(#{_region})" when _region is defined. +mixin makeExcerpt(_filePath, _region, _title, stylePatterns) + - var matches = _filePath.match(/(.*)\s+\(([^\)]*)\)$/); + - var parenText; + - if (matches) { _filePath = matches[1]; parenText = matches[2]; } + - var adjustments = adjustExamplePathAndTitle({filePath:_filePath, title:_title}); + - var filePath = adjustments.filePath; + - var title = adjustments.title; + - var region = _region || (_region === '' ? '' : parenText); + - var excerpt = parenText || region || 'excerpt'; + - if (title) title = title + ' (' + excerpt + ')'; + +makeExample(filePath, region, title, stylePatterns)(format='.') + +//- Get the doc example name either from `_example` if set, or +//- extract the example name from `current`. +- var getExampleName = function() { +- var dir = current.path[current.path.length - 1]; +- return _example ? _example : dir == 'latest' ? current.source : dir; +- }; mixin makeTabs(filePaths, regions, tabNames, stylePatterns) - filePaths = strSplit(filePaths); + - if (adjustTsExamplePathForDart) filePaths = filePaths.map(adjustTsExamplePathForDart); - regions = strSplit(regions, filePaths.length); - tabNames = strSplit(tabNames, filePaths.length); + - if (adjustTsExampleTitleForDart) tabNames = tabNames.map(adjustTsExampleTitleForDart); code-tabs each filePath,index in filePaths @@ -32,17 +148,140 @@ mixin makeJson( filePath, jsonConfig, title, stylePatterns) - var frag = getFrag(filePath, ''); - var json = unescapeHtml(frag); - var jsonExtract = extractJson(json, jsonConfig); + - var avoid = !!attributes.avoid; + - var avoidClass = avoid ? 'is-anti-pattern' : ''; - if (title) - .example-title #{title} - code-example(language="#{language}" format="#{format}") - if (jsonExtract == 'ERROR') - err ERROR: Unable to extract json using config: "#{jsonConfig.toString()}" - else - != styleString(jsonExtract, stylePatterns) + div(class="code-example #{avoidClass}") + if (title) + header + h4 #{title} + code-example(language="#{language}" format="#{format}") + if (jsonExtract == 'ERROR') + err ERROR: Unable to extract json using config: "#{jsonConfig.toString()}" + else + != styleString(jsonExtract, stylePatterns) +if !jade2ng + //- Open (and close) an explanation
. See QuickStart + script. + function why(id, backTo) { + var id = "#"+id; + var el = document.querySelector(id); + el.hidden=el.hidden=!el.hidden; + + if (el.hidden && backTo){ + // the next line is required to work around a bug in WebKit (Chrome / Safari) + location.href = "#"; + location.href = "#" + backTo; + } + } + script. + function verbose(isVerbose) { + isVerbose = !! isVerbose; + var el = document.querySelector('button.verbose.off'); + el.style.display = isVerbose ? 'block' : 'none'; + var el = document.querySelector('button.verbose.on'); + el.style.display = isVerbose ? 'none' : 'block'; + + CCSStylesheetRuleStyle('main','.l-verbose-section', 'display', + isVerbose ? 'block' : 'none'); + } + + script. + function CCSStylesheetRuleStyle(stylesheet, selectorText, style, value){ + /* returns the value of the element style of the rule in the stylesheet + * If no value is given, reads the value + * If value is given, the value is changed and returned + * If '' (empty string) is given, erases the value. + * The browser will apply the default one + * + * string stylesheet: part of the .css name to be recognized, e.g. 'default' + * string selectorText: css selector, e.g. '#myId', '.myClass', 'thead td' + * string style: camelCase element style, e.g. 'fontSize' + * string value optional : the new value + */ + var CCSstyle = undefined, rules, sheet; + for(var m in document.styleSheets){ + sheet = document.styleSheets[m]; + if(sheet.href && sheet.href.indexOf(stylesheet) != -1){ + rules = sheet[document.all ? 'rules' : 'cssRules']; + for(var n in rules){ + console.log(rules[n].selectorText); + if(rules[n].selectorText == selectorText){ + CCSstyle = rules[n].style; + break; + } + } + break; + } + } + if(value == undefined) + return CCSstyle[style] + else + return CCSstyle[style] = value + } //--------------------------------------------------------------------------------------------------------- +//- Converts the given project-relative path (like 'app/main.ts') +//- to a doc folder relative path (like 'quickstart/ts/app/main.ts') +//- by prefixing it with '/ts/'. If title is not given, +//- then the project-relative path is used, adjusted to remove numeric +//- file version qualifiers; e.g. 'styles.1.css' becomes 'styles.css'. +- var adjExampleProjPathAndTitle = function(ex/*:{filePath,title}*/) { +- // E.g. of a project relative path is 'app/main.ts' +- if (ex.title === null || ex.title === undefined) { +- // Title is not given so take it to be ex.filePath. +- // Title like styles.1.css or foo_1.dart? Then drop the '.1' or '_1' qualifier: +- var matches = ex.filePath.match(/^(.*)[\._]\d(\.\w+)$/); +- ex.title = matches ? matches[1] + matches[2] : ex.filePath; +- } +- ex.filePath = getExampleName() + '/' + _docsFor + '/' + ex.filePath; +- return ex; +- }; + +//- If the given path is project relative, then first convert it using +//- adjExampleProjPathAndTitle(ex). Then the path is adjusted to match +//- the documentation language. +- var adjustExamplePathAndTitle = function(ex/*:{filePath,title}*/) { +- // Not a doc folder relative path? Assume that it is app project relative. +- if(isProjRelDir(ex.filePath)) adjExampleProjPathAndTitle(ex); +- // Adjust doc folder relative paths if adjustment functions exist. +- if(adjustTsExamplePathForDart) ex.filePath = adjustTsExamplePathForDart(ex.filePath); +- if(adjustTsExampleTitleForDart) ex.title = adjustTsExampleTitleForDart(ex.title); +- return ex; +- }; + +//- Returns truthy iff path is example project relative. +- var isProjRelDir = function(path) { +- return !path.match(/\/(js|ts|dart)(-snippets)?\//) && !path.endsWith('e2e-spec.ts'); +- // Last conjunct handles case for shared project e2e test file like +- // cb-component-communication/e2e-spec.js (is shared between ts & dart) +- // TODO: generalize: compare start with getExampleName(); which needs to be fixed. +- }; + +- var translatePath = function(filePath, region) { +- filePath = filePath.trim(); +- var regionPad = (region && region.length) ? '-' + region.toString() : ''; +- var matches = /{(.*)}.*/.exec(filePath); +- if (matches) { +- var topLevelDir = matches[1]; +- var currentPath = current.path; +- var subPaths = currentPath.slice(2); +- // subPaths[subPaths.length - 1] = "_." + subPaths[subPaths.length - 1]; +- subPaths[subPaths.length - 1] = "_fragments/" + subPaths[subPaths.length - 1]; +- var newPath = getPathToDocs() + topLevelDir + '/' + subPaths.join("/"); +- var result = newPath + regionPad + ".jade"; +- } else { +- var extn = getExtn(filePath); +- var baseFileName = getBaseFileName(filePath); +- var noExtnFileName = baseFileName.substr(0,baseFileName.length - (extn.length + 1)); +- var folder = getFolder(filePath); +- // var result = folder + "/_." + noExtnFileName + regionPad + "." + extn; +- var result = folder + "/_fragments/" + noExtnFileName + regionPad + "." + extn; +- } +- return result +- } + - var EMPTY_STRINGS = [ '','','','','','','']; @@ -106,6 +345,7 @@ mixin makeJson( filePath, jsonConfig, title, stylePatterns) - return source; - } + - var getFragFilePath = function (filePath, region) { - filePath = filePath.trim(); - var extn = getExtn(filePath); @@ -145,6 +385,16 @@ mixin makeJson( filePath, jsonConfig, title, stylePatterns) - return ix > 0 ? fileName.substr(ix+1) : ""; - } +- var getBaseFileName = function(fileName) { +- var ix = fileName.lastIndexOf('/'); +- return ix > 0 ? fileName.substr(ix+1) : ""; +- } + +- var getFolder = function(fileName) { +- var ix = fileName.lastIndexOf('/'); +- return ix > 0 ? fileName.substr(0, ix) : ""; +- } + - var getPathToDocs = function() { - // simple way to only take as many '../' sections as we need to back up to the 'docs' dir - // from the current document @@ -207,4 +457,4 @@ mixin makeJson( filePath, jsonConfig, title, stylePatterns) - } - } - } -- } \ No newline at end of file +- } diff --git a/public/_includes/_version-dropdown.jade b/public/_includes/_version-dropdown.jade index 907f009656..d9ebe2b368 100644 --- a/public/_includes/_version-dropdown.jade +++ b/public/_includes/_version-dropdown.jade @@ -5,57 +5,73 @@ - var version = '' - var page = '' - +//- Replace _ underscores with . dots if current.path[2] - var version = current.path[2].replace(/\_+/gm, ".") - -if current.path[3] +if current.path[6] + if public.docs[current.path[1]][current.path[2]][current.path[3]][current.path[4]][current.path[5]][current.path[6]] + - var page = current.path[3] + '/' + current.path[4] + '/' + current.path[5] + '/' + current.path[6] + '/' + else + - var page = current.path[3] + '/' + current.path[4] + '/' + current.path[5] + '/' + current.path[6] + '.html' + +else if current.path[5] + if public.docs[current.path[1]][current.path[2]][current.path[3]][current.path[4]][current.path[5]] + - var page = current.path[3] + '/' + current.path[4] + '/' + current.path[5] + '/' + else + - var page = current.path[3] + '/' + current.path[4] + '/' + current.path[5] + '.html' + +else if current.path[4] + if public.docs[current.path[1]][current.path[2]][current.path[3]][current.path[4]] + - var page = current.path[3] + '/' + current.path[4] + '/' + else + - var page = current.path[3] + '/' + current.path[4] + '.html' + +else if current.path[3] if public.docs[current.path[1]][current.path[2]][current.path[3]] - var page = current.path[3] + '/' else - var page = current.path[3] + '.html' - +//- VERSION TREE CREATOR MIXIN mixin tree(directory, urlPrefix, name, latest) - ul - for val, semvar in directory - if semvar !== '.git' && semvar !== '_data' - - var libVersion = (semvar == "latest") ? latest : semvar.replace(/\_+/gm, ".") - li #{name} #{libVersion} + for val, semvar in directory + if semvar !== '.git' && semvar !== '_data' + - var libVersion = (semvar == "latest") ? latest : semvar.replace(/\_+/gm, ".") + li #{name} #{libVersion} - +//- BUTTON TITLE GENERATION if language == 'ts' if version == "latest" - - var title = 'Angular 2 for TypeScript' + - var title = 'Angular for TypeScript' else - var title = 'Angular ' + version + ' for TypeScript' if language == 'js' if version == "latest" - - var title = 'Angular 2 for JavaScript' + - var title = 'Angular for JavaScript' else - var title = 'Angular ' + version + ' for JavaScript' if language == 'dart' if version == "latest" - - var title = 'Angular 2 for Dart' + - var title = 'Angular for Dart' else - var title = 'Angular ' + version + ' for Dart' +if current.path[4] !== 'change-log' + //- DROPDOWN BUTTON + nav.dropdown + button(aria-label="Select a version of Angular" md-button class="dropdown-button" ng-click="appCtrl.toggleVersionMenu($event)") #{title} + div(class="overlay ng-hide" ng-click="appCtrl.toggleVersionMenu($event)" ng-show="appCtrl.showMenu") - -nav.hero-subtitle.text-subhead.dropdown - button(aria-label="Select a version of Angular" md-button class="dropdown-button" ng-click="toggleVersionMenu($event)") #{title} - div(class="overlay ng-hide" ng-click="toggleVersionMenu($event)" ng-show="showMenu") - - - div(class="dropdown-menu" ng-class="showMenu ? 'is-visible' : ''") - mixin tree(public.docs.ts, "/docs/ts", "Angular 2 for TypeScript") - mixin tree(public.docs.js, "/docs/js", "Angular 2 for JavaScript") - mixin tree(public.docs.dart, "/docs/dart", "Angular 2 for Dart") - ul - li #{name} Angular 1 for JavaScript - li #{name} Angular 1 for Dart + //- DROPDOWN MENU + ul(class="dropdown-menu" ng-class="appCtrl.showMenu ? 'is-visible' : ''") + mixin tree(public.docs.ts, "/docs/ts", "Angular for TypeScript") + mixin tree(public.docs.js, "/docs/js", "Angular for JavaScript") + //- Disable cross-language link for API entry pages (but keep for top API search page): + - var isApiEntryPage = current.path[3] === 'api' && public.docs[current.path[1]][current.path[2]][current.path[3]][current.path[4]] + if public.docs.dart && !isApiEntryPage + mixin tree(public.docs.dart, "/docs/dart", "Angular for Dart") diff --git a/public/_layout.jade b/public/_layout.jade index cfa3adc596..6f01183b22 100644 --- a/public/_layout.jade +++ b/public/_layout.jade @@ -1,22 +1,33 @@ -doctype html public -html(lang="en" ng-app="angularIOApp" itemscope itemtype="http://schema.org/Framework") - head - != partial("/_includes/_head-include") +if jade2ng + if hero == 'home' + != partial("/_includes/_hero-home") + else + != partial("/_includes/_hero") + != partial("../_includes/_banner") + - var format = autoformat ? 'docs-content' : '' + article(class="l-content #{format}") + != yield +else + doctype html public + html(lang="en" ng-app="angularIOApp" itemscope itemtype="http://schema.org/Framework") + head + != partial("/_includes/_head-include") - body(class="l-offset-nav" ng-controller="AppCtrl") - != partial("/_includes/_main-nav") + body.ng-cloak.l-offset-nav(ng-controller="AppCtrl as appCtrl") + != partial("/_includes/_main-nav") - - if hero == 'home' - != partial("/_includes/_hero-home") - else - != partial("/_includes/_hero") + + if hero == 'home' + != partial("/_includes/_hero-home") + else + != partial("/_includes/_hero") - - var format = autoformat ? 'docs-content' : '' + - var format = autoformat ? 'docs-content' : '' - article(class="l-content #{format}") - != yield + article(class="l-content #{format}") + != yield - != partial("/_includes/_footer") - != partial("/_includes/_scripts-include") \ No newline at end of file + != partial("/_includes/_footer") + != partial("/_includes/_scripts-include") + != partial("/_includes/_scripts-minimum") diff --git a/public/about/index.jade b/public/about/index.jade index b6b429b178..1150e51a56 100644 --- a/public/about/index.jade +++ b/public/about/index.jade @@ -22,7 +22,7 @@ for person, name in bios if person.type == type .c3 - md-card(biocard class="bio-card" website="#{person.website}" twitter="#{person.twitter}" pic="#{person.picture}" bio="#{person.bio}" name="#{person.name}") + md-card(biocard class="bio-card" website=person.website twitter=person.twitter pic=person.picture bio=person.bio name=person.name) header image(src="#{person.picture}" alt="#person.name") diff --git a/public/cardboard/_data.json b/public/cardboard/_data.json index 45c81997da..a092533354 100644 --- a/public/cardboard/_data.json +++ b/public/cardboard/_data.json @@ -1,7 +1,7 @@ { "index": { "title": "Angular Cardboard Hack-A-Thon", - "subtitle": "Win a free ticket to ng-conf 2016", + "subtitle": "Winners Announced", "autoformat": "true" } } \ No newline at end of file diff --git a/public/cardboard/index.jade b/public/cardboard/index.jade index cb6a1ff1c6..a0a76d0e80 100644 --- a/public/cardboard/index.jade +++ b/public/cardboard/index.jade @@ -10,8 +10,7 @@ style(rel='stylesheet'). .c8.text-center p.text-body. - We love exciting new technologies, it’s one of the reasons we created Angular, and we love Google Cardboard. What could be better (or more fun) than combining the two? - p Only the app you build and we can’t wait to see your ideas come to virtual life (literally!). + The results of the Angular Cardboard Hackathon are in! - for (var i = 0; i < 5; i++) img(src="https://github.com/resources/images/cardboard/cardboard.png" alt="" width="20%") @@ -21,37 +20,25 @@ style(rel='stylesheet'). .c2   .c8 div.l-space-bottom-4.banner.is-centered.banner-ng-annoucement - h2 Excited? So are we! + h2 Congratulations Albert Sanchez and Vlado Tešanović! p. - Submit your Angular Cardboard App before December 31, 2015
- and you could win a free ticket to ng-conf 2016! + Learn more about the winning entries on the Ionic blog. div.fake-centered - p Entries will be judged in two categories: + p Entries were judged in two categories: ul li Most Engaging App li. Best Technology Demonstration
- Huge hint: Angular 2 scores points + Huge hint: Angular scores points - p We’ve partnered with Ionic to give you a head’s start with this - img(src="https://github.com/resources/images/cardboard/Ionic_Logo.png" alt="" width="40%") - p - a(href="https://github.com/driftyco/ionic-starter-cardboard") Starter Template for Angular Cardboard - - h4 Ready? - p - a(href="https://docs.google.com/a/google.com/forms/d/1qHPldjf1VuR565IUhUDjz7VuC8dU_3WBF80zGi6xOss/edit") Sign-Up Here p Don’t have Cardboard and want one? Check out: p.text-center a(href="http://www.unofficialcardboard.com/") img(src="https://github.com/resources/images/cardboard/UC_Logo_Black.png" alt="Unofficial Cardboard" width="30%") h4 Rules of the Contest - p. - Changed your mind? That’s okay, we know stuff comes up, email us and - we will remove you from the contest and delete all of your submitted information. + p Terms and Conditions Link | Google Privacy Policy Link .c2   diff --git a/public/contribute.jade b/public/contribute.jade index 462388ec4a..994d46a36d 100644 --- a/public/contribute.jade +++ b/public/contribute.jade @@ -2,6 +2,13 @@ h2 Angular Projects p We'd love for you to contribute to our source code and to make Angular projects even better. + .l-sub-section + h3 Angular + + p Angular is a next generation mobile and desktop application development platform. + + a(href="https://github.com/angular/angular/blob/master/CONTRIBUTING.md" class="button" md-button) Contribute to Angular + .l-sub-section h3 Angular for JavaScript or Dart @@ -16,7 +23,7 @@ p Our goal is to deliver a lean, lightweight set of Angular-based UI elements that implement the material design specification for use in Angular single-page applications (SPAs). - a(href="https://github.com/angular/material/blob/master/docs/guides/CONTRIBUTING.md" class="button" md-button) Contribute to Angular Material + a(href="https://github.com/angular/material/blob/master/.github/CONTRIBUTING.md" class="button" md-button) Contribute to Angular Material .l-sub-section h3 AngularFire diff --git a/public/docs/ImageGuide.pdf b/public/docs/ImageGuide.pdf new file mode 100644 index 0000000000..21280dce88 Binary files /dev/null and b/public/docs/ImageGuide.pdf differ diff --git a/public/docs/README.md b/public/docs/README.md deleted file mode 100644 index 95d331b115..0000000000 --- a/public/docs/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Why the _fragments dir is checked in - -Within this repo files generated as a result of shredding the `_examples` dir ( the contents of the `_fragments` dir) are checked in so that we can avoid running the -shredder over the entire `_examples` dir each time someone refreshes the repo ( the `shred-full` gulp task). -The gulp `serve-and-watch` shredder is only a �partial� shredder. It only shred�s files in directories changed during -the current session. diff --git a/public/docs/_data.json b/public/docs/_data.json index daade7e3ea..121e4db32f 100644 --- a/public/docs/_data.json +++ b/public/docs/_data.json @@ -3,17 +3,5 @@ "title": "Angular Docs", "subtitle": "Choose a language for Angular", "layout": "../_layout" - }, - - "search": { - "title": "Docs Search Results", - "layout": "../_layout" - }, - - "styleguide": { - "title": "Docs Style Guide", - "subtitle": "Design & Layout Patterns For Documentation", - "layout": "../_layout", - "autoformat": "true" } } diff --git a/public/docs/_examples/.gitignore b/public/docs/_examples/.gitignore index 288b9aca3a..3fb5ae8562 100644 --- a/public/docs/_examples/.gitignore +++ b/public/docs/_examples/.gitignore @@ -1,2 +1,18 @@ -typings -*.js.map \ No newline at end of file +# _boilerplate files +!_boilerplate/* +*/*/src/styles.css +*/*/src/systemjs-angular-loader.js +*/*/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/public/docs/_examples/_boilerplate/bs-config.e2e.json b/public/docs/_examples/_boilerplate/bs-config.e2e.json new file mode 100644 index 0000000000..24570dbcc9 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/_boilerplate/bs-config.json b/public/docs/_examples/_boilerplate/bs-config.json new file mode 100644 index 0000000000..4e58595267 --- /dev/null +++ b/public/docs/_examples/_boilerplate/bs-config.json @@ -0,0 +1,8 @@ +{ + "server": { + "baseDir": "src", + "routes": { + "/node_modules": "node_modules" + } + } +} diff --git a/public/docs/_examples/_boilerplate/example-config.json b/public/docs/_examples/_boilerplate/example-config.json new file mode 100644 index 0000000000..ff5403e6ca --- /dev/null +++ b/public/docs/_examples/_boilerplate/example-config.json @@ -0,0 +1,4 @@ +{ + "build": "build", + "run": "serve" +} diff --git a/public/docs/_examples/_boilerplate/package.json b/public/docs/_examples/_boilerplate/package.json new file mode 100644 index 0000000000..2e4bc19f91 --- /dev/null +++ b/public/docs/_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-rc.0" + }, + "repository": {} +} diff --git a/public/docs/_examples/_boilerplate/plnkr.json b/public/docs/_examples/_boilerplate/plnkr.json new file mode 100644 index 0000000000..5fb55b50ad --- /dev/null +++ b/public/docs/_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/public/docs/_examples/_boilerplate/src/styles.css b/public/docs/_examples/_boilerplate/src/styles.css new file mode 100644 index 0000000000..d81835d0cd --- /dev/null +++ b/public/docs/_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/public/docs/_examples/_boilerplate/src/systemjs-angular-loader.js b/public/docs/_examples/_boilerplate/src/systemjs-angular-loader.js new file mode 100644 index 0000000000..8b1005444e --- /dev/null +++ b/public/docs/_examples/_boilerplate/src/systemjs-angular-loader.js @@ -0,0 +1,49 @@ +var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*)/gm; +var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g; +var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g; + +module.exports.translate = function(load){ + if (load.source.indexOf('moduleId') != -1) return load; + + var url = document.createElement('a'); + url.href = load.address; + + var basePathParts = url.pathname.split('/'); + + basePathParts.pop(); + var basePath = basePathParts.join('/'); + + var baseHref = document.createElement('a'); + baseHref.href = this.baseURL; + baseHref = baseHref.pathname; + + if (!baseHref.startsWith('/base/')) { // it is not karma + basePath = basePath.replace(baseHref, ''); + } + + load.source = load.source + .replace(templateUrlRegex, function(match, quote, url){ + var resolvedUrl = url; + + if (url.startsWith('.')) { + resolvedUrl = basePath + url.substr(1); + } + + return 'templateUrl: "' + resolvedUrl + '"'; + }) + .replace(stylesRegex, function(match, relativeUrls) { + var urls = []; + + while ((match = stringRegex.exec(relativeUrls)) !== null) { + if (match[2].startsWith('.')) { + urls.push('"' + basePath + match[2].substr(1) + '"'); + } else { + urls.push('"' + match[2] + '"'); + } + } + + return "styleUrls: [" + urls.join(', ') + "]"; + }); + + return load; +}; diff --git a/public/docs/_examples/_boilerplate/src/systemjs.config.js b/public/docs/_examples/_boilerplate/src/systemjs.config.js new file mode 100644 index 0000000000..ea7a3879ac --- /dev/null +++ b/public/docs/_examples/_boilerplate/src/systemjs.config.js @@ -0,0 +1,52 @@ +/** + * 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/animations': 'npm:@angular/animations/bundles/animations.umd.js', + '@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js', + '@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/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.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', + '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', + meta: { + './*.js': { + loader: 'systemjs-angular-loader.js' + } + } + }, + rxjs: { + defaultExtension: 'js' + } + } + }); +})(this); diff --git a/public/docs/_examples/_boilerplate/src/systemjs.config.web.build.js b/public/docs/_examples/_boilerplate/src/systemjs.config.web.build.js new file mode 100644 index 0000000000..5774fd8187 --- /dev/null +++ b/public/docs/_examples/_boilerplate/src/systemjs.config.web.build.js @@ -0,0 +1,96 @@ +/** + * 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/animations': 'ng:animations-builds/master/bundles/animations.umd.js', + '@angular/animations/browser': 'ng:animations-builds/master/bundles/animations-browser.umd.js', + '@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/animations': 'ng:animations-builds/master/bundles/platform-browser-animations.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.2.1/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', + meta: { + './*.ts': { + loader: 'systemjs-angular-loader.js' + } + } + }, + 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/public/docs/_examples/_boilerplate/src/systemjs.config.web.js b/public/docs/_examples/_boilerplate/src/systemjs.config.web.js new file mode 100644 index 0000000000..376fcdde8a --- /dev/null +++ b/public/docs/_examples/_boilerplate/src/systemjs.config.web.js @@ -0,0 +1,83 @@ +/** + * 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/animations': 'npm:@angular/animations/bundles/animations.umd.js', + '@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js', + '@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/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.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.2.1/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', + meta: { + './*.ts': { + loader: 'systemjs-angular-loader.js' + } + } + }, + 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/public/docs/_examples/_boilerplate/src/tsconfig.json b/public/docs/_examples/_boilerplate/src/tsconfig.json new file mode 100644 index 0000000000..05839ec2ff --- /dev/null +++ b/public/docs/_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/public/docs/_examples/_boilerplate/tslint.json b/public/docs/_examples/_boilerplate/tslint.json new file mode 100644 index 0000000000..276453f4f5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/animations/e2e-spec.ts b/public/docs/_examples/animations/e2e-spec.ts new file mode 100644 index 0000000000..4fba7ec475 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/animations/ts/.gitignore b/public/docs/_examples/animations/ts/.gitignore new file mode 100644 index 0000000000..2cb7d2a2e9 --- /dev/null +++ b/public/docs/_examples/animations/ts/.gitignore @@ -0,0 +1 @@ +**/*.js diff --git a/public/docs/_examples/animations/ts/example-config.json b/public/docs/_examples/animations/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/animations/ts/plnkr.json b/public/docs/_examples/animations/ts/plnkr.json new file mode 100644 index 0000000000..f047395e7f --- /dev/null +++ b/public/docs/_examples/animations/ts/plnkr.json @@ -0,0 +1,8 @@ +{ + "description": "Angular Animations", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js" + ] +} diff --git a/public/docs/_examples/animations/ts/src/app/app.module.ts b/public/docs/_examples/animations/ts/src/app/app.module.ts new file mode 100644 index 0000000000..0773357c58 --- /dev/null +++ b/public/docs/_examples/animations/ts/src/app/app.module.ts @@ -0,0 +1,38 @@ +// #docregion animations-module +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +// #enddocregion animations-module + +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'; + +// #docregion animation-module +@NgModule({ + imports: [ BrowserModule, BrowserAnimationsModule ], + // #enddocregion animation-module + declarations: [ + HeroTeamBuilderComponent, + HeroListBasicComponent, + HeroListInlineStylesComponent, + HeroListCombinedTransitionsComponent, + HeroListTwowayComponent, + HeroListEnterLeaveComponent, + HeroListEnterLeaveStatesComponent, + HeroListAutoComponent, + HeroListTimingsComponent, + HeroListMultistepComponent, + HeroListGroupsComponent + ], + bootstrap: [ HeroTeamBuilderComponent ] +}) +export class AppModule { } diff --git a/public/docs/_examples/animations/ts/src/app/hero-list-auto.component.ts b/public/docs/_examples/animations/ts/src/app/hero-list-auto.component.ts new file mode 100644 index 0000000000..84b25c05de --- /dev/null +++ b/public/docs/_examples/animations/ts/src/app/hero-list-auto.component.ts @@ -0,0 +1,47 @@ +import { + Component, + Input +} from '@angular/core'; +import { + trigger, + state, + style, + animate, + transition +} from '@angular/animations'; + +import { Heroes } from './hero.service'; + +@Component({ + selector: 'hero-list-auto', + // #docregion template + template: ` +
    +
  • + {{hero.name}} +
  • +
+ `, + // #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/public/docs/_examples/animations/ts/src/app/hero-list-basic.component.ts b/public/docs/_examples/animations/ts/src/app/hero-list-basic.component.ts new file mode 100644 index 0000000000..76d5ba686c --- /dev/null +++ b/public/docs/_examples/animations/ts/src/app/hero-list-basic.component.ts @@ -0,0 +1,70 @@ +// #docplaster +// #docregion +// #docregion imports +import { + Component, + Input +} from '@angular/core'; +import { + trigger, + state, + style, + animate, + transition +} from '@angular/animations'; +// #enddocregion imports + +import { Heroes } from './hero.service'; + +@Component({ + 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: ` +
    +
  • + {{hero.name}} +
  • +
+ `, + // #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/public/docs/_examples/animations/ts/src/app/hero-list-combined-transitions.component.ts b/public/docs/_examples/animations/ts/src/app/hero-list-combined-transitions.component.ts new file mode 100644 index 0000000000..fc654cbcb5 --- /dev/null +++ b/public/docs/_examples/animations/ts/src/app/hero-list-combined-transitions.component.ts @@ -0,0 +1,59 @@ +// #docregion +// #docregion imports +import { + Component, + Input +} from '@angular/core'; +import { + trigger, + state, + style, + animate, + transition +} from '@angular/animations'; +// #enddocregion imports + +import { Heroes } from './hero.service'; + +@Component({ + selector: 'hero-list-combined-transitions', + // #docregion template + template: ` +
    +
  • + {{hero.name}} +
  • +
+ `, + // #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/public/docs/_examples/animations/ts/src/app/hero-list-enter-leave-states.component.ts b/public/docs/_examples/animations/ts/src/app/hero-list-enter-leave-states.component.ts new file mode 100644 index 0000000000..c01e182e8b --- /dev/null +++ b/public/docs/_examples/animations/ts/src/app/hero-list-enter-leave-states.component.ts @@ -0,0 +1,63 @@ +import { + Component, + Input +} from '@angular/core'; +import { + trigger, + state, + style, + animate, + transition +} from '@angular/animations'; + +import { Heroes } from './hero.service'; + +@Component({ + selector: 'hero-list-enter-leave-states', + // #docregion template + template: ` +
    +
  • + {{hero.name}} +
  • +
+ `, + // #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/public/docs/_examples/animations/ts/src/app/hero-list-enter-leave.component.ts b/public/docs/_examples/animations/ts/src/app/hero-list-enter-leave.component.ts new file mode 100644 index 0000000000..f27b5f10e1 --- /dev/null +++ b/public/docs/_examples/animations/ts/src/app/hero-list-enter-leave.component.ts @@ -0,0 +1,51 @@ +import { + Component, + Input +} from '@angular/core'; +import { + trigger, + state, + style, + animate, + transition +} from '@angular/animations'; + +import { Heroes } from './hero.service'; + +@Component({ + selector: 'hero-list-enter-leave', + // #docregion template + template: ` +
    +
  • + {{hero.name}} +
  • +
+ `, + // #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/public/docs/_examples/animations/ts/src/app/hero-list-groups.component.ts b/public/docs/_examples/animations/ts/src/app/hero-list-groups.component.ts new file mode 100644 index 0000000000..12a57292f7 --- /dev/null +++ b/public/docs/_examples/animations/ts/src/app/hero-list-groups.component.ts @@ -0,0 +1,80 @@ +import { + Component, + Input +} from '@angular/core'; +import { + trigger, + state, + style, + animate, + transition, + group +} from '@angular/animations'; + +import { Heroes } from './hero.service'; + +@Component({ + selector: 'hero-list-groups', + template: ` +
    +
  • + {{hero.name}} +
  • +
+ `, + 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/public/docs/_examples/animations/ts/src/app/hero-list-inline-styles.component.ts b/public/docs/_examples/animations/ts/src/app/hero-list-inline-styles.component.ts new file mode 100644 index 0000000000..ed1bdb7646 --- /dev/null +++ b/public/docs/_examples/animations/ts/src/app/hero-list-inline-styles.component.ts @@ -0,0 +1,60 @@ +// #docregion +// #docregion imports +import { + Component, + Input, +} from '@angular/core'; +import { + trigger, + style, + animate, + transition +} from '@angular/animations'; +// #enddocregion imports + +import { Heroes } from './hero.service'; + +@Component({ + selector: 'hero-list-inline-styles', + // #docregion template + template: ` +
    +
  • + {{hero.name}} +
  • +
+ `, + // #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/public/docs/_examples/animations/ts/src/app/hero-list-multistep.component.ts b/public/docs/_examples/animations/ts/src/app/hero-list-multistep.component.ts new file mode 100644 index 0000000000..4e57896de5 --- /dev/null +++ b/public/docs/_examples/animations/ts/src/app/hero-list-multistep.component.ts @@ -0,0 +1,71 @@ +import { + Component, + Input, +} from '@angular/core'; +import { + trigger, + state, + style, + animate, + transition, + keyframes, + AnimationEvent +} from '@angular/animations'; + +import { Heroes } from './hero.service'; + +@Component({ + selector: 'hero-list-multistep', + // #docregion template + template: ` +
    +
  • + {{hero.name}} +
  • +
+ `, + // #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: AnimationEvent) { + console.warn('Animation started: ', event); + } + + animationDone(event: AnimationEvent) { + console.warn('Animation done: ', event); + } +} diff --git a/public/docs/_examples/animations/ts/src/app/hero-list-timings.component.ts b/public/docs/_examples/animations/ts/src/app/hero-list-timings.component.ts new file mode 100644 index 0000000000..3218609b4a --- /dev/null +++ b/public/docs/_examples/animations/ts/src/app/hero-list-timings.component.ts @@ -0,0 +1,58 @@ +import { + Component, + Input +} from '@angular/core'; +import { + trigger, + state, + style, + animate, + transition +} from '@angular/animations'; + +import { Heroes } from './hero.service'; + +@Component({ + selector: 'hero-list-timings', + template: ` +
    +
  • + {{hero.name}} +
  • +
+ `, + 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/public/docs/_examples/animations/ts/src/app/hero-list-twoway.component.ts b/public/docs/_examples/animations/ts/src/app/hero-list-twoway.component.ts new file mode 100644 index 0000000000..46d0c64a68 --- /dev/null +++ b/public/docs/_examples/animations/ts/src/app/hero-list-twoway.component.ts @@ -0,0 +1,58 @@ +// #docregion +// #docregion imports +import { + Component, + Input +} from '@angular/core'; +import { + trigger, + state, + style, + animate, + transition +} from '@angular/animations'; +// #enddocregion imports + +import { Heroes } from './hero.service'; + +@Component({ + selector: 'hero-list-twoway', + // #docregion template + template: ` +
    +
  • + {{hero.name}} +
  • +
+ `, + // #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/public/docs/_examples/animations/ts/src/app/hero-list.component.css b/public/docs/_examples/animations/ts/src/app/hero-list.component.css new file mode 100644 index 0000000000..b256521e49 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/animations/ts/src/app/hero-team-builder.component.ts b/public/docs/_examples/animations/ts/src/app/hero-team-builder.component.ts new file mode 100644 index 0000000000..e5413be50e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/animations/ts/src/app/hero.service.ts b/public/docs/_examples/animations/ts/src/app/hero.service.ts new file mode 100644 index 0000000000..6bdeb5a512 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/animations/ts/src/index.html b/public/docs/_examples/animations/ts/src/index.html new file mode 100644 index 0000000000..6c4a13adb2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/animations/ts/src/main.ts b/public/docs/_examples/animations/ts/src/main.ts new file mode 100644 index 0000000000..f22933ba8e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/architecture/e2e-spec.ts b/public/docs/_examples/architecture/e2e-spec.ts new file mode 100644 index 0000000000..e967804483 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/architecture/ts/example-config.json b/public/docs/_examples/architecture/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/architecture/ts/plnkr.json b/public/docs/_examples/architecture/ts/plnkr.json new file mode 100644 index 0000000000..b2f47131df --- /dev/null +++ b/public/docs/_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/public/docs/_examples/architecture/ts/src/app/app.component.ts b/public/docs/_examples/architecture/ts/src/app/app.component.ts new file mode 100644 index 0000000000..b987f17e44 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/architecture/ts/src/app/app.module.ts b/public/docs/_examples/architecture/ts/src/app/app.module.ts new file mode 100644 index 0000000000..f6e64beecd --- /dev/null +++ b/public/docs/_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/public/docs/_examples/architecture/ts/src/app/backend.service.ts b/public/docs/_examples/architecture/ts/src/app/backend.service.ts new file mode 100644 index 0000000000..e47cfc8ace --- /dev/null +++ b/public/docs/_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/public/docs/_examples/architecture/ts/src/app/hero-detail.component.html b/public/docs/_examples/architecture/ts/src/app/hero-detail.component.html new file mode 100644 index 0000000000..224de8bb86 --- /dev/null +++ b/public/docs/_examples/architecture/ts/src/app/hero-detail.component.html @@ -0,0 +1,9 @@ +
+

{{hero.name}} Detail

+
Id: {{hero.id}}
+
Name: + + + +
+
Power:
diff --git a/public/docs/_examples/architecture/ts/src/app/hero-detail.component.ts b/public/docs/_examples/architecture/ts/src/app/hero-detail.component.ts new file mode 100644 index 0000000000..b75d6dd225 --- /dev/null +++ b/public/docs/_examples/architecture/ts/src/app/hero-detail.component.ts @@ -0,0 +1,11 @@ +import { Component, Input } from '@angular/core'; + +import { Hero } from './hero'; + +@Component({ + selector: 'hero-detail', + templateUrl: './hero-detail.component.html' +}) +export class HeroDetailComponent { + @Input() hero: Hero; +} diff --git a/public/docs/_examples/architecture/ts/src/app/hero-list.component.1.html b/public/docs/_examples/architecture/ts/src/app/hero-list.component.1.html new file mode 100644 index 0000000000..c6e6dd5133 --- /dev/null +++ b/public/docs/_examples/architecture/ts/src/app/hero-list.component.1.html @@ -0,0 +1,9 @@ + +
  • {{hero.name}}
  • + +
  • + + + +
  • + diff --git a/public/docs/_examples/architecture/ts/src/app/hero-list.component.html b/public/docs/_examples/architecture/ts/src/app/hero-list.component.html new file mode 100644 index 0000000000..b46a307bd3 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/architecture/ts/src/app/hero-list.component.ts b/public/docs/_examples/architecture/ts/src/app/hero-list.component.ts new file mode 100644 index 0000000000..a3d372f00b --- /dev/null +++ b/public/docs/_examples/architecture/ts/src/app/hero-list.component.ts @@ -0,0 +1,29 @@ +import { Component, OnInit } from '@angular/core'; + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; + +// #docregion metadata, providers +@Component({ + 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/public/docs/_examples/architecture/ts/src/app/hero.service.ts b/public/docs/_examples/architecture/ts/src/app/hero.service.ts new file mode 100644 index 0000000000..493f064e40 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/architecture/ts/src/app/hero.ts b/public/docs/_examples/architecture/ts/src/app/hero.ts new file mode 100644 index 0000000000..b89557aa71 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/architecture/ts/src/app/logger.service.ts b/public/docs/_examples/architecture/ts/src/app/logger.service.ts new file mode 100644 index 0000000000..9277ee8bc0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/architecture/ts/src/app/mini-app.ts b/public/docs/_examples/architecture/ts/src/app/mini-app.ts new file mode 100644 index 0000000000..b064428be3 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/architecture/ts/src/app/sales-tax.component.ts b/public/docs/_examples/architecture/ts/src/app/sales-tax.component.ts new file mode 100644 index 0000000000..02201afe05 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/architecture/ts/src/app/sales-tax.service.ts b/public/docs/_examples/architecture/ts/src/app/sales-tax.service.ts new file mode 100644 index 0000000000..d859dc1595 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/architecture/ts/src/app/tax-rate.service.ts b/public/docs/_examples/architecture/ts/src/app/tax-rate.service.ts new file mode 100644 index 0000000000..fff2f4df8f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/architecture/ts/src/index.html b/public/docs/_examples/architecture/ts/src/index.html new file mode 100644 index 0000000000..9aadf6e109 --- /dev/null +++ b/public/docs/_examples/architecture/ts/src/index.html @@ -0,0 +1,26 @@ + + + + Architecture of Angular + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/public/docs/_examples/architecture/ts/src/main.ts b/public/docs/_examples/architecture/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/attribute-directives/e2e-spec.ts b/public/docs/_examples/attribute-directives/e2e-spec.ts new file mode 100644 index 0000000000..79425d7923 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/attribute-directives/ts/example-config.json b/public/docs/_examples/attribute-directives/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/attribute-directives/ts/plnkr.json b/public/docs/_examples/attribute-directives/ts/plnkr.json new file mode 100644 index 0000000000..112e1de6f4 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/attribute-directives/ts/src/app/app.component.1.html b/public/docs/_examples/attribute-directives/ts/src/app/app.component.1.html new file mode 100644 index 0000000000..9505bc9dff --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/src/app/app.component.1.html @@ -0,0 +1,14 @@ + +

    My First Attribute Directive

    + +

    Highlight me!

    + + + +

    Highlighted in yellow

    +

    Highlighted in orange

    + + + +

    Highlighted with parent component's color

    + diff --git a/public/docs/_examples/attribute-directives/ts/src/app/app.component.1.ts b/public/docs/_examples/attribute-directives/ts/src/app/app.component.1.ts new file mode 100644 index 0000000000..d9b98ef3fd --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/src/app/app.component.1.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + templateUrl: './app.component.1.html' +}) +// #docregion class +export class AppComponent { + color = 'yellow'; +} diff --git a/public/docs/_examples/attribute-directives/ts/src/app/app.component.html b/public/docs/_examples/attribute-directives/ts/src/app/app.component.html new file mode 100644 index 0000000000..3adb52bc1e --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/src/app/app.component.html @@ -0,0 +1,24 @@ + +

    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/public/docs/_examples/attribute-directives/ts/src/app/app.component.ts b/public/docs/_examples/attribute-directives/ts/src/app/app.component.ts new file mode 100644 index 0000000000..693918d8d6 --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/src/app/app.component.ts @@ -0,0 +1,11 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + templateUrl: './app.component.html' +}) +// #docregion class +export class AppComponent { + color: string; +} diff --git a/public/docs/_examples/attribute-directives/ts/src/app/app.module.ts b/public/docs/_examples/attribute-directives/ts/src/app/app.module.ts new file mode 100644 index 0000000000..ca35d560fb --- /dev/null +++ b/public/docs/_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/public/docs/_examples/attribute-directives/ts/src/app/dummy.module.1.ts b/public/docs/_examples/attribute-directives/ts/src/app/dummy.module.1.ts new file mode 100644 index 0000000000..7ba41d53bb --- /dev/null +++ b/public/docs/_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/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.1.ts b/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.1.ts new file mode 100644 index 0000000000..4cacc0d22e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.2.ts b/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.2.ts new file mode 100644 index 0000000000..4696132f64 --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.2.ts @@ -0,0 +1,41 @@ +/* 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 + + // #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, + + // #docregion color + @Input() highlightColor: string; + // #enddocregion color + + // #docregion color-2 + @Input() myHighlight: string; + // #enddocregion color-2 +} + diff --git a/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.3.ts b/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.3.ts new file mode 100644 index 0000000000..bf76769c93 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.ts b/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.ts new file mode 100644 index 0000000000..97b1497013 --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/src/app/highlight.directive.ts @@ -0,0 +1,34 @@ +/* tslint:disable:member-ordering */ +// #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/public/docs/_examples/attribute-directives/ts/src/index.html b/public/docs/_examples/attribute-directives/ts/src/index.html new file mode 100644 index 0000000000..97662437f0 --- /dev/null +++ b/public/docs/_examples/attribute-directives/ts/src/index.html @@ -0,0 +1,25 @@ + + + + + + Attribute Directives + + + + + + + + + + + + + + + loading... + + diff --git a/public/docs/_examples/attribute-directives/ts/src/main.ts b/public/docs/_examples/attribute-directives/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ajs-quick-reference/e2e-spec.ts b/public/docs/_examples/cb-ajs-quick-reference/e2e-spec.ts new file mode 100644 index 0000000000..81a5faa5e7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ajs-quick-reference/ts/example-config.json b/public/docs/_examples/cb-ajs-quick-reference/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/cb-ajs-quick-reference/ts/plnkr.json b/public/docs/_examples/cb-ajs-quick-reference/ts/plnkr.json new file mode 100644 index 0000000000..1ff34275f7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/app-routing.module.ts b/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/app-routing.module.ts new file mode 100644 index 0000000000..a7cbe8a74d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/app.component.css b/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/app.component.css new file mode 100644 index 0000000000..e454e9ea87 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/app.component.html b/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/app.component.html new file mode 100644 index 0000000000..72fd3de86f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/app.component.ts b/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/app.component.ts new file mode 100644 index 0000000000..e1b71e37dc --- /dev/null +++ b/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/app.component.ts @@ -0,0 +1,32 @@ +import { Component } from '@angular/core'; + +import { MovieService } from './movie.service'; +import { IMovie } from './movie'; + +@Component({ + 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/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/app.module.1.ts b/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/app.module.1.ts new file mode 100644 index 0000000000..5b24020186 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/app.module.ts b/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/app.module.ts new file mode 100644 index 0000000000..1dc46ad17c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/date.pipe.ts b/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/date.pipe.ts new file mode 100644 index 0000000000..e1421fa530 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/movie-list.component.css b/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/movie-list.component.css new file mode 100644 index 0000000000..a3d5bf8161 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/movie-list.component.html b/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/movie-list.component.html new file mode 100644 index 0000000000..9de98806d7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/movie-list.component.ts b/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/movie-list.component.ts new file mode 100644 index 0000000000..b0b57f018c --- /dev/null +++ b/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/movie-list.component.ts @@ -0,0 +1,42 @@ +/* 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({ + 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/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/movie.service.ts b/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/movie.service.ts new file mode 100644 index 0000000000..404fd4454c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/movie.ts b/public/docs/_examples/cb-ajs-quick-reference/ts/src/app/movie.ts new file mode 100644 index 0000000000..0e6f321520 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ajs-quick-reference/ts/src/images/hero.png b/public/docs/_examples/cb-ajs-quick-reference/ts/src/images/hero.png new file mode 100644 index 0000000000..2a128ac367 Binary files /dev/null and b/public/docs/_examples/cb-ajs-quick-reference/ts/src/images/hero.png differ diff --git a/public/docs/_examples/cb-ajs-quick-reference/ts/src/images/ng-logo.png b/public/docs/_examples/cb-ajs-quick-reference/ts/src/images/ng-logo.png new file mode 100644 index 0000000000..1e488b1a49 Binary files /dev/null and b/public/docs/_examples/cb-ajs-quick-reference/ts/src/images/ng-logo.png differ diff --git a/public/docs/_examples/cb-ajs-quick-reference/ts/src/images/villain.png b/public/docs/_examples/cb-ajs-quick-reference/ts/src/images/villain.png new file mode 100644 index 0000000000..26697d1a42 Binary files /dev/null and b/public/docs/_examples/cb-ajs-quick-reference/ts/src/images/villain.png differ diff --git a/public/docs/_examples/cb-ajs-quick-reference/ts/src/index.html b/public/docs/_examples/cb-ajs-quick-reference/ts/src/index.html new file mode 100644 index 0000000000..829f080ae3 --- /dev/null +++ b/public/docs/_examples/cb-ajs-quick-reference/ts/src/index.html @@ -0,0 +1,28 @@ + + + + + + AngularJS to Angular Quick Reference + + + + + + + + + + + + + + + + + Loading app... + + + diff --git a/public/docs/_examples/cb-ajs-quick-reference/ts/src/main.ts b/public/docs/_examples/cb-ajs-quick-reference/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-aot-compiler/e2e-spec.ts b/public/docs/_examples/cb-aot-compiler/e2e-spec.ts new file mode 100644 index 0000000000..4744f06e50 --- /dev/null +++ b/public/docs/_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: any) { + 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/public/docs/_examples/cb-aot-compiler/ts/.gitignore b/public/docs/_examples/cb-aot-compiler/ts/.gitignore new file mode 100644 index 0000000000..91da4c79a2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-aot-compiler/ts/example-config.json b/public/docs/_examples/cb-aot-compiler/ts/example-config.json new file mode 100644 index 0000000000..473b80a572 --- /dev/null +++ b/public/docs/_examples/cb-aot-compiler/ts/example-config.json @@ -0,0 +1,3 @@ +{ + "build": "build:aot" +} diff --git a/public/docs/_examples/cb-aot-compiler/ts/rollup-config.js b/public/docs/_examples/cb-aot-compiler/ts/rollup-config.js new file mode 100644 index 0000000000..1cd25515ba --- /dev/null +++ b/public/docs/_examples/cb-aot-compiler/ts/rollup-config.js @@ -0,0 +1,34 @@ +// #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; } + + // 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/public/docs/_examples/cb-aot-compiler/ts/src/app/app.component.html b/public/docs/_examples/cb-aot-compiler/ts/src/app/app.component.html new file mode 100644 index 0000000000..1d3a8de932 --- /dev/null +++ b/public/docs/_examples/cb-aot-compiler/ts/src/app/app.component.html @@ -0,0 +1,7 @@ + + +

    Hello Angular

    + +

    List of Heroes

    +
    {{hero}}
    + diff --git a/public/docs/_examples/cb-aot-compiler/ts/src/app/app.component.ts b/public/docs/_examples/cb-aot-compiler/ts/src/app/app.component.ts new file mode 100644 index 0000000000..879f7f663c --- /dev/null +++ b/public/docs/_examples/cb-aot-compiler/ts/src/app/app.component.ts @@ -0,0 +1,15 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + 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/public/docs/_examples/cb-aot-compiler/ts/src/app/app.module.ts b/public/docs/_examples/cb-aot-compiler/ts/src/app/app.module.ts new file mode 100644 index 0000000000..b4fc185c24 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-aot-compiler/ts/src/index-jit.html b/public/docs/_examples/cb-aot-compiler/ts/src/index-jit.html new file mode 100644 index 0000000000..713a04970e --- /dev/null +++ b/public/docs/_examples/cb-aot-compiler/ts/src/index-jit.html @@ -0,0 +1,24 @@ + + + + + Ahead of time compilation (JIT) + + + + + + + + + + + + + + + Loading... + + diff --git a/public/docs/_examples/cb-aot-compiler/ts/src/index.html b/public/docs/_examples/cb-aot-compiler/ts/src/index.html new file mode 100644 index 0000000000..09e5f0de0e --- /dev/null +++ b/public/docs/_examples/cb-aot-compiler/ts/src/index.html @@ -0,0 +1,20 @@ + + + + + Ahead of time compilation + + + + + + + + + + Loading... + + + + + diff --git a/public/docs/_examples/cb-aot-compiler/ts/src/main-jit.ts b/public/docs/_examples/cb-aot-compiler/ts/src/main-jit.ts new file mode 100644 index 0000000000..88e2c16ed5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-aot-compiler/ts/src/main.ts b/public/docs/_examples/cb-aot-compiler/ts/src/main.ts new file mode 100644 index 0000000000..4446bc07d1 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-aot-compiler/ts/tsconfig-aot.json b/public/docs/_examples/cb-aot-compiler/ts/tsconfig-aot.json new file mode 100644 index 0000000000..d0a3d00ad4 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-component-communication/e2e-spec.ts b/public/docs/_examples/cb-component-communication/e2e-spec.ts new file mode 100644 index 0000000000..d78e0784d9 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-component-communication/ts/.gitignore b/public/docs/_examples/cb-component-communication/ts/.gitignore new file mode 100644 index 0000000000..2cb7d2a2e9 --- /dev/null +++ b/public/docs/_examples/cb-component-communication/ts/.gitignore @@ -0,0 +1 @@ +**/*.js diff --git a/public/docs/_examples/cb-component-communication/ts/example-config.json b/public/docs/_examples/cb-component-communication/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/cb-component-communication/ts/plnkr.json b/public/docs/_examples/cb-component-communication/ts/plnkr.json new file mode 100644 index 0000000000..03bd55cd1a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-component-communication/ts/src/app/app.component.html b/public/docs/_examples/cb-component-communication/ts/src/app/app.component.html new file mode 100644 index 0000000000..ef94020efb --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-component-communication/ts/src/app/app.component.ts b/public/docs/_examples/cb-component-communication/ts/src/app/app.component.ts new file mode 100644 index 0000000000..7556beb1ff --- /dev/null +++ b/public/docs/_examples/cb-component-communication/ts/src/app/app.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + templateUrl: './app.component.html' +}) +export class AppComponent { } diff --git a/public/docs/_examples/cb-component-communication/ts/src/app/app.module.ts b/public/docs/_examples/cb-component-communication/ts/src/app/app.module.ts new file mode 100644 index 0000000000..a008fc5486 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-component-communication/ts/src/app/astronaut.component.ts b/public/docs/_examples/cb-component-communication/ts/src/app/astronaut.component.ts new file mode 100644 index 0000000000..bc24964f86 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-component-communication/ts/src/app/countdown-parent.component.ts b/public/docs/_examples/cb-component-communication/ts/src/app/countdown-parent.component.ts new file mode 100644 index 0000000000..5bcf0645c9 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-component-communication/ts/src/app/countdown-timer.component.ts b/public/docs/_examples/cb-component-communication/ts/src/app/countdown-timer.component.ts new file mode 100644 index 0000000000..a9a1a2fa7c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-component-communication/ts/src/app/hero-child.component.ts b/public/docs/_examples/cb-component-communication/ts/src/app/hero-child.component.ts new file mode 100644 index 0000000000..7447542a74 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-component-communication/ts/src/app/hero-parent.component.ts b/public/docs/_examples/cb-component-communication/ts/src/app/hero-parent.component.ts new file mode 100644 index 0000000000..bf3861c455 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-component-communication/ts/src/app/hero.ts b/public/docs/_examples/cb-component-communication/ts/src/app/hero.ts new file mode 100644 index 0000000000..a7b70f48e8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-component-communication/ts/src/app/mission.service.ts b/public/docs/_examples/cb-component-communication/ts/src/app/mission.service.ts new file mode 100644 index 0000000000..25c86866b0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-component-communication/ts/src/app/missioncontrol.component.ts b/public/docs/_examples/cb-component-communication/ts/src/app/missioncontrol.component.ts new file mode 100644 index 0000000000..a27e9b16b1 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-component-communication/ts/src/app/name-child.component.ts b/public/docs/_examples/cb-component-communication/ts/src/app/name-child.component.ts new file mode 100644 index 0000000000..bc6d3c6f59 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-component-communication/ts/src/app/name-parent.component.ts b/public/docs/_examples/cb-component-communication/ts/src/app/name-parent.component.ts new file mode 100644 index 0000000000..99753303aa --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-component-communication/ts/src/app/version-child.component.ts b/public/docs/_examples/cb-component-communication/ts/src/app/version-child.component.ts new file mode 100644 index 0000000000..89d365cf9f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-component-communication/ts/src/app/version-parent.component.ts b/public/docs/_examples/cb-component-communication/ts/src/app/version-parent.component.ts new file mode 100644 index 0000000000..bbc9101702 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-component-communication/ts/src/app/voter.component.ts b/public/docs/_examples/cb-component-communication/ts/src/app/voter.component.ts new file mode 100644 index 0000000000..c0cb23abc0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-component-communication/ts/src/app/votetaker.component.ts b/public/docs/_examples/cb-component-communication/ts/src/app/votetaker.component.ts new file mode 100644 index 0000000000..87f06161f5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-component-communication/ts/src/demo.css b/public/docs/_examples/cb-component-communication/ts/src/demo.css new file mode 100644 index 0000000000..b63a8b38dd --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-component-communication/ts/src/index.html b/public/docs/_examples/cb-component-communication/ts/src/index.html new file mode 100644 index 0000000000..64c8a3430f --- /dev/null +++ b/public/docs/_examples/cb-component-communication/ts/src/index.html @@ -0,0 +1,29 @@ + + + + + Passing information from parent to child + + + + + + + + + + + + + + + + + loading... + + + diff --git a/public/docs/_examples/cb-component-communication/ts/src/main.ts b/public/docs/_examples/cb-component-communication/ts/src/main.ts new file mode 100644 index 0000000000..311c44b76d --- /dev/null +++ b/public/docs/_examples/cb-component-communication/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/public/docs/_examples/cb-dependency-injection/e2e-spec.ts b/public/docs/_examples/cb-dependency-injection/e2e-spec.ts new file mode 100644 index 0000000000..8c9d163d5e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dependency-injection/ts/example-config.json b/public/docs/_examples/cb-dependency-injection/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/cb-dependency-injection/ts/plnkr.json b/public/docs/_examples/cb-dependency-injection/ts/plnkr.json new file mode 100644 index 0000000000..ff0aedca01 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dependency-injection/ts/src/app/app-routing.module.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/app-routing.module.ts new file mode 100644 index 0000000000..09a0592d00 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dependency-injection/ts/src/app/app.component.html b/public/docs/_examples/cb-dependency-injection/ts/src/app/app.component.html new file mode 100644 index 0000000000..a715e484fe --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dependency-injection/ts/src/app/app.component.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/app.component.ts new file mode 100644 index 0000000000..3045893c58 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/src/app/app.component.ts @@ -0,0 +1,29 @@ +// #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({ + 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/public/docs/_examples/cb-dependency-injection/ts/src/app/app.module.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/app.module.ts new file mode 100644 index 0000000000..a240e21f7c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dependency-injection/ts/src/app/date-logger.service.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/date-logger.service.ts new file mode 100644 index 0000000000..875779be40 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/src/app/date-logger.service.ts @@ -0,0 +1,19 @@ +/* tslint:disable:one-line:check-open-brace*/ +// #docregion +import { Injectable } from '@angular/core'; + +import { LoggerService } from './logger.service'; + +// #docregion date-logger-service +@Injectable() +// #docregion date-logger-service-signature +export class DateLoggerService extends LoggerService +// #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/public/docs/_examples/cb-dependency-injection/ts/src/app/hero-bio.component.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/hero-bio.component.ts new file mode 100644 index 0000000000..84f8e05e66 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dependency-injection/ts/src/app/hero-bios.component.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/hero-bios.component.ts new file mode 100644 index 0000000000..217c5edcd0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dependency-injection/ts/src/app/hero-cache.service.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/hero-cache.service.ts new file mode 100644 index 0000000000..6dbc7a0c4f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dependency-injection/ts/src/app/hero-contact.component.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/hero-contact.component.ts new file mode 100644 index 0000000000..add6df91c5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dependency-injection/ts/src/app/hero-data.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/hero-data.ts new file mode 100644 index 0000000000..10cdbcaab1 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dependency-injection/ts/src/app/hero-of-the-month.component.1.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/hero-of-the-month.component.1.ts new file mode 100644 index 0000000000..da2c57aeaf --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/src/app/hero-of-the-month.component.1.ts @@ -0,0 +1,26 @@ +// Illustrative (not used), mini-version of the actual HeroOfTheMonthComponent +// Injecting with the MinimalLogger "interface-class" +import { Component, NgModule } from '@angular/core'; +import { LoggerService } from './logger.service'; +import { MinimalLogger } from './minimal-logger.service'; + +// #docregion +@Component({ + selector: 'hero-of-the-month', + templateUrl: './hero-of-the-month.component.html', + // Todo: move this aliasing, `useExisting` provider to the AppModule + providers: [{ provide: MinimalLogger, useExisting: LoggerService }] +}) +export class HeroOfTheMonthComponent { + logs: string[] = []; + constructor(logger: MinimalLogger) { + logger.logInfo('starting up'); + } +} +// #enddocregion + +// This NgModule exists only to avoid the Angular language service's "undeclared component" error +@NgModule({ + declarations: [ HeroOfTheMonthComponent ] +}) +class NoopModule {} diff --git a/public/docs/_examples/cb-dependency-injection/ts/src/app/hero-of-the-month.component.html b/public/docs/_examples/cb-dependency-injection/ts/src/app/hero-of-the-month.component.html new file mode 100644 index 0000000000..f0ae619d6a --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/src/app/hero-of-the-month.component.html @@ -0,0 +1,9 @@ +

    {{title}}

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

    Logs:

    +
    +
    {{log}}
    +
    diff --git a/public/docs/_examples/cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts new file mode 100644 index 0000000000..90ad13d639 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/src/app/hero-of-the-month.component.ts @@ -0,0 +1,63 @@ +/* tslint:disable:one-line:check-open-brace*/ +// #docplaster +// #docregion injection-token +import { InjectionToken } from '@angular/core'; + +export const TITLE = new InjectionToken('title'); +// #enddocregion injection-token + +// #docregion hero-of-the-month +import { Component, Inject } from '@angular/core'; + +import { DateLoggerService } from './date-logger.service'; +import { Hero } from './hero'; +import { HeroService } from './hero.service'; +import { LoggerService } from './logger.service'; +import { MinimalLogger } from './minimal-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 + +// #docregion hero-of-the-month +@Component({ + selector: 'hero-of-the-month', + templateUrl: './hero-of-the-month.component.html', + providers: [ + // #docregion use-value + { provide: Hero, useValue: someHero }, + // #docregion provide-injection-token + { provide: TITLE, useValue: 'Hero of the Month' }, + // #enddocregion provide-injection-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-injection-token, use-factory + { provide: RUNNERS_UP, useFactory: runnersUpFactory(2), deps: [Hero, HeroService] } + // #enddocregion provide-injection-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/public/docs/_examples/cb-dependency-injection/ts/src/app/hero.service.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/hero.service.ts new file mode 100644 index 0000000000..2063c30d7a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dependency-injection/ts/src/app/hero.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/hero.ts new file mode 100644 index 0000000000..c17069e727 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dependency-injection/ts/src/app/highlight.directive.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/highlight.directive.ts new file mode 100644 index 0000000000..e220114daa --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dependency-injection/ts/src/app/logger.service.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/logger.service.ts new file mode 100644 index 0000000000..df8ee6b9c7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dependency-injection/ts/src/app/minimal-logger.service.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/minimal-logger.service.ts new file mode 100644 index 0000000000..d87fa594d1 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/src/app/minimal-logger.service.ts @@ -0,0 +1,22 @@ +// #docregion +// Class used as a "narrowing" interface that exposes a minimal logger +// Other members of the actual implementation are invisible +export abstract class MinimalLogger { + logs: string[]; + logInfo: (msg: string) => void; +} +// #enddocregion + +/* +// Transpiles to: +// #docregion minimal-logger-transpiled + var MinimalLogger = (function () { + function MinimalLogger() {} + return MinimalLogger; + }()); + exports("MinimalLogger", MinimalLogger); +// #enddocregion minimal-logger-transpiled +*/ + +// See http://stackoverflow.com/questions/43154832/unexpected-token-export-in-angular-app-with-systemjs-and-typescript/ +export const _ = 0; diff --git a/public/docs/_examples/cb-dependency-injection/ts/src/app/parent-finder.component.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/parent-finder.component.ts new file mode 100644 index 0000000000..c6f208f79b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dependency-injection/ts/src/app/runners-up.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/runners-up.ts new file mode 100644 index 0000000000..0ce81ca55c --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/src/app/runners-up.ts @@ -0,0 +1,26 @@ +// #docplaster +// #docregion +import { InjectionToken } from '@angular/core'; + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; + +// #docregion runners-up +export const RUNNERS_UP = new InjectionToken('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/public/docs/_examples/cb-dependency-injection/ts/src/app/sorted-heroes.component.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/sorted-heroes.component.ts new file mode 100644 index 0000000000..8cb6e3c69c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dependency-injection/ts/src/app/user-context.service.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/user-context.service.ts new file mode 100644 index 0000000000..ed394fc734 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dependency-injection/ts/src/app/user.service.ts b/public/docs/_examples/cb-dependency-injection/ts/src/app/user.service.ts new file mode 100644 index 0000000000..c48b025a08 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dependency-injection/ts/src/index.html b/public/docs/_examples/cb-dependency-injection/ts/src/index.html new file mode 100644 index 0000000000..0308c72155 --- /dev/null +++ b/public/docs/_examples/cb-dependency-injection/ts/src/index.html @@ -0,0 +1,29 @@ + + + + + + Dependency Injection + + + + + + + + + + + + + + + + + + Loading app... + + + diff --git a/public/docs/_examples/cb-dependency-injection/ts/src/main.ts b/public/docs/_examples/cb-dependency-injection/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dependency-injection/ts/src/sample.css b/public/docs/_examples/cb-dependency-injection/ts/src/sample.css new file mode 100644 index 0000000000..a8b59efd05 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-component-loader/e2e-spec.ts b/public/docs/_examples/cb-dynamic-component-loader/e2e-spec.ts new file mode 100644 index 0000000000..5036ac2a88 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-component-loader/ts/example-config.json b/public/docs/_examples/cb-dynamic-component-loader/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/cb-dynamic-component-loader/ts/plnkr.json b/public/docs/_examples/cb-dynamic-component-loader/ts/plnkr.json new file mode 100644 index 0000000000..c43fb15c12 --- /dev/null +++ b/public/docs/_examples/cb-dynamic-component-loader/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Dynamic Component Loader", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js" + ], + "tags":["cookbook component"] +} diff --git a/public/docs/_examples/cb-dynamic-component-loader/ts/src/app/ad-banner.component.ts b/public/docs/_examples/cb-dynamic-component-loader/ts/src/app/ad-banner.component.ts new file mode 100644 index 0000000000..8489fce5b2 --- /dev/null +++ b/public/docs/_examples/cb-dynamic-component-loader/ts/src/app/ad-banner.component.ts @@ -0,0 +1,57 @@ +// #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 +}) +// #docregion class +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); + } +} +// #enddocregion class diff --git a/public/docs/_examples/cb-dynamic-component-loader/ts/src/app/ad-item.ts b/public/docs/_examples/cb-dynamic-component-loader/ts/src/app/ad-item.ts new file mode 100644 index 0000000000..ef8ca70577 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-component-loader/ts/src/app/ad.component.ts b/public/docs/_examples/cb-dynamic-component-loader/ts/src/app/ad.component.ts new file mode 100644 index 0000000000..dee3b47953 --- /dev/null +++ b/public/docs/_examples/cb-dynamic-component-loader/ts/src/app/ad.component.ts @@ -0,0 +1,4 @@ +// #docregion +export interface AdComponent { + data: any; +} diff --git a/public/docs/_examples/cb-dynamic-component-loader/ts/src/app/ad.directive.ts b/public/docs/_examples/cb-dynamic-component-loader/ts/src/app/ad.directive.ts new file mode 100644 index 0000000000..312e605228 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-component-loader/ts/src/app/ad.service.ts b/public/docs/_examples/cb-dynamic-component-loader/ts/src/app/ad.service.ts new file mode 100644 index 0000000000..91b0758771 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-component-loader/ts/src/app/app.component.ts b/public/docs/_examples/cb-dynamic-component-loader/ts/src/app/app.component.ts new file mode 100644 index 0000000000..89359ccdf6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-component-loader/ts/src/app/app.module.ts b/public/docs/_examples/cb-dynamic-component-loader/ts/src/app/app.module.ts new file mode 100644 index 0000000000..a65d394709 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-component-loader/ts/src/app/hero-job-ad.component.ts b/public/docs/_examples/cb-dynamic-component-loader/ts/src/app/hero-job-ad.component.ts new file mode 100644 index 0000000000..675a03d0e0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-component-loader/ts/src/app/hero-profile.component.ts b/public/docs/_examples/cb-dynamic-component-loader/ts/src/app/hero-profile.component.ts new file mode 100644 index 0000000000..1c266db3c9 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-component-loader/ts/src/index.html b/public/docs/_examples/cb-dynamic-component-loader/ts/src/index.html new file mode 100644 index 0000000000..9239d91d9a --- /dev/null +++ b/public/docs/_examples/cb-dynamic-component-loader/ts/src/index.html @@ -0,0 +1,26 @@ + + + + + + Dynamic Component Loader + + + + + + + + + + + + + + + Loading app... + + + diff --git a/public/docs/_examples/cb-dynamic-component-loader/ts/src/main.ts b/public/docs/_examples/cb-dynamic-component-loader/ts/src/main.ts new file mode 100644 index 0000000000..53d16fa43a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-component-loader/ts/src/sample.css b/public/docs/_examples/cb-dynamic-component-loader/ts/src/sample.css new file mode 100644 index 0000000000..7a2ca1f2dc --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-form/e2e-spec.ts b/public/docs/_examples/cb-dynamic-form/e2e-spec.ts new file mode 100644 index 0000000000..408ac75766 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-form/ts/example-config.json b/public/docs/_examples/cb-dynamic-form/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/cb-dynamic-form/ts/plnkr.json b/public/docs/_examples/cb-dynamic-form/ts/plnkr.json new file mode 100644 index 0000000000..1f50b4a992 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-form/ts/src/app/app.component.ts b/public/docs/_examples/cb-dynamic-form/ts/src/app/app.component.ts new file mode 100644 index 0000000000..582daced2e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-form/ts/src/app/app.module.ts b/public/docs/_examples/cb-dynamic-form/ts/src/app/app.module.ts new file mode 100644 index 0000000000..7a68e45a92 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-form/ts/src/app/dynamic-form-question.component.html b/public/docs/_examples/cb-dynamic-form/ts/src/app/dynamic-form-question.component.html new file mode 100644 index 0000000000..9f1b8cd4a6 --- /dev/null +++ b/public/docs/_examples/cb-dynamic-form/ts/src/app/dynamic-form-question.component.html @@ -0,0 +1,17 @@ + +
    + + +
    + + + + + +
    + +
    {{question.label}} is required
    +
    diff --git a/public/docs/_examples/cb-dynamic-form/ts/src/app/dynamic-form-question.component.ts b/public/docs/_examples/cb-dynamic-form/ts/src/app/dynamic-form-question.component.ts new file mode 100644 index 0000000000..024571a7c2 --- /dev/null +++ b/public/docs/_examples/cb-dynamic-form/ts/src/app/dynamic-form-question.component.ts @@ -0,0 +1,15 @@ +// #docregion +import { Component, Input } from '@angular/core'; +import { FormGroup } from '@angular/forms'; + +import { QuestionBase } from './question-base'; + +@Component({ + 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/public/docs/_examples/cb-dynamic-form/ts/src/app/dynamic-form.component.html b/public/docs/_examples/cb-dynamic-form/ts/src/app/dynamic-form.component.html new file mode 100644 index 0000000000..717f09ff71 --- /dev/null +++ b/public/docs/_examples/cb-dynamic-form/ts/src/app/dynamic-form.component.html @@ -0,0 +1,17 @@ + +
    +
    + +
    + +
    + +
    + +
    +
    + +
    + Saved the following values
    {{payLoad}} +
    +
    diff --git a/public/docs/_examples/cb-dynamic-form/ts/src/app/dynamic-form.component.ts b/public/docs/_examples/cb-dynamic-form/ts/src/app/dynamic-form.component.ts new file mode 100644 index 0000000000..8d95c0d3a4 --- /dev/null +++ b/public/docs/_examples/cb-dynamic-form/ts/src/app/dynamic-form.component.ts @@ -0,0 +1,28 @@ +// #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({ + 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/public/docs/_examples/cb-dynamic-form/ts/src/app/question-base.ts b/public/docs/_examples/cb-dynamic-form/ts/src/app/question-base.ts new file mode 100644 index 0000000000..2b32b00f2a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-form/ts/src/app/question-control.service.ts b/public/docs/_examples/cb-dynamic-form/ts/src/app/question-control.service.ts new file mode 100644 index 0000000000..1378ba8490 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-form/ts/src/app/question-dropdown.ts b/public/docs/_examples/cb-dynamic-form/ts/src/app/question-dropdown.ts new file mode 100644 index 0000000000..35a9074c74 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-form/ts/src/app/question-textbox.ts b/public/docs/_examples/cb-dynamic-form/ts/src/app/question-textbox.ts new file mode 100644 index 0000000000..aaa7edf267 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-form/ts/src/app/question.service.ts b/public/docs/_examples/cb-dynamic-form/ts/src/app/question.service.ts new file mode 100644 index 0000000000..bb452cf5e6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-form/ts/src/index.html b/public/docs/_examples/cb-dynamic-form/ts/src/index.html new file mode 100644 index 0000000000..01963f71e2 --- /dev/null +++ b/public/docs/_examples/cb-dynamic-form/ts/src/index.html @@ -0,0 +1,28 @@ + + + + + + Dynamic Form + + + + + + + + + + + + + + + + + Loading app... + + + diff --git a/public/docs/_examples/cb-dynamic-form/ts/src/main.ts b/public/docs/_examples/cb-dynamic-form/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-dynamic-form/ts/src/sample.css b/public/docs/_examples/cb-dynamic-form/ts/src/sample.css new file mode 100644 index 0000000000..fe2cc28481 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-form-validation/e2e-spec.ts b/public/docs/_examples/cb-form-validation/e2e-spec.ts new file mode 100644 index 0000000000..8ffc01e250 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-form-validation/ts/example-config.json b/public/docs/_examples/cb-form-validation/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/cb-form-validation/ts/plnkr.json b/public/docs/_examples/cb-form-validation/ts/plnkr.json new file mode 100644 index 0000000000..c5656d77f7 --- /dev/null +++ b/public/docs/_examples/cb-form-validation/ts/plnkr.json @@ -0,0 +1,8 @@ +{ + "description": "Validation", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js" + ] +} diff --git a/public/docs/_examples/cb-form-validation/ts/src/app/app.component.ts b/public/docs/_examples/cb-form-validation/ts/src/app/app.component.ts new file mode 100644 index 0000000000..2da4dc4d0a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-form-validation/ts/src/app/app.module.ts b/public/docs/_examples/cb-form-validation/ts/src/app/app.module.ts new file mode 100644 index 0000000000..72b4e3a770 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.html b/public/docs/_examples/cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.html new file mode 100644 index 0000000000..149537bd3e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.ts b/public/docs/_examples/cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.ts new file mode 100644 index 0000000000..241ff1e782 --- /dev/null +++ b/public/docs/_examples/cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.ts @@ -0,0 +1,116 @@ +/* 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({ + 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/public/docs/_examples/cb-form-validation/ts/src/app/reactive/hero-form-reactive.module.ts b/public/docs/_examples/cb-form-validation/ts/src/app/reactive/hero-form-reactive.module.ts new file mode 100644 index 0000000000..6ff9265e92 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-form-validation/ts/src/app/shared/forbidden-name.directive.ts b/public/docs/_examples/cb-form-validation/ts/src/app/shared/forbidden-name.directive.ts new file mode 100644 index 0000000000..870f514842 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-form-validation/ts/src/app/shared/hero.ts b/public/docs/_examples/cb-form-validation/ts/src/app/shared/hero.ts new file mode 100644 index 0000000000..fe2b55e51a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-form-validation/ts/src/app/shared/shared.module.ts b/public/docs/_examples/cb-form-validation/ts/src/app/shared/shared.module.ts new file mode 100644 index 0000000000..2b0ada59bd --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-form-validation/ts/src/app/shared/submitted.component.ts b/public/docs/_examples/cb-form-validation/ts/src/app/shared/submitted.component.ts new file mode 100644 index 0000000000..18cea6563f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-form-validation/ts/src/app/template/hero-form-template.module.ts b/public/docs/_examples/cb-form-validation/ts/src/app/template/hero-form-template.module.ts new file mode 100644 index 0000000000..042c019d5e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-form-validation/ts/src/app/template/hero-form-template1.component.html b/public/docs/_examples/cb-form-validation/ts/src/app/template/hero-form-template1.component.html new file mode 100644 index 0000000000..22b374b622 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-form-validation/ts/src/app/template/hero-form-template1.component.ts b/public/docs/_examples/cb-form-validation/ts/src/app/template/hero-form-template1.component.ts new file mode 100644 index 0000000000..1bc29db44d --- /dev/null +++ b/public/docs/_examples/cb-form-validation/ts/src/app/template/hero-form-template1.component.ts @@ -0,0 +1,47 @@ +/* tslint:disable: member-ordering */ +// #docplaster +// #docregion +import { Component } from '@angular/core'; + + +import { Hero } from '../shared/hero'; + +@Component({ + 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/public/docs/_examples/cb-form-validation/ts/src/app/template/hero-form-template2.component.html b/public/docs/_examples/cb-form-validation/ts/src/app/template/hero-form-template2.component.html new file mode 100644 index 0000000000..8bb7066541 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-form-validation/ts/src/app/template/hero-form-template2.component.ts b/public/docs/_examples/cb-form-validation/ts/src/app/template/hero-form-template2.component.ts new file mode 100644 index 0000000000..320ef09efd --- /dev/null +++ b/public/docs/_examples/cb-form-validation/ts/src/app/template/hero-form-template2.component.ts @@ -0,0 +1,99 @@ +/* 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({ + 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/public/docs/_examples/cb-form-validation/ts/src/forms.css b/public/docs/_examples/cb-form-validation/ts/src/forms.css new file mode 100644 index 0000000000..67ad13037b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-form-validation/ts/src/index.html b/public/docs/_examples/cb-form-validation/ts/src/index.html new file mode 100644 index 0000000000..e7201508d7 --- /dev/null +++ b/public/docs/_examples/cb-form-validation/ts/src/index.html @@ -0,0 +1,29 @@ + + + Hero Form with Validation + + + + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/public/docs/_examples/cb-form-validation/ts/src/main.ts b/public/docs/_examples/cb-form-validation/ts/src/main.ts new file mode 100644 index 0000000000..f332d1d245 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-i18n/e2e-spec.ts b/public/docs/_examples/cb-i18n/e2e-spec.ts new file mode 100644 index 0000000000..3dcca5a670 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-i18n/ts/.gitignore b/public/docs/_examples/cb-i18n/ts/.gitignore new file mode 100644 index 0000000000..d38bb50aa9 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-i18n/ts/example-config.json b/public/docs/_examples/cb-i18n/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/cb-i18n/ts/messages.xlf b/public/docs/_examples/cb-i18n/ts/messages.xlf new file mode 100644 index 0000000000..dee8b65aca --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-i18n/ts/plnkr.json b/public/docs/_examples/cb-i18n/ts/plnkr.json new file mode 100644 index 0000000000..36f2685129 --- /dev/null +++ b/public/docs/_examples/cb-i18n/ts/plnkr.json @@ -0,0 +1,19 @@ +{ + "description": "i18n", + "basePath": "src/", + "files": [ + "app/**/*.css", + "app/**/*.html", + "app/**/*.ts", + "messages.xlf", + "locale/messages.*.xlf", + + "!**/*.[1].*", + + "main.ts", + "styles.css", + "systemjs-text-plugin.js", + "index.html" + ], + "tags": ["i18n"] +} diff --git a/public/docs/_examples/cb-i18n/ts/src/app/app.component.1.html b/public/docs/_examples/cb-i18n/ts/src/app/app.component.1.html new file mode 100644 index 0000000000..cc7d4f1155 --- /dev/null +++ b/public/docs/_examples/cb-i18n/ts/src/app/app.component.1.html @@ -0,0 +1,15 @@ + +

    Hello i18n!

    + + + +

    Hello i18n!

    + + + +

    Hello i18n!

    + + + + + diff --git a/public/docs/_examples/cb-i18n/ts/src/app/app.component.html b/public/docs/_examples/cb-i18n/ts/src/app/app.component.html new file mode 100644 index 0000000000..39ace24f79 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-i18n/ts/src/app/app.component.ts b/public/docs/_examples/cb-i18n/ts/src/app/app.component.ts new file mode 100644 index 0000000000..09b8722e9b --- /dev/null +++ b/public/docs/_examples/cb-i18n/ts/src/app/app.component.ts @@ -0,0 +1,19 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + templateUrl: './app.component.html' +}) +export class AppComponent { + wolves = 0; + gender = 'f'; + fly = true; + logo = 'https://angular.io/resources/images/logos/angular/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/public/docs/_examples/cb-i18n/ts/src/app/app.module.ts b/public/docs/_examples/cb-i18n/ts/src/app/app.module.ts new file mode 100644 index 0000000000..64ad44075b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-i18n/ts/src/app/i18n-providers.ts b/public/docs/_examples/cb-i18n/ts/src/app/i18n-providers.ts new file mode 100644 index 0000000000..f0bb662dc3 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-i18n/ts/src/index.html b/public/docs/_examples/cb-i18n/ts/src/index.html new file mode 100644 index 0000000000..ce90cd24c4 --- /dev/null +++ b/public/docs/_examples/cb-i18n/ts/src/index.html @@ -0,0 +1,39 @@ + + + + + Angular i18n example + + + + + + + + + + + + + + + + + + + Loading... + + diff --git a/public/docs/_examples/cb-i18n/ts/src/locale/messages.es.xlf b/public/docs/_examples/cb-i18n/ts/src/locale/messages.es.xlf new file mode 100644 index 0000000000..7b813c38dd --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-i18n/ts/src/locale/messages.es.xlf.html b/public/docs/_examples/cb-i18n/ts/src/locale/messages.es.xlf.html new file mode 100644 index 0000000000..a6cdccc6c1 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-i18n/ts/src/main.1.ts b/public/docs/_examples/cb-i18n/ts/src/main.1.ts new file mode 100644 index 0000000000..f332d1d245 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-i18n/ts/src/main.ts b/public/docs/_examples/cb-i18n/ts/src/main.ts new file mode 100644 index 0000000000..894cecfb10 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-i18n/ts/src/systemjs-text-plugin.js b/public/docs/_examples/cb-i18n/ts/src/systemjs-text-plugin.js new file mode 100644 index 0000000000..d5ca508fe0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-set-document-title/e2e-spec.ts b/public/docs/_examples/cb-set-document-title/e2e-spec.ts new file mode 100644 index 0000000000..801b732995 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-set-document-title/ts/example-config.json b/public/docs/_examples/cb-set-document-title/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/cb-set-document-title/ts/plnkr.json b/public/docs/_examples/cb-set-document-title/ts/plnkr.json new file mode 100644 index 0000000000..020d5a1db2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-set-document-title/ts/src/app/app.component.ts b/public/docs/_examples/cb-set-document-title/ts/src/app/app.component.ts new file mode 100644 index 0000000000..f1905e635d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-set-document-title/ts/src/app/app.module.ts b/public/docs/_examples/cb-set-document-title/ts/src/app/app.module.ts new file mode 100644 index 0000000000..81f13c244c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-set-document-title/ts/src/index.html b/public/docs/_examples/cb-set-document-title/ts/src/index.html new file mode 100644 index 0000000000..6b00948530 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-set-document-title/ts/src/main.ts b/public/docs/_examples/cb-set-document-title/ts/src/main.ts new file mode 100644 index 0000000000..f332d1d245 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/e2e-spec.ts b/public/docs/_examples/cb-ts-to-js/e2e-spec.ts new file mode 100644 index 0000000000..bc67bac8f0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6-decorators/example-config.json b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/example-config.json new file mode 100644 index 0000000000..81f31aaf0d --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/example-config.json @@ -0,0 +1,3 @@ +{ + "build": "build:babel" +} diff --git a/public/docs/_examples/cb-ts-to-js/js-es6-decorators/plnkr.json b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/plnkr.json new file mode 100644 index 0000000000..447fc5f605 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/.babelrc b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/.babelrc new file mode 100644 index 0000000000..3aaf507508 --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/.babelrc @@ -0,0 +1,6 @@ +{ + "presets": [ + "es2015", + "angular2" + ] +} diff --git a/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/app.component.es6 b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/app.component.es6 new file mode 100644 index 0000000000..d425788f46 --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/app.component.es6 @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; + +@Component({ + 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/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/app.component.html b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/app.component.html new file mode 100644 index 0000000000..995645073a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/app.module.es6 b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/app.module.es6 new file mode 100644 index 0000000000..9c248a7ad3 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/confirm.component.es6 b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/confirm.component.es6 new file mode 100644 index 0000000000..f01fa4de40 --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/confirm.component.es6 @@ -0,0 +1,21 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +// #docregion +@Component({ + 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/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/confirm.component.html b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/confirm.component.html new file mode 100644 index 0000000000..45275d218a --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/confirm.component.html @@ -0,0 +1,6 @@ + + diff --git a/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/data.service.es6 b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/data.service.es6 new file mode 100644 index 0000000000..cd7f9e1aae --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero-di-inject-additional.component.es6 b/public/docs/_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/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero-di-inject.component.es6 b/public/docs/_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/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero-di.component.es6 b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero-di.component.es6 new file mode 100644 index 0000000000..3a17abd281 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero-host-meta.component.es6 b/public/docs/_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/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero-host.component.es6 b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero-host.component.es6 new file mode 100644 index 0000000000..e8d72233c8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero-io.component.es6 b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero-io.component.es6 new file mode 100644 index 0000000000..4b36411e78 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero-lifecycle.component.es6 b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero-lifecycle.component.es6 new file mode 100644 index 0000000000..2539266597 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6 b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6 new file mode 100644 index 0000000000..fced43d4d7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero-title.component.es6 b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero-title.component.es6 new file mode 100644 index 0000000000..04c14a9631 --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero-title.component.es6 @@ -0,0 +1,25 @@ +import { Attribute, Component, Inject, Optional } from '@angular/core'; + +// #docregion +// #docregion templateUrl +@Component({ + 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/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero-title.component.html b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero-title.component.html new file mode 100644 index 0000000000..164683cb7c --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero-title.component.html @@ -0,0 +1,4 @@ + +

    {{titlePrefix}} {{title}}

    + +

    {{ msg }}

    diff --git a/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero.component.es6 b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/app/hero.component.es6 new file mode 100644 index 0000000000..2976ec605e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/index.html b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/index.html new file mode 100644 index 0000000000..2a94db38cc --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/index.html @@ -0,0 +1,26 @@ + + + + + + + + TypeScript to JavaScript + + + + + + + + + + + + + Loading... + + + diff --git a/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/main.es6 b/public/docs/_examples/cb-ts-to-js/js-es6-decorators/src/main.es6 new file mode 100644 index 0000000000..f22933ba8e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6/example-config.json b/public/docs/_examples/cb-ts-to-js/js-es6/example-config.json new file mode 100644 index 0000000000..81f31aaf0d --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js-es6/example-config.json @@ -0,0 +1,3 @@ +{ + "build": "build:babel" +} diff --git a/public/docs/_examples/cb-ts-to-js/js-es6/plnkr.json b/public/docs/_examples/cb-ts-to-js/js-es6/plnkr.json new file mode 100644 index 0000000000..447fc5f605 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6/src/.babelrc b/public/docs/_examples/cb-ts-to-js/js-es6/src/.babelrc new file mode 100644 index 0000000000..3c078e9f99 --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js-es6/src/.babelrc @@ -0,0 +1,5 @@ +{ + "presets": [ + "es2015" + ] +} diff --git a/public/docs/_examples/cb-ts-to-js/js-es6/src/app/app.component.es6 b/public/docs/_examples/cb-ts-to-js/js-es6/src/app/app.component.es6 new file mode 100644 index 0000000000..6079f7f246 --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js-es6/src/app/app.component.es6 @@ -0,0 +1,19 @@ +import { Component } from '@angular/core'; + +export class AppComponent { + constructor() { + this.title = 'Plain ES6 JavaScript'; + } +} + +AppComponent.annotations = [ + new Component({ + 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/public/docs/_examples/cb-ts-to-js/js-es6/src/app/app.component.html b/public/docs/_examples/cb-ts-to-js/js-es6/src/app/app.component.html new file mode 100644 index 0000000000..52b9b4580e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6/src/app/app.module.es6 b/public/docs/_examples/cb-ts-to-js/js-es6/src/app/app.module.es6 new file mode 100644 index 0000000000..f31141e78f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6/src/app/confirm.component.es6 b/public/docs/_examples/cb-ts-to-js/js-es6/src/app/confirm.component.es6 new file mode 100644 index 0000000000..296268dda4 --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js-es6/src/app/confirm.component.es6 @@ -0,0 +1,31 @@ +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({ + selector: 'app-confirm', + templateUrl: './confirm.component.html', + inputs: [ + 'okMsg', + 'notOkMsg: cancelMsg' + ], + outputs: [ + 'ok', + 'notOk: cancel' + ] + }) +]; +// #enddocregion diff --git a/public/docs/_examples/cb-ts-to-js/js-es6/src/app/confirm.component.html b/public/docs/_examples/cb-ts-to-js/js-es6/src/app/confirm.component.html new file mode 100644 index 0000000000..45275d218a --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js-es6/src/app/confirm.component.html @@ -0,0 +1,6 @@ + + diff --git a/public/docs/_examples/cb-ts-to-js/js-es6/src/app/data.service.es6 b/public/docs/_examples/cb-ts-to-js/js-es6/src/app/data.service.es6 new file mode 100644 index 0000000000..de023ce5a0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero-di-inject-additional.component.es6 b/public/docs/_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/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero-di-inject.component.es6 b/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero-di-inject.component.es6 new file mode 100644 index 0000000000..2f95a0b981 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero-di.component.es6 b/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero-di.component.es6 new file mode 100644 index 0000000000..a18b1ba777 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero-host.component.es6 b/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero-host.component.es6 new file mode 100644 index 0000000000..b06701ee8d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero-io.component.es6 b/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero-io.component.es6 new file mode 100644 index 0000000000..0dc8c08f2d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero-lifecycle.component.es6 b/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero-lifecycle.component.es6 new file mode 100644 index 0000000000..b394ff59aa --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero-queries.component.es6 b/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero-queries.component.es6 new file mode 100644 index 0000000000..bf3b914406 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero-title.component.es6 b/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero-title.component.es6 new file mode 100644 index 0000000000..4b89dfba83 --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero-title.component.es6 @@ -0,0 +1,28 @@ +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({ + selector: 'hero-title', + templateUrl: './hero-title.component.html' + }) +]; +// #enddocregion templateUrl + +HeroTitleComponent.parameters = [ + [new Optional(), new Inject('titlePrefix')], + [new Attribute('title')] +]; diff --git a/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero-title.component.html b/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero-title.component.html new file mode 100644 index 0000000000..164683cb7c --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero-title.component.html @@ -0,0 +1,4 @@ + +

    {{titlePrefix}} {{title}}

    + +

    {{ msg }}

    diff --git a/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero.component.es6 b/public/docs/_examples/cb-ts-to-js/js-es6/src/app/hero.component.es6 new file mode 100644 index 0000000000..350f946460 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js-es6/src/index.html b/public/docs/_examples/cb-ts-to-js/js-es6/src/index.html new file mode 100644 index 0000000000..2a94db38cc --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js-es6/src/index.html @@ -0,0 +1,26 @@ + + + + + + + + TypeScript to JavaScript + + + + + + + + + + + + + Loading... + + + diff --git a/public/docs/_examples/cb-ts-to-js/js-es6/src/main.es6 b/public/docs/_examples/cb-ts-to-js/js-es6/src/main.es6 new file mode 100644 index 0000000000..f22933ba8e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js/example-config.json b/public/docs/_examples/cb-ts-to-js/js/example-config.json new file mode 100644 index 0000000000..81f31aaf0d --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js/example-config.json @@ -0,0 +1,3 @@ +{ + "build": "build:babel" +} diff --git a/public/docs/_examples/cb-ts-to-js/js/plnkr.json b/public/docs/_examples/cb-ts-to-js/js/plnkr.json new file mode 100644 index 0000000000..5e1eb188be --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js/src/app/app.component.html b/public/docs/_examples/cb-ts-to-js/js/src/app/app.component.html new file mode 100644 index 0000000000..4ca26f2657 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js/src/app/app.component.js b/public/docs/_examples/cb-ts-to-js/js/src/app/app.component.js new file mode 100644 index 0000000000..b6d1ec29b0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js/src/app/app.module.js b/public/docs/_examples/cb-ts-to-js/js/src/app/app.module.js new file mode 100644 index 0000000000..fc2300a89b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js/src/app/confirm.component.html b/public/docs/_examples/cb-ts-to-js/js/src/app/confirm.component.html new file mode 100644 index 0000000000..45275d218a --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js/src/app/confirm.component.html @@ -0,0 +1,6 @@ + + diff --git a/public/docs/_examples/cb-ts-to-js/js/src/app/confirm.component.js b/public/docs/_examples/cb-ts-to-js/js/src/app/confirm.component.js new file mode 100644 index 0000000000..b76d3f54aa --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js/src/app/data.service.js b/public/docs/_examples/cb-ts-to-js/js/src/app/data.service.js new file mode 100644 index 0000000000..643bb57dca --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js/src/app/hero-di-inject-additional.component.js b/public/docs/_examples/cb-ts-to-js/js/src/app/hero-di-inject-additional.component.js new file mode 100644 index 0000000000..450cc53847 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js/src/app/hero-di-inject.component.js b/public/docs/_examples/cb-ts-to-js/js/src/app/hero-di-inject.component.js new file mode 100644 index 0000000000..879365208c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js/src/app/hero-di.component.js b/public/docs/_examples/cb-ts-to-js/js/src/app/hero-di.component.js new file mode 100644 index 0000000000..2f49a85079 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js/src/app/hero-host.component.js b/public/docs/_examples/cb-ts-to-js/js/src/app/hero-host.component.js new file mode 100644 index 0000000000..b73e50aae0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js/src/app/hero-io-dsl.component.html b/public/docs/_examples/cb-ts-to-js/js/src/app/hero-io-dsl.component.html new file mode 100644 index 0000000000..c8023ccb45 --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js/src/app/hero-io-dsl.component.html @@ -0,0 +1,7 @@ + + +OK clicked +Cancel clicked diff --git a/public/docs/_examples/cb-ts-to-js/js/src/app/hero-io.component.html b/public/docs/_examples/cb-ts-to-js/js/src/app/hero-io.component.html new file mode 100644 index 0000000000..215ddccf92 --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js/src/app/hero-io.component.html @@ -0,0 +1,7 @@ + + +OK clicked +Cancel clicked diff --git a/public/docs/_examples/cb-ts-to-js/js/src/app/hero-io.component.js b/public/docs/_examples/cb-ts-to-js/js/src/app/hero-io.component.js new file mode 100644 index 0000000000..b09208ce68 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js/src/app/hero-lifecycle.component.js b/public/docs/_examples/cb-ts-to-js/js/src/app/hero-lifecycle.component.js new file mode 100644 index 0000000000..2a68288f92 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js/src/app/hero-queries.component.js b/public/docs/_examples/cb-ts-to-js/js/src/app/hero-queries.component.js new file mode 100644 index 0000000000..5e8de58d41 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js/src/app/hero-title.component.html b/public/docs/_examples/cb-ts-to-js/js/src/app/hero-title.component.html new file mode 100644 index 0000000000..164683cb7c --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js/src/app/hero-title.component.html @@ -0,0 +1,4 @@ + +

    {{titlePrefix}} {{title}}

    + +

    {{ msg }}

    diff --git a/public/docs/_examples/cb-ts-to-js/js/src/app/hero-title.component.js b/public/docs/_examples/cb-ts-to-js/js/src/app/hero-title.component.js new file mode 100644 index 0000000000..f0770e1228 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js/src/app/hero.component.js b/public/docs/_examples/cb-ts-to-js/js/src/app/hero.component.js new file mode 100644 index 0000000000..e1407b2635 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/js/src/index.html b/public/docs/_examples/cb-ts-to-js/js/src/index.html new file mode 100644 index 0000000000..00338aa55a --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/js/src/index.html @@ -0,0 +1,43 @@ + + + + + + + TypeScript to JavaScript + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/public/docs/_examples/cb-ts-to-js/js/src/main.js b/public/docs/_examples/cb-ts-to-js/js/src/main.js new file mode 100644 index 0000000000..fd3e737c9e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/ts/example-config.json b/public/docs/_examples/cb-ts-to-js/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/cb-ts-to-js/ts/plnkr.json b/public/docs/_examples/cb-ts-to-js/ts/plnkr.json new file mode 100644 index 0000000000..447fc5f605 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/ts/src/app/app.component.html b/public/docs/_examples/cb-ts-to-js/ts/src/app/app.component.html new file mode 100644 index 0000000000..995645073a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/ts/src/app/app.component.ts b/public/docs/_examples/cb-ts-to-js/ts/src/app/app.component.ts new file mode 100644 index 0000000000..912c9e1a80 --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/ts/src/app/app.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; + +@Component({ + 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/public/docs/_examples/cb-ts-to-js/ts/src/app/app.module.ts b/public/docs/_examples/cb-ts-to-js/ts/src/app/app.module.ts new file mode 100644 index 0000000000..992c3c3514 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/ts/src/app/confirm.component.html b/public/docs/_examples/cb-ts-to-js/ts/src/app/confirm.component.html new file mode 100644 index 0000000000..45275d218a --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/ts/src/app/confirm.component.html @@ -0,0 +1,6 @@ + + diff --git a/public/docs/_examples/cb-ts-to-js/ts/src/app/confirm.component.ts b/public/docs/_examples/cb-ts-to-js/ts/src/app/confirm.component.ts new file mode 100644 index 0000000000..f01fa4de40 --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/ts/src/app/confirm.component.ts @@ -0,0 +1,21 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +// #docregion +@Component({ + 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/public/docs/_examples/cb-ts-to-js/ts/src/app/data.service.ts b/public/docs/_examples/cb-ts-to-js/ts/src/app/data.service.ts new file mode 100644 index 0000000000..cd7f9e1aae --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-di-inject-additional.component.ts b/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-di-inject-additional.component.ts new file mode 100644 index 0000000000..ec460a9dbc --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-di-inject.component.ts b/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-di-inject.component.ts new file mode 100644 index 0000000000..c583a1b0f6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-di.component.ts b/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-di.component.ts new file mode 100644 index 0000000000..3a17abd281 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-host-meta.component.ts b/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-host-meta.component.ts new file mode 100644 index 0000000000..fefe4a5470 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-host.component.ts b/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-host.component.ts new file mode 100644 index 0000000000..e8d72233c8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-io.component.ts b/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-io.component.ts new file mode 100644 index 0000000000..4b36411e78 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-lifecycle.component.ts b/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-lifecycle.component.ts new file mode 100644 index 0000000000..2629c85a1a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-queries.component.ts b/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-queries.component.ts new file mode 100644 index 0000000000..8b2d91ea85 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-title.component.html b/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-title.component.html new file mode 100644 index 0000000000..164683cb7c --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-title.component.html @@ -0,0 +1,4 @@ + +

    {{titlePrefix}} {{title}}

    + +

    {{ msg }}

    diff --git a/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-title.component.ts b/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-title.component.ts new file mode 100644 index 0000000000..48aaae7d44 --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/ts/src/app/hero-title.component.ts @@ -0,0 +1,21 @@ +import { Attribute, Component, Inject, Optional } from '@angular/core'; + +// #docregion +// #docregion templateUrl +@Component({ + 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/public/docs/_examples/cb-ts-to-js/ts/src/app/hero.component.ts b/public/docs/_examples/cb-ts-to-js/ts/src/app/hero.component.ts new file mode 100644 index 0000000000..2976ec605e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-ts-to-js/ts/src/index.html b/public/docs/_examples/cb-ts-to-js/ts/src/index.html new file mode 100644 index 0000000000..d9ad1f7aef --- /dev/null +++ b/public/docs/_examples/cb-ts-to-js/ts/src/index.html @@ -0,0 +1,26 @@ + + + + + + + + TypeScript to JavaScript + + + + + + + + + + + + + Loading... + + + diff --git a/public/docs/_examples/cb-ts-to-js/ts/src/main.ts b/public/docs/_examples/cb-ts-to-js/ts/src/main.ts new file mode 100644 index 0000000000..f22933ba8e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cb-visual-studio-2015/ts/.gitignore b/public/docs/_examples/cb-visual-studio-2015/ts/.gitignore new file mode 100644 index 0000000000..ca3f2dbd5e --- /dev/null +++ b/public/docs/_examples/cb-visual-studio-2015/ts/.gitignore @@ -0,0 +1 @@ +!tsconfig.json diff --git a/public/docs/_examples/cb-visual-studio-2015/ts/tsconfig.json b/public/docs/_examples/cb-visual-studio-2015/ts/tsconfig.json new file mode 100644 index 0000000000..2624488890 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cli-quickstart/e2e-spec.ts b/public/docs/_examples/cli-quickstart/e2e-spec.ts new file mode 100644 index 0000000000..59816e13c2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cli-quickstart/ts/.angular-cli.json b/public/docs/_examples/cli-quickstart/ts/.angular-cli.json new file mode 100644 index 0000000000..49a6b93bd0 --- /dev/null +++ b/public/docs/_examples/cli-quickstart/ts/.angular-cli.json @@ -0,0 +1,57 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "project": { + "name": "master-project" + }, + "apps": [ + { + "root": "src", + "outDir": "dist", + "assets": [ + "assets", + "favicon.ico" + ], + "index": "index.html", + "main": "main.ts", + "polyfills": "polyfills.ts", + "test": "test.ts", + "tsconfig": "tsconfig.app.json", + "testTsconfig": "tsconfig.spec.json", + "prefix": "app", + "styles": [ + "styles.css" + ], + "scripts": [], + "environmentSource": "environments/environment.ts", + "environments": { + "dev": "environments/environment.ts", + "prod": "environments/environment.prod.ts" + } + } + ], + "e2e": { + "protractor": { + "config": "./protractor.conf.js" + } + }, + "lint": [ + { + "project": "src/tsconfig.app.json" + }, + { + "project": "src/tsconfig.spec.json" + }, + { + "project": "e2e/tsconfig.e2e.json" + } + ], + "test": { + "karma": { + "config": "./karma.conf.js" + } + }, + "defaults": { + "styleExt": "css", + "component": {} + } +} diff --git a/public/docs/_examples/cli-quickstart/ts/.editorconfig b/public/docs/_examples/cli-quickstart/ts/.editorconfig new file mode 100644 index 0000000000..6e87a003da --- /dev/null +++ b/public/docs/_examples/cli-quickstart/ts/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/public/docs/_examples/cli-quickstart/ts/.gitignore b/public/docs/_examples/cli-quickstart/ts/.gitignore new file mode 100644 index 0000000000..04a48dc643 --- /dev/null +++ b/public/docs/_examples/cli-quickstart/ts/.gitignore @@ -0,0 +1,45 @@ +# 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/ +*.sublime-workspace + +# 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 +!karma.conf.js +!protractor.conf.js diff --git a/public/docs/_examples/cli-quickstart/ts/README.md b/public/docs/_examples/cli-quickstart/ts/README.md new file mode 100644 index 0000000000..efe9ec67ae --- /dev/null +++ b/public/docs/_examples/cli-quickstart/ts/README.md @@ -0,0 +1,27 @@ +# MasterProject + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0-rc.0. + +## 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`. + +## 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/public/docs/_examples/cli-quickstart/ts/bs-config.cli.json b/public/docs/_examples/cli-quickstart/ts/bs-config.cli.json new file mode 100644 index 0000000000..ac61d35f83 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cli-quickstart/ts/e2e/app.e2e-spec.ts b/public/docs/_examples/cli-quickstart/ts/e2e/app.e2e-spec.ts new file mode 100644 index 0000000000..4346d120c8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cli-quickstart/ts/e2e/app.po.ts b/public/docs/_examples/cli-quickstart/ts/e2e/app.po.ts new file mode 100644 index 0000000000..5d82157326 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cli-quickstart/ts/e2e/tsconfig.e2e.json b/public/docs/_examples/cli-quickstart/ts/e2e/tsconfig.e2e.json new file mode 100644 index 0000000000..74c2bca131 --- /dev/null +++ b/public/docs/_examples/cli-quickstart/ts/e2e/tsconfig.e2e.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ + "es2016" + ], + "outDir": "../dist/out-tsc-e2e", + "module": "commonjs", + "target": "es6", + "types":[ + "jasmine", + "node" + ] + } +} diff --git a/public/docs/_examples/cli-quickstart/ts/example-config.json b/public/docs/_examples/cli-quickstart/ts/example-config.json new file mode 100644 index 0000000000..313764c3c6 --- /dev/null +++ b/public/docs/_examples/cli-quickstart/ts/example-config.json @@ -0,0 +1,4 @@ +{ + "build": "build:cli", + "run": "serve:cli" +} diff --git a/public/docs/_examples/cli-quickstart/ts/karma.conf.js b/public/docs/_examples/cli-quickstart/ts/karma.conf.js new file mode 100644 index 0000000000..84b4cd5aca --- /dev/null +++ b/public/docs/_examples/cli-quickstart/ts/karma.conf.js @@ -0,0 +1,44 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/0.13/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular/cli'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage-istanbul-reporter'), + require('@angular/cli/plugins/karma') + ], + client:{ + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + files: [ + { pattern: './src/test.ts', watched: false } + ], + preprocessors: { + './src/test.ts': ['@angular/cli'] + }, + mime: { + 'text/x-typescript': ['ts','tsx'] + }, + coverageIstanbulReporter: { + reports: [ 'html', 'lcovonly' ], + fixWebpackSourcePaths: true + }, + angularCli: { + environment: 'dev' + }, + reporters: config.angularCli && config.angularCli.codeCoverage + ? ['progress', 'coverage-istanbul'] + : ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false + }); +}; diff --git a/public/docs/_examples/cli-quickstart/ts/protractor.conf.js b/public/docs/_examples/cli-quickstart/ts/protractor.conf.js new file mode 100644 index 0000000000..2cbc329391 --- /dev/null +++ b/public/docs/_examples/cli-quickstart/ts/protractor.conf.js @@ -0,0 +1,31 @@ +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +/*global jasmine */ +const { SpecReporter } = require('jasmine-spec-reporter'); + +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './e2e/**/*.e2e-spec.ts' + ], + capabilities: { + 'browserName': 'chrome' + }, + directConnect: true, + baseUrl: 'http://localhost:4200/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + beforeLaunch: function() { + require('ts-node').register({ + project: 'e2e/tsconfig.e2e.json' + }); + }, + onPrepare() { + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); + } +}; diff --git a/public/docs/_examples/cli-quickstart/ts/src/app/app.component.css b/public/docs/_examples/cli-quickstart/ts/src/app/app.component.css new file mode 100644 index 0000000000..a2b21fae82 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cli-quickstart/ts/src/app/app.component.html b/public/docs/_examples/cli-quickstart/ts/src/app/app.component.html new file mode 100644 index 0000000000..b6931b538a --- /dev/null +++ b/public/docs/_examples/cli-quickstart/ts/src/app/app.component.html @@ -0,0 +1,3 @@ +

    + {{title}} +

    diff --git a/public/docs/_examples/cli-quickstart/ts/src/app/app.component.spec.ts b/public/docs/_examples/cli-quickstart/ts/src/app/app.component.spec.ts new file mode 100644 index 0000000000..c740bcd745 --- /dev/null +++ b/public/docs/_examples/cli-quickstart/ts/src/app/app.component.spec.ts @@ -0,0 +1,32 @@ +import { TestBed, async } from '@angular/core/testing'; + +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + AppComponent + ], + }).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/public/docs/_examples/cli-quickstart/ts/src/app/app.component.ts b/public/docs/_examples/cli-quickstart/ts/src/app/app.component.ts new file mode 100644 index 0000000000..4c1b4a9bf7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cli-quickstart/ts/src/app/app.module.ts b/public/docs/_examples/cli-quickstart/ts/src/app/app.module.ts new file mode 100644 index 0000000000..67ae49119b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cli-quickstart/ts/src/assets/.gitkeep b/public/docs/_examples/cli-quickstart/ts/src/assets/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/cli-quickstart/ts/src/environments/environment.prod.ts b/public/docs/_examples/cli-quickstart/ts/src/environments/environment.prod.ts new file mode 100644 index 0000000000..3612073bc3 --- /dev/null +++ b/public/docs/_examples/cli-quickstart/ts/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true +}; diff --git a/public/docs/_examples/cli-quickstart/ts/src/environments/environment.ts b/public/docs/_examples/cli-quickstart/ts/src/environments/environment.ts new file mode 100644 index 0000000000..b7f639aeca --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cli-quickstart/ts/src/favicon.ico b/public/docs/_examples/cli-quickstart/ts/src/favicon.ico new file mode 100644 index 0000000000..8081c7ceaf Binary files /dev/null and b/public/docs/_examples/cli-quickstart/ts/src/favicon.ico differ diff --git a/public/docs/_examples/cli-quickstart/ts/src/index.html b/public/docs/_examples/cli-quickstart/ts/src/index.html new file mode 100644 index 0000000000..8a1a7b32b3 --- /dev/null +++ b/public/docs/_examples/cli-quickstart/ts/src/index.html @@ -0,0 +1,14 @@ + + + + + MyApp + + + + + + + Loading... + + diff --git a/public/docs/_examples/cli-quickstart/ts/src/main.ts b/public/docs/_examples/cli-quickstart/ts/src/main.ts new file mode 100644 index 0000000000..a9ca1caf8c --- /dev/null +++ b/public/docs/_examples/cli-quickstart/ts/src/main.ts @@ -0,0 +1,11 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule); diff --git a/public/docs/_examples/cli-quickstart/ts/src/polyfills.ts b/public/docs/_examples/cli-quickstart/ts/src/polyfills.ts new file mode 100644 index 0000000000..53bdaf1b86 --- /dev/null +++ b/public/docs/_examples/cli-quickstart/ts/src/polyfills.ts @@ -0,0 +1,68 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** IE9, IE10 and IE11 requires all of the following polyfills. **/ +// 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'; + +/** IE10 and IE11 requires the following for NgClass support on SVG elements */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** IE10 and IE11 requires the following to support `@angular/animation`. */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + +/** Evergreen browsers require these. **/ +import 'core-js/es6/reflect'; +import 'core-js/es7/reflect'; + + +/** ALL Firefox browsers require the following to support `@angular/animation`. **/ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + + + +/*************************************************************************************************** + * Zone JS is required by Angular itself. + */ +import 'zone.js/dist/zone'; // Included with Angular CLI. + + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ + +/** + * Date, currency, decimal and percent pipes. + * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 + */ +// import 'intl'; // Run `npm install --save intl`. diff --git a/public/docs/_examples/cli-quickstart/ts/src/styles.css b/public/docs/_examples/cli-quickstart/ts/src/styles.css new file mode 100644 index 0000000000..d81835d0cd --- /dev/null +++ b/public/docs/_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/public/docs/_examples/cli-quickstart/ts/src/test.ts b/public/docs/_examples/cli-quickstart/ts/src/test.ts new file mode 100644 index 0000000000..9bf72267e9 --- /dev/null +++ b/public/docs/_examples/cli-quickstart/ts/src/test.ts @@ -0,0 +1,32 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +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/public/docs/_examples/cli-quickstart/ts/src/tsconfig.app.json b/public/docs/_examples/cli-quickstart/ts/src/tsconfig.app.json new file mode 100644 index 0000000000..9f12c4b849 --- /dev/null +++ b/public/docs/_examples/cli-quickstart/ts/src/tsconfig.app.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ + "es2016", + "dom" + ], + "outDir": "../out-tsc/app", + "target": "es5", + "module": "es2015", + "baseUrl": "", + "types": [] + }, + "exclude": [ + "test.ts", + "**/*.spec.ts" + ] +} diff --git a/public/docs/_examples/cli-quickstart/ts/src/tsconfig.spec.json b/public/docs/_examples/cli-quickstart/ts/src/tsconfig.spec.json new file mode 100644 index 0000000000..6c5160e12e --- /dev/null +++ b/public/docs/_examples/cli-quickstart/ts/src/tsconfig.spec.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ + "es2016" + ], + "outDir": "../out-tsc/spec", + "module": "commonjs", + "target": "es6", + "baseUrl": "", + "types": [ + "jasmine", + "node" + ] + }, + "files": [ + "test.ts" + ], + "include": [ + "**/*.spec.ts" + ] +} diff --git a/public/docs/_examples/cli-quickstart/ts/tsconfig.json b/public/docs/_examples/cli-quickstart/ts/tsconfig.json new file mode 100644 index 0000000000..cde5e35054 --- /dev/null +++ b/public/docs/_examples/cli-quickstart/ts/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "lib": [ + "es2016" + ] + } +} diff --git a/public/docs/_examples/cli-quickstart/ts/zipper.json b/public/docs/_examples/cli-quickstart/ts/zipper.json new file mode 100644 index 0000000000..70bf41659e --- /dev/null +++ b/public/docs/_examples/cli-quickstart/ts/zipper.json @@ -0,0 +1,11 @@ +{ + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[0-9].*", + ".angular-cli.json", + "protractor.conf.js" + ], + "removeSystemJsConfig": true, + "type": "cli" +} diff --git a/public/docs/_examples/component-styles/e2e-spec.ts b/public/docs/_examples/component-styles/e2e-spec.ts new file mode 100644 index 0000000000..32bd30832c --- /dev/null +++ b/public/docs/_examples/component-styles/e2e-spec.ts @@ -0,0 +1,68 @@ +'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'); + }); + +}); diff --git a/public/docs/_examples/component-styles/ts/example-config.json b/public/docs/_examples/component-styles/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/component-styles/ts/plnkr.json b/public/docs/_examples/component-styles/ts/plnkr.json new file mode 100644 index 0000000000..e045ebcb38 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/component-styles/ts/src/app/app.module.ts b/public/docs/_examples/component-styles/ts/src/app/app.module.ts new file mode 100644 index 0000000000..31c72cbbf2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/component-styles/ts/src/app/hero-app-main.component.ts b/public/docs/_examples/component-styles/ts/src/app/hero-app-main.component.ts new file mode 100644 index 0000000000..aebb1f729e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/component-styles/ts/src/app/hero-app.component.ts b/public/docs/_examples/component-styles/ts/src/app/hero-app.component.ts new file mode 100644 index 0000000000..5f1923e6f3 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/component-styles/ts/src/app/hero-controls.component.ts b/public/docs/_examples/component-styles/ts/src/app/hero-controls.component.ts new file mode 100644 index 0000000000..5d293596d2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/component-styles/ts/src/app/hero-details-box.css b/public/docs/_examples/component-styles/ts/src/app/hero-details-box.css new file mode 100644 index 0000000000..f240073005 --- /dev/null +++ b/public/docs/_examples/component-styles/ts/src/app/hero-details-box.css @@ -0,0 +1,3 @@ +:host { + padding: 10px; +} diff --git a/public/docs/_examples/component-styles/ts/src/app/hero-details.component.css b/public/docs/_examples/component-styles/ts/src/app/hero-details.component.css new file mode 100644 index 0000000000..fd938ca55c --- /dev/null +++ b/public/docs/_examples/component-styles/ts/src/app/hero-details.component.css @@ -0,0 +1,28 @@ +/* #docregion import */ +@import 'https://github.com/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/public/docs/_examples/component-styles/ts/src/app/hero-details.component.ts b/public/docs/_examples/component-styles/ts/src/app/hero-details.component.ts new file mode 100644 index 0000000000..bd86a63e04 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/component-styles/ts/src/app/hero-team.component.css b/public/docs/_examples/component-styles/ts/src/app/hero-team.component.css new file mode 100644 index 0000000000..b87679886b --- /dev/null +++ b/public/docs/_examples/component-styles/ts/src/app/hero-team.component.css @@ -0,0 +1,3 @@ +li { + list-style-type: square; +} diff --git a/public/docs/_examples/component-styles/ts/src/app/hero-team.component.ts b/public/docs/_examples/component-styles/ts/src/app/hero-team.component.ts new file mode 100644 index 0000000000..4f092d2827 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/component-styles/ts/src/app/hero.ts b/public/docs/_examples/component-styles/ts/src/app/hero.ts new file mode 100644 index 0000000000..7f8969e682 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/component-styles/ts/src/app/quest-summary.component.css b/public/docs/_examples/component-styles/ts/src/app/quest-summary.component.css new file mode 100644 index 0000000000..207fa981dd --- /dev/null +++ b/public/docs/_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/public/docs/_examples/component-styles/ts/src/app/quest-summary.component.html b/public/docs/_examples/component-styles/ts/src/app/quest-summary.component.html new file mode 100644 index 0000000000..ace27d2a1c --- /dev/null +++ b/public/docs/_examples/component-styles/ts/src/app/quest-summary.component.html @@ -0,0 +1 @@ +

    No quests in progress

    diff --git a/public/docs/_examples/component-styles/ts/src/app/quest-summary.component.ts b/public/docs/_examples/component-styles/ts/src/app/quest-summary.component.ts new file mode 100644 index 0000000000..2a2dc65fe0 --- /dev/null +++ b/public/docs/_examples/component-styles/ts/src/app/quest-summary.component.ts @@ -0,0 +1,20 @@ +/* tslint:disable:no-unused-variable */ +// #docplaster +import { Component, ViewEncapsulation } from '@angular/core'; + +// #docregion +@Component({ + 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/public/docs/_examples/component-styles/ts/src/index.html b/public/docs/_examples/component-styles/ts/src/index.html new file mode 100644 index 0000000000..6e9dc51b32 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/component-styles/ts/src/main.ts b/public/docs/_examples/component-styles/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/e2e-spec.ts b/public/docs/_examples/dependency-injection/e2e-spec.ts new file mode 100644 index 0000000000..5b46dd77d9 --- /dev/null +++ b/public/docs/_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: any) { + 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/public/docs/_examples/dependency-injection/ts/example-config.json b/public/docs/_examples/dependency-injection/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/dependency-injection/ts/plnkr.json b/public/docs/_examples/dependency-injection/ts/plnkr.json new file mode 100644 index 0000000000..e8d1ab24b2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/app.component.1.ts b/public/docs/_examples/dependency-injection/ts/src/app/app.component.1.ts new file mode 100644 index 0000000000..b398ebeb57 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/app.component.2.ts b/public/docs/_examples/dependency-injection/ts/src/app/app.component.2.ts new file mode 100644 index 0000000000..d24df5568c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/app.component.ts b/public/docs/_examples/dependency-injection/ts/src/app/app.component.ts new file mode 100644 index 0000000000..3563fdc070 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/app.config.ts b/public/docs/_examples/dependency-injection/ts/src/app/app.config.ts new file mode 100644 index 0000000000..f224e6e1ab --- /dev/null +++ b/public/docs/_examples/dependency-injection/ts/src/app/app.config.ts @@ -0,0 +1,16 @@ +// #docregion token +import { InjectionToken } from '@angular/core'; + +export let APP_CONFIG = new InjectionToken('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/public/docs/_examples/dependency-injection/ts/src/app/app.module.ts b/public/docs/_examples/dependency-injection/ts/src/app/app.module.ts new file mode 100644 index 0000000000..67ae8ae913 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/car/car-creations.ts b/public/docs/_examples/dependency-injection/ts/src/app/car/car-creations.ts new file mode 100644 index 0000000000..c758c72951 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/car/car-factory.ts b/public/docs/_examples/dependency-injection/ts/src/app/car/car-factory.ts new file mode 100644 index 0000000000..06daafe63b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/car/car-injector.ts b/public/docs/_examples/dependency-injection/ts/src/app/car/car-injector.ts new file mode 100644 index 0000000000..4f7498ee4e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/car/car-no-di.ts b/public/docs/_examples/dependency-injection/ts/src/app/car/car-no-di.ts new file mode 100644 index 0000000000..9026edebc2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/car/car.component.ts b/public/docs/_examples/dependency-injection/ts/src/app/car/car.component.ts new file mode 100644 index 0000000000..9a835ef124 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/car/car.ts b/public/docs/_examples/dependency-injection/ts/src/app/car/car.ts new file mode 100644 index 0000000000..37162c570b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/heroes/hero-list.component.1.ts b/public/docs/_examples/dependency-injection/ts/src/app/heroes/hero-list.component.1.ts new file mode 100644 index 0000000000..ba32366aa9 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/heroes/hero-list.component.2.ts b/public/docs/_examples/dependency-injection/ts/src/app/heroes/hero-list.component.2.ts new file mode 100644 index 0000000000..cb23d3257c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/heroes/hero-list.component.ts b/public/docs/_examples/dependency-injection/ts/src/app/heroes/hero-list.component.ts new file mode 100644 index 0000000000..db3a325bdb --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/heroes/hero.service.1.ts b/public/docs/_examples/dependency-injection/ts/src/app/heroes/hero.service.1.ts new file mode 100644 index 0000000000..2e0e3ca734 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/heroes/hero.service.2.ts b/public/docs/_examples/dependency-injection/ts/src/app/heroes/hero.service.2.ts new file mode 100644 index 0000000000..6b3a98306a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/heroes/hero.service.provider.ts b/public/docs/_examples/dependency-injection/ts/src/app/heroes/hero.service.provider.ts new file mode 100644 index 0000000000..6de4ebee90 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/heroes/hero.service.ts b/public/docs/_examples/dependency-injection/ts/src/app/heroes/hero.service.ts new file mode 100644 index 0000000000..fb03ec1de6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/heroes/hero.ts b/public/docs/_examples/dependency-injection/ts/src/app/heroes/hero.ts new file mode 100644 index 0000000000..5c49328241 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/heroes/heroes.component.1.ts b/public/docs/_examples/dependency-injection/ts/src/app/heroes/heroes.component.1.ts new file mode 100644 index 0000000000..e0e9deae08 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/heroes/heroes.component.ts b/public/docs/_examples/dependency-injection/ts/src/app/heroes/heroes.component.ts new file mode 100644 index 0000000000..5923f7590b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/heroes/mock-heroes.ts b/public/docs/_examples/dependency-injection/ts/src/app/heroes/mock-heroes.ts new file mode 100644 index 0000000000..79a91dc03a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/injector.component.ts b/public/docs/_examples/dependency-injection/ts/src/app/injector.component.ts new file mode 100644 index 0000000000..1b1a065e4c --- /dev/null +++ b/public/docs/_examples/dependency-injection/ts/src/app/injector.component.ts @@ -0,0 +1,49 @@ +// #docplaster +// #docregion +import { Component, Injector, OnInit } 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 implements OnInit { + car: Car; + + // #docregion get-hero-service + heroService: HeroService; + // #enddocregion get-hero-service + hero: Hero; + + constructor(private injector: Injector) { } + + ngOnInit() { + this.car = this.injector.get(Car); + this.heroService = this.injector.get(HeroService); + this.hero = this.heroService.getHeroes()[0]; + } + + 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/public/docs/_examples/dependency-injection/ts/src/app/logger.service.ts b/public/docs/_examples/dependency-injection/ts/src/app/logger.service.ts new file mode 100644 index 0000000000..e943523ad2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/providers.component.ts b/public/docs/_examples/dependency-injection/ts/src/app/providers.component.ts new file mode 100644 index 0000000000..a96bfce99e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/test.component.ts b/public/docs/_examples/dependency-injection/ts/src/app/test.component.ts new file mode 100644 index 0000000000..fc0fef75a8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/app/user.service.ts b/public/docs/_examples/dependency-injection/ts/src/app/user.service.ts new file mode 100644 index 0000000000..8fdda925db --- /dev/null +++ b/public/docs/_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/public/docs/_examples/dependency-injection/ts/src/index.html b/public/docs/_examples/dependency-injection/ts/src/index.html new file mode 100644 index 0000000000..87a9d7e694 --- /dev/null +++ b/public/docs/_examples/dependency-injection/ts/src/index.html @@ -0,0 +1,27 @@ + + + + + Dependency Injection + + + + + + + + + + + + + + + + + Loading my-app ... + + + diff --git a/public/docs/_examples/dependency-injection/ts/src/main.ts b/public/docs/_examples/dependency-injection/ts/src/main.ts new file mode 100644 index 0000000000..1a1d481719 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/deployment/ts/.gitignore b/public/docs/_examples/deployment/ts/.gitignore new file mode 100644 index 0000000000..7f794a0b16 --- /dev/null +++ b/public/docs/_examples/deployment/ts/.gitignore @@ -0,0 +1 @@ +!systemjs.config.server.js diff --git a/public/docs/_examples/deployment/ts/example-config.json b/public/docs/_examples/deployment/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/deployment/ts/src/app/app.component.ts b/public/docs/_examples/deployment/ts/src/app/app.component.ts new file mode 100644 index 0000000000..47731c13a4 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/deployment/ts/src/app/app.module.ts b/public/docs/_examples/deployment/ts/src/app/app.module.ts new file mode 100644 index 0000000000..a885bc2918 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/deployment/ts/src/app/crisis-list.component.ts b/public/docs/_examples/deployment/ts/src/app/crisis-list.component.ts new file mode 100644 index 0000000000..62ef9e7555 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/deployment/ts/src/app/hero-list.component.ts b/public/docs/_examples/deployment/ts/src/app/hero-list.component.ts new file mode 100644 index 0000000000..479f73b508 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/deployment/ts/src/index.html b/public/docs/_examples/deployment/ts/src/index.html new file mode 100644 index 0000000000..770e64bc4d --- /dev/null +++ b/public/docs/_examples/deployment/ts/src/index.html @@ -0,0 +1,38 @@ + + + + + + + + + Simple Deployment + + + + + + + + + + + + + + + + + + + + + + + loading... + + + diff --git a/public/docs/_examples/deployment/ts/src/main.ts b/public/docs/_examples/deployment/ts/src/main.ts new file mode 100644 index 0000000000..6f82bbc745 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/deployment/ts/src/systemjs.config.server.js b/public/docs/_examples/deployment/ts/src/systemjs.config.server.js new file mode 100644 index 0000000000..40c4426064 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/displaying-data/e2e-spec.ts b/public/docs/_examples/displaying-data/e2e-spec.ts new file mode 100644 index 0000000000..96c52c5d00 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/displaying-data/ts/.gitignore b/public/docs/_examples/displaying-data/ts/.gitignore deleted file mode 100644 index 6724ce3596..0000000000 --- a/public/docs/_examples/displaying-data/ts/.gitignore +++ /dev/null @@ -1 +0,0 @@ -src/**/*.js \ No newline at end of file diff --git a/public/docs/_examples/displaying-data/ts/example-config.json b/public/docs/_examples/displaying-data/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/displaying-data/ts/package.json b/public/docs/_examples/displaying-data/ts/package.json deleted file mode 100644 index dd26082d7d..0000000000 --- a/public/docs/_examples/displaying-data/ts/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "angular2-getting-started", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "tsc": "tsc -p src -w", - "start": "live-server --open=src" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "angular2": "2.0.0-alpha.44", - "systemjs": "0.19.2" - }, - "devDependencies": { - "live-server": "^0.8.1", - "typescript": "^1.6.2" - } -} diff --git a/public/docs/_examples/displaying-data/ts/plnkr.json b/public/docs/_examples/displaying-data/ts/plnkr.json new file mode 100644 index 0000000000..b4572f0fb6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/displaying-data/ts/src/app/app-ctor.component.ts b/public/docs/_examples/displaying-data/ts/src/app/app-ctor.component.ts new file mode 100644 index 0000000000..b275baa8e6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/displaying-data/ts/src/app/app-ctor.ts b/public/docs/_examples/displaying-data/ts/src/app/app-ctor.ts deleted file mode 100644 index d257de577c..0000000000 --- a/public/docs/_examples/displaying-data/ts/src/app/app-ctor.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {Component, bootstrap} from 'angular2/angular2'; - -@Component({ - selector: 'my-app-ctor', - template: ` -

    {{title}} [Ctor version]

    -

    My favorite hero is: {{myHero}}

    - ` -}) -// #docregion app-ctor -export class AppCtorComponent { - title: string; - myHero: string; - - constructor() { - this.title = 'Tour of Heroes'; - this.myHero = 'Windstorm'; - } -} -// #enddocregion app-ctor \ No newline at end of file diff --git a/public/docs/_examples/displaying-data/ts/src/app/app.1.ts b/public/docs/_examples/displaying-data/ts/src/app/app.1.ts deleted file mode 100644 index a12c6f44e6..0000000000 --- a/public/docs/_examples/displaying-data/ts/src/app/app.1.ts +++ /dev/null @@ -1,18 +0,0 @@ -// #docregion -import {Component, bootstrap} from 'angular2/angular2'; - -@Component({ - selector: 'my-app', - // #docregion template - template: ` -

    {{title}}

    -

    My favorite hero is: {{myHero}}

    - ` - // #enddocregion template -}) -export class AppComponent { - title = 'Tour of Heroes'; - myHero = 'Windstorm'; -} - -bootstrap(AppComponent); diff --git a/public/docs/_examples/displaying-data/ts/src/app/app.2.ts b/public/docs/_examples/displaying-data/ts/src/app/app.2.ts deleted file mode 100644 index bdefa492da..0000000000 --- a/public/docs/_examples/displaying-data/ts/src/app/app.2.ts +++ /dev/null @@ -1,33 +0,0 @@ -// #docregion -// #docregion imports -import {Component, bootstrap, NgFor} from 'angular2/angular2'; -// #enddocregion imports - -@Component({ - selector: 'my-app', - // #docregion template - template: ` -

    {{title}}

    -

    My favorite hero is: {{myHero}}

    -

    Heroes:

    -
      - // #docregion li-repeater -
    • - {{ hero }} -
    • - // #enddocregion li-repeater -
    - `, - // #enddocregion template - // #docregion directives - directives: [NgFor] - // #enddocregion directives -}) -// #docregion mock-heroes -export class AppComponent { - title = 'Tour of Heroes'; - heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado']; - myHero = this.heroes[0]; -} -// #enddocregion mock-heroes -// #enddocregion diff --git a/public/docs/_examples/displaying-data/ts/src/app/app.3.ts b/public/docs/_examples/displaying-data/ts/src/app/app.3.ts deleted file mode 100644 index 327882bd11..0000000000 --- a/public/docs/_examples/displaying-data/ts/src/app/app.3.ts +++ /dev/null @@ -1,41 +0,0 @@ -// #docregion -// #docregion import-ng-if -import {Component, bootstrap, NgFor, NgIf} from 'angular2/angular2'; -// #enddocregion import-ng-if -// #docregion import-hero -import {Hero} from './hero'; -// #enddocregion import-hero - -@Component({ - selector: 'my-app', - // #docregion template - template: ` -

    {{title}}

    -

    My favorite hero is: {{myHero}}

    -

    Heroes:

    -
      -
    • - {{ hero.name }} -
    • -
    - `, - // #enddocregion template - // #docregion directives - directives: [NgFor, NgIf] - // #enddocregion directives -}) -// #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 -} -// #enddocregion class -// #enddocregion diff --git a/public/docs/_examples/displaying-data/ts/src/app/app.component.1.ts b/public/docs/_examples/displaying-data/ts/src/app/app.component.1.ts new file mode 100644 index 0000000000..1cbeb0f731 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/displaying-data/ts/src/app/app.component.2.ts b/public/docs/_examples/displaying-data/ts/src/app/app.component.2.ts new file mode 100644 index 0000000000..da7a653973 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/displaying-data/ts/src/app/app.component.3.ts b/public/docs/_examples/displaying-data/ts/src/app/app.component.3.ts new file mode 100644 index 0000000000..06ab060557 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/displaying-data/ts/src/app/app.component.ts b/public/docs/_examples/displaying-data/ts/src/app/app.component.ts new file mode 100644 index 0000000000..7234959265 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/displaying-data/ts/src/app/app.final.ts b/public/docs/_examples/displaying-data/ts/src/app/app.final.ts deleted file mode 100644 index a2a7ddee8d..0000000000 --- a/public/docs/_examples/displaying-data/ts/src/app/app.final.ts +++ /dev/null @@ -1,44 +0,0 @@ -// #docplaster -// #docregion final -// #docregion imports -import {Component, bootstrap, CORE_DIRECTIVES} from 'angular2/angular2'; -// #enddocregion imports -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 -`, - // #docregion directives - directives: [CORE_DIRECTIVES] - // #enddocregion directives -}) - -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]; -} -//#enddocregion final -/* -// #docregion final - -bootstrap(AppComponent); -//#enddocregion final -*/ \ No newline at end of file diff --git a/public/docs/_examples/displaying-data/ts/src/app/app.module.ts b/public/docs/_examples/displaying-data/ts/src/app/app.module.ts new file mode 100644 index 0000000000..362f3401fa --- /dev/null +++ b/public/docs/_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/public/docs/_examples/displaying-data/ts/src/app/bootstrap.ts b/public/docs/_examples/displaying-data/ts/src/app/bootstrap.ts deleted file mode 100644 index 2c459c04b6..0000000000 --- a/public/docs/_examples/displaying-data/ts/src/app/bootstrap.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {bootstrap} from 'angular2/angular2'; -import {AppCtorComponent} from './app-ctor'; -import {AppComponent as v1} from './app.1'; -import {AppComponent as v2} from './app.2'; -import {AppComponent as v3} from './app.3'; - -import {AppComponent as final} from './app.final'; - -// pick one -//bootstrap(v1); -//bootstrap(v2); -//bootstrap(v3); -bootstrap(final); - -// for doc testing -bootstrap(AppCtorComponent); \ No newline at end of file diff --git a/public/docs/_examples/displaying-data/ts/src/app/hero.ts b/public/docs/_examples/displaying-data/ts/src/app/hero.ts index 8b4f06b658..f89d26ad63 100644 --- a/public/docs/_examples/displaying-data/ts/src/app/hero.ts +++ b/public/docs/_examples/displaying-data/ts/src/app/hero.ts @@ -1,9 +1,9 @@ // #docregion export class Hero { constructor( - // #docregion id-parameter - public id:number, - // #enddocregion id-parameter - public name:string) { } + // #docregion id + public id: number, + // #enddocregion id + public name: string) { } } -// #enddocregion \ No newline at end of file +// #enddocregion diff --git a/public/docs/_examples/displaying-data/ts/src/index.html b/public/docs/_examples/displaying-data/ts/src/index.html index 5209ce465a..ddcbade46b 100644 --- a/public/docs/_examples/displaying-data/ts/src/index.html +++ b/public/docs/_examples/displaying-data/ts/src/index.html @@ -1,27 +1,28 @@ - - Getting Started - - - + Displaying Data + + + + + + + + + + + + + - loading... - -
    - loading... + diff --git a/public/docs/_examples/displaying-data/ts/src/main.ts b/public/docs/_examples/displaying-data/ts/src/main.ts new file mode 100644 index 0000000000..f332d1d245 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/displaying-data/ts/src/tsconfig.json b/public/docs/_examples/displaying-data/ts/src/tsconfig.json deleted file mode 100644 index 6a58b35a58..0000000000 --- a/public/docs/_examples/displaying-data/ts/src/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "target": "ES5", - "module": "commonjs", - "sourceMap": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "removeComments": false, - "noImplicitAny": false - } -} \ No newline at end of file diff --git a/public/docs/_examples/forms/e2e-spec.ts b/public/docs/_examples/forms/e2e-spec.ts new file mode 100644 index 0000000000..2afd370103 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/forms/js/example-config.json b/public/docs/_examples/forms/js/example-config.json new file mode 100644 index 0000000000..81f31aaf0d --- /dev/null +++ b/public/docs/_examples/forms/js/example-config.json @@ -0,0 +1,3 @@ +{ + "build": "build:babel" +} diff --git a/public/docs/_examples/forms/js/plnkr.json b/public/docs/_examples/forms/js/plnkr.json new file mode 100644 index 0000000000..946cbb88f6 --- /dev/null +++ b/public/docs/_examples/forms/js/plnkr.json @@ -0,0 +1,5 @@ +{ + "description": "Forms", + "basePath": "src/", + "files":["app/**/*.js", "**/*.html", "**/*.css"] +} diff --git a/public/docs/_examples/forms/js/src/app/app.component.js b/public/docs/_examples/forms/js/src/app/app.component.js new file mode 100644 index 0000000000..56bd982416 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/forms/js/src/app/app.module.js b/public/docs/_examples/forms/js/src/app/app.module.js new file mode 100644 index 0000000000..92c7f8b9e5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/forms/js/src/app/hero-form.component.html b/public/docs/_examples/forms/js/src/app/hero-form.component.html new file mode 100644 index 0000000000..279ded0866 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/forms/js/src/app/hero-form.component.js b/public/docs/_examples/forms/js/src/app/hero-form.component.js new file mode 100644 index 0000000000..505993a1fd --- /dev/null +++ b/public/docs/_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/public/docs/_examples/forms/js/src/app/hero.js b/public/docs/_examples/forms/js/src/app/hero.js new file mode 100644 index 0000000000..9c2449c922 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/forms/ts/src/styles.css b/public/docs/_examples/forms/js/src/forms.css similarity index 100% rename from public/docs/_examples/forms/ts/src/styles.css rename to public/docs/_examples/forms/js/src/forms.css diff --git a/public/docs/_examples/forms/js/src/index.html b/public/docs/_examples/forms/js/src/index.html new file mode 100644 index 0000000000..3a41d74a3b --- /dev/null +++ b/public/docs/_examples/forms/js/src/index.html @@ -0,0 +1,47 @@ + + + + + + Hero Form + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/public/docs/_examples/forms/js/src/main.js b/public/docs/_examples/forms/js/src/main.js new file mode 100644 index 0000000000..785823fa84 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/forms/ts/.gitignore b/public/docs/_examples/forms/ts/.gitignore deleted file mode 100644 index 6724ce3596..0000000000 --- a/public/docs/_examples/forms/ts/.gitignore +++ /dev/null @@ -1 +0,0 @@ -src/**/*.js \ No newline at end of file diff --git a/public/docs/_examples/forms/ts/example-config.json b/public/docs/_examples/forms/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/forms/ts/package.json b/public/docs/_examples/forms/ts/package.json deleted file mode 100644 index 5bffa1904d..0000000000 --- a/public/docs/_examples/forms/ts/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "angular2-getting-started", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "tsc": "tsc -p src -w", - "start": "live-server --open=src" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "angular2": "2.0.0-alpha.44", - "bootstrap": "^3.3.5", - "systemjs": "0.19.2" - }, - "devDependencies": { - "live-server": "^0.8.1", - "typescript": "^1.6.2" - } -} diff --git a/public/docs/_examples/forms/ts/plnkr.json b/public/docs/_examples/forms/ts/plnkr.json new file mode 100644 index 0000000000..3f0abbfc3d --- /dev/null +++ b/public/docs/_examples/forms/ts/plnkr.json @@ -0,0 +1,8 @@ +{ + "description": "Forms", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js" + ] +} diff --git a/public/docs/_examples/forms/ts/src/app/app.component.ts b/public/docs/_examples/forms/ts/src/app/app.component.ts new file mode 100644 index 0000000000..454f7e03db --- /dev/null +++ b/public/docs/_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/public/docs/_examples/forms/ts/src/app/app.module.ts b/public/docs/_examples/forms/ts/src/app/app.module.ts new file mode 100644 index 0000000000..f214c02714 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/forms/ts/src/app/app.ts b/public/docs/_examples/forms/ts/src/app/app.ts deleted file mode 100644 index a9c8970d3c..0000000000 --- a/public/docs/_examples/forms/ts/src/app/app.ts +++ /dev/null @@ -1,12 +0,0 @@ -// #docregion -import {bootstrap, Component} from 'angular2/angular2' -import {HeroFormComponent} from './hero-form.component' - -@Component({ - selector: 'my-app', - template: '', - directives: [HeroFormComponent] -}) -class AppComponent { } - -bootstrap(AppComponent); \ No newline at end of file diff --git a/public/docs/_examples/forms/ts/src/app/hero-form.component.html b/public/docs/_examples/forms/ts/src/app/hero-form.component.html index ed07b487f5..73b703f789 100644 --- a/public/docs/_examples/forms/ts/src/app/hero-form.component.html +++ b/public/docs/_examples/forms/ts/src/app/hero-form.component.html @@ -1,21 +1,23 @@ - - + +

    Hero Form

    - -
    - - + + +
    + - - -
    + + +
    + Name is required
    @@ -23,27 +25,45 @@

    Hero Form

    - +
    - + -
    +
    Power is required
    - - - + + + + + + + + with reset + +    + + + + without reset + + +
    +
    + Name via form.controls = {{showFormControls(heroForm)}} +
    + +
    @@ -63,7 +83,7 @@

    You submitted the following:

    {{ model.power }}

    - +
    @@ -82,6 +102,7 @@

    You submitted the following:


    + + + + + + + + + + - - Loading... + + Loading AppComponent content here ... + - \ No newline at end of file + diff --git a/public/docs/_examples/quickstart/ts/src/main.ts b/public/docs/_examples/quickstart/ts/src/main.ts new file mode 100644 index 0000000000..311c44b76d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/quickstart/ts/src/tsconfig.1.json b/public/docs/_examples/quickstart/ts/src/tsconfig.1.json new file mode 100644 index 0000000000..2c7260d1bc --- /dev/null +++ b/public/docs/_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/public/docs/_examples/quickstart/ts/src/tsconfig.json b/public/docs/_examples/quickstart/ts/src/tsconfig.json deleted file mode 100644 index 6a58b35a58..0000000000 --- a/public/docs/_examples/quickstart/ts/src/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "target": "ES5", - "module": "commonjs", - "sourceMap": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "removeComments": false, - "noImplicitAny": false - } -} \ No newline at end of file diff --git a/public/docs/_examples/reactive-forms/e2e-spec.ts b/public/docs/_examples/reactive-forms/e2e-spec.ts new file mode 100644 index 0000000000..bb788397ef --- /dev/null +++ b/public/docs/_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/public/docs/_examples/reactive-forms/ts/example-config.json b/public/docs/_examples/reactive-forms/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/reactive-forms/ts/final.plnkr.json b/public/docs/_examples/reactive-forms/ts/final.plnkr.json new file mode 100644 index 0000000000..41481acc99 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/reactive-forms/ts/plnkr.json b/public/docs/_examples/reactive-forms/ts/plnkr.json new file mode 100644 index 0000000000..f0daeb4aad --- /dev/null +++ b/public/docs/_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/public/docs/_examples/reactive-forms/ts/src/app/app.component.1.ts b/public/docs/_examples/reactive-forms/ts/src/app/app.component.1.ts new file mode 100644 index 0000000000..3023618334 --- /dev/null +++ b/public/docs/_examples/reactive-forms/ts/src/app/app.component.1.ts @@ -0,0 +1,12 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: ` +
    +

    Reactive Forms

    + +
    ` +}) +export class AppComponent { } diff --git a/public/docs/_examples/reactive-forms/ts/src/app/app.component.ts b/public/docs/_examples/reactive-forms/ts/src/app/app.component.ts new file mode 100644 index 0000000000..d30a38e979 --- /dev/null +++ b/public/docs/_examples/reactive-forms/ts/src/app/app.component.ts @@ -0,0 +1,12 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: ` +
    +

    Reactive Forms

    + +
    ` +}) +export class AppComponent { } diff --git a/public/docs/_examples/reactive-forms/ts/src/app/app.module.ts b/public/docs/_examples/reactive-forms/ts/src/app/app.module.ts new file mode 100644 index 0000000000..b9f2ea8f99 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/reactive-forms/ts/src/app/data-model.ts b/public/docs/_examples/reactive-forms/ts/src/app/data-model.ts new file mode 100644 index 0000000000..ad01ddee56 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/reactive-forms/ts/src/app/demo.component.html b/public/docs/_examples/reactive-forms/ts/src/app/demo.component.html new file mode 100644 index 0000000000..1caae127af --- /dev/null +++ b/public/docs/_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/public/docs/_examples/reactive-forms/ts/src/app/demo.component.ts b/public/docs/_examples/reactive-forms/ts/src/app/demo.component.ts new file mode 100644 index 0000000000..181726eb66 --- /dev/null +++ b/public/docs/_examples/reactive-forms/ts/src/app/demo.component.ts @@ -0,0 +1,47 @@ +/* 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({ + 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/public/docs/_examples/reactive-forms/ts/src/app/demo.module.ts b/public/docs/_examples/reactive-forms/ts/src/app/demo.module.ts new file mode 100644 index 0000000000..dac9145ca9 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-1.component.html b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-1.component.html new file mode 100644 index 0000000000..7217708d22 --- /dev/null +++ b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-1.component.html @@ -0,0 +1,8 @@ + +

    Hero Detail

    +

    Just a FormControl

    + + + diff --git a/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-1.component.ts b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-1.component.ts new file mode 100644 index 0000000000..318dfff145 --- /dev/null +++ b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-1.component.ts @@ -0,0 +1,14 @@ +/* tslint:disable:component-class-suffix */ +// #docregion imports +import { Component } from '@angular/core'; +import { FormControl } from '@angular/forms'; +// #enddocregion + +@Component({ + selector: 'hero-detail-1', + templateUrl: './hero-detail-1.component.html' +}) +// #docregion v1 +export class HeroDetailComponent1 { + name = new FormControl(); +} diff --git a/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-2.component.html b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-2.component.html new file mode 100644 index 0000000000..1e98354842 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-2.component.ts b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-2.component.ts new file mode 100644 index 0000000000..6aa3c1ed3a --- /dev/null +++ b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-2.component.ts @@ -0,0 +1,17 @@ +/* tslint:disable:component-class-suffix */ +// #docregion imports +import { Component } from '@angular/core'; +import { FormControl, FormGroup } from '@angular/forms'; +// #enddocregion imports + +@Component({ + 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/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-3.component.html b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-3.component.html new file mode 100644 index 0000000000..8edc544dd4 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-3.component.ts b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-3.component.ts new file mode 100644 index 0000000000..54a4e93361 --- /dev/null +++ b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-3.component.ts @@ -0,0 +1,27 @@ +/* tslint:disable:component-class-suffix */ +// #docregion imports +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +// #enddocregion imports + +@Component({ + 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/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-3a.component.ts b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-3a.component.ts new file mode 100644 index 0000000000..2347869967 --- /dev/null +++ b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-3a.component.ts @@ -0,0 +1,25 @@ +/* tslint:disable:component-class-suffix */ +// #docregion imports +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup } from '@angular/forms'; +// #enddocregion imports + +@Component({ + 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/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-4.component.html b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-4.component.html new file mode 100644 index 0000000000..30529868e9 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-4.component.ts b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-4.component.ts new file mode 100644 index 0000000000..d2f1ea2f96 --- /dev/null +++ b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-4.component.ts @@ -0,0 +1,34 @@ +/* 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({ + 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/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-5.component.html b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-5.component.html new file mode 100644 index 0000000000..2a41b4f2f9 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-5.component.ts b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-5.component.ts new file mode 100644 index 0000000000..f4be33389f --- /dev/null +++ b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-5.component.ts @@ -0,0 +1,35 @@ +/* tslint:disable:component-class-suffix */ +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; + +import { states } from './data-model'; + +@Component({ + 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/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-6.component.html b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-6.component.html new file mode 100644 index 0000000000..4c8b5c9e05 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-6.component.ts b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-6.component.ts new file mode 100644 index 0000000000..d5136822d1 --- /dev/null +++ b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-6.component.ts @@ -0,0 +1,58 @@ +/* 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({ + 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/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-7.component.html b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-7.component.html new file mode 100644 index 0000000000..6d68b49b4d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-7.component.ts b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-7.component.ts new file mode 100644 index 0000000000..497c7f6a56 --- /dev/null +++ b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-7.component.ts @@ -0,0 +1,69 @@ +/* tslint:disable:component-class-suffix */ +// #docplaster +// #docregion imports +import { Component, Input, OnChanges } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; + +// #docregion import-address +import { Address, Hero, states } from './data-model'; +// #enddocregion import-address + +// #enddocregion imports + +@Component({ + 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/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-8.component.html b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-8.component.html new file mode 100644 index 0000000000..d8e47ae798 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-8.component.ts b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-8.component.ts new file mode 100644 index 0000000000..1141c836a7 --- /dev/null +++ b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail-8.component.ts @@ -0,0 +1,68 @@ +/* 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({ + 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/public/docs/_examples/reactive-forms/ts/src/app/hero-detail.component.html b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail.component.html new file mode 100644 index 0000000000..8f5b8bf2c8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/reactive-forms/ts/src/app/hero-detail.component.ts b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail.component.ts new file mode 100644 index 0000000000..4cb69aaedf --- /dev/null +++ b/public/docs/_examples/reactive-forms/ts/src/app/hero-detail.component.ts @@ -0,0 +1,107 @@ +// #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({ + 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/public/docs/_examples/reactive-forms/ts/src/app/hero-list.component.1.html b/public/docs/_examples/reactive-forms/ts/src/app/hero-list.component.1.html new file mode 100644 index 0000000000..fa76c84f54 --- /dev/null +++ b/public/docs/_examples/reactive-forms/ts/src/app/hero-list.component.1.html @@ -0,0 +1,8 @@ + + + +
    + +
    diff --git a/public/docs/_examples/reactive-forms/ts/src/app/hero-list.component.html b/public/docs/_examples/reactive-forms/ts/src/app/hero-list.component.html new file mode 100644 index 0000000000..d0fb2ee920 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/reactive-forms/ts/src/app/hero-list.component.ts b/public/docs/_examples/reactive-forms/ts/src/app/hero-list.component.ts new file mode 100644 index 0000000000..98092adcdb --- /dev/null +++ b/public/docs/_examples/reactive-forms/ts/src/app/hero-list.component.ts @@ -0,0 +1,31 @@ +// #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({ + 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/public/docs/_examples/reactive-forms/ts/src/app/hero.service.ts b/public/docs/_examples/reactive-forms/ts/src/app/hero.service.ts new file mode 100644 index 0000000000..6600586de7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/reactive-forms/ts/src/index-final.html b/public/docs/_examples/reactive-forms/ts/src/index-final.html new file mode 100644 index 0000000000..9ef0a379e0 --- /dev/null +++ b/public/docs/_examples/reactive-forms/ts/src/index-final.html @@ -0,0 +1,31 @@ + + + + + Hero Form + + + + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/public/docs/_examples/reactive-forms/ts/src/index.html b/public/docs/_examples/reactive-forms/ts/src/index.html new file mode 100644 index 0000000000..802d12f211 --- /dev/null +++ b/public/docs/_examples/reactive-forms/ts/src/index.html @@ -0,0 +1,31 @@ + + + + + Hero Form + + + + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/public/docs/_examples/reactive-forms/ts/src/main-final.ts b/public/docs/_examples/reactive-forms/ts/src/main-final.ts new file mode 100644 index 0000000000..7572d1f1d3 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/reactive-forms/ts/src/main.ts b/public/docs/_examples/reactive-forms/ts/src/main.ts new file mode 100644 index 0000000000..f415a71708 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/e2e-spec.ts b/public/docs/_examples/router/e2e-spec.ts new file mode 100644 index 0000000000..2e9c6bafeb --- /dev/null +++ b/public/docs/_examples/router/e2e-spec.ts @@ -0,0 +1,162 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by, ExpectedConditions } from 'protractor'; + +const numDashboardTabs = 5; +const numCrises = 4; +const numHeroes = 6; +const EC = ExpectedConditions; + +describe('Router', () => { + + beforeAll(() => browser.get('')); + + function getPageStruct() { + const hrefEles = element.all(by.css('my-app a')); + const crisisDetail = element.all(by.css('my-app > ng-component > ng-component > ng-component > div')).first(); + const heroDetail = element(by.css('my-app > ng-component > div')); + + return { + hrefs: hrefEles, + activeHref: element(by.css('my-app a.active')), + + crisisHref: hrefEles.get(0), + crisisList: element.all(by.css('my-app > ng-component > ng-component li')), + crisisDetail: crisisDetail, + crisisDetailTitle: crisisDetail.element(by.xpath('*[1]')), + + heroesHref: hrefEles.get(1), + heroesList: element.all(by.css('my-app > ng-component li')), + heroDetail: heroDetail, + heroDetailTitle: heroDetail.element(by.xpath('*[1]')), + + 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('has expected dashboard tabs', () => { + const page = getPageStruct(); + expect(page.hrefs.count()).toEqual(numDashboardTabs, 'dashboard tab count'); + 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('has heroes selected as opening tab', () => { + const page = getPageStruct(); + expect(page.activeHref.getText()).toEqual('Heroes'); + }); + + it('has crises center items', async () => { + const page = getPageStruct(); + await page.crisisHref.click(); + expect(page.activeHref.getText()).toEqual('Crisis Center'); + expect(page.crisisList.count()).toBe(numCrises, 'crisis list count'); + }); + + it('has hero items', async () => { + const page = getPageStruct(); + await page.heroesHref.click(); + expect(page.activeHref.getText()).toEqual('Heroes'); + expect(page.heroesList.count()).toBe(numHeroes, 'hero list count'); + }); + + it('toggles views', async () => { + const page = getPageStruct(); + await page.crisisHref.click(); + expect(page.activeHref.getText()).toEqual('Crisis Center'); + expect(page.crisisList.count()).toBe(numCrises, 'crisis list count'); + await page.heroesHref.click(); + expect(page.activeHref.getText()).toEqual('Heroes'); + expect(page.heroesList.count()).toBe(numHeroes, 'hero list count'); + }); + + it('saves changed crisis details', async () => { + const page = getPageStruct(); + await page.crisisHref.click(); + await crisisCenterEdit(2, true); + }); + + it('can cancel changed crisis details', async () => { + const page = getPageStruct(); + await page.crisisHref.click(); + await crisisCenterEdit(3, false); + }); + + it('saves changed hero details', async () => { + const page = getPageStruct(); + await page.heroesHref.click(); + const heroEle = page.heroesList.get(4); + let text = await heroEle.getText(); + expect(text.length).toBeGreaterThan(0, 'hero item text length'); + // remove leading id from text + const heroText = text.substr(text.indexOf(' ')).trim(); + + await heroEle.click(); + expect(page.heroesList.count()).toBe(0, 'hero list count'); + expect(page.heroDetail.isPresent()).toBe(true, 'hero detail'); + expect(page.heroDetailTitle.getText()).toContain(heroText); + let inputEle = page.heroDetail.element(by.css('input')); + await inputEle.sendKeys('-foo'); + expect(page.heroDetailTitle.getText()).toContain(heroText + '-foo'); + + let buttonEle = page.heroDetail.element(by.css('button')); + await buttonEle.click(); + expect(heroEle.getText()).toContain(heroText + '-foo'); + }); + + it('sees preloaded modules', async () => { + const page = getPageStruct(); + await page.loginHref.click(); + await page.loginButton.click(); + const list = page.adminPreloadList; + expect(list.count()).toBe(1, 'preloaded module'); + expect(await list.first().getText()).toBe('crisis-center', 'first preloaded module'); + }); + + it('sees the secondary route', async () => { + const page = getPageStruct(); + await page.heroesHref.click(); + await page.contactHref.click(); + expect(page.outletComponents.count()).toBe(2, 'route count'); + }); + + async function crisisCenterEdit(index: number, save: boolean) { + const page = getPageStruct(); + await page.crisisHref.click(); + let crisisEle = page.crisisList.get(index); + let text = await crisisEle.getText(); + expect(text.length).toBeGreaterThan(0, 'crisis item text length'); + // remove leading id from text + const crisisText = text.substr(text.indexOf(' ')).trim(); + + await crisisEle.click(); + expect(page.crisisDetail.isPresent()).toBe(true, 'crisis detail present'); + expect(page.crisisDetailTitle.getText()).toContain(crisisText); + let inputEle = page.crisisDetail.element(by.css('input')); + await inputEle.sendKeys('-foo'); + + let buttonEle = page.crisisDetail.element(by.buttonText(save ? 'Save' : 'Cancel')); + await buttonEle.click(); + crisisEle = page.crisisList.get(index); + if (save) { + expect(crisisEle.getText()).toEqual(crisisText + '-foo'); + } else { + await browser.wait(EC.alertIsPresent(), 4000); + await browser.switchTo().alert().accept(); + expect(crisisEle.getText()).toEqual(crisisText); + } + } + +}); diff --git a/public/docs/_examples/router/ts/example-config.json b/public/docs/_examples/router/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/router/ts/plnkr.json b/public/docs/_examples/router/ts/plnkr.json new file mode 100644 index 0000000000..23ed1a1606 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/admin/admin-dashboard.component.1.ts b/public/docs/_examples/router/ts/src/app/admin/admin-dashboard.component.1.ts new file mode 100644 index 0000000000..ffa3e3cb8f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/admin/admin-dashboard.component.2.ts b/public/docs/_examples/router/ts/src/app/admin/admin-dashboard.component.2.ts new file mode 100644 index 0000000000..8c8e481643 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/admin/admin-dashboard.component.ts b/public/docs/_examples/router/ts/src/app/admin/admin-dashboard.component.ts new file mode 100644 index 0000000000..b3fc839616 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/admin/admin-routing.module.1.ts b/public/docs/_examples/router/ts/src/app/admin/admin-routing.module.1.ts new file mode 100644 index 0000000000..e7d83f113f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/admin/admin-routing.module.2.ts b/public/docs/_examples/router/ts/src/app/admin/admin-routing.module.2.ts new file mode 100644 index 0000000000..d945201afe --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/admin/admin-routing.module.3.ts b/public/docs/_examples/router/ts/src/app/admin/admin-routing.module.3.ts new file mode 100644 index 0000000000..63f1c9aaf4 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/admin/admin-routing.module.ts b/public/docs/_examples/router/ts/src/app/admin/admin-routing.module.ts new file mode 100644 index 0000000000..2b1048d110 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/admin/admin.component.ts b/public/docs/_examples/router/ts/src/app/admin/admin.component.ts new file mode 100644 index 0000000000..30abfa4524 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/admin/admin.module.ts b/public/docs/_examples/router/ts/src/app/admin/admin.module.ts new file mode 100644 index 0000000000..2736f00e1d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/admin/manage-crises.component.ts b/public/docs/_examples/router/ts/src/app/admin/manage-crises.component.ts new file mode 100644 index 0000000000..d3176563eb --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/admin/manage-heroes.component.ts b/public/docs/_examples/router/ts/src/app/admin/manage-heroes.component.ts new file mode 100644 index 0000000000..7f3a39893d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/animations.ts b/public/docs/_examples/router/ts/src/app/animations.ts new file mode 100644 index 0000000000..d7a55d721f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app-routing.module.1.ts b/public/docs/_examples/router/ts/src/app/app-routing.module.1.ts new file mode 100644 index 0000000000..8146e54671 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app-routing.module.2.ts b/public/docs/_examples/router/ts/src/app/app-routing.module.2.ts new file mode 100644 index 0000000000..42ac84a481 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app-routing.module.3.ts b/public/docs/_examples/router/ts/src/app/app-routing.module.3.ts new file mode 100644 index 0000000000..538ff9aafc --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app-routing.module.4.ts b/public/docs/_examples/router/ts/src/app/app-routing.module.4.ts new file mode 100644 index 0000000000..6835d24a85 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app-routing.module.5.ts b/public/docs/_examples/router/ts/src/app/app-routing.module.5.ts new file mode 100644 index 0000000000..2badf7f593 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app-routing.module.6.ts b/public/docs/_examples/router/ts/src/app/app-routing.module.6.ts new file mode 100644 index 0000000000..df2c8c097d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app-routing.module.ts b/public/docs/_examples/router/ts/src/app/app-routing.module.ts new file mode 100644 index 0000000000..cc01ced890 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app.component.1.ts b/public/docs/_examples/router/ts/src/app/app.component.1.ts new file mode 100644 index 0000000000..21e9aa417d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app.component.2.ts b/public/docs/_examples/router/ts/src/app/app.component.2.ts new file mode 100644 index 0000000000..ffd4d8dfae --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app.component.3.ts b/public/docs/_examples/router/ts/src/app/app.component.3.ts new file mode 100644 index 0000000000..6013df6321 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app.component.4.ts b/public/docs/_examples/router/ts/src/app/app.component.4.ts new file mode 100644 index 0000000000..a630703c28 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app.component.5.ts b/public/docs/_examples/router/ts/src/app/app.component.5.ts new file mode 100644 index 0000000000..24162c6136 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app.component.ts b/public/docs/_examples/router/ts/src/app/app.component.ts new file mode 100644 index 0000000000..a479680cbe --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app.module.0.ts b/public/docs/_examples/router/ts/src/app/app.module.0.ts new file mode 100644 index 0000000000..a195dbdd7a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app.module.1.ts b/public/docs/_examples/router/ts/src/app/app.module.1.ts new file mode 100644 index 0000000000..32f93b8f79 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app.module.2.ts b/public/docs/_examples/router/ts/src/app/app.module.2.ts new file mode 100644 index 0000000000..2ba739168c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app.module.3.ts b/public/docs/_examples/router/ts/src/app/app.module.3.ts new file mode 100644 index 0000000000..862faf1c51 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app.module.4.ts b/public/docs/_examples/router/ts/src/app/app.module.4.ts new file mode 100644 index 0000000000..4825572361 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app.module.5.ts b/public/docs/_examples/router/ts/src/app/app.module.5.ts new file mode 100644 index 0000000000..ad34668cea --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app.module.6.ts b/public/docs/_examples/router/ts/src/app/app.module.6.ts new file mode 100644 index 0000000000..4cb0b1fdd5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app.module.7.ts b/public/docs/_examples/router/ts/src/app/app.module.7.ts new file mode 100644 index 0000000000..b6ca81ddea --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/app.module.ts b/public/docs/_examples/router/ts/src/app/app.module.ts new file mode 100644 index 0000000000..dcf3401ded --- /dev/null +++ b/public/docs/_examples/router/ts/src/app/app.module.ts @@ -0,0 +1,56 @@ +// #docplaster +// #docregion +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; +// #docregion animations-module +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; + +// #enddocregion animations-module +// #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'; + +// #docregion animations-module +@NgModule({ + imports: [ + // #enddocregion animations-module + BrowserModule, + FormsModule, + HeroesModule, + LoginRoutingModule, + AppRoutingModule, + // #docregion animations-module + BrowserAnimationsModule + // #enddocregion animations-module + ], + 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/public/docs/_examples/router/ts/src/app/auth-guard.service.1.ts b/public/docs/_examples/router/ts/src/app/auth-guard.service.1.ts new file mode 100644 index 0000000000..c824bcb208 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/auth-guard.service.2.ts b/public/docs/_examples/router/ts/src/app/auth-guard.service.2.ts new file mode 100644 index 0000000000..8fd00e151a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/auth-guard.service.3.ts b/public/docs/_examples/router/ts/src/app/auth-guard.service.3.ts new file mode 100644 index 0000000000..dd89006411 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/auth-guard.service.4.ts b/public/docs/_examples/router/ts/src/app/auth-guard.service.4.ts new file mode 100644 index 0000000000..5d239a8432 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/auth-guard.service.ts b/public/docs/_examples/router/ts/src/app/auth-guard.service.ts new file mode 100644 index 0000000000..a32b5cc2b8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/auth.service.ts b/public/docs/_examples/router/ts/src/app/auth.service.ts new file mode 100644 index 0000000000..f86a80ebfe --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/can-deactivate-guard.service.1.ts b/public/docs/_examples/router/ts/src/app/can-deactivate-guard.service.1.ts new file mode 100644 index 0000000000..0b7c8247cf --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/can-deactivate-guard.service.ts b/public/docs/_examples/router/ts/src/app/can-deactivate-guard.service.ts new file mode 100644 index 0000000000..44da69f9c7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/compose-message.component.html b/public/docs/_examples/router/ts/src/app/compose-message.component.html new file mode 100644 index 0000000000..f0b964e6ac --- /dev/null +++ b/public/docs/_examples/router/ts/src/app/compose-message.component.html @@ -0,0 +1,17 @@ + +

    Contact Crisis Center

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

    + + +

    diff --git a/public/docs/_examples/router/ts/src/app/compose-message.component.ts b/public/docs/_examples/router/ts/src/app/compose-message.component.ts new file mode 100644 index 0000000000..17a0953378 --- /dev/null +++ b/public/docs/_examples/router/ts/src/app/compose-message.component.ts @@ -0,0 +1,43 @@ +// #docregion +import { Component, HostBinding } from '@angular/core'; +import { Router } from '@angular/router'; + +import { slideInDownAnimation } from './animations'; + +@Component({ + 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/public/docs/_examples/router/ts/src/app/crisis-center/crisis-center-home.component.ts b/public/docs/_examples/router/ts/src/app/crisis-center/crisis-center-home.component.ts new file mode 100644 index 0000000000..a71d485c02 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/crisis-center/crisis-center-routing.module.1.ts b/public/docs/_examples/router/ts/src/app/crisis-center/crisis-center-routing.module.1.ts new file mode 100644 index 0000000000..e646f467d1 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/crisis-center/crisis-center-routing.module.2.ts b/public/docs/_examples/router/ts/src/app/crisis-center/crisis-center-routing.module.2.ts new file mode 100644 index 0000000000..9e9b514968 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/crisis-center/crisis-center-routing.module.3.ts b/public/docs/_examples/router/ts/src/app/crisis-center/crisis-center-routing.module.3.ts new file mode 100644 index 0000000000..6d605dbe84 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/crisis-center/crisis-center-routing.module.4.ts b/public/docs/_examples/router/ts/src/app/crisis-center/crisis-center-routing.module.4.ts new file mode 100644 index 0000000000..b7ac88e852 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/crisis-center/crisis-center-routing.module.ts b/public/docs/_examples/router/ts/src/app/crisis-center/crisis-center-routing.module.ts new file mode 100644 index 0000000000..c01d592455 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/crisis-center/crisis-center.component.ts b/public/docs/_examples/router/ts/src/app/crisis-center/crisis-center.component.ts new file mode 100644 index 0000000000..31d1790f45 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/crisis-center/crisis-center.module.1.ts b/public/docs/_examples/router/ts/src/app/crisis-center/crisis-center.module.1.ts new file mode 100644 index 0000000000..5a3e45f58f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/crisis-center/crisis-center.module.ts b/public/docs/_examples/router/ts/src/app/crisis-center/crisis-center.module.ts new file mode 100644 index 0000000000..4061ceac60 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/crisis-center/crisis-detail-resolver.service.ts b/public/docs/_examples/router/ts/src/app/crisis-center/crisis-detail-resolver.service.ts new file mode 100644 index 0000000000..94b4cd33e7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/crisis-center/crisis-detail.component.1.ts b/public/docs/_examples/router/ts/src/app/crisis-center/crisis-detail.component.1.ts new file mode 100644 index 0000000000..d6fa27f629 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/crisis-center/crisis-detail.component.ts b/public/docs/_examples/router/ts/src/app/crisis-center/crisis-detail.component.ts new file mode 100644 index 0000000000..f0939b47a8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/crisis-center/crisis-list.component.1.ts b/public/docs/_examples/router/ts/src/app/crisis-center/crisis-list.component.1.ts new file mode 100644 index 0000000000..0000dde082 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/crisis-center/crisis-list.component.ts b/public/docs/_examples/router/ts/src/app/crisis-center/crisis-list.component.ts new file mode 100644 index 0000000000..4498a55c0f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/crisis-center/crisis.service.ts b/public/docs/_examples/router/ts/src/app/crisis-center/crisis.service.ts new file mode 100644 index 0000000000..e7fd34d387 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/crisis-list.component.ts b/public/docs/_examples/router/ts/src/app/crisis-list.component.ts new file mode 100644 index 0000000000..6caa3653b5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/dialog.service.ts b/public/docs/_examples/router/ts/src/app/dialog.service.ts new file mode 100644 index 0000000000..0f09e4936d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/hero-list.component.ts b/public/docs/_examples/router/ts/src/app/hero-list.component.ts new file mode 100644 index 0000000000..7a8f97ca1e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/heroes/hero-detail.component.1.ts b/public/docs/_examples/router/ts/src/app/heroes/hero-detail.component.1.ts new file mode 100644 index 0000000000..93f0efaf0b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/heroes/hero-detail.component.2.ts b/public/docs/_examples/router/ts/src/app/heroes/hero-detail.component.2.ts new file mode 100644 index 0000000000..c3b69be965 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/heroes/hero-detail.component.ts b/public/docs/_examples/router/ts/src/app/heroes/hero-detail.component.ts new file mode 100644 index 0000000000..8135d37d32 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/heroes/hero-list.component.1.ts b/public/docs/_examples/router/ts/src/app/heroes/hero-list.component.1.ts new file mode 100644 index 0000000000..59552830a4 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/heroes/hero-list.component.ts b/public/docs/_examples/router/ts/src/app/heroes/hero-list.component.ts new file mode 100644 index 0000000000..c7dcc3877e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/heroes/hero.service.ts b/public/docs/_examples/router/ts/src/app/heroes/hero.service.ts new file mode 100644 index 0000000000..6e4e7bee60 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/heroes/heroes-routing.module.ts b/public/docs/_examples/router/ts/src/app/heroes/heroes-routing.module.ts new file mode 100644 index 0000000000..dbee521793 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/heroes/heroes.module.ts b/public/docs/_examples/router/ts/src/app/heroes/heroes.module.ts new file mode 100644 index 0000000000..95ee64a182 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/login-routing.module.ts b/public/docs/_examples/router/ts/src/app/login-routing.module.ts new file mode 100644 index 0000000000..96d05e7972 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/login.component.1.ts b/public/docs/_examples/router/ts/src/app/login.component.1.ts new file mode 100644 index 0000000000..ddee339011 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/login.component.ts b/public/docs/_examples/router/ts/src/app/login.component.ts new file mode 100644 index 0000000000..41c88f4068 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/not-found.component.ts b/public/docs/_examples/router/ts/src/app/not-found.component.ts new file mode 100644 index 0000000000..2e74544e17 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/router/ts/src/app/selective-preloading-strategy.ts b/public/docs/_examples/router/ts/src/app/selective-preloading-strategy.ts new file mode 100644 index 0000000000..395f1056ef --- /dev/null +++ b/public/docs/_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 the 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/public/docs/_examples/router/ts/src/index.html b/public/docs/_examples/router/ts/src/index.html new file mode 100644 index 0000000000..0fa6453c36 --- /dev/null +++ b/public/docs/_examples/router/ts/src/index.html @@ -0,0 +1,32 @@ + + + + + + + + + Angular Router + + + + + + + + + + + + + + + + loading... + + + + diff --git a/public/docs/_examples/router/ts/src/main.ts b/public/docs/_examples/router/ts/src/main.ts new file mode 100644 index 0000000000..f332d1d245 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/security/e2e-spec.ts b/public/docs/_examples/security/e2e-spec.ts new file mode 100644 index 0000000000..23d11cd12b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/security/ts/example-config.json b/public/docs/_examples/security/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/security/ts/plnkr.json b/public/docs/_examples/security/ts/plnkr.json new file mode 100644 index 0000000000..4c9e85ce1e --- /dev/null +++ b/public/docs/_examples/security/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "Content Security", + "basePath": "src/", + "files": [ + "!**/*.d.ts", + "!**/*.js" + ], + "tags": ["security"] +} diff --git a/public/docs/_examples/security/ts/src/app/app.component.ts b/public/docs/_examples/security/ts/src/app/app.component.ts new file mode 100644 index 0000000000..c30235e8e7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/security/ts/src/app/app.module.ts b/public/docs/_examples/security/ts/src/app/app.module.ts new file mode 100644 index 0000000000..21d880be3b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/security/ts/src/app/bypass-security.component.html b/public/docs/_examples/security/ts/src/app/bypass-security.component.html new file mode 100644 index 0000000000..96adf058e2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/security/ts/src/app/bypass-security.component.ts b/public/docs/_examples/security/ts/src/app/bypass-security.component.ts new file mode 100644 index 0000000000..ff41e287ba --- /dev/null +++ b/public/docs/_examples/security/ts/src/app/bypass-security.component.ts @@ -0,0 +1,38 @@ +// #docplaster +// #docregion +import { Component } from '@angular/core'; +import { DomSanitizer, SafeResourceUrl, SafeUrl } from '@angular/platform-browser'; + +@Component({ + selector: 'bypass-security', + 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/public/docs/_examples/security/ts/src/app/inner-html-binding.component.html b/public/docs/_examples/security/ts/src/app/inner-html-binding.component.html new file mode 100644 index 0000000000..fe540d25fe --- /dev/null +++ b/public/docs/_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/public/docs/_examples/security/ts/src/app/inner-html-binding.component.ts b/public/docs/_examples/security/ts/src/app/inner-html-binding.component.ts new file mode 100644 index 0000000000..8319503686 --- /dev/null +++ b/public/docs/_examples/security/ts/src/app/inner-html-binding.component.ts @@ -0,0 +1,12 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + 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/public/docs/_examples/security/ts/src/index.html b/public/docs/_examples/security/ts/src/index.html new file mode 100644 index 0000000000..d5dd11c038 --- /dev/null +++ b/public/docs/_examples/security/ts/src/index.html @@ -0,0 +1,26 @@ + + + + + Angular Content Security + + + + + + + + + + + + + + + + + Loading... + + diff --git a/public/docs/_examples/security/ts/src/main.ts b/public/docs/_examples/security/ts/src/main.ts new file mode 100644 index 0000000000..105b06712d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/server-communication/e2e-spec.ts b/public/docs/_examples/server-communication/e2e-spec.ts new file mode 100644 index 0000000000..a85c7489f8 --- /dev/null +++ b/public/docs/_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: any) { + testForRefreshedResult('B', done); + }); + + it('should fetch results after "Ba"', function(done: any) { + testForRefreshedResult('a', done); + }); + + it('should fetch results after "Bas"', function(done: any) { + testForRefreshedResult('s', done); + }); + + it('should fetch results after "Basic"', function(done: any) { + 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: any) { + testForNewResult('Java', done); + }); + + it('should fetch results after "JavaS"', function(done: any) { + testForStaleResult('S', done); + }); + + it('should fetch results after "JavaSc"', function(done: any) { + testForStaleResult('c', done); + }); + + it('should fetch results after "JavaScript"', function(done: any) { + 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/public/docs/_examples/server-communication/ts/example-config.json b/public/docs/_examples/server-communication/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/server-communication/ts/plnkr.json b/public/docs/_examples/server-communication/ts/plnkr.json new file mode 100644 index 0000000000..fe966be012 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/server-communication/ts/src/app/app.component.ts b/public/docs/_examples/server-communication/ts/src/app/app.component.ts new file mode 100644 index 0000000000..780d044cab --- /dev/null +++ b/public/docs/_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/public/docs/_examples/server-communication/ts/src/app/app.module.1.ts b/public/docs/_examples/server-communication/ts/src/app/app.module.1.ts new file mode 100644 index 0000000000..fb7012aa02 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/server-communication/ts/src/app/app.module.ts b/public/docs/_examples/server-communication/ts/src/app/app.module.ts new file mode 100644 index 0000000000..fd0c720c3c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/server-communication/ts/src/app/default-request-options.service.ts b/public/docs/_examples/server-communication/ts/src/app/default-request-options.service.ts new file mode 100644 index 0000000000..9ec52daa80 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/server-communication/ts/src/app/hero-data.ts b/public/docs/_examples/server-communication/ts/src/app/hero-data.ts new file mode 100644 index 0000000000..4db6aca115 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/server-communication/ts/src/app/heroes.json b/public/docs/_examples/server-communication/ts/src/app/heroes.json new file mode 100644 index 0000000000..dfb589066b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/server-communication/ts/src/app/toh/hero-list.component.html b/public/docs/_examples/server-communication/ts/src/app/toh/hero-list.component.html new file mode 100644 index 0000000000..65ca9cfbb7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/server-communication/ts/src/app/toh/hero-list.component.promise.ts b/public/docs/_examples/server-communication/ts/src/app/toh/hero-list.component.promise.ts new file mode 100644 index 0000000000..4bbe7eade2 --- /dev/null +++ b/public/docs/_examples/server-communication/ts/src/app/toh/hero-list.component.promise.ts @@ -0,0 +1,40 @@ +// #docregion +// Promise Version +import { Component, OnInit } from '@angular/core'; +import { Hero } from './hero'; +import { HeroService } from './hero.service.promise'; + +@Component({ + selector: 'hero-list-promise', + 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/public/docs/_examples/server-communication/ts/src/app/toh/hero-list.component.ts b/public/docs/_examples/server-communication/ts/src/app/toh/hero-list.component.ts new file mode 100644 index 0000000000..8cca504762 --- /dev/null +++ b/public/docs/_examples/server-communication/ts/src/app/toh/hero-list.component.ts @@ -0,0 +1,44 @@ +// #docregion +// Observable Version +import { Component, OnInit } from '@angular/core'; +import { Hero } from './hero'; +import { HeroService } from './hero.service'; + +@Component({ + 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.create(name) + .subscribe( + hero => this.heroes.push(hero), + error => this.errorMessage = error); + } + // #enddocregion addHero + // #enddocregion methods +} +// #enddocregion component diff --git a/public/docs/_examples/server-communication/ts/src/app/toh/hero.service.promise.ts b/public/docs/_examples/server-communication/ts/src/app/toh/hero.service.promise.ts new file mode 100644 index 0000000000..e38bd4bebf --- /dev/null +++ b/public/docs/_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/public/docs/_examples/server-communication/ts/src/app/toh/hero.service.ts b/public/docs/_examples/server-communication/ts/src/app/toh/hero.service.ts new file mode 100644 index 0000000000..4f0da49021 --- /dev/null +++ b/public/docs/_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 = 'api/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 create, create-sig + create(name: string): Observable { + // #enddocregion create-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 create + + // #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, you 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/public/docs/_examples/server-communication/ts/src/app/toh/hero.ts b/public/docs/_examples/server-communication/ts/src/app/toh/hero.ts new file mode 100644 index 0000000000..09b8d295ce --- /dev/null +++ b/public/docs/_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/public/docs/_examples/server-communication/ts/src/app/wiki/wiki-smart.component.ts b/public/docs/_examples/server-communication/ts/src/app/wiki/wiki-smart.component.ts new file mode 100644 index 0000000000..fc453a820e --- /dev/null +++ b/public/docs/_examples/server-communication/ts/src/app/wiki/wiki-smart.component.ts @@ -0,0 +1,47 @@ +/* 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({ + 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/public/docs/_examples/server-communication/ts/src/app/wiki/wiki.component.ts b/public/docs/_examples/server-communication/ts/src/app/wiki/wiki.component.ts new file mode 100644 index 0000000000..4230df12a1 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/server-communication/ts/src/app/wiki/wikipedia.service.1.ts b/public/docs/_examples/server-communication/ts/src/app/wiki/wikipedia.service.1.ts new file mode 100644 index 0000000000..5cbcb7d707 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/server-communication/ts/src/app/wiki/wikipedia.service.ts b/public/docs/_examples/server-communication/ts/src/app/wiki/wikipedia.service.ts new file mode 100644 index 0000000000..a38167d1c6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/server-communication/ts/src/index.html b/public/docs/_examples/server-communication/ts/src/index.html new file mode 100644 index 0000000000..5c51f21c91 --- /dev/null +++ b/public/docs/_examples/server-communication/ts/src/index.html @@ -0,0 +1,27 @@ + + + + + Angular Http Demo + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/public/docs/_examples/server-communication/ts/src/main.ts b/public/docs/_examples/server-communication/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/setup/e2e-spec.ts b/public/docs/_examples/setup/e2e-spec.ts new file mode 100644 index 0000000000..73921707ee --- /dev/null +++ b/public/docs/_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/public/docs/_examples/setup/ts/example-config.json b/public/docs/_examples/setup/ts/example-config.json new file mode 100644 index 0000000000..eb33c943c7 --- /dev/null +++ b/public/docs/_examples/setup/ts/example-config.json @@ -0,0 +1,3 @@ +{ + "unittesting": true +} diff --git a/public/docs/_examples/setup/ts/non-essential-files.txt b/public/docs/_examples/setup/ts/non-essential-files.txt new file mode 100644 index 0000000000..3c9cdde9dc --- /dev/null +++ b/public/docs/_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/public/docs/_examples/setup/ts/plnkr.json b/public/docs/_examples/setup/ts/plnkr.json new file mode 100644 index 0000000000..22b528eec5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/setup/ts/quickstart-specs.plnkr.json b/public/docs/_examples/setup/ts/quickstart-specs.plnkr.json new file mode 100644 index 0000000000..a7dfedb595 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/setup/ts/src/app/app.component.spec.ts b/public/docs/_examples/setup/ts/src/app/app.component.spec.ts new file mode 100644 index 0000000000..7769024464 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/setup/ts/src/app/app.component.ts b/public/docs/_examples/setup/ts/src/app/app.component.ts new file mode 100644 index 0000000000..1ef28fc5c4 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/setup/ts/src/app/app.module.ts b/public/docs/_examples/setup/ts/src/app/app.module.ts new file mode 100644 index 0000000000..50a0e6eb47 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/setup/ts/src/index.html b/public/docs/_examples/setup/ts/src/index.html new file mode 100644 index 0000000000..58e5112308 --- /dev/null +++ b/public/docs/_examples/setup/ts/src/index.html @@ -0,0 +1,28 @@ + + + + + Angular Quickstart Seed + + + + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/setup/ts/src/main.ts b/public/docs/_examples/setup/ts/src/main.ts new file mode 100644 index 0000000000..02d58dceac --- /dev/null +++ b/public/docs/_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/public/docs/_examples/setup/ts/src/quickstart-specs.html b/public/docs/_examples/setup/ts/src/quickstart-specs.html new file mode 100644 index 0000000000..9bc81ccf2e --- /dev/null +++ b/public/docs/_examples/setup/ts/src/quickstart-specs.html @@ -0,0 +1,41 @@ + + + + + + + 1st Specs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/setup/ts/src/systemjs.config.extras.js b/public/docs/_examples/setup/ts/src/systemjs.config.extras.js new file mode 100644 index 0000000000..027dfe58cf --- /dev/null +++ b/public/docs/_examples/setup/ts/src/systemjs.config.extras.js @@ -0,0 +1,11 @@ +/** + * Add barrels and stuff + * Adjust as necessary for your application needs. + */ +// (function (global) { +// System.config({ +// packages: { +// // add packages here +// } +// }); +// })(this); diff --git a/public/docs/_examples/structural-directives/e2e-spec.ts b/public/docs/_examples/structural-directives/e2e-spec.ts new file mode 100644 index 0000000000..51f30bf0bb --- /dev/null +++ b/public/docs/_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/public/docs/_examples/structural-directives/ts/example-config.json b/public/docs/_examples/structural-directives/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/structural-directives/ts/plnkr.json b/public/docs/_examples/structural-directives/ts/plnkr.json new file mode 100644 index 0000000000..58a26d7b4f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/structural-directives/ts/src/app/app.component.css b/public/docs/_examples/structural-directives/ts/src/app/app.component.css new file mode 100644 index 0000000000..e28be894f8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/structural-directives/ts/src/app/app.component.html b/public/docs/_examples/structural-directives/ts/src/app/app.component.html new file mode 100644 index 0000000000..49c4928aad --- /dev/null +++ b/public/docs/_examples/structural-directives/ts/src/app/app.component.html @@ -0,0 +1,250 @@ + + +

    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

    +

    <ng-template> element

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

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

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

    NgSwitch

    + +
    Pick your favorite hero
    +

    + + +

    + +

    NgSwitch

    + + +
    + + + + +
    + + +

    NgSwitch with template attribute

    + +
    + + + + +
    + + +

    NgSwitch with <ng-template>

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

    <template>

    + +

    Hip!

    + +

    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"> +

    + + +

    + (A) <template [myUnless]="condition"> +

    +
    + diff --git a/public/docs/_examples/structural-directives/ts/src/app/app.component.ts b/public/docs/_examples/structural-directives/ts/src/app/app.component.ts new file mode 100644 index 0000000000..ff6f0e65a1 --- /dev/null +++ b/public/docs/_examples/structural-directives/ts/src/app/app.component.ts @@ -0,0 +1,23 @@ +// #docregion +import { Component } from '@angular/core'; + +import { Hero, heroes } from './hero'; + +@Component({ + 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/public/docs/_examples/structural-directives/ts/src/app/app.module.ts b/public/docs/_examples/structural-directives/ts/src/app/app.module.ts new file mode 100644 index 0000000000..b6ffb456c9 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/structural-directives/ts/src/app/hero-switch.components.ts b/public/docs/_examples/structural-directives/ts/src/app/hero-switch.components.ts new file mode 100644 index 0000000000..6608422d15 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/structural-directives/ts/src/app/hero.ts b/public/docs/_examples/structural-directives/ts/src/app/hero.ts new file mode 100644 index 0000000000..a1de3b3b82 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/structural-directives/ts/src/app/scrap.txt b/public/docs/_examples/structural-directives/ts/src/app/scrap.txt new file mode 100644 index 0000000000..96426d37f0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/structural-directives/ts/src/app/unless.directive.ts b/public/docs/_examples/structural-directives/ts/src/app/unless.directive.ts new file mode 100644 index 0000000000..470bbd932f --- /dev/null +++ b/public/docs/_examples/structural-directives/ts/src/app/unless.directive.ts @@ -0,0 +1,51 @@ +// #docplaster +// #docregion +// #docregion no-docs, 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 +} diff --git a/public/docs/_examples/structural-directives/ts/src/index.html b/public/docs/_examples/structural-directives/ts/src/index.html new file mode 100644 index 0000000000..ce7a33266d --- /dev/null +++ b/public/docs/_examples/structural-directives/ts/src/index.html @@ -0,0 +1,27 @@ + + + + + Angular Structural Directives + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/public/docs/_examples/structural-directives/ts/src/main.ts b/public/docs/_examples/structural-directives/ts/src/main.ts new file mode 100644 index 0000000000..105b06712d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/e2e-spec.ts b/public/docs/_examples/style-guide/e2e-spec.ts new file mode 100644 index 0000000000..d143ae5573 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/.gitignore b/public/docs/_examples/style-guide/ts/.gitignore new file mode 100644 index 0000000000..bd6423cecb --- /dev/null +++ b/public/docs/_examples/style-guide/ts/.gitignore @@ -0,0 +1,2 @@ +*.js +!systemjs.custom.js diff --git a/public/docs/_examples/style-guide/ts/example-config.json b/public/docs/_examples/style-guide/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/style-guide/ts/plnkr.json b/public/docs/_examples/style-guide/ts/plnkr.json new file mode 100644 index 0000000000..bf9e9681c5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/01-01/app/app.component.css b/public/docs/_examples/style-guide/ts/src/01-01/app/app.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/style-guide/ts/src/01-01/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/01-01/app/app.component.ts new file mode 100644 index 0000000000..d5bb953540 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/01-01/app/app.component.ts @@ -0,0 +1,14 @@ +// #docregion +import { Component } from '@angular/core'; + +import { HeroService } from './heroes'; + +@Component({ + selector: 'toh-app', + template: ` + + `, + styleUrls: ['./app.component.css'], + providers: [ HeroService ] +}) +export class AppComponent { } diff --git a/public/docs/_examples/style-guide/ts/src/01-01/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/01-01/app/app.module.ts new file mode 100644 index 0000000000..53f29ea8cb --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/01-01/app/heroes/hero.component.avoid.ts b/public/docs/_examples/style-guide/ts/src/01-01/app/heroes/hero.component.avoid.ts new file mode 100644 index 0000000000..853e6ab64e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/01-01/app/heroes/heroes.component.ts b/public/docs/_examples/style-guide/ts/src/01-01/app/heroes/heroes.component.ts new file mode 100644 index 0000000000..9e3546db90 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/01-01/app/heroes/index.ts b/public/docs/_examples/style-guide/ts/src/01-01/app/heroes/index.ts new file mode 100644 index 0000000000..a8d7f1d422 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/01-01/app/heroes/shared/hero.model.ts b/public/docs/_examples/style-guide/ts/src/01-01/app/heroes/shared/hero.model.ts new file mode 100644 index 0000000000..8f7cc205c8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/01-01/app/heroes/shared/hero.service.ts b/public/docs/_examples/style-guide/ts/src/01-01/app/heroes/shared/hero.service.ts new file mode 100644 index 0000000000..d94e5bacac --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/01-01/app/heroes/shared/index.ts b/public/docs/_examples/style-guide/ts/src/01-01/app/heroes/shared/index.ts new file mode 100644 index 0000000000..c0c1a87eb2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/01-01/app/heroes/shared/mock-heroes.ts b/public/docs/_examples/style-guide/ts/src/01-01/app/heroes/shared/mock-heroes.ts new file mode 100644 index 0000000000..2e9a69f59d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/01-01/app/index.ts b/public/docs/_examples/style-guide/ts/src/01-01/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/01-01/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/01-01/main.ts b/public/docs/_examples/style-guide/ts/src/01-01/main.ts new file mode 100644 index 0000000000..7e8269bd65 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/02-05/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/02-05/app/app.component.ts new file mode 100644 index 0000000000..66ebfa954e --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/02-05/app/app.component.ts @@ -0,0 +1,10 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'toh-app', + template: ` + Tour of Heroes + ` +}) +export class AppComponent { } diff --git a/public/docs/_examples/style-guide/ts/src/02-05/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/02-05/app/app.module.ts new file mode 100644 index 0000000000..306ec125e0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/02-05/main.ts b/public/docs/_examples/style-guide/ts/src/02-05/main.ts new file mode 100644 index 0000000000..6c32161f84 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/02-07/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/02-07/app/app.component.ts new file mode 100644 index 0000000000..c82e12624d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/02-07/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/02-07/app/app.module.ts new file mode 100644 index 0000000000..4320fe67d5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/02-07/app/heroes/hero.component.avoid.ts b/public/docs/_examples/style-guide/ts/src/02-07/app/heroes/hero.component.avoid.ts new file mode 100644 index 0000000000..976fcb6cdf --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/02-07/app/heroes/hero.component.ts b/public/docs/_examples/style-guide/ts/src/02-07/app/heroes/hero.component.ts new file mode 100644 index 0000000000..44c04dd855 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/02-07/app/heroes/index.ts b/public/docs/_examples/style-guide/ts/src/02-07/app/heroes/index.ts new file mode 100644 index 0000000000..084f36d703 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/02-07/app/heroes/index.ts @@ -0,0 +1 @@ +export * from './hero.component'; diff --git a/public/docs/_examples/style-guide/ts/src/02-07/app/index.ts b/public/docs/_examples/style-guide/ts/src/02-07/app/index.ts new file mode 100644 index 0000000000..fc87f976b3 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/02-07/app/users/index.ts b/public/docs/_examples/style-guide/ts/src/02-07/app/users/index.ts new file mode 100644 index 0000000000..475ba6d2a7 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/02-07/app/users/index.ts @@ -0,0 +1 @@ +export * from './users.component'; diff --git a/public/docs/_examples/style-guide/ts/src/02-07/app/users/users.component.avoid.ts b/public/docs/_examples/style-guide/ts/src/02-07/app/users/users.component.avoid.ts new file mode 100644 index 0000000000..9a5bd936e9 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/02-07/app/users/users.component.ts b/public/docs/_examples/style-guide/ts/src/02-07/app/users/users.component.ts new file mode 100644 index 0000000000..2fb6d54ebe --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/02-08/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/02-08/app/app.component.ts new file mode 100644 index 0000000000..bf27aeaf8a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/02-08/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/02-08/app/app.module.ts new file mode 100644 index 0000000000..e840cc50a5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/02-08/app/index.ts b/public/docs/_examples/style-guide/ts/src/02-08/app/index.ts new file mode 100644 index 0000000000..ebe5c92f03 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/02-08/app/index.ts @@ -0,0 +1,2 @@ +export * from './shared'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/02-08/app/shared/index.ts b/public/docs/_examples/style-guide/ts/src/02-08/app/shared/index.ts new file mode 100644 index 0000000000..b844b75492 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/02-08/app/shared/input-highlight.directive.ts b/public/docs/_examples/style-guide/ts/src/02-08/app/shared/input-highlight.directive.ts new file mode 100644 index 0000000000..fd74771981 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/02-08/app/shared/validate.directive.avoid.ts b/public/docs/_examples/style-guide/ts/src/02-08/app/shared/validate.directive.avoid.ts new file mode 100644 index 0000000000..b038f37075 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/02-08/app/shared/validate.directive.ts b/public/docs/_examples/style-guide/ts/src/02-08/app/shared/validate.directive.ts new file mode 100644 index 0000000000..039afb846f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-01/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/03-01/app/app.component.ts new file mode 100644 index 0000000000..cb9479d2d9 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-01/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/03-01/app/app.module.ts new file mode 100644 index 0000000000..48079f21c7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-01/app/core/exception.service.avoid.ts b/public/docs/_examples/style-guide/ts/src/03-01/app/core/exception.service.avoid.ts new file mode 100644 index 0000000000..0a22811fe3 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-01/app/core/exception.service.ts b/public/docs/_examples/style-guide/ts/src/03-01/app/core/exception.service.ts new file mode 100644 index 0000000000..dd77b4f7dc --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-01/app/core/index.ts b/public/docs/_examples/style-guide/ts/src/03-01/app/core/index.ts new file mode 100644 index 0000000000..8acaa4bcf9 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/03-01/app/core/index.ts @@ -0,0 +1 @@ +export * from './exception.service'; diff --git a/public/docs/_examples/style-guide/ts/src/03-01/app/index.ts b/public/docs/_examples/style-guide/ts/src/03-01/app/index.ts new file mode 100644 index 0000000000..e120e2dbfd --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/03-01/app/index.ts @@ -0,0 +1,2 @@ +export * from './core'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/03-02/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/03-02/app/app.component.ts new file mode 100644 index 0000000000..132ea54c85 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-02/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/03-02/app/app.module.ts new file mode 100644 index 0000000000..2db4012ebf --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-02/app/core/data.service.ts b/public/docs/_examples/style-guide/ts/src/03-02/app/core/data.service.ts new file mode 100644 index 0000000000..5c26478c7b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-02/app/core/index.ts b/public/docs/_examples/style-guide/ts/src/03-02/app/core/index.ts new file mode 100644 index 0000000000..2ba773ede8 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/03-02/app/core/index.ts @@ -0,0 +1 @@ +export * from './data.service'; diff --git a/public/docs/_examples/style-guide/ts/src/03-02/app/index.ts b/public/docs/_examples/style-guide/ts/src/03-02/app/index.ts new file mode 100644 index 0000000000..e120e2dbfd --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/03-02/app/index.ts @@ -0,0 +1,2 @@ +export * from './core'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/03-03/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/03-03/app/app.component.ts new file mode 100644 index 0000000000..3ca522bc45 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-03/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/03-03/app/app.module.ts new file mode 100644 index 0000000000..29b3d2e765 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-03/app/core/hero-collector.service.avoid.ts b/public/docs/_examples/style-guide/ts/src/03-03/app/core/hero-collector.service.avoid.ts new file mode 100644 index 0000000000..f481af18b6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-03/app/core/hero-collector.service.ts b/public/docs/_examples/style-guide/ts/src/03-03/app/core/hero-collector.service.ts new file mode 100644 index 0000000000..1df5c0deb0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-03/app/core/hero.model.avoid.ts b/public/docs/_examples/style-guide/ts/src/03-03/app/core/hero.model.avoid.ts new file mode 100644 index 0000000000..ce93b2c59a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-03/app/core/hero.model.ts b/public/docs/_examples/style-guide/ts/src/03-03/app/core/hero.model.ts new file mode 100644 index 0000000000..c3277621cb --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-03/app/core/index.ts b/public/docs/_examples/style-guide/ts/src/03-03/app/core/index.ts new file mode 100644 index 0000000000..17ad67b0b1 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-03/app/index.ts b/public/docs/_examples/style-guide/ts/src/03-03/app/index.ts new file mode 100644 index 0000000000..e120e2dbfd --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/03-03/app/index.ts @@ -0,0 +1,2 @@ +export * from './core'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/03-04/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/03-04/app/app.component.ts new file mode 100644 index 0000000000..555c9e9441 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-04/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/03-04/app/app.module.ts new file mode 100644 index 0000000000..a5a8d5bb4e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-04/app/core/index.ts b/public/docs/_examples/style-guide/ts/src/03-04/app/core/index.ts new file mode 100644 index 0000000000..e78b628f9c --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/03-04/app/core/index.ts @@ -0,0 +1 @@ +export * from './toast.service'; diff --git a/public/docs/_examples/style-guide/ts/src/03-04/app/core/toast.service.avoid.ts b/public/docs/_examples/style-guide/ts/src/03-04/app/core/toast.service.avoid.ts new file mode 100644 index 0000000000..0f3a7c25ea --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-04/app/core/toast.service.ts b/public/docs/_examples/style-guide/ts/src/03-04/app/core/toast.service.ts new file mode 100644 index 0000000000..ab148a1732 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-04/app/index.ts b/public/docs/_examples/style-guide/ts/src/03-04/app/index.ts new file mode 100644 index 0000000000..e120e2dbfd --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/03-04/app/index.ts @@ -0,0 +1,2 @@ +export * from './core'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/03-06/app/app.component.html b/public/docs/_examples/style-guide/ts/src/03-06/app/app.component.html new file mode 100644 index 0000000000..67fb0d5964 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/03-06/app/app.component.html @@ -0,0 +1,6 @@ +
    Actual favorite: {{favorite?.name}}
    +
      +
    • + {{hero.name}} +
    • +
    diff --git a/public/docs/_examples/style-guide/ts/src/03-06/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/03-06/app/app.component.ts new file mode 100644 index 0000000000..8ec308bc6a --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/03-06/app/app.component.ts @@ -0,0 +1,21 @@ +import { Component, OnInit } from '@angular/core'; + +import { Hero, HeroService } from './heroes'; +import { ExceptionService, SpinnerService, ToastService } from './core'; + +@Component({ + 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/public/docs/_examples/style-guide/ts/src/03-06/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/03-06/app/app.module.ts new file mode 100644 index 0000000000..f259ce23a2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-06/app/core/exception.service.ts b/public/docs/_examples/style-guide/ts/src/03-06/app/core/exception.service.ts new file mode 100644 index 0000000000..7180c88e6b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-06/app/core/index.ts b/public/docs/_examples/style-guide/ts/src/03-06/app/core/index.ts new file mode 100644 index 0000000000..e4e6723f91 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-06/app/core/spinner/index.ts b/public/docs/_examples/style-guide/ts/src/03-06/app/core/spinner/index.ts new file mode 100644 index 0000000000..1d619300c0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-06/app/core/spinner/spinner.component.ts b/public/docs/_examples/style-guide/ts/src/03-06/app/core/spinner/spinner.component.ts new file mode 100644 index 0000000000..1fd2a01500 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-06/app/core/spinner/spinner.service.ts b/public/docs/_examples/style-guide/ts/src/03-06/app/core/spinner/spinner.service.ts new file mode 100644 index 0000000000..ad5d2ed6e0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-06/app/core/toast/index.ts b/public/docs/_examples/style-guide/ts/src/03-06/app/core/toast/index.ts new file mode 100644 index 0000000000..01b41aff98 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-06/app/core/toast/toast.component.ts b/public/docs/_examples/style-guide/ts/src/03-06/app/core/toast/toast.component.ts new file mode 100644 index 0000000000..dd0bba5eba --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-06/app/core/toast/toast.service.ts b/public/docs/_examples/style-guide/ts/src/03-06/app/core/toast/toast.service.ts new file mode 100644 index 0000000000..e92e75ee45 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-06/app/heroes/index.ts b/public/docs/_examples/style-guide/ts/src/03-06/app/heroes/index.ts new file mode 100644 index 0000000000..c3da79f741 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/03-06/app/heroes/index.ts @@ -0,0 +1 @@ +export * from './shared'; diff --git a/public/docs/_examples/style-guide/ts/src/03-06/app/heroes/shared/hero.model.ts b/public/docs/_examples/style-guide/ts/src/03-06/app/heroes/shared/hero.model.ts new file mode 100644 index 0000000000..c3277621cb --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-06/app/heroes/shared/hero.service.avoid.ts b/public/docs/_examples/style-guide/ts/src/03-06/app/heroes/shared/hero.service.avoid.ts new file mode 100644 index 0000000000..8287c567a7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-06/app/heroes/shared/hero.service.ts b/public/docs/_examples/style-guide/ts/src/03-06/app/heroes/shared/hero.service.ts new file mode 100644 index 0000000000..5792cd1ab2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-06/app/heroes/shared/index.ts b/public/docs/_examples/style-guide/ts/src/03-06/app/heroes/shared/index.ts new file mode 100644 index 0000000000..dbb150d3f8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-06/app/index.ts b/public/docs/_examples/style-guide/ts/src/03-06/app/index.ts new file mode 100644 index 0000000000..cf861e261a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/03-06/app/shared/toast/toast.component.ts b/public/docs/_examples/style-guide/ts/src/03-06/app/shared/toast/toast.component.ts new file mode 100644 index 0000000000..e1c1ae6665 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-08/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/04-08/app/app.component.ts new file mode 100644 index 0000000000..fdd8e6ef56 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-08/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/04-08/app/app.module.ts new file mode 100644 index 0000000000..25568b7fb4 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-08/app/heroes/heroes.component.html b/public/docs/_examples/style-guide/ts/src/04-08/app/heroes/heroes.component.html new file mode 100644 index 0000000000..1244e68a4a --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/04-08/app/heroes/heroes.component.html @@ -0,0 +1 @@ +
    This is heroes component
    diff --git a/public/docs/_examples/style-guide/ts/src/04-08/app/heroes/heroes.component.ts b/public/docs/_examples/style-guide/ts/src/04-08/app/heroes/heroes.component.ts new file mode 100644 index 0000000000..e2df4c91fd --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/04-08/app/heroes/heroes.component.ts @@ -0,0 +1,11 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'toh-heroes', + templateUrl: './heroes.component.html' +}) +export class HeroesComponent implements OnInit { + constructor() { /* ... */ } + + ngOnInit() { /* ... */ } +} diff --git a/public/docs/_examples/style-guide/ts/src/04-10/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/04-10/app/app.component.ts new file mode 100644 index 0000000000..fdd8e6ef56 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-10/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/04-10/app/app.module.ts new file mode 100644 index 0000000000..78106657ad --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-10/app/heroes/heroes.component.html b/public/docs/_examples/style-guide/ts/src/04-10/app/heroes/heroes.component.html new file mode 100644 index 0000000000..170f76aee1 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-10/app/heroes/heroes.component.ts b/public/docs/_examples/style-guide/ts/src/04-10/app/heroes/heroes.component.ts new file mode 100644 index 0000000000..376567650e --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/04-10/app/heroes/heroes.component.ts @@ -0,0 +1,27 @@ +// #docregion +import { Component } from '@angular/core'; + +import { FilterTextService } from '../shared/filter-text/filter-text.service'; + +@Component({ + 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/public/docs/_examples/style-guide/ts/src/04-10/app/shared/filter-text/filter-text.component.ts b/public/docs/_examples/style-guide/ts/src/04-10/app/shared/filter-text/filter-text.component.ts new file mode 100644 index 0000000000..fffe4bf0b7 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/04-10/app/shared/filter-text/filter-text.component.ts @@ -0,0 +1,26 @@ +// #docregion +import { Component, EventEmitter, Output } from '@angular/core'; + +@Component({ + 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/public/docs/_examples/style-guide/ts/src/04-10/app/shared/filter-text/filter-text.service.ts b/public/docs/_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/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-10/app/shared/init-caps.pipe.ts b/public/docs/_examples/style-guide/ts/src/04-10/app/shared/init-caps.pipe.ts new file mode 100644 index 0000000000..5019bcb234 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-10/app/shared/shared.module.ts b/public/docs/_examples/style-guide/ts/src/04-10/app/shared/shared.module.ts new file mode 100644 index 0000000000..6160abcc84 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-11/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/04-11/app/app.component.ts new file mode 100644 index 0000000000..693619a982 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-11/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/04-11/app/app.module.ts new file mode 100644 index 0000000000..ef3c156bcf --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-11/app/core/core.module.ts b/public/docs/_examples/style-guide/ts/src/04-11/app/core/core.module.ts new file mode 100644 index 0000000000..4ba5c79a14 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-11/app/core/index.ts b/public/docs/_examples/style-guide/ts/src/04-11/app/core/index.ts new file mode 100644 index 0000000000..098f40c7d5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-11/app/core/logger.service.ts b/public/docs/_examples/style-guide/ts/src/04-11/app/core/logger.service.ts new file mode 100644 index 0000000000..9c7080f07a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-11/app/core/nav/nav.component.css b/public/docs/_examples/style-guide/ts/src/04-11/app/core/nav/nav.component.css new file mode 100644 index 0000000000..c7903fd25c --- /dev/null +++ b/public/docs/_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("https://github.com/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/public/docs/_examples/style-guide/ts/src/04-11/app/core/nav/nav.component.html b/public/docs/_examples/style-guide/ts/src/04-11/app/core/nav/nav.component.html new file mode 100644 index 0000000000..b3c7e441e0 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/04-11/app/core/nav/nav.component.html @@ -0,0 +1,14 @@ + +
    +
    +

    Tour of Heroes

    +
    + +
    +
    diff --git a/public/docs/_examples/style-guide/ts/src/04-11/app/core/nav/nav.component.ts b/public/docs/_examples/style-guide/ts/src/04-11/app/core/nav/nav.component.ts new file mode 100644 index 0000000000..7e21016e2b --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/04-11/app/core/nav/nav.component.ts @@ -0,0 +1,19 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; + +@Component({ + 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/public/docs/_examples/style-guide/ts/src/04-11/app/core/spinner/spinner.component.css b/public/docs/_examples/style-guide/ts/src/04-11/app/core/spinner/spinner.component.css new file mode 100644 index 0000000000..afad0fe8e3 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-11/app/core/spinner/spinner.component.html b/public/docs/_examples/style-guide/ts/src/04-11/app/core/spinner/spinner.component.html new file mode 100644 index 0000000000..a07e3378c0 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/04-11/app/core/spinner/spinner.component.html @@ -0,0 +1,2 @@ + +
    diff --git a/public/docs/_examples/style-guide/ts/src/04-11/app/core/spinner/spinner.component.ts b/public/docs/_examples/style-guide/ts/src/04-11/app/core/spinner/spinner.component.ts new file mode 100644 index 0000000000..de24167a2b --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/04-11/app/core/spinner/spinner.component.ts @@ -0,0 +1,35 @@ +// #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({ + 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/public/docs/_examples/style-guide/ts/src/04-11/app/core/spinner/spinner.service.ts b/public/docs/_examples/style-guide/ts/src/04-11/app/core/spinner/spinner.service.ts new file mode 100644 index 0000000000..85e366c43e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-11/app/heroes/heroes.component.html b/public/docs/_examples/style-guide/ts/src/04-11/app/heroes/heroes.component.html new file mode 100644 index 0000000000..9035a4b8ff --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/04-11/app/heroes/heroes.component.html @@ -0,0 +1,12 @@ + +
    + + + +
      +
    • + {{hero.name}} +
    • +
    + +
    diff --git a/public/docs/_examples/style-guide/ts/src/04-11/app/heroes/heroes.component.ts b/public/docs/_examples/style-guide/ts/src/04-11/app/heroes/heroes.component.ts new file mode 100644 index 0000000000..856baadd74 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/04-11/app/heroes/heroes.component.ts @@ -0,0 +1,32 @@ +import { Component } from '@angular/core'; + +import { LoggerService } from '../core/logger.service'; +import { SpinnerService } from '../core/spinner/spinner.service'; + +@Component({ + 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/public/docs/_examples/style-guide/ts/src/04-12/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/04-12/app/app.component.ts new file mode 100644 index 0000000000..dc85b06e9f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-12/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/04-12/app/app.module.ts new file mode 100644 index 0000000000..7c9fb44182 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-12/app/core/core.module.ts b/public/docs/_examples/style-guide/ts/src/04-12/app/core/core.module.ts new file mode 100644 index 0000000000..069141bf2a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-12/app/core/index.ts b/public/docs/_examples/style-guide/ts/src/04-12/app/core/index.ts new file mode 100644 index 0000000000..8768b77f41 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-12/app/core/logger.service.ts b/public/docs/_examples/style-guide/ts/src/04-12/app/core/logger.service.ts new file mode 100644 index 0000000000..9c7080f07a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-12/app/core/module-import-guard.ts b/public/docs/_examples/style-guide/ts/src/04-12/app/core/module-import-guard.ts new file mode 100644 index 0000000000..5248b15b2e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/04-12/app/core/nav/nav.component.css b/public/docs/_examples/style-guide/ts/src/04-12/app/core/nav/nav.component.css new file mode 100644 index 0000000000..c7903fd25c --- /dev/null +++ b/public/docs/_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("https://github.com/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/public/docs/_examples/style-guide/ts/src/04-12/app/core/nav/nav.component.html b/public/docs/_examples/style-guide/ts/src/04-12/app/core/nav/nav.component.html new file mode 100644 index 0000000000..b3c7e441e0 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/04-12/app/core/nav/nav.component.html @@ -0,0 +1,14 @@ + +
    +
    +

    Tour of Heroes

    +
    + +
    +
    diff --git a/public/docs/_examples/style-guide/ts/src/04-12/app/core/nav/nav.component.ts b/public/docs/_examples/style-guide/ts/src/04-12/app/core/nav/nav.component.ts new file mode 100644 index 0000000000..7e21016e2b --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/04-12/app/core/nav/nav.component.ts @@ -0,0 +1,19 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; + +@Component({ + 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/public/docs/_examples/style-guide/ts/src/04-12/app/heroes/heroes.component.html b/public/docs/_examples/style-guide/ts/src/04-12/app/heroes/heroes.component.html new file mode 100644 index 0000000000..9035a4b8ff --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/04-12/app/heroes/heroes.component.html @@ -0,0 +1,12 @@ + +
    + + + +
      +
    • + {{hero.name}} +
    • +
    + +
    diff --git a/public/docs/_examples/style-guide/ts/src/04-12/app/heroes/heroes.component.ts b/public/docs/_examples/style-guide/ts/src/04-12/app/heroes/heroes.component.ts new file mode 100644 index 0000000000..f84b16c140 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/04-12/app/heroes/heroes.component.ts @@ -0,0 +1,24 @@ +import { Component } from '@angular/core'; + +import { LoggerService } from '../core/logger.service'; + +@Component({ + 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/public/docs/_examples/style-guide/ts/src/05-02/app/app.component.html b/public/docs/_examples/style-guide/ts/src/05-02/app/app.component.html new file mode 100644 index 0000000000..607d068557 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-02/app/app.component.html @@ -0,0 +1,2 @@ + + diff --git a/public/docs/_examples/style-guide/ts/src/05-02/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/05-02/app/app.component.ts new file mode 100644 index 0000000000..7c9f37919f --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-02/app/app.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'sg-app', + templateUrl: './app.component.html' +}) +export class AppComponent { } diff --git a/public/docs/_examples/style-guide/ts/src/05-02/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/05-02/app/app.module.ts new file mode 100644 index 0000000000..1c458e2ca1 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-02/app/heroes/index.ts b/public/docs/_examples/style-guide/ts/src/05-02/app/heroes/index.ts new file mode 100644 index 0000000000..c3da79f741 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-02/app/heroes/index.ts @@ -0,0 +1 @@ +export * from './shared'; diff --git a/public/docs/_examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/hero-button.component.avoid.ts b/public/docs/_examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/hero-button.component.avoid.ts new file mode 100644 index 0000000000..e48d41f721 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/hero-button.component.avoid.ts @@ -0,0 +1,11 @@ +// #docplaster +import { Component } from '@angular/core'; +// #docregion example +/* avoid */ + +@Component({ + selector: 'tohHeroButton', + templateUrl: './hero-button.component.html' +}) +export class HeroButtonComponent {} +// #enddocregion example diff --git a/public/docs/_examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/hero-button.component.html b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/hero-button.component.html @@ -0,0 +1 @@ + diff --git a/public/docs/_examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/hero-button.component.ts b/public/docs/_examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/hero-button.component.ts new file mode 100644 index 0000000000..c90c966f1a --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/hero-button.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +// #docregion example +@Component({ + selector: 'toh-hero-button', + templateUrl: './hero-button.component.html' +}) +export class HeroButtonComponent {} +// #enddocregion example diff --git a/public/docs/_examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/index.ts b/public/docs/_examples/style-guide/ts/src/05-02/app/heroes/shared/hero-button/index.ts new file mode 100644 index 0000000000..6bb67c5670 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-02/app/heroes/shared/index.ts b/public/docs/_examples/style-guide/ts/src/05-02/app/heroes/shared/index.ts new file mode 100644 index 0000000000..2334d49c9a --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-02/app/heroes/shared/index.ts @@ -0,0 +1 @@ +export * from './hero-button'; diff --git a/public/docs/_examples/style-guide/ts/src/05-02/app/index.ts b/public/docs/_examples/style-guide/ts/src/05-02/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-02/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/05-03/app/app.component.avoid.html b/public/docs/_examples/style-guide/ts/src/05-03/app/app.component.avoid.html new file mode 100644 index 0000000000..91bdbe403c --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-03/app/app.component.avoid.html @@ -0,0 +1,4 @@ + + + +
    diff --git a/public/docs/_examples/style-guide/ts/src/05-03/app/app.component.html b/public/docs/_examples/style-guide/ts/src/05-03/app/app.component.html new file mode 100644 index 0000000000..607d068557 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-03/app/app.component.html @@ -0,0 +1,2 @@ + + diff --git a/public/docs/_examples/style-guide/ts/src/05-03/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/05-03/app/app.component.ts new file mode 100644 index 0000000000..7c9f37919f --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-03/app/app.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'sg-app', + templateUrl: './app.component.html' +}) +export class AppComponent { } diff --git a/public/docs/_examples/style-guide/ts/src/05-03/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/05-03/app/app.module.ts new file mode 100644 index 0000000000..1b754e3ee5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-03/app/heroes/index.ts b/public/docs/_examples/style-guide/ts/src/05-03/app/heroes/index.ts new file mode 100644 index 0000000000..c3da79f741 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-03/app/heroes/index.ts @@ -0,0 +1 @@ +export * from './shared'; diff --git a/public/docs/_examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/hero-button.component.avoid.ts b/public/docs/_examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/hero-button.component.avoid.ts new file mode 100644 index 0000000000..74f5c36339 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/hero-button.component.avoid.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; +// #docregion example +/* avoid */ + +@Component({ + selector: '[tohHeroButton]', + templateUrl: './hero-button.component.html' +}) +export class HeroButtonComponent {} +// #enddocregion example diff --git a/public/docs/_examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/hero-button.component.html b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/hero-button.component.html @@ -0,0 +1 @@ + diff --git a/public/docs/_examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/hero-button.component.ts b/public/docs/_examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/hero-button.component.ts new file mode 100644 index 0000000000..c90c966f1a --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/hero-button.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +// #docregion example +@Component({ + selector: 'toh-hero-button', + templateUrl: './hero-button.component.html' +}) +export class HeroButtonComponent {} +// #enddocregion example diff --git a/public/docs/_examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/index.ts b/public/docs/_examples/style-guide/ts/src/05-03/app/heroes/shared/hero-button/index.ts new file mode 100644 index 0000000000..6bb67c5670 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-03/app/heroes/shared/index.ts b/public/docs/_examples/style-guide/ts/src/05-03/app/heroes/shared/index.ts new file mode 100644 index 0000000000..2334d49c9a --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-03/app/heroes/shared/index.ts @@ -0,0 +1 @@ +export * from './hero-button'; diff --git a/public/docs/_examples/style-guide/ts/src/05-03/app/index.ts b/public/docs/_examples/style-guide/ts/src/05-03/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-03/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/05-04/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/05-04/app/app.component.ts new file mode 100644 index 0000000000..0e43893f7f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-04/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/05-04/app/app.module.ts new file mode 100644 index 0000000000..07f97cc6e4 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-04/app/heroes/heroes.component.avoid.ts b/public/docs/_examples/style-guide/ts/src/05-04/app/heroes/heroes.component.avoid.ts new file mode 100644 index 0000000000..0ceb37032d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-04/app/heroes/heroes.component.css b/public/docs/_examples/style-guide/ts/src/05-04/app/heroes/heroes.component.css new file mode 100644 index 0000000000..82f0c1d0ab --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-04/app/heroes/heroes.component.html b/public/docs/_examples/style-guide/ts/src/05-04/app/heroes/heroes.component.html new file mode 100644 index 0000000000..bab05ceb2b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-04/app/heroes/heroes.component.ts b/public/docs/_examples/style-guide/ts/src/05-04/app/heroes/heroes.component.ts new file mode 100644 index 0000000000..c19bc997ae --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-04/app/heroes/heroes.component.ts @@ -0,0 +1,22 @@ +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; + +import { Hero, HeroService } from './shared'; + +// #docregion example +@Component({ + 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/public/docs/_examples/style-guide/ts/src/05-04/app/heroes/index.ts b/public/docs/_examples/style-guide/ts/src/05-04/app/heroes/index.ts new file mode 100644 index 0000000000..a8d7f1d422 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-04/app/heroes/shared/hero.model.ts b/public/docs/_examples/style-guide/ts/src/05-04/app/heroes/shared/hero.model.ts new file mode 100644 index 0000000000..8f7cc205c8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-04/app/heroes/shared/hero.service.ts b/public/docs/_examples/style-guide/ts/src/05-04/app/heroes/shared/hero.service.ts new file mode 100644 index 0000000000..9d388780a6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-04/app/heroes/shared/index.ts b/public/docs/_examples/style-guide/ts/src/05-04/app/heroes/shared/index.ts new file mode 100644 index 0000000000..dbb150d3f8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-04/app/index.ts b/public/docs/_examples/style-guide/ts/src/05-04/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-04/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/05-12/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/05-12/app/app.component.ts new file mode 100644 index 0000000000..dac40205c9 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-12/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/05-12/app/app.module.ts new file mode 100644 index 0000000000..5177b2cc64 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-12/app/heroes/index.ts b/public/docs/_examples/style-guide/ts/src/05-12/app/heroes/index.ts new file mode 100644 index 0000000000..c3da79f741 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-12/app/heroes/index.ts @@ -0,0 +1 @@ +export * from './shared'; diff --git a/public/docs/_examples/style-guide/ts/src/05-12/app/heroes/shared/hero-button/hero-button.component.avoid.ts b/public/docs/_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/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-12/app/heroes/shared/hero-button/hero-button.component.ts b/public/docs/_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/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-12/app/heroes/shared/hero-button/index.ts b/public/docs/_examples/style-guide/ts/src/05-12/app/heroes/shared/hero-button/index.ts new file mode 100644 index 0000000000..6bb67c5670 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-12/app/heroes/shared/index.ts b/public/docs/_examples/style-guide/ts/src/05-12/app/heroes/shared/index.ts new file mode 100644 index 0000000000..2334d49c9a --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-12/app/heroes/shared/index.ts @@ -0,0 +1 @@ +export * from './hero-button'; diff --git a/public/docs/_examples/style-guide/ts/src/05-12/app/index.ts b/public/docs/_examples/style-guide/ts/src/05-12/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-12/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/05-13/app/app.component.avoid.html b/public/docs/_examples/style-guide/ts/src/05-13/app/app.component.avoid.html new file mode 100644 index 0000000000..0a263a6a95 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-13/app/app.component.avoid.html @@ -0,0 +1,5 @@ + + + + + diff --git a/public/docs/_examples/style-guide/ts/src/05-13/app/app.component.html b/public/docs/_examples/style-guide/ts/src/05-13/app/app.component.html new file mode 100644 index 0000000000..3cd94ca772 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-13/app/app.component.html @@ -0,0 +1,6 @@ + + + + + +

    The Great Bombasto

    diff --git a/public/docs/_examples/style-guide/ts/src/05-13/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/05-13/app/app.component.ts new file mode 100644 index 0000000000..7c9f37919f --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-13/app/app.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'sg-app', + templateUrl: './app.component.html' +}) +export class AppComponent { } diff --git a/public/docs/_examples/style-guide/ts/src/05-13/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/05-13/app/app.module.ts new file mode 100644 index 0000000000..7ebe91dbbc --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-13/app/heroes/index.ts b/public/docs/_examples/style-guide/ts/src/05-13/app/heroes/index.ts new file mode 100644 index 0000000000..c3da79f741 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-13/app/heroes/index.ts @@ -0,0 +1 @@ +export * from './shared'; diff --git a/public/docs/_examples/style-guide/ts/src/05-13/app/heroes/shared/hero-button/hero-button.component.avoid.ts b/public/docs/_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/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-13/app/heroes/shared/hero-button/hero-button.component.ts b/public/docs/_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/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-13/app/heroes/shared/hero-button/index.ts b/public/docs/_examples/style-guide/ts/src/05-13/app/heroes/shared/hero-button/index.ts new file mode 100644 index 0000000000..6bb67c5670 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-13/app/heroes/shared/hero-highlight.directive.ts b/public/docs/_examples/style-guide/ts/src/05-13/app/heroes/shared/hero-highlight.directive.ts new file mode 100644 index 0000000000..737af31f4f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-13/app/heroes/shared/index.ts b/public/docs/_examples/style-guide/ts/src/05-13/app/heroes/shared/index.ts new file mode 100644 index 0000000000..565f46cf4f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-13/app/index.ts b/public/docs/_examples/style-guide/ts/src/05-13/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-13/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/05-14/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/05-14/app/app.component.ts new file mode 100644 index 0000000000..8ed6da4c82 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-14/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/05-14/app/app.module.ts new file mode 100644 index 0000000000..0b294573d2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-14/app/index.ts b/public/docs/_examples/style-guide/ts/src/05-14/app/index.ts new file mode 100644 index 0000000000..ebe5c92f03 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-14/app/index.ts @@ -0,0 +1,2 @@ +export * from './shared'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/05-14/app/shared/index.ts b/public/docs/_examples/style-guide/ts/src/05-14/app/shared/index.ts new file mode 100644 index 0000000000..7ff6d415e7 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-14/app/shared/index.ts @@ -0,0 +1 @@ +export * from './toast'; diff --git a/public/docs/_examples/style-guide/ts/src/05-14/app/shared/toast/index.ts b/public/docs/_examples/style-guide/ts/src/05-14/app/shared/toast/index.ts new file mode 100644 index 0000000000..6502de796e --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-14/app/shared/toast/index.ts @@ -0,0 +1 @@ +export * from './toast.component'; diff --git a/public/docs/_examples/style-guide/ts/src/05-14/app/shared/toast/toast.component.avoid.ts b/public/docs/_examples/style-guide/ts/src/05-14/app/shared/toast/toast.component.avoid.ts new file mode 100644 index 0000000000..037ff2c8b5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-14/app/shared/toast/toast.component.ts b/public/docs/_examples/style-guide/ts/src/05-14/app/shared/toast/toast.component.ts new file mode 100644 index 0000000000..d7b8ea4af0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-15/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/05-15/app/app.component.ts new file mode 100644 index 0000000000..91b569b1e7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-15/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/05-15/app/app.module.ts new file mode 100644 index 0000000000..9bd4b8c9a2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-15/app/heroes/hero-list/hero-list.component.avoid.ts b/public/docs/_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/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-15/app/heroes/hero-list/hero-list.component.ts b/public/docs/_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/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-15/app/heroes/hero-list/index.ts b/public/docs/_examples/style-guide/ts/src/05-15/app/heroes/hero-list/index.ts new file mode 100644 index 0000000000..c4bcb3278e --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-15/app/heroes/hero-list/index.ts @@ -0,0 +1 @@ +export * from './hero-list.component'; diff --git a/public/docs/_examples/style-guide/ts/src/05-15/app/heroes/index.ts b/public/docs/_examples/style-guide/ts/src/05-15/app/heroes/index.ts new file mode 100644 index 0000000000..f1112f1c7c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-15/app/heroes/shared/hero.model.ts b/public/docs/_examples/style-guide/ts/src/05-15/app/heroes/shared/hero.model.ts new file mode 100644 index 0000000000..8f7cc205c8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-15/app/heroes/shared/hero.service.ts b/public/docs/_examples/style-guide/ts/src/05-15/app/heroes/shared/hero.service.ts new file mode 100644 index 0000000000..72d07bbed4 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-15/app/heroes/shared/index.ts b/public/docs/_examples/style-guide/ts/src/05-15/app/heroes/shared/index.ts new file mode 100644 index 0000000000..27516fdedd --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-15/app/index.ts b/public/docs/_examples/style-guide/ts/src/05-15/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-15/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/05-16/app/app.component.avoid.html b/public/docs/_examples/style-guide/ts/src/05-16/app/app.component.avoid.html new file mode 100644 index 0000000000..2c0cea58e2 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-16/app/app.component.avoid.html @@ -0,0 +1,4 @@ + + + + diff --git a/public/docs/_examples/style-guide/ts/src/05-16/app/app.component.html b/public/docs/_examples/style-guide/ts/src/05-16/app/app.component.html new file mode 100644 index 0000000000..4883a6940a --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-16/app/app.component.html @@ -0,0 +1,2 @@ + + diff --git a/public/docs/_examples/style-guide/ts/src/05-16/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/05-16/app/app.component.ts new file mode 100644 index 0000000000..7c9f37919f --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-16/app/app.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'sg-app', + templateUrl: './app.component.html' +}) +export class AppComponent { } diff --git a/public/docs/_examples/style-guide/ts/src/05-16/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/05-16/app/app.module.ts new file mode 100644 index 0000000000..c3fb36f8ac --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-16/app/heroes/hero.component.avoid.ts b/public/docs/_examples/style-guide/ts/src/05-16/app/heroes/hero.component.avoid.ts new file mode 100644 index 0000000000..823aa2e1c7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-16/app/heroes/hero.component.ts b/public/docs/_examples/style-guide/ts/src/05-16/app/heroes/hero.component.ts new file mode 100644 index 0000000000..bbd4a4b5f3 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-16/app/heroes/index.ts b/public/docs/_examples/style-guide/ts/src/05-16/app/heroes/index.ts new file mode 100644 index 0000000000..084f36d703 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-16/app/heroes/index.ts @@ -0,0 +1 @@ +export * from './hero.component'; diff --git a/public/docs/_examples/style-guide/ts/src/05-16/app/index.ts b/public/docs/_examples/style-guide/ts/src/05-16/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-16/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/05-17/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/05-17/app/app.component.ts new file mode 100644 index 0000000000..86728b8b80 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-17/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/05-17/app/app.module.ts new file mode 100644 index 0000000000..e850d80ae3 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-17/app/heroes/hero-list/hero-list.component.avoid.ts b/public/docs/_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/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-17/app/heroes/hero-list/hero-list.component.ts b/public/docs/_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/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-17/app/heroes/hero-list/index.ts b/public/docs/_examples/style-guide/ts/src/05-17/app/heroes/hero-list/index.ts new file mode 100644 index 0000000000..c4bcb3278e --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-17/app/heroes/hero-list/index.ts @@ -0,0 +1 @@ +export * from './hero-list.component'; diff --git a/public/docs/_examples/style-guide/ts/src/05-17/app/heroes/hero/hero.component.ts b/public/docs/_examples/style-guide/ts/src/05-17/app/heroes/hero/hero.component.ts new file mode 100644 index 0000000000..334f836a7d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-17/app/heroes/hero/index.ts b/public/docs/_examples/style-guide/ts/src/05-17/app/heroes/hero/index.ts new file mode 100644 index 0000000000..084f36d703 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-17/app/heroes/hero/index.ts @@ -0,0 +1 @@ +export * from './hero.component'; diff --git a/public/docs/_examples/style-guide/ts/src/05-17/app/heroes/index.ts b/public/docs/_examples/style-guide/ts/src/05-17/app/heroes/index.ts new file mode 100644 index 0000000000..dcf3e79bd3 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-17/app/heroes/shared/hero.model.ts b/public/docs/_examples/style-guide/ts/src/05-17/app/heroes/shared/hero.model.ts new file mode 100644 index 0000000000..8f7cc205c8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/05-17/app/heroes/shared/index.ts b/public/docs/_examples/style-guide/ts/src/05-17/app/heroes/shared/index.ts new file mode 100644 index 0000000000..0dceb684c4 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-17/app/heroes/shared/index.ts @@ -0,0 +1 @@ +export * from './hero.model'; diff --git a/public/docs/_examples/style-guide/ts/src/05-17/app/index.ts b/public/docs/_examples/style-guide/ts/src/05-17/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/05-17/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/06-01/app/app.component.html b/public/docs/_examples/style-guide/ts/src/06-01/app/app.component.html new file mode 100644 index 0000000000..2ccf87d0f5 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/06-01/app/app.component.html @@ -0,0 +1,2 @@ + +
    Bombasta
    diff --git a/public/docs/_examples/style-guide/ts/src/06-01/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/06-01/app/app.component.ts new file mode 100644 index 0000000000..7c9f37919f --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/06-01/app/app.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'sg-app', + templateUrl: './app.component.html' +}) +export class AppComponent { } diff --git a/public/docs/_examples/style-guide/ts/src/06-01/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/06-01/app/app.module.ts new file mode 100644 index 0000000000..318cd306d7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/06-01/app/index.ts b/public/docs/_examples/style-guide/ts/src/06-01/app/index.ts new file mode 100644 index 0000000000..ebe5c92f03 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/06-01/app/index.ts @@ -0,0 +1,2 @@ +export * from './shared'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/06-01/app/shared/highlight.directive.ts b/public/docs/_examples/style-guide/ts/src/06-01/app/shared/highlight.directive.ts new file mode 100644 index 0000000000..991a6c5d25 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/06-01/app/shared/index.ts b/public/docs/_examples/style-guide/ts/src/06-01/app/shared/index.ts new file mode 100644 index 0000000000..105a035680 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/06-01/app/shared/index.ts @@ -0,0 +1 @@ +export * from './highlight.directive'; diff --git a/public/docs/_examples/style-guide/ts/src/06-03/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/06-03/app/app.component.ts new file mode 100644 index 0000000000..0d0a7d107b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/06-03/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/06-03/app/app.module.ts new file mode 100644 index 0000000000..b19f3fdc00 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/06-03/app/index.ts b/public/docs/_examples/style-guide/ts/src/06-03/app/index.ts new file mode 100644 index 0000000000..ebe5c92f03 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/06-03/app/index.ts @@ -0,0 +1,2 @@ +export * from './shared'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/06-03/app/shared/index.ts b/public/docs/_examples/style-guide/ts/src/06-03/app/shared/index.ts new file mode 100644 index 0000000000..ba25e4c458 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/06-03/app/shared/validator.directive.ts b/public/docs/_examples/style-guide/ts/src/06-03/app/shared/validator.directive.ts new file mode 100644 index 0000000000..d9e32c017f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/06-03/app/shared/validator2.directive.ts b/public/docs/_examples/style-guide/ts/src/06-03/app/shared/validator2.directive.ts new file mode 100644 index 0000000000..7936a83cb1 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/07-01/app/app.component.html b/public/docs/_examples/style-guide/ts/src/07-01/app/app.component.html new file mode 100644 index 0000000000..3c05329f3f --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/07-01/app/app.component.html @@ -0,0 +1,5 @@ +
      +
    • + {{hero.name}} +
    • +
    diff --git a/public/docs/_examples/style-guide/ts/src/07-01/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/07-01/app/app.component.ts new file mode 100644 index 0000000000..354b0ec303 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/07-01/app/app.component.ts @@ -0,0 +1,18 @@ +import { Component, OnInit } from '@angular/core'; + +import { Hero, HeroService } from './heroes'; + +@Component({ + 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/public/docs/_examples/style-guide/ts/src/07-01/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/07-01/app/app.module.ts new file mode 100644 index 0000000000..0077500dea --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/07-01/app/heroes/index.ts b/public/docs/_examples/style-guide/ts/src/07-01/app/heroes/index.ts new file mode 100644 index 0000000000..c3da79f741 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/07-01/app/heroes/index.ts @@ -0,0 +1 @@ +export * from './shared'; diff --git a/public/docs/_examples/style-guide/ts/src/07-01/app/heroes/shared/hero.model.ts b/public/docs/_examples/style-guide/ts/src/07-01/app/heroes/shared/hero.model.ts new file mode 100644 index 0000000000..8f7cc205c8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/07-01/app/heroes/shared/hero.service.ts b/public/docs/_examples/style-guide/ts/src/07-01/app/heroes/shared/hero.service.ts new file mode 100644 index 0000000000..b5aba5d00c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/07-01/app/heroes/shared/index.ts b/public/docs/_examples/style-guide/ts/src/07-01/app/heroes/shared/index.ts new file mode 100644 index 0000000000..dbb150d3f8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/07-01/app/index.ts b/public/docs/_examples/style-guide/ts/src/07-01/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/07-01/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/07-03/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/07-03/app/app.component.ts new file mode 100644 index 0000000000..f4d25e1ab6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/07-03/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/07-03/app/app.module.ts new file mode 100644 index 0000000000..8ba06d22be --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/07-03/app/heroes/hero-list/hero-list.component.ts b/public/docs/_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/public/docs/_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/public/docs/_examples/style-guide/ts/src/07-03/app/heroes/hero-list/index.ts b/public/docs/_examples/style-guide/ts/src/07-03/app/heroes/hero-list/index.ts new file mode 100644 index 0000000000..c4bcb3278e --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/07-03/app/heroes/hero-list/index.ts @@ -0,0 +1 @@ +export * from './hero-list.component'; diff --git a/public/docs/_examples/style-guide/ts/src/07-03/app/heroes/index.ts b/public/docs/_examples/style-guide/ts/src/07-03/app/heroes/index.ts new file mode 100644 index 0000000000..f1112f1c7c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/07-03/app/heroes/shared/hero.model.ts b/public/docs/_examples/style-guide/ts/src/07-03/app/heroes/shared/hero.model.ts new file mode 100644 index 0000000000..8f7cc205c8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/07-03/app/heroes/shared/hero.service.ts b/public/docs/_examples/style-guide/ts/src/07-03/app/heroes/shared/hero.service.ts new file mode 100644 index 0000000000..72d07bbed4 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/07-03/app/heroes/shared/index.ts b/public/docs/_examples/style-guide/ts/src/07-03/app/heroes/shared/index.ts new file mode 100644 index 0000000000..27516fdedd --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/07-03/app/index.ts b/public/docs/_examples/style-guide/ts/src/07-03/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/07-03/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/07-04/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/07-04/app/app.component.ts new file mode 100644 index 0000000000..b0bc9677fe --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/07-04/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/07-04/app/app.module.ts new file mode 100644 index 0000000000..71c515c9c9 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/07-04/app/heroes/index.ts b/public/docs/_examples/style-guide/ts/src/07-04/app/heroes/index.ts new file mode 100644 index 0000000000..c3da79f741 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/07-04/app/heroes/index.ts @@ -0,0 +1 @@ +export * from './shared'; diff --git a/public/docs/_examples/style-guide/ts/src/07-04/app/heroes/shared/hero-arena.service.avoid.ts b/public/docs/_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/public/docs/_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/public/docs/_examples/style-guide/ts/src/07-04/app/heroes/shared/hero-arena.service.ts b/public/docs/_examples/style-guide/ts/src/07-04/app/heroes/shared/hero-arena.service.ts new file mode 100644 index 0000000000..42bc51f9e9 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/07-04/app/heroes/shared/hero.model.ts b/public/docs/_examples/style-guide/ts/src/07-04/app/heroes/shared/hero.model.ts new file mode 100644 index 0000000000..8f7cc205c8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/07-04/app/heroes/shared/hero.service.ts b/public/docs/_examples/style-guide/ts/src/07-04/app/heroes/shared/hero.service.ts new file mode 100644 index 0000000000..72d07bbed4 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/07-04/app/heroes/shared/index.ts b/public/docs/_examples/style-guide/ts/src/07-04/app/heroes/shared/index.ts new file mode 100644 index 0000000000..e8ba54b540 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/07-04/app/index.ts b/public/docs/_examples/style-guide/ts/src/07-04/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/07-04/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/09-01/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/09-01/app/app.component.ts new file mode 100644 index 0000000000..ebc904f722 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/09-01/app/app.module.ts b/public/docs/_examples/style-guide/ts/src/09-01/app/app.module.ts new file mode 100644 index 0000000000..5872e801d6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/09-01/app/heroes/index.ts b/public/docs/_examples/style-guide/ts/src/09-01/app/heroes/index.ts new file mode 100644 index 0000000000..c3da79f741 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/09-01/app/heroes/index.ts @@ -0,0 +1 @@ +export * from './shared'; diff --git a/public/docs/_examples/style-guide/ts/src/09-01/app/heroes/shared/hero-button/hero-button.component.avoid.ts b/public/docs/_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/public/docs/_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/public/docs/_examples/style-guide/ts/src/09-01/app/heroes/shared/hero-button/index.ts b/public/docs/_examples/style-guide/ts/src/09-01/app/heroes/shared/hero-button/index.ts new file mode 100644 index 0000000000..6bb67c5670 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/09-01/app/heroes/shared/index.ts b/public/docs/_examples/style-guide/ts/src/09-01/app/heroes/shared/index.ts new file mode 100644 index 0000000000..2334d49c9a --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/09-01/app/heroes/shared/index.ts @@ -0,0 +1 @@ +export * from './hero-button'; diff --git a/public/docs/_examples/style-guide/ts/src/09-01/app/index.ts b/public/docs/_examples/style-guide/ts/src/09-01/app/index.ts new file mode 100644 index 0000000000..fe8300f1dd --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/09-01/app/index.ts @@ -0,0 +1,2 @@ +export * from './heroes'; +export * from './app.component'; diff --git a/public/docs/_examples/style-guide/ts/src/app/app.component.html b/public/docs/_examples/style-guide/ts/src/app/app.component.html new file mode 100644 index 0000000000..0680b43f9c --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/app/app.component.html @@ -0,0 +1 @@ + diff --git a/public/docs/_examples/style-guide/ts/src/app/app.component.ts b/public/docs/_examples/style-guide/ts/src/app/app.component.ts new file mode 100644 index 0000000000..7556beb1ff --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/app/app.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + templateUrl: './app.component.html' +}) +export class AppComponent { } diff --git a/public/docs/_examples/style-guide/ts/src/app/app.routes.ts b/public/docs/_examples/style-guide/ts/src/app/app.routes.ts new file mode 100644 index 0000000000..bce6b4df06 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/app/hero-data.ts b/public/docs/_examples/style-guide/ts/src/app/hero-data.ts new file mode 100644 index 0000000000..f3e6feb91c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/index.html b/public/docs/_examples/style-guide/ts/src/index.html new file mode 100644 index 0000000000..188c2c26a1 --- /dev/null +++ b/public/docs/_examples/style-guide/ts/src/index.html @@ -0,0 +1,30 @@ + + + + + + + Style Guide Sample + + + + + + + + + + + + + + + + + loading... + + + + diff --git a/public/docs/_examples/style-guide/ts/src/main.ts b/public/docs/_examples/style-guide/ts/src/main.ts new file mode 100644 index 0000000000..5b4c98ba69 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/style-guide/ts/src/systemjs.custom.js b/public/docs/_examples/style-guide/ts/src/systemjs.custom.js new file mode 100644 index 0000000000..3424b2b62d --- /dev/null +++ b/public/docs/_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', meta: { './*.js': { loader: 'systemjs-angular-loader.js' }} }; + }); + + var config = { + packages: packages + } + + System.config(config); + +})(this); diff --git a/public/docs/_examples/styleguide/e2e-spec.js b/public/docs/_examples/styleguide/e2e-spec.js deleted file mode 100644 index 14edeb17ef..0000000000 --- a/public/docs/_examples/styleguide/e2e-spec.js +++ /dev/null @@ -1,27 +0,0 @@ -/*global browser, element, by */ -describe('Getting Started E2E Tests', function() { - - // #docregion shared - var expectedMsg = 'My First Angular 2 App'; - - // tests shared across languages - function sharedTests(basePath) { - beforeEach(function () { - browser.get(basePath + 'index.html'); - }); - - it('should display: '+ expectedMsg, function() { - expect(element(by.id('output')).getText()).toEqual(expectedMsg); - }); - } - // #enddocregion - - describe('Getting Started in JavaScript', function() { - sharedTests('gettingstarted/js/'); - }); - - describe('Getting Started in TypeScript', function() { - sharedTests('gettingstarted/ts/'); - }); - -}); diff --git a/public/docs/_examples/styleguide/e2e-spec.ts b/public/docs/_examples/styleguide/e2e-spec.ts new file mode 100644 index 0000000000..af10d2b71d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/styleguide/js/app.js b/public/docs/_examples/styleguide/js/app.js deleted file mode 100644 index a590cd7ed7..0000000000 --- a/public/docs/_examples/styleguide/js/app.js +++ /dev/null @@ -1,46 +0,0 @@ -(function() { -// #docregion -// #docregion class-w-annotations -var AppComponent = ng - // #docregion component - .Component({ - selector: 'my-app' - }) - // #enddocregion - // #docregion view - .View({ - template: '

    My First Angular 2 App

    ' - }) - // #enddocregion - // #docregion class - .Class({ - constructor: function () { } - }); - // #enddocregion -// #enddocregion - -// #docregion bootstrap -document.addEventListener('DOMContentLoaded', function() { - ng.bootstrap(AppComponent); -}); -// #enddocregion -// #enddocregion - -})(); - -/* Non DSL Approach */ -(function() { - -// #docregion no-dsl -function AppComponent () {} - -AppComponent.annotations = [ - new ng.ComponentAnnotation({ - selector: 'my-app' - }), - new ng.ViewAnnotation({ - template: '

    My First Angular 2 App

    ' - }) -]; -// #enddocregion -})(); diff --git a/public/docs/_examples/styleguide/js/example-config.json b/public/docs/_examples/styleguide/js/example-config.json new file mode 100644 index 0000000000..81f31aaf0d --- /dev/null +++ b/public/docs/_examples/styleguide/js/example-config.json @@ -0,0 +1,3 @@ +{ + "build": "build:babel" +} diff --git a/public/docs/_examples/styleguide/js/index.html b/public/docs/_examples/styleguide/js/index.html deleted file mode 100644 index a4e52d76a2..0000000000 --- a/public/docs/_examples/styleguide/js/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - foo2 - - diff --git a/public/docs/_examples/styleguide/js/src/app.js b/public/docs/_examples/styleguide/js/src/app.js new file mode 100644 index 0000000000..d38669cd78 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/styleguide/js/src/index.html b/public/docs/_examples/styleguide/js/src/index.html new file mode 100644 index 0000000000..ac2d34ba5d --- /dev/null +++ b/public/docs/_examples/styleguide/js/src/index.html @@ -0,0 +1,27 @@ + + + + Documentation Style + + + + + + + + + + + + + + + + + + + + foo2 + + + diff --git a/public/docs/_examples/styleguide/package.1.json b/public/docs/_examples/styleguide/package.1.json new file mode 100644 index 0000000000..481f99fb12 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/styleguide/package.json b/public/docs/_examples/styleguide/package.json deleted file mode 100644 index a1cf0d7df3..0000000000 --- a/public/docs/_examples/styleguide/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "getting-started", - "version": "0.0.1", - "dependencies": { - "angular2": "2.0.0-alpha.37", - "systemjs": "^0.18.17", - "traceur": "0.0.91" - }, - "scripts": { - "postinstall": "cd src && tsd reinstall -r -o && cd ..", - "tsc": "tsc -p src -w", - "start": "live-server --open=src" - } -} \ No newline at end of file diff --git a/public/docs/_examples/styleguide/ts/app.js b/public/docs/_examples/styleguide/ts/app.js deleted file mode 100644 index 8a05c64c63..0000000000 --- a/public/docs/_examples/styleguide/ts/app.js +++ /dev/null @@ -1,37 +0,0 @@ -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") return Reflect.decorate(decorators, target, key, desc); - switch (arguments.length) { - case 2: return decorators.reduceRight(function(o, d) { return (d && d(o)) || o; }, target); - case 3: return decorators.reduceRight(function(o, d) { return (d && d(target, key)), void 0; }, void 0); - case 4: return decorators.reduceRight(function(o, d) { return (d && d(target, key, o)) || o; }, desc); - } -}; -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -// #docregion -// #docregion import -var angular2_1 = require('angular2/angular2'); -// #enddocregion -// #docregion class-w-annotations -var AppComponent = (function () { - function AppComponent() { - } - AppComponent = __decorate([ - angular2_1.Component({ - selector: 'my-app' - }), - angular2_1.View({ - template: '

    My First Angular 2 App

    ' - }), - __metadata('design:paramtypes', []) - ], AppComponent); - return AppComponent; -})(); -// #enddocregion -// #enddocregion -// #docregion bootstrap -angular2_1.bootstrap(AppComponent); -// #enddocregion -// #enddocregion -//# sourceMappingURL=app.js.map \ No newline at end of file diff --git a/public/docs/_examples/styleguide/ts/app.ts b/public/docs/_examples/styleguide/ts/app.ts deleted file mode 100644 index 1e6cd84e77..0000000000 --- a/public/docs/_examples/styleguide/ts/app.ts +++ /dev/null @@ -1,21 +0,0 @@ -// #docregion -// #docregion import -import {Component, View, bootstrap} from 'angular2/angular2'; -// #enddocregion - -// #docregion class-w-annotations -@Component({ - selector: 'my-app' -}) -@View({ - template: '

    My First Angular 2 App

    ' -}) -// #docregion class -class AppComponent { } -// #enddocregion -// #enddocregion - -// #docregion bootstrap -bootstrap(AppComponent); -// #enddocregion -// #enddocregion \ No newline at end of file diff --git a/public/docs/_examples/styleguide/ts/dummy.spec.js b/public/docs/_examples/styleguide/ts/dummy.spec.js deleted file mode 100644 index 3e9bcf8842..0000000000 --- a/public/docs/_examples/styleguide/ts/dummy.spec.js +++ /dev/null @@ -1,9 +0,0 @@ -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/public/docs/_examples/styleguide/ts/example-config.json b/public/docs/_examples/styleguide/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/styleguide/ts/index.html b/public/docs/_examples/styleguide/ts/index.html deleted file mode 100644 index 7a5acc493d..0000000000 --- a/public/docs/_examples/styleguide/ts/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - diff --git a/public/docs/_examples/styleguide/ts/src/app/app.component.ts b/public/docs/_examples/styleguide/ts/src/app/app.component.ts new file mode 100644 index 0000000000..8b71f6ddc4 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/styleguide/ts/src/app/app.module.ts b/public/docs/_examples/styleguide/ts/src/app/app.module.ts new file mode 100644 index 0000000000..0a9ee6adf7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/styleguide/ts/src/index.html b/public/docs/_examples/styleguide/ts/src/index.html new file mode 100644 index 0000000000..5cb8919509 --- /dev/null +++ b/public/docs/_examples/styleguide/ts/src/index.html @@ -0,0 +1,25 @@ + + + + + Documentation Style + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/styleguide/ts/src/main.ts b/public/docs/_examples/styleguide/ts/src/main.ts new file mode 100644 index 0000000000..6b6532d428 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/template-syntax/e2e-spec.ts b/public/docs/_examples/template-syntax/e2e-spec.ts new file mode 100644 index 0000000000..71f1c58165 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/template-syntax/ts/.gitignore b/public/docs/_examples/template-syntax/ts/.gitignore deleted file mode 100644 index 6724ce3596..0000000000 --- a/public/docs/_examples/template-syntax/ts/.gitignore +++ /dev/null @@ -1 +0,0 @@ -src/**/*.js \ No newline at end of file diff --git a/public/docs/_examples/template-syntax/ts/example-config.json b/public/docs/_examples/template-syntax/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/template-syntax/ts/images/hero.png b/public/docs/_examples/template-syntax/ts/images/hero.png deleted file mode 100644 index b60c3ecc30..0000000000 Binary files a/public/docs/_examples/template-syntax/ts/images/hero.png and /dev/null differ diff --git a/public/docs/_examples/template-syntax/ts/images/ng-logo.png b/public/docs/_examples/template-syntax/ts/images/ng-logo.png deleted file mode 100644 index 7929242740..0000000000 Binary files a/public/docs/_examples/template-syntax/ts/images/ng-logo.png and /dev/null differ diff --git a/public/docs/_examples/template-syntax/ts/images/villain.png b/public/docs/_examples/template-syntax/ts/images/villain.png deleted file mode 100644 index d1e71cf00b..0000000000 Binary files a/public/docs/_examples/template-syntax/ts/images/villain.png and /dev/null differ diff --git a/public/docs/_examples/template-syntax/ts/package.json b/public/docs/_examples/template-syntax/ts/package.json deleted file mode 100644 index 245a10d30a..0000000000 --- a/public/docs/_examples/template-syntax/ts/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "angular2-template-syntax", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "tsc": "tsc -p src -w", - "start": "live-server --open=src" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "angular2": "2.0.0-alpha.44", - "systemjs": "0.19.2" - }, - "devDependencies": { - "live-server": "^0.8.1", - "typescript": "^1.6.2" - } -} diff --git a/public/docs/_examples/template-syntax/ts/plnkr.json b/public/docs/_examples/template-syntax/ts/plnkr.json new file mode 100644 index 0000000000..099616ad7c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/template-syntax/ts/src/app/app.component.css b/public/docs/_examples/template-syntax/ts/src/app/app.component.css new file mode 100644 index 0000000000..23f9667623 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/template-syntax/ts/src/app/app.component.html b/public/docs/_examples/template-syntax/ts/src/app/app.component.html new file mode 100644 index 0000000000..760fa3a367 --- /dev/null +++ b/public/docs/_examples/template-syntax/ts/src/app/app.component.html @@ -0,0 +1,820 @@ + + +

    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: + + + + {{name}} +
    +

    + + + + +

    + + +
    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 is {{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 or smaller. +
    + + +

    [ngStyle] binding to currentStyles - CSS property names

    +

    currentStyles is {{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}}
    + + + + +Add {{currentHero.name}} with template + + +
    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/public/docs/_examples/template-syntax/ts/src/app/app.component.ts b/public/docs/_examples/template-syntax/ts/src/app/app.component.ts new file mode 100644 index 0000000000..f0fa2a2d23 --- /dev/null +++ b/public/docs/_examples/template-syntax/ts/src/app/app.component.ts @@ -0,0 +1,183 @@ +/* tslint:disable:forin member-ordering */ +// #docplaster + +import { AfterViewInit, Component, ElementRef, OnInit, QueryList, ViewChildren } from '@angular/core'; + +import { Hero } from './hero'; + +export enum Color {Red, Green, Blue}; + +/** + * Giant grab bag of stuff to drive the chapter + */ +@Component({ + 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++); + trackChanges(this.heroesWithTrackBy, () => this.heroesWithTrackByCount++); + } + + @ViewChildren('noTrackBy') heroesNoTrackBy: QueryList; + @ViewChildren('withTrackBy') heroesWithTrackBy: QueryList; + + actionName = 'Go for it'; + badCurly = 'bad curly'; + classes = 'special'; + help = ''; + + alert(msg?: string) { window.alert(msg); } + 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'; + + getVal(): number { return 2; } + + name: string = Hero.heroes[0].name; + 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'; + // 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'; + + iconUrl = 'images/ng-logo.png'; + isActive = false; + isSpecial = true; + isUnchanged = true; + + get nullHero(): Hero { return null; } + + 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); + if (event) { event.stopPropagation(); } + } + + 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; + } + + 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() { + // CSS styles: set per current state of component properties + this.currentStyles = { + '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 +} + +// 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/public/docs/_examples/template-syntax/ts/src/app/app.html b/public/docs/_examples/template-syntax/ts/src/app/app.html deleted file mode 100644 index 3af210b88a..0000000000 --- a/public/docs/_examples/template-syntax/ts/src/app/app.html +++ /dev/null @@ -1,376 +0,0 @@ - -

    My First Angular Application

    -

    - {{title}} - -

    -

    My current hero is {{currentHero.firstName}}

    - -
    - Hey there, {{currentHero.firstName}} -
    - -

    -

    The title is {{title}}

    -

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

    -

    The element id is {{f.id}}

    - -
    - - - -

    The title is {{title}}

    -

    - - - - -
    -
    Mental Model
    - -
    - - - -
    - -
    - -
    - -
    - - - - -
    -

    -
    - - - - - - - -
    - - - - -
    - - - - - -
    - - - - - - - - - - - - -
    Click me -
    Click me too!
    -
    - - - -

    keyup loop-back component

    - - - - - - - -
    -
    - -
    - - -
    - - - - - - - - - - - - - - - - - - -
    OneTwo
    Five-Six
    -
    - - - - - -
    -
    The class is special
    -
    The class is special
    - -
    The class binding is special
    - -
    This one is not so special
    - -
    This class binding is special too
    - - - - - -
    - - - - - - - - - -
    -
    {{currentHero?.firstName}}
    - - - - - - -
    - -
    - -
    - -
    - -
    - -
    {{currentHero.fullName}}
    - - - - - -
    -
    This div is special
    - -
    This div is saveable and special
    -
    - After setClasses(), the classes are "{{classDiv.className}}" -
    - - - - - -
    -
    This div is larger
    - -
    This div is italic, normal weight, and larger
    -
    - After setStyles(), the styles are "{{getStyles(classDiv)}}" -
    - - - - - -
    -
    NgIf Binding
    - -
    Add {{currentHero.firstName}}
    -
    Remove {{nullHero.firstName}}
    - -
    Hero Detail removed from DOM because isActive is false
    - - - - - - - - - - -
    Hero Detail removed from DOM (via template) because isActive is false
    - - - -
    Show with class
    -
    Hide with class
    - - -
    Show with style
    -
    Hide with style
    - - - - - -
    -
    NgSwitch Binding
    -
    - Eenie - Meanie - Miney - Moe - ??? -
    -
    You picked - - - - - - - -
    - - - - -
    -
    NgFor Binding
    -
    - -
    - -
    {{hero.fullName}}
    -
    -
    - -
    - - -
    {{i+1}} - {{hero.fullName}}
    -
    -
    - -
    - - -
    -
    - -
    - - -
    -
    - -
    - - -
    - - - - - -
    - -
    {{ title | uppercase }}
    - - -
    {{ title | uppercase | lowercase }}
    - - -
    Birthdate: {{currentHero?.birthdate | date:'longDate'}}
    - -
    Birthdate: {{(currentHero?.birthdate | date:'longDate') | uppercase}}
    - - -
    - - {{product.price | currency:'USD':true}} -
    - - - -
    -
    The title is {{ title }}
    - -
    The current hero's name is {{currentHero?.firstName}}
    - -
    The current hero's name is {{currentHero.firstName}}
    - - - -
    The null hero's name is {{nullHero?.firstName}}
    - - - - -
    The null hero's name is {{nullHero?.firstName}}
    - - - - - -
    -

    Template Driven Form

    -
    -
    - - -
    - -
    -
    - - - - - - - - - - - - diff --git a/public/docs/_examples/template-syntax/ts/src/app/app.module.1.ts b/public/docs/_examples/template-syntax/ts/src/app/app.module.1.ts new file mode 100644 index 0000000000..8ea0d3d207 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/template-syntax/ts/src/app/app.module.ts b/public/docs/_examples/template-syntax/ts/src/app/app.module.ts new file mode 100644 index 0000000000..5c2fbed6f1 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/template-syntax/ts/src/app/app.ts b/public/docs/_examples/template-syntax/ts/src/app/app.ts deleted file mode 100644 index fd4e4d238e..0000000000 --- a/public/docs/_examples/template-syntax/ts/src/app/app.ts +++ /dev/null @@ -1,290 +0,0 @@ -// NOT EVERYTHING IS NEEDED BY TEMPLATE-SYNTAX CHAPTER -// Much left-over from support for "User Input" chapter such as -// ClickMeComponent, -// KeyUpComponent, KeyUpComponentV2, KeyUpComponentV3, -// LittleTour, LoopbackComponent, -// TODO: purge extraneous material - - -/// #docplaster - -import {bootstrap, Component, CORE_DIRECTIVES, - Input, Output, - Directive, - ElementRef, EventEmitter, - FORM_DIRECTIVES -} from 'angular2/angular2'; - -class Hero { - public id:number - - constructor( - public firstName:string, - public lastName?:string, - public birthdate?:Date, - public url?:string, - public rate:number = 100) { - this.id = Hero.nextId++; - } - - get fullName() {return `${this.firstName} ${this.lastName}`;} - - static nextId = 1; - - static MockHeroes = [ - new Hero( - 'Hercules', - 'Son of Zeus', - new Date(1970, 1, 25), - 'http://www.imdb.com/title/tt0065832/', - 325), - - new Hero('eenie', 'toe'), - new Hero('Meanie', 'Toe'), - new Hero('Miny', 'Toe'), - new Hero('Moe', 'Toe') - ]; -} - -// for fun; not used (yet) -@Directive({selector: 'select'}) -class DecoratorDirective { - constructor(el: ElementRef){ - console.log(el) - } -} - -@Component({ - selector: 'hero-detail', - /* - inputs: ['hero'], - outputs: ['deleted'], - */ - template: ` -
    -
    Hero Detail: {{hero?.fullName}}
    -
    First: {{hero?.firstName}}
    -
    Last: {{hero?.lastName}}
    -
    Birthdate: {{hero?.birthdate | date:'longDate'}}
    - -
    Rate/hr: {{hero?.rate | currency:'EUR'}}
    - -
    - ` -}) -class HeroDetailComponent { - - @Input() - hero: Hero; - - @Output() - deleted = new EventEmitter(); - - onDelete() { - this.deleted.next(this.hero); - } -} - -@Component({ - selector: 'little-hero', - template: '
    {{hero?.fullName}}
    ' -}) -class LittleHeroComponent { - @Input() - hero: Hero; -} - -@Component({ - selector: 'click-me', - template: '' -}) -class ClickMeComponent { - onClickMe(){ - alert('You are my hero!') - } -} - -@Component({ - selector: 'loop-back', - template: '

    {{box.value}}

    ' -}) -class LoopbackComponent { -} - -@Component({ - selector: 'key-up', - template: ` -

    Give me some keys!

    -
    -
    {{values}}
    - ` -}) -class KeyUpComponent { - values=''; - onKey(event) { - this.values += event.target.value + ' | '; - } -} - -@Component({ - selector: 'key-up2', - template: ` -

    Give me some more keys!

    -
    -
    {{values}}
    - ` -}) -class KeyUpComponentV2 { - values=''; - onKey(value) { - this.values += value + ' | '; - } -} - -@Component({ - selector: 'key-up3', - template: ` -

    Type away! Press [enter] when done.

    -
    -
    {{values}}
    - ` -}) -class KeyUpComponentV3 { - values=''; -} - -@Component({ - selector: 'little-tour', - template: ` -

    Little Tour of Heroes

    - - -
    • {{hero}}
    - `, - directives: [CORE_DIRECTIVES] -}) -class LittleTour { - heroes=['Windstorm', 'Bombasto', 'Magneta', 'Tornado']; - - addHero(newHero) { - if (newHero.value) { - this.heroes.push(newHero.value); - newHero.value = null; // clear the newHero textbox - } - } -} - -bootstrap(LittleTour); - -@Component({ - selector: 'my-app', - templateUrl: 'app/app.html', - directives: [ - CORE_DIRECTIVES, FORM_DIRECTIVES, - ClickMeComponent, - KeyUpComponent, KeyUpComponentV2, KeyUpComponentV3, - LittleTour, LoopbackComponent, - HeroDetailComponent, LittleHeroComponent - ] -}) -class AppComponent { - - actionName = 'Go for it'; - callFax(value:string) {alert(`Faxing ${value} ...`)} - callPhone(value:string) {alert(`Calling ${value} ...`)} - canSave = true; - - currentHero = Hero.MockHeroes[0]; - - getStyles(el){ - let styles = window.getComputedStyle(el); - let showStyles = {}; - for (var p in this.setStyles()){ - showStyles[p] = styles[p]; - } - return JSON.stringify(showStyles); - } - - getVal() {return this.val}; - - heroes = Hero.MockHeroes; - - //heroImageUrl = 'http://www.wpclipart.com/cartoon/people/hero/hero_silhoutte_T.png'; - heroImageUrl = '../../images/hero.png'; - - //iconUrl = 'https://angular.io/resources/images/logos/standard/shield-large.png'; - iconUrl = '../../images/ng-logo.png'; - isActive = false; - isSpecial = true; - isUnchanged = true; - - nullHero:Hero = null; // or undefined - - onCancel(event){ - let evtMsg = event ? ' Event target is '+ event.target.innerHTML : ''; - alert('Canceled.'+evtMsg) - } - - onClickMe(event){ - let evtMsg = event ? ' Event target class is '+ event.target.className : ''; - alert('Click me.'+evtMsg) - } - - onDeleted(hero){ - alert('Deleted hero: '+ (hero && hero.firstName)) - } - - onSave(event){ - let evtMsg = event ? ' Event target is '+ event.target.innerText : ''; - alert('Saved.'+evtMsg) - } - - onSubmit(form){ - let evtMsg = form.valid ? - ' Form value is '+ JSON.stringify(form.value) : - ' Form is invalid'; - alert('Form submitted.'+evtMsg) - } - - product = { - name: 'frimfram', - price: 42 - }; - - setLastName(event){ - console.log(event); - this.currentHero.lastName = event; - } - - setClasses() { - return { - saveable: this.canSave, // true - modified: !this.isUnchanged, // false - special: this.isSpecial, // true - } - } - - setStyles() { - return { - 'font-style': this.canSave ? 'italic' : 'normal', // italic - 'font-weight': !this.isUnchanged ? 'bold' : 'normal', // normal - 'font-size': this.isSpecial ? 'larger' : 'smaller', // larger - } - } - - toeChoice(picker){ - let choices = picker.children; - for (let i=0; i(); // @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/public/docs/_examples/template-syntax/ts/src/app/hero-detail.component.ts b/public/docs/_examples/template-syntax/ts/src/app/hero-detail.component.ts new file mode 100644 index 0000000000..725849d692 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/template-syntax/ts/src/app/hero-form.component.html b/public/docs/_examples/template-syntax/ts/src/app/hero-form.component.html new file mode 100644 index 0000000000..2182060439 --- /dev/null +++ b/public/docs/_examples/template-syntax/ts/src/app/hero-form.component.html @@ -0,0 +1,15 @@ +
    + +
    +
    + +
    + +
    +
    + {{submitMessage}} +
    + +
    diff --git a/public/docs/_examples/template-syntax/ts/src/app/hero-form.component.ts b/public/docs/_examples/template-syntax/ts/src/app/hero-form.component.ts new file mode 100644 index 0000000000..7bdc4a0bba --- /dev/null +++ b/public/docs/_examples/template-syntax/ts/src/app/hero-form.component.ts @@ -0,0 +1,30 @@ +import { Component, Input, ViewChild } from '@angular/core'; +import { NgForm } from '@angular/forms'; + +import { Hero } from './hero'; + +@Component({ + 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/public/docs/_examples/template-syntax/ts/src/app/hero-switch.components.ts b/public/docs/_examples/template-syntax/ts/src/app/hero-switch.components.ts new file mode 100644 index 0000000000..a21892e248 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/template-syntax/ts/src/app/hero.ts b/public/docs/_examples/template-syntax/ts/src/app/hero.ts new file mode 100644 index 0000000000..f8cc3b16a6 --- /dev/null +++ b/public/docs/_examples/template-syntax/ts/src/app/hero.ts @@ -0,0 +1,34 @@ +export class Hero { + static nextId = 0; + + static heroes: Hero[] = [ + new Hero( + null, + 'Hercules', + 'happy', + new Date(1970, 1, 25), + 'http://www.imdb.com/title/tt0065832/', + 325 + ), + 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/public/docs/_examples/template-syntax/ts/src/app/sizer.component.ts b/public/docs/_examples/template-syntax/ts/src/app/sizer.component.ts new file mode 100644 index 0000000000..b6065c8cd1 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/template-syntax/ts/src/images/hero.png b/public/docs/_examples/template-syntax/ts/src/images/hero.png new file mode 100644 index 0000000000..2a128ac367 Binary files /dev/null and b/public/docs/_examples/template-syntax/ts/src/images/hero.png differ diff --git a/public/docs/_examples/template-syntax/ts/src/images/ng-logo.png b/public/docs/_examples/template-syntax/ts/src/images/ng-logo.png new file mode 100644 index 0000000000..1e488b1a49 Binary files /dev/null and b/public/docs/_examples/template-syntax/ts/src/images/ng-logo.png differ diff --git a/public/docs/_examples/template-syntax/ts/src/images/villain.png b/public/docs/_examples/template-syntax/ts/src/images/villain.png new file mode 100644 index 0000000000..26697d1a42 Binary files /dev/null and b/public/docs/_examples/template-syntax/ts/src/images/villain.png differ diff --git a/public/docs/_examples/template-syntax/ts/src/index.html b/public/docs/_examples/template-syntax/ts/src/index.html index 6424b81743..3e711397cb 100644 --- a/public/docs/_examples/template-syntax/ts/src/index.html +++ b/public/docs/_examples/template-syntax/ts/src/index.html @@ -2,21 +2,25 @@ Template Syntax - - - + + + + + + + + + + + + Loading... -
    - Loading... - \ No newline at end of file + diff --git a/public/docs/_examples/template-syntax/ts/src/main.ts b/public/docs/_examples/template-syntax/ts/src/main.ts new file mode 100644 index 0000000000..311c44b76d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/template-syntax/ts/src/tsconfig.json b/public/docs/_examples/template-syntax/ts/src/tsconfig.json deleted file mode 100644 index 6a58b35a58..0000000000 --- a/public/docs/_examples/template-syntax/ts/src/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "target": "ES5", - "module": "commonjs", - "sourceMap": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "removeComments": false, - "noImplicitAny": false - } -} \ No newline at end of file diff --git a/public/docs/_examples/testing/ts/.gitignore b/public/docs/_examples/testing/ts/.gitignore new file mode 100644 index 0000000000..5374943421 --- /dev/null +++ b/public/docs/_examples/testing/ts/.gitignore @@ -0,0 +1 @@ +!src/browser-test-shim.js diff --git a/public/docs/_examples/testing/ts/1st-specs.plnkr.json b/public/docs/_examples/testing/ts/1st-specs.plnkr.json new file mode 100644 index 0000000000..9fe15ddef1 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/app-specs.plnkr.json b/public/docs/_examples/testing/ts/app-specs.plnkr.json new file mode 100644 index 0000000000..d97bf82c86 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/bag-specs.plnkr.json b/public/docs/_examples/testing/ts/bag-specs.plnkr.json new file mode 100644 index 0000000000..cd22e10c28 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/bag.plnkr.json b/public/docs/_examples/testing/ts/bag.plnkr.json new file mode 100644 index 0000000000..4bb0ac9c5b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/banner-inline-specs.plnkr.json b/public/docs/_examples/testing/ts/banner-inline-specs.plnkr.json new file mode 100644 index 0000000000..77b8c212cf --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/banner-specs.plnkr.json b/public/docs/_examples/testing/ts/banner-specs.plnkr.json new file mode 100644 index 0000000000..6e5f20bccb --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/example-config.json b/public/docs/_examples/testing/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/testing/ts/karma-test-shim.js b/public/docs/_examples/testing/ts/karma-test-shim.js new file mode 100644 index 0000000000..1b8d6acdd4 --- /dev/null +++ b/public/docs/_examples/testing/ts/karma-test-shim.js @@ -0,0 +1,96 @@ +// #docregion +// /*global jasmine, __karma__, window*/ +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 = 1000; + +// builtPaths: root paths for output ("built") files +// get from karma.config.js, then prefix with '/src/' (default is 'app/') +var builtPaths = (__karma__.config.builtPaths || ['src/']) + .map(function(p) { return '/base/'+p;}); + +__karma__.loaded = function () { }; + +function isJsFile(path) { + return path.slice(-3) == '.js'; +} + +function isSpecFile(path) { + return /\.spec\.(.*\.)?js$/.test(path); +} + +// Is a "built" file if is JavaScript file in one of the "built" folders +function isBuiltFile(path) { + return isJsFile(path) && + builtPaths.reduce(function(keep, bp) { + return keep || (path.substr(0, bp.length) === bp); + }, false); +} + +var allSpecFiles = Object.keys(window.__karma__.files) + .filter(isSpecFile) + .filter(isBuiltFile); + +System.config({ + baseURL: 'base/src', + // Extend usual application package list with testing 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( + 'Warning: System.import could not load the optional "systemjs.config.extras.js". Did you omit it by accident? Continuing 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 and start karma +function initTesting () { + return Promise.all( + allSpecFiles.map(function (moduleName) { + return System.import(moduleName); + }) + ) + .then(__karma__.start, __karma__.error); +} diff --git a/public/docs/_examples/testing/ts/karma.conf.js b/public/docs/_examples/testing/ts/karma.conf.js new file mode 100644 index 0000000000..a00b8add54 --- /dev/null +++ b/public/docs/_examples/testing/ts/karma.conf.js @@ -0,0 +1,98 @@ +// #docregion +module.exports = function(config) { + + var appBase = 'src/'; // transpiled app JS and map files + var appAssets = '/base/app/'; // component assets fetched by Angular's compiler + + // Testing helpers (optional) are conventionally in a folder called `testing` + var testingBase = 'src/testing/'; // transpiled test JS and map files + var testingSrcBase = 'src/testing/'; // test source TS files + + config.set({ + basePath: '', + frameworks: ['jasmine'], + + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter') + ], + + client: { + builtPaths: [appBase, testingBase], // add more spec base paths as needed + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + + customLaunchers: { + // From the CLI. Not used here but interesting + // chrome setup for travis CI using chromium + Chrome_travis_ci: { + base: 'Chrome', + flags: ['--no-sandbox'] + } + }, + + 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 }, + + // Paths loaded via module imports: + // Angular itself + { pattern: 'node_modules/@angular/**/*.js', included: false, watched: false }, + { pattern: 'node_modules/@angular/**/*.js.map', included: false, watched: false }, + + { pattern: appBase + '/systemjs.config.js', included: false, watched: false }, + { pattern: appBase + '/systemjs.config.extras.js', included: false, watched: false }, + 'karma-test-shim.js', // optionally extend SystemJS mapping e.g., with barrels + + // transpiled application & spec code paths loaded via module imports + { pattern: appBase + '**/*.js', included: false, watched: true }, + { pattern: testingBase + '**/*.js', included: false, watched: true }, + + + // Asset (HTML & CSS) paths loaded via Angular's component compiler + // (these paths need to be rewritten, see proxies section) + { pattern: appBase + '**/*.html', included: false, watched: true }, + { pattern: appBase + '**/*.css', included: false, watched: true }, + + // Paths for debugging with source maps in dev tools + { pattern: appBase + '**/*.ts', included: false, watched: false }, + { pattern: appBase + '**/*.js.map', included: false, watched: false }, + { pattern: testingSrcBase + '**/*.ts', included: false, watched: false }, + { pattern: testingBase + '**/*.js.map', included: false, watched: false} + ], + + // Proxied base paths for loading assets + proxies: { + // required for modules fetched by SystemJS + '/base/src/node_modules/': '/base/node_modules/' + }, + + exclude: [], + preprocessors: {}, + reporters: ['progress', 'kjhtml'], + + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false + }) +} diff --git a/public/docs/_examples/testing/ts/plnkr.json b/public/docs/_examples/testing/ts/plnkr.json new file mode 100644 index 0000000000..899867159f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/1st-specs.html b/public/docs/_examples/testing/ts/src/1st-specs.html new file mode 100644 index 0000000000..5876a65b9a --- /dev/null +++ b/public/docs/_examples/testing/ts/src/1st-specs.html @@ -0,0 +1,41 @@ + + + + + + + 1st Specs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/testing/ts/src/app-specs.html b/public/docs/_examples/testing/ts/src/app-specs.html new file mode 100644 index 0000000000..7b7292ed7c --- /dev/null +++ b/public/docs/_examples/testing/ts/src/app-specs.html @@ -0,0 +1,56 @@ + + + + + + + Sample App Specs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/testing/ts/src/app/1st.spec.ts b/public/docs/_examples/testing/ts/src/app/1st.spec.ts new file mode 100644 index 0000000000..63f1ab134c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/about.component.spec.ts b/public/docs/_examples/testing/ts/src/app/about.component.spec.ts new file mode 100644 index 0000000000..0909e74434 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/about.component.ts b/public/docs/_examples/testing/ts/src/app/about.component.ts new file mode 100644 index 0000000000..90e7132b4c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/app-routing.module.ts b/public/docs/_examples/testing/ts/src/app/app-routing.module.ts new file mode 100644 index 0000000000..6096a513df --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/app.component.html b/public/docs/_examples/testing/ts/src/app/app.component.html new file mode 100644 index 0000000000..232bcebb6d --- /dev/null +++ b/public/docs/_examples/testing/ts/src/app/app.component.html @@ -0,0 +1,11 @@ + + + + + + + diff --git a/public/docs/_examples/testing/ts/src/app/app.component.router.spec.ts b/public/docs/_examples/testing/ts/src/app/app.component.router.spec.ts new file mode 100644 index 0000000000..226d6d1ff3 --- /dev/null +++ b/public/docs/_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) as SpyLocation; + 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/public/docs/_examples/testing/ts/src/app/app.component.spec.ts b/public/docs/_examples/testing/ts/src/app/app.component.spec.ts new file mode 100644 index 0000000000..20c40767c0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/app.component.ts b/public/docs/_examples/testing/ts/src/app/app.component.ts new file mode 100644 index 0000000000..5bd535a113 --- /dev/null +++ b/public/docs/_examples/testing/ts/src/app/app.component.ts @@ -0,0 +1,7 @@ +// #docregion +import { Component } from '@angular/core'; +@Component({ + selector: 'my-app', + templateUrl: './app.component.html' +}) +export class AppComponent { } diff --git a/public/docs/_examples/testing/ts/src/app/app.module.ts b/public/docs/_examples/testing/ts/src/app/app.module.ts new file mode 100644 index 0000000000..d3c288ad11 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/bag/async-helper.spec.ts b/public/docs/_examples/testing/ts/src/app/bag/async-helper.spec.ts new file mode 100644 index 0000000000..12c8a4fbbd --- /dev/null +++ b/public/docs/_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: any) => { + 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/public/docs/_examples/testing/ts/src/app/bag/bag-external-template.html b/public/docs/_examples/testing/ts/src/app/bag/bag-external-template.html new file mode 100644 index 0000000000..4c2b23755f --- /dev/null +++ b/public/docs/_examples/testing/ts/src/app/bag/bag-external-template.html @@ -0,0 +1 @@ +from external template diff --git a/public/docs/_examples/testing/ts/src/app/bag/bag-main.ts b/public/docs/_examples/testing/ts/src/app/bag/bag-main.ts new file mode 100644 index 0000000000..27b78200ae --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/bag/bag.no-testbed.spec.ts b/public/docs/_examples/testing/ts/src/app/bag/bag.no-testbed.spec.ts new file mode 100644 index 0000000000..f29672ecf6 --- /dev/null +++ b/public/docs/_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: DoneFn) => { + service.getAsyncValue().then(value => { + expect(value).toBe('async value'); + done(); + }); + }); + + // #docregion getTimeoutValue + it('#getTimeoutValue should return timeout value', (done: DoneFn) => { + service = new FancyService(); + service.getTimeoutValue().then(value => { + expect(value).toBe('timeout value'); + done(); + }); + }); + // #enddocregion getTimeoutValue + + it('#getObservableValue should return observable value', (done: DoneFn) => { + 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/public/docs/_examples/testing/ts/src/app/bag/bag.spec.ts b/public/docs/_examples/testing/ts/src/app/bag/bag.spec.ts new file mode 100644 index 0000000000..d6fea7ca94 --- /dev/null +++ b/public/docs/_examples/testing/ts/src/app/bag/bag.spec.ts @@ -0,0 +1,681 @@ +// #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: DoneFn) => { + 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 component', () => { + const fixture = TestBed.createComponent(IoParentComponent); + const comp = fixture.componentInstance; + const heroName = comp.heroes[0].name; // first hero's name + + fixture.detectChanges(); + const ngForRow = fixture.debugElement.query(By.directive(IoComponent)); // first hero ngForRow + + const hero = ngForRow.context['hero']; // the hero object passed into the row + expect(hero.name).toBe(heroName, 'ngRow.context.hero'); + + const rowComp = ngForRow.componentInstance; + // jasmine.any is an "instance-of-type" test. + expect(rowComp).toEqual(jasmine.any(IoComponent), 'component is IoComp'); + expect(rowComp.hero.name).toBe(heroName, 'component.hero'); + }); + + + // #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(childComp, 'context is the child 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) as FakeFancyService; + + 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/public/docs/_examples/testing/ts/src/app/bag/bag.ts b/public/docs/_examples/testing/ts/src/app/bag/bag.ts new file mode 100644 index 0000000000..cbf1c21136 --- /dev/null +++ b/public/docs/_examples/testing/ts/src/app/bag/bag.ts @@ -0,0 +1,454 @@ +/* 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({ + 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({ + 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/public/docs/_examples/testing/ts/src/app/banner-inline.component.spec.ts b/public/docs/_examples/testing/ts/src/app/banner-inline.component.spec.ts new file mode 100644 index 0000000000..7f35d6b956 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/banner-inline.component.ts b/public/docs/_examples/testing/ts/src/app/banner-inline.component.ts new file mode 100644 index 0000000000..7ec4ef6999 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/banner.component.css b/public/docs/_examples/testing/ts/src/app/banner.component.css new file mode 100644 index 0000000000..cd09a55b3c --- /dev/null +++ b/public/docs/_examples/testing/ts/src/app/banner.component.css @@ -0,0 +1 @@ +h1 { color: green; font-size: 350%} diff --git a/public/docs/_examples/testing/ts/src/app/banner.component.detect-changes.spec.ts b/public/docs/_examples/testing/ts/src/app/banner.component.detect-changes.spec.ts new file mode 100644 index 0000000000..412f5be586 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/banner.component.html b/public/docs/_examples/testing/ts/src/app/banner.component.html new file mode 100644 index 0000000000..6213adcb47 --- /dev/null +++ b/public/docs/_examples/testing/ts/src/app/banner.component.html @@ -0,0 +1 @@ +

    {{title}}

    diff --git a/public/docs/_examples/testing/ts/src/app/banner.component.spec.ts b/public/docs/_examples/testing/ts/src/app/banner.component.spec.ts new file mode 100644 index 0000000000..a564c45c85 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/banner.component.ts b/public/docs/_examples/testing/ts/src/app/banner.component.ts new file mode 100644 index 0000000000..4355a40867 --- /dev/null +++ b/public/docs/_examples/testing/ts/src/app/banner.component.ts @@ -0,0 +1,12 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-banner', + templateUrl: './banner.component.html', + styleUrls: ['./banner.component.css'] +}) +export class BannerComponent { + title = 'Test Tour of Heroes'; +} + diff --git a/public/docs/_examples/testing/ts/src/app/dashboard/dashboard-hero.component.css b/public/docs/_examples/testing/ts/src/app/dashboard/dashboard-hero.component.css new file mode 100644 index 0000000000..eb54d181d8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/dashboard/dashboard-hero.component.html b/public/docs/_examples/testing/ts/src/app/dashboard/dashboard-hero.component.html new file mode 100644 index 0000000000..ff49bd17a5 --- /dev/null +++ b/public/docs/_examples/testing/ts/src/app/dashboard/dashboard-hero.component.html @@ -0,0 +1,4 @@ + +
    + {{hero.name | uppercase}} +
    diff --git a/public/docs/_examples/testing/ts/src/app/dashboard/dashboard-hero.component.spec.ts b/public/docs/_examples/testing/ts/src/app/dashboard/dashboard-hero.component.spec.ts new file mode 100644 index 0000000000..40c01571e6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/dashboard/dashboard-hero.component.ts b/public/docs/_examples/testing/ts/src/app/dashboard/dashboard-hero.component.ts new file mode 100644 index 0000000000..9f6be16d7d --- /dev/null +++ b/public/docs/_examples/testing/ts/src/app/dashboard/dashboard-hero.component.ts @@ -0,0 +1,17 @@ +// #docregion +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +import { Hero } from '../model'; + +// #docregion component +@Component({ + 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/public/docs/_examples/testing/ts/src/app/dashboard/dashboard.component.css b/public/docs/_examples/testing/ts/src/app/dashboard/dashboard.component.css new file mode 100644 index 0000000000..98130b61c6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/dashboard/dashboard.component.html b/public/docs/_examples/testing/ts/src/app/dashboard/dashboard.component.html new file mode 100644 index 0000000000..b26e16b828 --- /dev/null +++ b/public/docs/_examples/testing/ts/src/app/dashboard/dashboard.component.html @@ -0,0 +1,9 @@ +

    {{title}}

    + +
    + + + + +
    diff --git a/public/docs/_examples/testing/ts/src/app/dashboard/dashboard.component.no-testbed.spec.ts b/public/docs/_examples/testing/ts/src/app/dashboard/dashboard.component.no-testbed.spec.ts new file mode 100644 index 0000000000..125e5193b7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/dashboard/dashboard.component.spec.ts b/public/docs/_examples/testing/ts/src/app/dashboard/dashboard.component.spec.ts new file mode 100644 index 0000000000..0b0f9e213a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/dashboard/dashboard.component.ts b/public/docs/_examples/testing/ts/src/app/dashboard/dashboard.component.ts new file mode 100644 index 0000000000..3edaa0c0ab --- /dev/null +++ b/public/docs/_examples/testing/ts/src/app/dashboard/dashboard.component.ts @@ -0,0 +1,40 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +import { Hero, HeroService } from '../model'; + +@Component({ + 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/public/docs/_examples/testing/ts/src/app/dashboard/dashboard.module.ts b/public/docs/_examples/testing/ts/src/app/dashboard/dashboard.module.ts new file mode 100644 index 0000000000..8a70c851a0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/hero/hero-detail.component.css b/public/docs/_examples/testing/ts/src/app/hero/hero-detail.component.css new file mode 100644 index 0000000000..f6139ba274 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/hero/hero-detail.component.html b/public/docs/_examples/testing/ts/src/app/hero/hero-detail.component.html new file mode 100644 index 0000000000..7e86a668f6 --- /dev/null +++ b/public/docs/_examples/testing/ts/src/app/hero/hero-detail.component.html @@ -0,0 +1,12 @@ + +
    +

    {{hero.name | titlecase}} Details

    +
    + {{hero.id}}
    +
    + + +
    + + +
    diff --git a/public/docs/_examples/testing/ts/src/app/hero/hero-detail.component.no-testbed.spec.ts b/public/docs/_examples/testing/ts/src/app/hero/hero-detail.component.no-testbed.spec.ts new file mode 100644 index 0000000000..422dae6f77 --- /dev/null +++ b/public/docs/_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: any) => { + 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: any) => { + 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/public/docs/_examples/testing/ts/src/app/hero/hero-detail.component.spec.ts b/public/docs/_examples/testing/ts/src/app/hero/hero-detail.component.spec.ts new file mode 100644 index 0000000000..80b6450ac5 --- /dev/null +++ b/public/docs/_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) as any; + })); + + 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/public/docs/_examples/testing/ts/src/app/hero/hero-detail.component.ts b/public/docs/_examples/testing/ts/src/app/hero/hero-detail.component.ts new file mode 100644 index 0000000000..25f13b0cd5 --- /dev/null +++ b/public/docs/_examples/testing/ts/src/app/hero/hero-detail.component.ts @@ -0,0 +1,63 @@ +/* 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({ + 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/public/docs/_examples/testing/ts/src/app/hero/hero-detail.service.ts b/public/docs/_examples/testing/ts/src/app/hero/hero-detail.service.ts new file mode 100644 index 0000000000..6239ae5b80 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/hero/hero-list.component.css b/public/docs/_examples/testing/ts/src/app/hero/hero-list.component.css new file mode 100644 index 0000000000..d939ab565d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/hero/hero-list.component.html b/public/docs/_examples/testing/ts/src/app/hero/hero-list.component.html new file mode 100644 index 0000000000..cd37301fd6 --- /dev/null +++ b/public/docs/_examples/testing/ts/src/app/hero/hero-list.component.html @@ -0,0 +1,8 @@ +

    My Heroes

    +
      +
    • + {{hero.id}} {{hero.name}} +
    • +
    diff --git a/public/docs/_examples/testing/ts/src/app/hero/hero-list.component.spec.ts b/public/docs/_examples/testing/ts/src/app/hero/hero-list.component.spec.ts new file mode 100644 index 0000000000..dbf9d37d71 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/hero/hero-list.component.ts b/public/docs/_examples/testing/ts/src/app/hero/hero-list.component.ts new file mode 100644 index 0000000000..c61ff23f0e --- /dev/null +++ b/public/docs/_examples/testing/ts/src/app/hero/hero-list.component.ts @@ -0,0 +1,27 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +import { Hero, HeroService } from '../model'; + +@Component({ + 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/public/docs/_examples/testing/ts/src/app/hero/hero-routing.module.ts b/public/docs/_examples/testing/ts/src/app/hero/hero-routing.module.ts new file mode 100644 index 0000000000..59ec14474c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/hero/hero.module.ts b/public/docs/_examples/testing/ts/src/app/hero/hero.module.ts new file mode 100644 index 0000000000..dfe33cc199 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/model/hero.service.ts b/public/docs/_examples/testing/ts/src/app/model/hero.service.ts new file mode 100644 index 0000000000..667d47312b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/model/hero.spec.ts b/public/docs/_examples/testing/ts/src/app/model/hero.spec.ts new file mode 100644 index 0000000000..e8acf913f2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/model/hero.ts b/public/docs/_examples/testing/ts/src/app/model/hero.ts new file mode 100644 index 0000000000..6a98f0dfdc --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/model/http-hero.service.spec.ts b/public/docs/_examples/testing/ts/src/app/model/http-hero.service.spec.ts new file mode 100644 index 0000000000..c16b421274 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/model/http-hero.service.ts b/public/docs/_examples/testing/ts/src/app/model/http-hero.service.ts new file mode 100644 index 0000000000..a5fe46b801 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/model/index.ts b/public/docs/_examples/testing/ts/src/app/model/index.ts new file mode 100644 index 0000000000..227004d5be --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/model/test-heroes.ts b/public/docs/_examples/testing/ts/src/app/model/test-heroes.ts new file mode 100644 index 0000000000..d40ce5d564 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/model/testing/fake-hero.service.ts b/public/docs/_examples/testing/ts/src/app/model/testing/fake-hero.service.ts new file mode 100644 index 0000000000..79a865cc44 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/model/testing/index.ts b/public/docs/_examples/testing/ts/src/app/model/testing/index.ts new file mode 100644 index 0000000000..6da76e67db --- /dev/null +++ b/public/docs/_examples/testing/ts/src/app/model/testing/index.ts @@ -0,0 +1 @@ +export * from './fake-hero.service'; diff --git a/public/docs/_examples/testing/ts/src/app/model/user.service.ts b/public/docs/_examples/testing/ts/src/app/model/user.service.ts new file mode 100644 index 0000000000..280efefeec --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/shared/highlight.directive.spec.ts b/public/docs/_examples/testing/ts/src/app/shared/highlight.directive.spec.ts new file mode 100644 index 0000000000..b990f20b69 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/shared/highlight.directive.ts b/public/docs/_examples/testing/ts/src/app/shared/highlight.directive.ts new file mode 100644 index 0000000000..20901878c4 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/shared/shared.module.ts b/public/docs/_examples/testing/ts/src/app/shared/shared.module.ts new file mode 100644 index 0000000000..17c41c0410 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/shared/title-case.pipe.spec.ts b/public/docs/_examples/testing/ts/src/app/shared/title-case.pipe.spec.ts new file mode 100644 index 0000000000..5dfc5d91b0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/shared/title-case.pipe.ts b/public/docs/_examples/testing/ts/src/app/shared/title-case.pipe.ts new file mode 100644 index 0000000000..df2567778d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/shared/twain.component.spec.ts b/public/docs/_examples/testing/ts/src/app/shared/twain.component.spec.ts new file mode 100644 index 0000000000..b177c0bfc3 --- /dev/null +++ b/public/docs/_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: any) => { + 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/public/docs/_examples/testing/ts/src/app/shared/twain.component.timer.spec.ts.no-work b/public/docs/_examples/testing/ts/src/app/shared/twain.component.timer.spec.ts.no-work new file mode 100644 index 0000000000..74dec3e766 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/shared/twain.component.timer.ts.no-work b/public/docs/_examples/testing/ts/src/app/shared/twain.component.timer.ts.no-work new file mode 100644 index 0000000000..d3dc1f205d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/shared/twain.component.ts b/public/docs/_examples/testing/ts/src/app/shared/twain.component.ts new file mode 100644 index 0000000000..29f24459ab --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/shared/twain.service.ts b/public/docs/_examples/testing/ts/src/app/shared/twain.service.ts new file mode 100644 index 0000000000..9e394df1ee --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/welcome.component.spec.ts b/public/docs/_examples/testing/ts/src/app/welcome.component.spec.ts new file mode 100644 index 0000000000..e506dda396 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/app/welcome.component.ts b/public/docs/_examples/testing/ts/src/app/welcome.component.ts new file mode 100644 index 0000000000..35958cc5c9 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/bag-specs.html b/public/docs/_examples/testing/ts/src/bag-specs.html new file mode 100644 index 0000000000..89b46f7056 --- /dev/null +++ b/public/docs/_examples/testing/ts/src/bag-specs.html @@ -0,0 +1,42 @@ + + + + + + + Specs Bag + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/testing/ts/src/bag.html b/public/docs/_examples/testing/ts/src/bag.html new file mode 100644 index 0000000000..3e0fcb9025 --- /dev/null +++ b/public/docs/_examples/testing/ts/src/bag.html @@ -0,0 +1,26 @@ + + + + + + Specs Bag + + + + + + + + + + + + + + + + Loading ... + + diff --git a/public/docs/_examples/testing/ts/src/banner-inline-specs.html b/public/docs/_examples/testing/ts/src/banner-inline-specs.html new file mode 100644 index 0000000000..2a512a5647 --- /dev/null +++ b/public/docs/_examples/testing/ts/src/banner-inline-specs.html @@ -0,0 +1,40 @@ + + + + + + + Banner Component (inline template) Specs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/testing/ts/src/banner-specs.html b/public/docs/_examples/testing/ts/src/banner-specs.html new file mode 100644 index 0000000000..d16dd977a4 --- /dev/null +++ b/public/docs/_examples/testing/ts/src/banner-specs.html @@ -0,0 +1,40 @@ + + + + + + + Banner Component Specs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/testing/ts/src/browser-test-shim.js b/public/docs/_examples/testing/ts/src/browser-test-shim.js new file mode 100644 index 0000000000..ee21831e22 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/index.html b/public/docs/_examples/testing/ts/src/index.html new file mode 100644 index 0000000000..fff2464efc --- /dev/null +++ b/public/docs/_examples/testing/ts/src/index.html @@ -0,0 +1,27 @@ + + + + + + App Under Test + + + + + + + + + + + + + + + + + Loading... + + diff --git a/public/docs/_examples/testing/ts/src/main.ts b/public/docs/_examples/testing/ts/src/main.ts new file mode 100644 index 0000000000..fadce2f3c1 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/systemjs.config.extras.js b/public/docs/_examples/testing/ts/src/systemjs.config.extras.js new file mode 100644 index 0000000000..218e65715c --- /dev/null +++ b/public/docs/_examples/testing/ts/src/systemjs.config.extras.js @@ -0,0 +1,9 @@ +// #docregion +/** App specific SystemJS configuration */ +System.config({ + packages: { + // barrels + 'app/model': {main:'index.js', defaultExtension:'js'}, + 'app/model/testing': {main:'index.js', defaultExtension:'js'} + } +}); diff --git a/public/docs/_examples/testing/ts/src/testing/index.ts b/public/docs/_examples/testing/ts/src/testing/index.ts new file mode 100644 index 0000000000..e3de5164ca --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/testing/jasmine-matchers.d.ts b/public/docs/_examples/testing/ts/src/testing/jasmine-matchers.d.ts new file mode 100644 index 0000000000..f1c5acf77c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/testing/jasmine-matchers.ts b/public/docs/_examples/testing/ts/src/testing/jasmine-matchers.ts new file mode 100644 index 0000000000..4cab02e148 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/src/testing/router-stubs.ts b/public/docs/_examples/testing/ts/src/testing/router-stubs.ts new file mode 100644 index 0000000000..75a2858f65 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/testing/ts/wallaby.js b/public/docs/_examples/testing/ts/wallaby.js new file mode 100644 index 0000000000..cd31e158fe --- /dev/null +++ b/public/docs/_examples/testing/ts/wallaby.js @@ -0,0 +1,118 @@ +// Configuration for the Wallaby Visual Studio Code testing extension +// https://marketplace.visualstudio.com/items?itemName=WallabyJs.wallaby-vscode +// Note: Wallaby is not open source and costs money + +module.exports = function () { + return { + files: [ + // System.js for module loading + {pattern: 'node_modules/systemjs/dist/system.js', instrument: false}, + {pattern: 'systemjs.config.js', instrument: false}, + {pattern: 'systemjs.config.extras.js', instrument: false}, + + // Polyfills + {pattern: 'node_modules/core-js/client/shim.min.js', instrument: false}, + + // zone.js + {pattern: 'node_modules/zone.js/dist/zone.js', instrument: false}, + {pattern: 'node_modules/zone.js/dist/long-stack-trace-zone.js', instrument: false}, + {pattern: 'node_modules/zone.js/dist/proxy.js', instrument: false}, + {pattern: 'node_modules/zone.js/dist/sync-test.js', instrument: false}, + {pattern: 'node_modules/zone.js/dist/jasmine-patch.js', instrument: false}, + {pattern: 'node_modules/zone.js/dist/async-test.js', instrument: false}, + {pattern: 'node_modules/zone.js/dist/fake-async-test.js', instrument: false}, + + // application (but not specs) loaded via module imports + {pattern: 'app/**/*+(ts|html|css)', load: false}, + {pattern: 'app/**/*.spec.ts', ignore: true}, + + {pattern: 'testing/**/*+(ts|html|css)', load: false}, + ], + + tests: [ + {pattern: 'app/**/*.spec.ts', load: false} + ], + + middleware: function (app, express) { + app.use('/node_modules', express.static(require('path').join(__dirname, 'node_modules'))); + }, + + testFramework: 'jasmine', + + debug: true, + + bootstrap: bootstrap + }; +}; + +// Like karma-test-shim.js +function bootstrap (wallaby) { + wallaby.delayStart(); + + System.config({ + // 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( + 'Warning: System.import could not load the optional "systemjs.config.extras.js". Did you omit it by accident? Continuing 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()); + }) + } + + // Load all spec files and start wallaby + function initTesting () { + return Promise.all( + wallaby.tests.map(function (specFile) { + return System.import(specFile); + }) + ) + .then(function () { + wallaby.start(); + }) + .catch(function (e) { + setTimeout(function () { + throw e; + }, 0); + }); + } +} diff --git a/public/docs/_examples/toh-1/dart-snippets/app_component_snippets_pt1.dart b/public/docs/_examples/toh-1/dart-snippets/app_component_snippets_pt1.dart new file mode 100644 index 0000000000..3bfa89762d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-1/e2e-spec.ts b/public/docs/_examples/toh-1/e2e-spec.ts new file mode 100644 index 0000000000..75f99788a6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-1/ts/app/app.component.1.ts b/public/docs/_examples/toh-1/ts/app/app.component.1.ts new file mode 100644 index 0000000000..371fe39f90 --- /dev/null +++ b/public/docs/_examples/toh-1/ts/app/app.component.1.ts @@ -0,0 +1,44 @@ +import { Component } from '@angular/core'; + +let t = { +// #docregion show-hero +template: `

    {{title}}

    {{hero}} details!

    ` +// #enddocregion show-hero +}; + +t = { +// #docregion show-hero-2 +template: `

    {{title}}

    {{hero.name}} details!

    ` +// #enddocregion show-hero-2 +}; + +t = { +// #docregion multi-line-strings +template: ` +

    {{title}}

    +

    {{hero.name}} details!

    +
    {{hero.id}}
    +
    {{hero.name}}
    + ` +// #enddocregion multi-line-strings +}; + + +/* +// #docregion name-input +
    + + +
    +// #enddocregion name-input +*/ + +///////////////// + +@Component(t) +// #docregion app-component-1 +export class AppComponent { + title = 'Tour of Heroes'; + hero = 'Windstorm'; +} +// #enddocregion app-component-1 diff --git a/public/docs/_examples/toh-1/ts/example-config.json b/public/docs/_examples/toh-1/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/toh-1/ts/plnkr.json b/public/docs/_examples/toh-1/ts/plnkr.json new file mode 100644 index 0000000000..ca75131d96 --- /dev/null +++ b/public/docs/_examples/toh-1/ts/plnkr.json @@ -0,0 +1,10 @@ +{ + "description": "Tour of Heroes: Part 1", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1].*" + ], + "tags": ["tutorial", "tour", "heroes"] +} diff --git a/public/docs/_examples/toh-1/ts/src/app/app.component.ts b/public/docs/_examples/toh-1/ts/src/app/app.component.ts new file mode 100644 index 0000000000..602781b9ba --- /dev/null +++ b/public/docs/_examples/toh-1/ts/src/app/app.component.ts @@ -0,0 +1,33 @@ +// #docregion +import { Component } from '@angular/core'; + +// #docregion hero-class-1 +export class Hero { + id: number; + name: string; +} +// #enddocregion hero-class-1 + +@Component({ + selector: 'my-app', + // #docregion editing-Hero + template: ` +

    {{title}}

    +

    {{hero.name}} details!

    +
    {{hero.id}}
    +
    + + +
    + ` + // #enddocregion editing-Hero +}) +export class AppComponent { + title = 'Tour of Heroes'; + // #docregion hero-property-1 + hero: Hero = { + id: 1, + name: 'Windstorm' + }; + // #enddocregion hero-property-1 +} diff --git a/public/docs/_examples/toh-1/ts/src/app/app.module.ts b/public/docs/_examples/toh-1/ts/src/app/app.module.ts new file mode 100644 index 0000000000..8e87678efc --- /dev/null +++ b/public/docs/_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'; // <-- NgModel lives here + +import { AppComponent } from './app.component'; + +@NgModule({ + imports: [ + BrowserModule, + FormsModule // <-- import the FormsModule before binding with [(ngModel)] + ], + declarations: [ + AppComponent + ], + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/public/docs/_examples/toh-1/ts/src/index.html b/public/docs/_examples/toh-1/ts/src/index.html new file mode 100644 index 0000000000..a217238c6c --- /dev/null +++ b/public/docs/_examples/toh-1/ts/src/index.html @@ -0,0 +1,25 @@ + + + + Angular Tour of Heroes + + + + + + + + + + + + + + + + + Loading... + + diff --git a/public/docs/_examples/toh-1/ts/src/main.ts b/public/docs/_examples/toh-1/ts/src/main.ts new file mode 100644 index 0000000000..80ece654a5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-2/dart-snippets/app_component_snippets_pt2.dart b/public/docs/_examples/toh-2/dart-snippets/app_component_snippets_pt2.dart new file mode 100644 index 0000000000..9f805c5bc5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-2/e2e-spec.ts b/public/docs/_examples/toh-2/e2e-spec.ts new file mode 100644 index 0000000000..34f9fba573 --- /dev/null +++ b/public/docs/_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 > hero-detail > div')) + }; +} diff --git a/public/docs/_examples/toh-2/ts/example-config.json b/public/docs/_examples/toh-2/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/toh-2/ts/plnkr.json b/public/docs/_examples/toh-2/ts/plnkr.json new file mode 100644 index 0000000000..08c7b9581a --- /dev/null +++ b/public/docs/_examples/toh-2/ts/plnkr.json @@ -0,0 +1,10 @@ +{ + "description": "Tour of Heroes: Part 2", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1].*" + ], + "tags": ["tutorial", "tour", "heroes"] +} diff --git a/public/docs/_examples/toh-2/ts/src/app/app.component.1.html b/public/docs/_examples/toh-2/ts/src/app/app.component.1.html new file mode 100644 index 0000000000..86f8228723 --- /dev/null +++ b/public/docs/_examples/toh-2/ts/src/app/app.component.1.html @@ -0,0 +1,69 @@ + +
  • + {{hero.id}} {{hero.name}} +
  • + + + +

    My Heroes

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

    {{selectedHero.name}} details!

    +
    {{selectedHero.id}}
    +
    + + +
    + + + +
    +

    {{selectedHero.name}} details!

    +
    {{selectedHero.id}}
    +
    + + +
    +
    + + + +heroes = HEROES; + + + +

    My Heroes

    +
      +
    • + +
    • +
    + + + +
  • + + + +[class.selected]="hero === selectedHero" + + + +
  • + {{hero.id}} {{hero.name}} +
  • + diff --git a/public/docs/_examples/toh-2/ts/src/app/app.component.ts b/public/docs/_examples/toh-2/ts/src/app/app.component.ts new file mode 100644 index 0000000000..3e7c86f150 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-2/ts/src/app/app.module.ts b/public/docs/_examples/toh-2/ts/src/app/app.module.ts new file mode 100644 index 0000000000..4c0b77ea48 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-2/ts/src/index.html b/public/docs/_examples/toh-2/ts/src/index.html new file mode 100644 index 0000000000..a217238c6c --- /dev/null +++ b/public/docs/_examples/toh-2/ts/src/index.html @@ -0,0 +1,25 @@ + + + + Angular Tour of Heroes + + + + + + + + + + + + + + + + + Loading... + + diff --git a/public/docs/_examples/toh-2/ts/src/main.ts b/public/docs/_examples/toh-2/ts/src/main.ts new file mode 100644 index 0000000000..80ece654a5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-3/e2e-spec.ts b/public/docs/_examples/toh-3/e2e-spec.ts new file mode 100644 index 0000000000..842716c5fc --- /dev/null +++ b/public/docs/_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 > hero-detail > div')) + }; +} diff --git a/public/docs/_examples/toh-3/ts/app/app.component.1.html b/public/docs/_examples/toh-3/ts/app/app.component.1.html new file mode 100644 index 0000000000..bab9dda877 --- /dev/null +++ b/public/docs/_examples/toh-3/ts/app/app.component.1.html @@ -0,0 +1,12 @@ +

    {{title}}

    +

    My Heroes

    +
      +
    • + {{hero.id}} {{hero.name}} +
    • +
    + + + diff --git a/public/docs/_examples/toh-3/ts/app/hero-detail.component.1.ts b/public/docs/_examples/toh-3/ts/app/hero-detail.component.1.ts new file mode 100644 index 0000000000..fec477fbd7 --- /dev/null +++ b/public/docs/_examples/toh-3/ts/app/hero-detail.component.1.ts @@ -0,0 +1,35 @@ +// #docplaster +// #docregion v1 +import { Component } from '@angular/core'; + +// #enddocregion v1 +// #docregion hero-import +import { Hero } from './hero'; +// #enddocregion hero-import + +// #docregion v1 +@Component({ + selector: 'hero-detail', +// #enddocregion v1 + // #docregion template + template: ` +
    +

    {{hero.name}} details!

    +
    {{hero.id}}
    +
    + + +
    +
    + ` + // #enddocregion template +// #docregion v1 +}) +export class HeroDetailComponent { +// #enddocregion v1 +// #docregion hero + hero: Hero; +// #enddocregion hero +// #docregion v1 +} +// #enddocregion v1 diff --git a/public/docs/_examples/toh-3/ts/example-config.json b/public/docs/_examples/toh-3/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/toh-3/ts/plnkr.json b/public/docs/_examples/toh-3/ts/plnkr.json new file mode 100644 index 0000000000..829715877e --- /dev/null +++ b/public/docs/_examples/toh-3/ts/plnkr.json @@ -0,0 +1,10 @@ +{ + "description": "Tour of Heroes: Part 3", + "basePath": "src/", + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1].*" + ], + "tags": ["tutorial", "tour", "heroes"] +} diff --git a/public/docs/_examples/toh-3/ts/src/app/app.component.ts b/public/docs/_examples/toh-3/ts/src/app/app.component.ts new file mode 100644 index 0000000000..3e242914aa --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-3/ts/src/app/app.module.ts b/public/docs/_examples/toh-3/ts/src/app/app.module.ts new file mode 100644 index 0000000000..e04b8d304b --- /dev/null +++ b/public/docs/_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 + +@NgModule({ + imports: [ + BrowserModule, + FormsModule + ], +// #docregion declarations + declarations: [ + AppComponent, + HeroDetailComponent + ], +// #enddocregion declarations + bootstrap: [ AppComponent ] +}) +export class AppModule { } diff --git a/public/docs/_examples/toh-3/ts/src/app/hero-detail.component.ts b/public/docs/_examples/toh-3/ts/src/app/hero-detail.component.ts new file mode 100644 index 0000000000..45a1a1e7e1 --- /dev/null +++ b/public/docs/_examples/toh-3/ts/src/app/hero-detail.component.ts @@ -0,0 +1,29 @@ +// #docregion +// #docregion import-input +import { Component, Input } from '@angular/core'; +// #enddocregion import-input + +import { Hero } from './hero'; +// #docregion template +@Component({ + selector: 'hero-detail', + template: ` +
    +

    {{hero.name}} details!

    +
    {{hero.id}}
    +
    + + +
    +
    + ` +}) +// #enddocregion template +// #docregion class +export class HeroDetailComponent { +// #docregion hero + @Input() hero: Hero; +// #enddocregion hero +} +// #enddocregion class + diff --git a/public/docs/_examples/toh-3/ts/src/app/hero.ts b/public/docs/_examples/toh-3/ts/src/app/hero.ts new file mode 100644 index 0000000000..f4b0cd6b35 --- /dev/null +++ b/public/docs/_examples/toh-3/ts/src/app/hero.ts @@ -0,0 +1,6 @@ +// #docregion +export class Hero { + id: number; + name: string; +} +// #enddocregion diff --git a/public/docs/_examples/toh-3/ts/src/index.html b/public/docs/_examples/toh-3/ts/src/index.html new file mode 100644 index 0000000000..a217238c6c --- /dev/null +++ b/public/docs/_examples/toh-3/ts/src/index.html @@ -0,0 +1,25 @@ + + + + Angular Tour of Heroes + + + + + + + + + + + + + + + + + Loading... + + diff --git a/public/docs/_examples/toh-3/ts/src/main.ts b/public/docs/_examples/toh-3/ts/src/main.ts new file mode 100644 index 0000000000..aa939b3241 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-4/e2e-spec.ts b/public/docs/_examples/toh-4/e2e-spec.ts new file mode 100644 index 0000000000..d6a223dd12 --- /dev/null +++ b/public/docs/_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 > hero-detail > div')) + }; +} diff --git a/public/docs/_examples/toh-4/ts/example-config.json b/public/docs/_examples/toh-4/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/toh-4/ts/plnkr.json b/public/docs/_examples/toh-4/ts/plnkr.json new file mode 100644 index 0000000000..95987d95ce --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-4/ts/src/app/app.component.1.ts b/public/docs/_examples/toh-4/ts/src/app/app.component.1.ts new file mode 100644 index 0000000000..cfddbab537 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-4/ts/src/app/app.component.ts b/public/docs/_examples/toh-4/ts/src/app/app.component.ts new file mode 100644 index 0000000000..0d57acb5b0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-4/ts/src/app/app.module.ts b/public/docs/_examples/toh-4/ts/src/app/app.module.ts new file mode 100644 index 0000000000..3df186c62a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-4/ts/src/app/hero-detail.component.ts b/public/docs/_examples/toh-4/ts/src/app/hero-detail.component.ts new file mode 100644 index 0000000000..865fb98da7 --- /dev/null +++ b/public/docs/_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: 'hero-detail', + template: ` +
    +

    {{hero.name}} details!

    +
    + {{hero.id}} +
    +
    + + +
    +
    + ` +}) +export class HeroDetailComponent { + @Input() hero: Hero; +} diff --git a/public/docs/_examples/toh-4/ts/src/app/hero.service.1.ts b/public/docs/_examples/toh-4/ts/src/app/hero.service.1.ts new file mode 100644 index 0000000000..2366215259 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-4/ts/src/app/hero.service.2.ts b/public/docs/_examples/toh-4/ts/src/app/hero.service.2.ts new file mode 100644 index 0000000000..d14fe02410 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-4/ts/src/app/hero.service.ts b/public/docs/_examples/toh-4/ts/src/app/hero.service.ts new file mode 100644 index 0000000000..03a1c10a4a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-4/ts/src/app/hero.ts b/public/docs/_examples/toh-4/ts/src/app/hero.ts new file mode 100644 index 0000000000..e3eac516da --- /dev/null +++ b/public/docs/_examples/toh-4/ts/src/app/hero.ts @@ -0,0 +1,4 @@ +export class Hero { + id: number; + name: string; +} diff --git a/public/docs/_examples/toh-4/ts/src/app/mock-heroes.ts b/public/docs/_examples/toh-4/ts/src/app/mock-heroes.ts new file mode 100644 index 0000000000..6f7c5d83a0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-4/ts/src/index.html b/public/docs/_examples/toh-4/ts/src/index.html new file mode 100644 index 0000000000..a217238c6c --- /dev/null +++ b/public/docs/_examples/toh-4/ts/src/index.html @@ -0,0 +1,25 @@ + + + + Angular Tour of Heroes + + + + + + + + + + + + + + + + + Loading... + + diff --git a/public/docs/_examples/toh-4/ts/src/main.1.ts b/public/docs/_examples/toh-4/ts/src/main.1.ts new file mode 100644 index 0000000000..f22933ba8e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-4/ts/src/main.ts b/public/docs/_examples/toh-4/ts/src/main.ts new file mode 100644 index 0000000000..f22933ba8e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-5/e2e-spec.ts b/public/docs/_examples/toh-5/e2e-spec.ts new file mode 100644 index 0000000000..a49f2ddb62 --- /dev/null +++ b/public/docs/_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 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/public/docs/_examples/toh-5/ts/example-config.json b/public/docs/_examples/toh-5/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/toh-5/ts/plnkr.json b/public/docs/_examples/toh-5/ts/plnkr.json new file mode 100644 index 0000000000..db4b15d160 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-5/ts/src/app/app-routing.module.ts b/public/docs/_examples/toh-5/ts/src/app/app-routing.module.ts new file mode 100644 index 0000000000..dfd957782b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-5/ts/src/app/app.component.1.ts b/public/docs/_examples/toh-5/ts/src/app/app.component.1.ts new file mode 100644 index 0000000000..c9f5db9712 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-5/ts/src/app/app.component.css b/public/docs/_examples/toh-5/ts/src/app/app.component.css new file mode 100644 index 0000000000..071e665767 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-5/ts/src/app/app.component.ts b/public/docs/_examples/toh-5/ts/src/app/app.component.ts new file mode 100644 index 0000000000..96bc3fe694 --- /dev/null +++ b/public/docs/_examples/toh-5/ts/src/app/app.component.ts @@ -0,0 +1,22 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + 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/public/docs/_examples/toh-5/ts/src/app/app.module.1.ts b/public/docs/_examples/toh-5/ts/src/app/app.module.1.ts new file mode 100644 index 0000000000..e1cda9b620 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-5/ts/src/app/app.module.2.ts b/public/docs/_examples/toh-5/ts/src/app/app.module.2.ts new file mode 100644 index 0000000000..00876570f3 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-5/ts/src/app/app.module.3.ts b/public/docs/_examples/toh-5/ts/src/app/app.module.3.ts new file mode 100644 index 0000000000..306d9958f0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-5/ts/src/app/app.module.ts b/public/docs/_examples/toh-5/ts/src/app/app.module.ts new file mode 100644 index 0000000000..b376d69aba --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-5/ts/src/app/dashboard.component.1.html b/public/docs/_examples/toh-5/ts/src/app/dashboard.component.1.html new file mode 100644 index 0000000000..0c556b8de0 --- /dev/null +++ b/public/docs/_examples/toh-5/ts/src/app/dashboard.component.1.html @@ -0,0 +1,9 @@ + +

    Top Heroes

    +
    +
    +
    +

    {{hero.name}}

    +
    +
    +
    diff --git a/public/docs/_examples/toh-5/ts/src/app/dashboard.component.1.ts b/public/docs/_examples/toh-5/ts/src/app/dashboard.component.1.ts new file mode 100644 index 0000000000..3c92b205c8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-5/ts/src/app/dashboard.component.css b/public/docs/_examples/toh-5/ts/src/app/dashboard.component.css new file mode 100644 index 0000000000..dc7fb7ce06 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-5/ts/src/app/dashboard.component.html b/public/docs/_examples/toh-5/ts/src/app/dashboard.component.html new file mode 100644 index 0000000000..49e77c460c --- /dev/null +++ b/public/docs/_examples/toh-5/ts/src/app/dashboard.component.html @@ -0,0 +1,11 @@ + +

    Top Heroes

    + diff --git a/public/docs/_examples/toh-5/ts/src/app/dashboard.component.ts b/public/docs/_examples/toh-5/ts/src/app/dashboard.component.ts new file mode 100644 index 0000000000..416cf8868e --- /dev/null +++ b/public/docs/_examples/toh-5/ts/src/app/dashboard.component.ts @@ -0,0 +1,33 @@ +// #docplaster +// #docregion , imports +import { Component, OnInit } from '@angular/core'; + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; +// #enddocregion imports + +// #docregion metadata +@Component({ + 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/public/docs/_examples/toh-5/ts/src/app/hero-detail.component.1.ts b/public/docs/_examples/toh-5/ts/src/app/hero-detail.component.1.ts new file mode 100644 index 0000000000..2f5081bdce --- /dev/null +++ b/public/docs/_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, you'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/public/docs/_examples/toh-5/ts/src/app/hero-detail.component.css b/public/docs/_examples/toh-5/ts/src/app/hero-detail.component.css new file mode 100644 index 0000000000..ab2437efd8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-5/ts/src/app/hero-detail.component.html b/public/docs/_examples/toh-5/ts/src/app/hero-detail.component.html new file mode 100644 index 0000000000..8f2ff9d90c --- /dev/null +++ b/public/docs/_examples/toh-5/ts/src/app/hero-detail.component.html @@ -0,0 +1,14 @@ + + +
    +

    {{hero.name}} details!

    +
    + {{hero.id}}
    +
    + + +
    + + + +
    diff --git a/public/docs/_examples/toh-5/ts/src/app/hero-detail.component.ts b/public/docs/_examples/toh-5/ts/src/app/hero-detail.component.ts new file mode 100644 index 0000000000..222b6705d7 --- /dev/null +++ b/public/docs/_examples/toh-5/ts/src/app/hero-detail.component.ts @@ -0,0 +1,46 @@ +// #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({ + selector: '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/public/docs/_examples/toh-5/ts/src/app/hero.service.ts b/public/docs/_examples/toh-5/ts/src/app/hero.service.ts new file mode 100644 index 0000000000..ee5a684762 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-5/ts/src/app/hero.ts b/public/docs/_examples/toh-5/ts/src/app/hero.ts new file mode 100644 index 0000000000..e3eac516da --- /dev/null +++ b/public/docs/_examples/toh-5/ts/src/app/hero.ts @@ -0,0 +1,4 @@ +export class Hero { + id: number; + name: string; +} diff --git a/public/docs/_examples/toh-5/ts/src/app/heroes.component.css b/public/docs/_examples/toh-5/ts/src/app/heroes.component.css new file mode 100644 index 0000000000..b49fa0a419 --- /dev/null +++ b/public/docs/_examples/toh-5/ts/src/app/heroes.component.css @@ -0,0 +1,60 @@ +/* #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; +} diff --git a/public/docs/_examples/toh-5/ts/src/app/heroes.component.html b/public/docs/_examples/toh-5/ts/src/app/heroes.component.html new file mode 100644 index 0000000000..db41c4692e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-5/ts/src/app/heroes.component.ts b/public/docs/_examples/toh-5/ts/src/app/heroes.component.ts new file mode 100644 index 0000000000..def615e6b9 --- /dev/null +++ b/public/docs/_examples/toh-5/ts/src/app/heroes.component.ts @@ -0,0 +1,46 @@ +// #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({ + 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/public/docs/_examples/toh-5/ts/src/app/mock-heroes.ts b/public/docs/_examples/toh-5/ts/src/app/mock-heroes.ts new file mode 100644 index 0000000000..69afde3d34 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-5/ts/src/index.html b/public/docs/_examples/toh-5/ts/src/index.html new file mode 100644 index 0000000000..cbacc9b83b --- /dev/null +++ b/public/docs/_examples/toh-5/ts/src/index.html @@ -0,0 +1,33 @@ + + + + + + + + Angular Tour of Heroes + + + + + + + + + + + + + + + + + + + + + Loading... + + diff --git a/public/docs/_examples/toh-5/ts/src/main.ts b/public/docs/_examples/toh-5/ts/src/main.ts new file mode 100644 index 0000000000..505f60b35b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-6/e2e-spec.ts b/public/docs/_examples/toh-6/e2e-spec.ts new file mode 100644 index 0000000000..80d9660263 --- /dev/null +++ b/public/docs/_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 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/public/docs/_examples/toh-6/ts/.gitignore b/public/docs/_examples/toh-6/ts/.gitignore new file mode 100644 index 0000000000..804879d44a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-6/ts/aot/index.html b/public/docs/_examples/toh-6/ts/aot/index.html new file mode 100644 index 0000000000..cb8ecc4461 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/aot/index.html @@ -0,0 +1,19 @@ + + + + + + Angular Tour of Heroes + + + + + + + + + + Loading... + + + diff --git a/public/docs/_examples/toh-6/ts/aot/styles.css b/public/docs/_examples/toh-6/ts/aot/styles.css new file mode 100644 index 0000000000..d81835d0cd --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-6/ts/bs-config.aot.json b/public/docs/_examples/toh-6/ts/bs-config.aot.json new file mode 100644 index 0000000000..e59a7403a0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-6/ts/copy-dist-files.js b/public/docs/_examples/toh-6/ts/copy-dist-files.js new file mode 100644 index 0000000000..080864e99c --- /dev/null +++ b/public/docs/_examples/toh-6/ts/copy-dist-files.js @@ -0,0 +1,12 @@ +// #docregion +var fs = require('fs'); +var resources = [ + 'node_modules/core-js/client/shim.min.js', + 'node_modules/zone.js/dist/zone.min.js', + 'src/styles.css' +]; +resources.map(function(f) { + var path = f.split('/'); + var t = 'aot/' + path[path.length-1]; + fs.createReadStream(f).pipe(fs.createWriteStream(t)); +}); diff --git a/public/docs/_examples/toh-6/ts/example-config.json b/public/docs/_examples/toh-6/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/toh-6/ts/plnkr.json b/public/docs/_examples/toh-6/ts/plnkr.json new file mode 100644 index 0000000000..d355bc9ff2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-6/ts/rollup-config.js b/public/docs/_examples/toh-6/ts/rollup-config.js new file mode 100644 index 0000000000..5689edcff4 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/rollup-config.js @@ -0,0 +1,30 @@ +// #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; } + + // console.warn everything else + console.warn( warning.message ); + }, + plugins: [ + nodeResolve({jsnext: true, module: true}), + commonjs({ + include: ['node_modules/rxjs/**'] + }), + uglify() + ] +} diff --git a/public/docs/_examples/toh-6/ts/src/app/app-routing.module.ts b/public/docs/_examples/toh-6/ts/src/app/app-routing.module.ts new file mode 100644 index 0000000000..bc070f6c31 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-6/ts/src/app/app.component.css b/public/docs/_examples/toh-6/ts/src/app/app.component.css new file mode 100644 index 0000000000..071e665767 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-6/ts/src/app/app.component.ts b/public/docs/_examples/toh-6/ts/src/app/app.component.ts new file mode 100644 index 0000000000..a9fe05a9a8 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/src/app/app.component.ts @@ -0,0 +1,19 @@ +// #docplaster +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: ` +

    {{title}}

    + + + `, + styleUrls: ['./app.component.css'] +}) +export class AppComponent { + title = 'Tour of Heroes'; +} diff --git a/public/docs/_examples/toh-6/ts/src/app/app.module.ts b/public/docs/_examples/toh-6/ts/src/app/app.module.ts new file mode 100644 index 0000000000..58eeb10c54 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-6/ts/src/app/dashboard.component.css b/public/docs/_examples/toh-6/ts/src/app/dashboard.component.css new file mode 100644 index 0000000000..dc7fb7ce06 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-6/ts/src/app/dashboard.component.html b/public/docs/_examples/toh-6/ts/src/app/dashboard.component.html new file mode 100644 index 0000000000..db8546ccd2 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/src/app/dashboard.component.html @@ -0,0 +1,10 @@ + +

    Top Heroes

    + + diff --git a/public/docs/_examples/toh-6/ts/src/app/dashboard.component.ts b/public/docs/_examples/toh-6/ts/src/app/dashboard.component.ts new file mode 100644 index 0000000000..9960aa77d4 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/src/app/dashboard.component.ts @@ -0,0 +1,22 @@ +// #docregion , search +import { Component, OnInit } from '@angular/core'; + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; + +@Component({ + 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/public/docs/_examples/toh-6/ts/src/app/hero-detail.component.css b/public/docs/_examples/toh-6/ts/src/app/hero-detail.component.css new file mode 100644 index 0000000000..ab2437efd8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-6/ts/src/app/hero-detail.component.html b/public/docs/_examples/toh-6/ts/src/app/hero-detail.component.html new file mode 100644 index 0000000000..32fe6d4391 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/src/app/hero-detail.component.html @@ -0,0 +1,14 @@ + +
    +

    {{hero.name}} details!

    +
    + {{hero.id}}
    +
    + + +
    + + + + +
    diff --git a/public/docs/_examples/toh-6/ts/src/app/hero-detail.component.ts b/public/docs/_examples/toh-6/ts/src/app/hero-detail.component.ts new file mode 100644 index 0000000000..6224f10ac1 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/src/app/hero-detail.component.ts @@ -0,0 +1,40 @@ +// #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({ + selector: '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/public/docs/_examples/toh-6/ts/src/app/hero-search.component.css b/public/docs/_examples/toh-6/ts/src/app/hero-search.component.css new file mode 100644 index 0000000000..9bf8d13457 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/src/app/hero-search.component.css @@ -0,0 +1,21 @@ +/* #docregion */ +.search-result{ + border-bottom: 1px solid gray; + border-left: 1px solid gray; + border-right: 1px solid gray; + width:195px; + height: 16px; + padding: 5px; + background-color: white; + cursor: pointer; +} + +.search-result:hover { + color: #eee; + background-color: #607D8B; +} + +#search-box{ + width: 200px; + height: 20px; +} diff --git a/public/docs/_examples/toh-6/ts/src/app/hero-search.component.html b/public/docs/_examples/toh-6/ts/src/app/hero-search.component.html new file mode 100644 index 0000000000..08c0560c5b --- /dev/null +++ b/public/docs/_examples/toh-6/ts/src/app/hero-search.component.html @@ -0,0 +1,11 @@ + +
    +

    Hero Search

    + +
    +
    + {{hero.name}} +
    +
    +
    diff --git a/public/docs/_examples/toh-6/ts/src/app/hero-search.component.ts b/public/docs/_examples/toh-6/ts/src/app/hero-search.component.ts new file mode 100644 index 0000000000..8b2d32f06b --- /dev/null +++ b/public/docs/_examples/toh-6/ts/src/app/hero-search.component.ts @@ -0,0 +1,69 @@ +// #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({ + 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/public/docs/_examples/toh-6/ts/src/app/hero-search.service.ts b/public/docs/_examples/toh-6/ts/src/app/hero-search.service.ts new file mode 100644 index 0000000000..d24e0fba41 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-6/ts/src/app/hero.service.ts b/public/docs/_examples/toh-6/ts/src/app/hero.service.ts new file mode 100644 index 0000000000..29fe5c2e0e --- /dev/null +++ b/public/docs/_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 as Hero) + .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/public/docs/_examples/toh-6/ts/src/app/hero.ts b/public/docs/_examples/toh-6/ts/src/app/hero.ts new file mode 100644 index 0000000000..e3eac516da --- /dev/null +++ b/public/docs/_examples/toh-6/ts/src/app/hero.ts @@ -0,0 +1,4 @@ +export class Hero { + id: number; + name: string; +} diff --git a/public/docs/_examples/toh-6/ts/src/app/heroes.component.css b/public/docs/_examples/toh-6/ts/src/app/heroes.component.css new file mode 100644 index 0000000000..d2c958a911 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-6/ts/src/app/heroes.component.html b/public/docs/_examples/toh-6/ts/src/app/heroes.component.html new file mode 100644 index 0000000000..392d241d52 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-6/ts/src/app/heroes.component.ts b/public/docs/_examples/toh-6/ts/src/app/heroes.component.ts new file mode 100644 index 0000000000..6350b803c4 --- /dev/null +++ b/public/docs/_examples/toh-6/ts/src/app/heroes.component.ts @@ -0,0 +1,61 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; + +@Component({ + 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/public/docs/_examples/toh-6/ts/src/app/in-memory-data.service.ts b/public/docs/_examples/toh-6/ts/src/app/in-memory-data.service.ts new file mode 100644 index 0000000000..c915955e22 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-6/ts/src/index.html b/public/docs/_examples/toh-6/ts/src/index.html new file mode 100644 index 0000000000..18977969bb --- /dev/null +++ b/public/docs/_examples/toh-6/ts/src/index.html @@ -0,0 +1,26 @@ + + + + + + Angular Tour of Heroes + + + + + + + + + + + + + + + + Loading... + + diff --git a/public/docs/_examples/toh-6/ts/src/main-aot.ts b/public/docs/_examples/toh-6/ts/src/main-aot.ts new file mode 100644 index 0000000000..bd2ca604a3 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-6/ts/src/main.ts b/public/docs/_examples/toh-6/ts/src/main.ts new file mode 100644 index 0000000000..f332d1d245 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/toh-6/ts/src/tsconfig.1.json b/public/docs/_examples/toh-6/ts/src/tsconfig.1.json new file mode 100644 index 0000000000..fb3d43db90 --- /dev/null +++ b/public/docs/_examples/toh-6/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 + } +} \ No newline at end of file diff --git a/public/docs/_examples/toh-6/ts/tsconfig-aot.json b/public/docs/_examples/toh-6/ts/tsconfig-aot.json new file mode 100644 index 0000000000..fe1e6df520 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/tsconfig.json b/public/docs/_examples/tsconfig.json index 24f8573a6f..51ea56dc7f 100644 --- a/public/docs/_examples/tsconfig.json +++ b/public/docs/_examples/tsconfig.json @@ -1,9 +1,24 @@ +// this tsconfig is used for protractor tests +// see _boilerplate/tsconfig.json for the tsconfig used in examples { "compilerOptions": { - "target": "ES5", + "target": "es6", "module": "commonjs", + "moduleResolution": "node", "sourceMap": true, "emitDecoratorMetadata": true, - "experimentalDecorators": true - } -} \ No newline at end of file + "experimentalDecorators": true, + "lib": ["es2015", "dom"], + "noImplicitAny": true, + "suppressImplicitAnyIndexErrors": true, + "typeRoots": [ + "node_modules/@types" + ] + }, + "files": [ + "protractor-helpers.ts" + ], + "include": [ + "*/e2e-spec.ts" + ] +} diff --git a/public/docs/_examples/tsd.json b/public/docs/_examples/tsd.json deleted file mode 100644 index 0bed83f7e6..0000000000 --- a/public/docs/_examples/tsd.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "version": "v4", - "repo": "borisyankov/DefinitelyTyped", - "ref": "master", - "path": "typings", - "bundle": "typings/tsd.d.ts", - "installed": { - "angular2/angular2.d.ts": { - "commit": "cd2e71bb1f0459197e733be66fdeafaec600514d" - }, - "es6-promise/es6-promise.d.ts": { - "commit": "71d072b7354936b88d57c2029042d2da7c6ec0e7" - }, - "jasmine/jasmine.d.ts": { - "commit": "71d072b7354936b88d57c2029042d2da7c6ec0e7" - }, - "rx/rx.d.ts": { - "commit": "71d072b7354936b88d57c2029042d2da7c6ec0e7" - }, - "rx/rx-lite.d.ts": { - "commit": "71d072b7354936b88d57c2029042d2da7c6ec0e7" - } - } -} diff --git a/public/docs/_examples/universal/ts/.gitignore b/public/docs/_examples/universal/ts/.gitignore new file mode 100644 index 0000000000..62993511f8 --- /dev/null +++ b/public/docs/_examples/universal/ts/.gitignore @@ -0,0 +1,8 @@ +aot/**/*.ts +**/*.ngfactory.ts +**/*.ngsummary.json +**/*.metadata.json +**/*.js +dist +!app/tsconfig.json +!/*.js diff --git a/public/docs/_examples/universal/ts/bs-config.aot.js b/public/docs/_examples/universal/ts/bs-config.aot.js new file mode 100644 index 0000000000..7bb795b9e2 --- /dev/null +++ b/public/docs/_examples/universal/ts/bs-config.aot.js @@ -0,0 +1,18 @@ +module.exports = { + port: 3100, + server: { + baseDir: "src", + routes: { + "/node_modules": "node_modules" + }, + middleware: { + // overrides the fallback middleware to use index-aot + 1: require('connect-history-api-fallback')({ index: '/index-aot.html' }) + } + } + // ,"snippetOptions": { + // "rule": { + // "match": "/dummy/" // disable browsersync by giving it an invalid injection target + // } + // } +}; diff --git a/public/docs/_examples/universal/ts/bs-config.uni.js b/public/docs/_examples/universal/ts/bs-config.uni.js new file mode 100644 index 0000000000..e15de19940 --- /dev/null +++ b/public/docs/_examples/universal/ts/bs-config.uni.js @@ -0,0 +1,41 @@ +module.exports = function(bs) { + + return { + "open": true, + "port": 3200, + "server": { + "baseDir": "aot", + "routes": { + "/node_modules": "node_modules" + }, + "middleware": [ + testMiddleware + ] + } + }; + +}; + +function testMiddleware (req, res, next) { + + var routes = []; //['/', '/dashboard', '/heroes']; + var prefixRoutes = ['/detail/', '/styles.css']; + var url = req.originalUrl; + + if (routes.indexOf(url) >= 0 || prefixRoutes.some(function(r) { return url.startsWith(r); })) { + console.log('test handling ' + url); + res.setHeader('Content-Type', 'text/css'); + res.end("this should be css"); + return; + } + + console.log('test skipping ' + url); + // var parsed = require("url").parse(req.url); + // if (parsed.pathname.match(/\.less$/)) { + // return less(parsed.pathname).then(function (o) { + // res.setHeader('Content-Type', 'text/css'); + // res.end(o.css); + // }); + // } + next(); +} diff --git a/public/docs/_examples/universal/ts/example-config.json b/public/docs/_examples/universal/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/universal/ts/package.steve.json b/public/docs/_examples/universal/ts/package.steve.json new file mode 100644 index 0000000000..15ef45e849 --- /dev/null +++ b/public/docs/_examples/universal/ts/package.steve.json @@ -0,0 +1,62 @@ +{ + "name": "toh-universal", + "version": "1.0.0", + "description": "Tour-of-Heroes application with ng-universal server-side rendering", + "scripts": { + "build": "tsc -p src/", + "build:watch": "tsc -w", + "build:aot": "webpack --config webpack.config.aot.js", + "build:uni": "webpack --config webpack.config.uni.js", + + "serve": "lite-server -c=bs-config.json", + "serve:aot": "lite-server -c=bs-config.aot.js", + "serve:uni": "node src/dist/server.js", + "serve:uni2": "lite-server -c bs-config.uni.js", + + "prestart": "npm run build", + "start": "concurrently \"npm run build:watch\" \"npm run serve\"", + + "lint": "tslint ./src/**/*.ts -t verbose", + "ngc": "ngc", + "clean": "rimraf src/dist && rimraf src/app/*.js* && rimraf src/uni/*.js* && rimraf src/main.js*" + }, + "keywords": [], + "author": "", + "license": "MIT", + "dependencies": { + "@angular/common": "angular/common-builds", + "@angular/compiler": "angular/compiler-builds", + "@angular/compiler-cli": "angular/compiler-cli-builds", + "@angular/core": "angular/core-builds", + "@angular/forms": "angular/forms-builds", + "@angular/http": "angular/http-builds", + "@angular/platform-browser": "angular/platform-browser-builds", + "@angular/platform-browser-dynamic": "angular/platform-browser-dynamic-builds", + "@angular/platform-server": "angular/platform-server-builds", + "@angular/router": "angular/router-builds", + + "angular-in-memory-web-api": "^0.3.1", + "systemjs": "0.19.40", + "core-js": "^2.4.1", + "rxjs": "5.1.1", + "zone.js": "^0.7.7" + }, + "devDependencies": { + "concurrently": "^3.2.0", + "lite-server": "^2.2.2", + "typescript": "~2.1.6", + + "canonical-path": "0.0.2", + "tslint": "^3.15.1", + "lodash": "^4.16.4", + "rimraf": "^2.5.4", + + "@types/node": "^6.0.46", + + "@ngtools/webpack": "^1.2.11", + "@types/express": "^4.0.35", + "raw-loader": "^0.5.1", + "webpack": "^2.2.1" + }, + "repository": {} +} diff --git a/public/docs/_examples/universal/ts/src/app/app-routing.module.ts b/public/docs/_examples/universal/ts/src/app/app-routing.module.ts new file mode 100644 index 0000000000..bc070f6c31 --- /dev/null +++ b/public/docs/_examples/universal/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/public/docs/_examples/universal/ts/src/app/app.component.css b/public/docs/_examples/universal/ts/src/app/app.component.css new file mode 100644 index 0000000000..071e665767 --- /dev/null +++ b/public/docs/_examples/universal/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/public/docs/_examples/universal/ts/src/app/app.component.ts b/public/docs/_examples/universal/ts/src/app/app.component.ts new file mode 100644 index 0000000000..a9fe05a9a8 --- /dev/null +++ b/public/docs/_examples/universal/ts/src/app/app.component.ts @@ -0,0 +1,19 @@ +// #docplaster +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + template: ` +

    {{title}}

    + + + `, + styleUrls: ['./app.component.css'] +}) +export class AppComponent { + title = 'Tour of Heroes'; +} diff --git a/public/docs/_examples/universal/ts/src/app/app.module.ts b/public/docs/_examples/universal/ts/src/app/app.module.ts new file mode 100644 index 0000000000..e078044928 --- /dev/null +++ b/public/docs/_examples/universal/ts/src/app/app.module.ts @@ -0,0 +1,54 @@ +// #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.withServerTransition({ + appId: 'toh-universal' + }), + 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/public/docs/_examples/universal/ts/src/app/dashboard.component.css b/public/docs/_examples/universal/ts/src/app/dashboard.component.css new file mode 100644 index 0000000000..dc7fb7ce06 --- /dev/null +++ b/public/docs/_examples/universal/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/public/docs/_examples/universal/ts/src/app/dashboard.component.html b/public/docs/_examples/universal/ts/src/app/dashboard.component.html new file mode 100644 index 0000000000..db8546ccd2 --- /dev/null +++ b/public/docs/_examples/universal/ts/src/app/dashboard.component.html @@ -0,0 +1,10 @@ + +

    Top Heroes

    + + diff --git a/public/docs/_examples/universal/ts/src/app/dashboard.component.ts b/public/docs/_examples/universal/ts/src/app/dashboard.component.ts new file mode 100644 index 0000000000..9960aa77d4 --- /dev/null +++ b/public/docs/_examples/universal/ts/src/app/dashboard.component.ts @@ -0,0 +1,22 @@ +// #docregion , search +import { Component, OnInit } from '@angular/core'; + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; + +@Component({ + 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/public/docs/_examples/universal/ts/src/app/hero-detail.component.css b/public/docs/_examples/universal/ts/src/app/hero-detail.component.css new file mode 100644 index 0000000000..ab2437efd8 --- /dev/null +++ b/public/docs/_examples/universal/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/public/docs/_examples/universal/ts/src/app/hero-detail.component.html b/public/docs/_examples/universal/ts/src/app/hero-detail.component.html new file mode 100644 index 0000000000..32fe6d4391 --- /dev/null +++ b/public/docs/_examples/universal/ts/src/app/hero-detail.component.html @@ -0,0 +1,14 @@ + +
    +

    {{hero.name}} details!

    +
    + {{hero.id}}
    +
    + + +
    + + + + +
    diff --git a/public/docs/_examples/universal/ts/src/app/hero-detail.component.ts b/public/docs/_examples/universal/ts/src/app/hero-detail.component.ts new file mode 100644 index 0000000000..b3bbbd2f3d --- /dev/null +++ b/public/docs/_examples/universal/ts/src/app/hero-detail.component.ts @@ -0,0 +1,40 @@ +// #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({ + 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/public/docs/_examples/universal/ts/src/app/hero-search.component.css b/public/docs/_examples/universal/ts/src/app/hero-search.component.css new file mode 100644 index 0000000000..9bf8d13457 --- /dev/null +++ b/public/docs/_examples/universal/ts/src/app/hero-search.component.css @@ -0,0 +1,21 @@ +/* #docregion */ +.search-result{ + border-bottom: 1px solid gray; + border-left: 1px solid gray; + border-right: 1px solid gray; + width:195px; + height: 16px; + padding: 5px; + background-color: white; + cursor: pointer; +} + +.search-result:hover { + color: #eee; + background-color: #607D8B; +} + +#search-box{ + width: 200px; + height: 20px; +} diff --git a/public/docs/_examples/universal/ts/src/app/hero-search.component.html b/public/docs/_examples/universal/ts/src/app/hero-search.component.html new file mode 100644 index 0000000000..08c0560c5b --- /dev/null +++ b/public/docs/_examples/universal/ts/src/app/hero-search.component.html @@ -0,0 +1,11 @@ + +
    +

    Hero Search

    + +
    +
    + {{hero.name}} +
    +
    +
    diff --git a/public/docs/_examples/universal/ts/src/app/hero-search.component.ts b/public/docs/_examples/universal/ts/src/app/hero-search.component.ts new file mode 100644 index 0000000000..8b2d32f06b --- /dev/null +++ b/public/docs/_examples/universal/ts/src/app/hero-search.component.ts @@ -0,0 +1,69 @@ +// #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({ + 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/public/docs/_examples/universal/ts/src/app/hero-search.service.ts b/public/docs/_examples/universal/ts/src/app/hero-search.service.ts new file mode 100644 index 0000000000..d24e0fba41 --- /dev/null +++ b/public/docs/_examples/universal/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/public/docs/_examples/universal/ts/src/app/hero.service.ts b/public/docs/_examples/universal/ts/src/app/hero.service.ts new file mode 100644 index 0000000000..18af476123 --- /dev/null +++ b/public/docs/_examples/universal/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/public/docs/_examples/universal/ts/src/app/hero.ts b/public/docs/_examples/universal/ts/src/app/hero.ts new file mode 100644 index 0000000000..e3eac516da --- /dev/null +++ b/public/docs/_examples/universal/ts/src/app/hero.ts @@ -0,0 +1,4 @@ +export class Hero { + id: number; + name: string; +} diff --git a/public/docs/_examples/universal/ts/src/app/heroes.component.css b/public/docs/_examples/universal/ts/src/app/heroes.component.css new file mode 100644 index 0000000000..d2c958a911 --- /dev/null +++ b/public/docs/_examples/universal/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/public/docs/_examples/universal/ts/src/app/heroes.component.html b/public/docs/_examples/universal/ts/src/app/heroes.component.html new file mode 100644 index 0000000000..392d241d52 --- /dev/null +++ b/public/docs/_examples/universal/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/public/docs/_examples/universal/ts/src/app/heroes.component.ts b/public/docs/_examples/universal/ts/src/app/heroes.component.ts new file mode 100644 index 0000000000..6350b803c4 --- /dev/null +++ b/public/docs/_examples/universal/ts/src/app/heroes.component.ts @@ -0,0 +1,61 @@ +// #docregion +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +import { Hero } from './hero'; +import { HeroService } from './hero.service'; + +@Component({ + 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/public/docs/_examples/universal/ts/src/app/in-memory-data.service.ts b/public/docs/_examples/universal/ts/src/app/in-memory-data.service.ts new file mode 100644 index 0000000000..c915955e22 --- /dev/null +++ b/public/docs/_examples/universal/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/public/docs/_examples/universal/ts/src/index-aot.html b/public/docs/_examples/universal/ts/src/index-aot.html new file mode 100644 index 0000000000..096bfbb512 --- /dev/null +++ b/public/docs/_examples/universal/ts/src/index-aot.html @@ -0,0 +1,18 @@ + + + + + Angular Tour of Heroes + + + + + + + + Loading... + + + + + diff --git a/public/docs/_examples/universal/ts/src/index.html b/public/docs/_examples/universal/ts/src/index.html new file mode 100644 index 0000000000..727b1fc833 --- /dev/null +++ b/public/docs/_examples/universal/ts/src/index.html @@ -0,0 +1,26 @@ + + + + + + Angular Universal Tour of Heroes + + + + + + + + + + + + + + + + Loading... + + diff --git a/public/docs/_examples/universal/ts/src/main-aot.ts b/public/docs/_examples/universal/ts/src/main-aot.ts new file mode 100644 index 0000000000..fcb35dc9dc --- /dev/null +++ b/public/docs/_examples/universal/ts/src/main-aot.ts @@ -0,0 +1,5 @@ +// #docregion +import { platformBrowser } from '@angular/platform-browser'; +import { AppModuleNgFactory } from '../aot/src/app/app.module.ngfactory'; + +platformBrowser().bootstrapModuleFactory(AppModuleNgFactory); diff --git a/public/docs/_examples/universal/ts/src/main.ts b/public/docs/_examples/universal/ts/src/main.ts new file mode 100644 index 0000000000..f332d1d245 --- /dev/null +++ b/public/docs/_examples/universal/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/public/docs/_examples/universal/ts/src/uni/app.server.ts b/public/docs/_examples/universal/ts/src/uni/app.server.ts new file mode 100644 index 0000000000..585470cd09 --- /dev/null +++ b/public/docs/_examples/universal/ts/src/uni/app.server.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { APP_BASE_HREF } from '@angular/common'; +import { ServerModule } from '@angular/platform-server'; +import { AppComponent } from '../app/app.component'; +import { AppModule } from '../app/app.module'; + +@NgModule({ + imports: [ + ServerModule, + AppModule + ], + bootstrap: [ + AppComponent + ], + providers: [ + {provide: APP_BASE_HREF, useValue: '/'} + // { provide: NgModuleFactoryLoader, useClass: ServerRouterLoader } + ] +}) +export class AppServerModule { +} diff --git a/public/docs/_examples/universal/ts/src/uni/server-aot.ts b/public/docs/_examples/universal/ts/src/uni/server-aot.ts new file mode 100644 index 0000000000..38eb3f6b33 --- /dev/null +++ b/public/docs/_examples/universal/ts/src/uni/server-aot.ts @@ -0,0 +1,40 @@ +import 'zone.js/dist/zone-node'; +import { enableProdMode } from '@angular/core'; +// import { AppServerModule } from './app.server'; +import { AppServerModuleNgFactory } from '../../aot/src/uni/app.server.ngfactory'; +import * as express from 'express'; +import { ngUniversalEngine } from './universal-engine'; + +enableProdMode(); + +const server = express(); + +// set our angular engine as the handler for html files, so it will be used to render them. +server.engine('html', ngUniversalEngine({ + bootstrap: [AppServerModuleNgFactory] +})); + +// set default view directory +server.set('views', 'src'); + +// handle requests for routes in the app. ngExpressEngine does the rendering. +server.get(['/', '/dashboard', '/heroes', '/detail/:id'], (req, res) => { + res.render('index-aot.html', {req}); +}); + +// handle requests for static files +server.get(['/*.js', '/*.css'], (req, res, next) => { + let fileName: string = req.originalUrl; + console.log(fileName); + let root = fileName.startsWith('/node_modules/') ? '.' : 'src'; + res.sendFile(fileName, { root: root }, function (err) { + if (err) { + next(err); + } + }); +}); + +// start the server +server.listen(3200, () => { + console.log('listening on port 3200...'); +}); diff --git a/public/docs/_examples/universal/ts/src/uni/universal-engine.ts b/public/docs/_examples/universal/ts/src/uni/universal-engine.ts new file mode 100644 index 0000000000..3c0ac6b528 --- /dev/null +++ b/public/docs/_examples/universal/ts/src/uni/universal-engine.ts @@ -0,0 +1,38 @@ +/** + * Express/Connect middleware for rendering pages using Angular Universal + */ +import * as fs from 'fs'; +import { renderModuleFactory } from '@angular/platform-server'; + +const templateCache = {}; // cache for page templates +const outputCache = {}; // cache for rendered pages + +export function ngUniversalEngine(setupOptions: any) { + + return function (filePath: string, options: { req: Request }, callback: (err: Error, html: string) => void) { + let url: string = options.req.url; + let html: string = outputCache[url]; + if (html) { + // return already-built page for this url + console.log('from cache: ' + url); + callback(null, html); + return; + } + + console.log('building: ' + url); + if (!templateCache[filePath]) { + let file = fs.readFileSync(filePath); + templateCache[filePath] = file.toString(); + } + + // render the page via angular platform-server + let appModuleFactory = setupOptions.bootstrap[0]; + renderModuleFactory(appModuleFactory, { + document: templateCache[filePath], + url: url + }).then(str => { + outputCache[url] = str; + callback(null, str); + }); + }; +} diff --git a/public/docs/_examples/universal/ts/tsconfig-aot.json b/public/docs/_examples/universal/ts/tsconfig-aot.json new file mode 100644 index 0000000000..023bf2fb68 --- /dev/null +++ b/public/docs/_examples/universal/ts/tsconfig-aot.json @@ -0,0 +1,27 @@ +{ + "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", + "entryModule": "./src/app/app.module#AppModule", + "skipMetadataEmit" : true + } +} diff --git a/public/docs/_examples/universal/ts/tsconfig-uni.json b/public/docs/_examples/universal/ts/tsconfig-uni.json new file mode 100644 index 0000000000..fcb6a7c92d --- /dev/null +++ b/public/docs/_examples/universal/ts/tsconfig-uni.json @@ -0,0 +1,27 @@ +{ + "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/uni/app.server.ts", + "src/uni/server-aot.ts" + ], + + "angularCompilerOptions": { + "genDir": "aot", + "entryModule": "./src/app/app.module#AppModule", + "skipMetadataEmit" : true + } +} diff --git a/public/docs/_examples/universal/ts/tsconfig.json b/public/docs/_examples/universal/ts/tsconfig.json new file mode 100644 index 0000000000..58307adfe0 --- /dev/null +++ b/public/docs/_examples/universal/ts/tsconfig.json @@ -0,0 +1,20 @@ +{ + "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/*" + ] +} diff --git a/public/docs/_examples/universal/ts/webpack.config.aot.js b/public/docs/_examples/universal/ts/webpack.config.aot.js new file mode 100644 index 0000000000..f16ce50294 --- /dev/null +++ b/public/docs/_examples/universal/ts/webpack.config.aot.js @@ -0,0 +1,30 @@ +const ngtools = require('@ngtools/webpack'); +const webpack = require('webpack'); + +module.exports = { + devtool: 'source-map', + entry: { + main: './src/main-aot.ts' + }, + resolve: { + extensions: ['.ts', '.js'] + }, + target: 'node', + output: { + path: 'src/dist', + filename: 'build.js' + }, + plugins: [ + new ngtools.AotPlugin({ + tsConfigPath: './tsconfig-aot.json' + }), + new webpack.optimize.UglifyJsPlugin({ sourceMap: true }) + ], + module: { + rules: [ + { test: /\.css$/, loader: 'raw-loader' }, + { test: /\.html$/, loader: 'raw-loader' }, + { test: /\.ts$/, loader: '@ngtools/webpack' } + ] + } +} diff --git a/public/docs/_examples/universal/ts/webpack.config.uni.js b/public/docs/_examples/universal/ts/webpack.config.uni.js new file mode 100644 index 0000000000..1ccccb6f1a --- /dev/null +++ b/public/docs/_examples/universal/ts/webpack.config.uni.js @@ -0,0 +1,30 @@ +const ngtools = require('@ngtools/webpack'); +const webpack = require('webpack'); + +module.exports = { + devtool: 'source-map', + entry: { + main: ['./src/uni/app.server.ts', './src/uni/server-aot.ts'] + }, + resolve: { + extensions: ['.ts', '.js'] + }, + target: 'node', + output: { + path: 'src/dist', + filename: 'server.js' + }, + plugins: [ + new ngtools.AotPlugin({ + tsConfigPath: './tsconfig-uni.json' + }), + new webpack.optimize.UglifyJsPlugin({ sourceMap: true }) + ], + module: { + rules: [ + { test: /\.css$/, loader: 'raw-loader' }, + { test: /\.html$/, loader: 'raw-loader' }, + { test: /\.ts$/, loader: '@ngtools/webpack' } + ] + } +} diff --git a/public/docs/_examples/upgrade-module/e2e-spec.ts b/public/docs/_examples/upgrade-module/e2e-spec.ts new file mode 100644 index 0000000000..0c45fb78b8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/.gitignore b/public/docs/_examples/upgrade-module/ts/.gitignore new file mode 100644 index 0000000000..7f5c313a3e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/example-config.json b/public/docs/_examples/upgrade-module/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/upgrade-module/ts/src/app/a-to-ajs-providers/app.module.ts b/public/docs/_examples/upgrade-module/ts/src/app/a-to-ajs-providers/app.module.ts new file mode 100644 index 0000000000..91235a1485 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/a-to-ajs-providers/hero-detail.component.ts b/public/docs/_examples/upgrade-module/ts/src/app/a-to-ajs-providers/hero-detail.component.ts new file mode 100644 index 0000000000..dd7aa10d79 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/a-to-ajs-providers/heroes.ts b/public/docs/_examples/upgrade-module/ts/src/app/a-to-ajs-providers/heroes.ts new file mode 100644 index 0000000000..f5f6d87ed8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/a-to-ajs-transclusion/app.module.ts b/public/docs/_examples/upgrade-module/ts/src/app/a-to-ajs-transclusion/app.module.ts new file mode 100644 index 0000000000..599e711b5e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/a-to-ajs-transclusion/container.component.ts b/public/docs/_examples/upgrade-module/ts/src/app/a-to-ajs-transclusion/container.component.ts new file mode 100644 index 0000000000..1b740d6554 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/a-to-ajs-transclusion/hero-detail.component.ts b/public/docs/_examples/upgrade-module/ts/src/app/a-to-ajs-transclusion/hero-detail.component.ts new file mode 100644 index 0000000000..a1bec385e0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/ajs-a-hybrid-bootstrap/app.module.ts b/public/docs/_examples/upgrade-module/ts/src/app/ajs-a-hybrid-bootstrap/app.module.ts new file mode 100644 index 0000000000..7a6b82cf92 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/ajs-bootstrap/app.module.ts b/public/docs/_examples/upgrade-module/ts/src/app/ajs-bootstrap/app.module.ts new file mode 100644 index 0000000000..639b780d1b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/ajs-ng-app/app.module.ts b/public/docs/_examples/upgrade-module/ts/src/app/ajs-ng-app/app.module.ts new file mode 100644 index 0000000000..904f7578b8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/ajs-to-a-projection/app.module.ts b/public/docs/_examples/upgrade-module/ts/src/app/ajs-to-a-projection/app.module.ts new file mode 100644 index 0000000000..080f80f0ef --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/ajs-to-a-projection/hero-detail.component.ts b/public/docs/_examples/upgrade-module/ts/src/app/ajs-to-a-projection/hero-detail.component.ts new file mode 100644 index 0000000000..7a2956eb26 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/ajs-to-a-projection/main.controller.ts b/public/docs/_examples/upgrade-module/ts/src/app/ajs-to-a-projection/main.controller.ts new file mode 100644 index 0000000000..c6041ac788 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/ajs-to-a-providers/ajs-upgraded-providers.ts b/public/docs/_examples/upgrade-module/ts/src/app/ajs-to-a-providers/ajs-upgraded-providers.ts new file mode 100644 index 0000000000..52d4e74a1f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/ajs-to-a-providers/app.module.ts b/public/docs/_examples/upgrade-module/ts/src/app/ajs-to-a-providers/app.module.ts new file mode 100644 index 0000000000..4e0eca003a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/ajs-to-a-providers/hero-detail.component.ts b/public/docs/_examples/upgrade-module/ts/src/app/ajs-to-a-providers/hero-detail.component.ts new file mode 100644 index 0000000000..b02f85b99a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/ajs-to-a-providers/heroes.service.ts b/public/docs/_examples/upgrade-module/ts/src/app/ajs-to-a-providers/heroes.service.ts new file mode 100644 index 0000000000..4a258e205a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/divide-routes/app.component.ts b/public/docs/_examples/upgrade-module/ts/src/app/divide-routes/app.component.ts new file mode 100644 index 0000000000..1fae3f84c6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/divide-routes/app.module.ts b/public/docs/_examples/upgrade-module/ts/src/app/divide-routes/app.module.ts new file mode 100644 index 0000000000..7d85a23c94 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/divide-routes/hero.module.ts b/public/docs/_examples/upgrade-module/ts/src/app/divide-routes/hero.module.ts new file mode 100644 index 0000000000..33099d0a9f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/divide-routes/main.ts b/public/docs/_examples/upgrade-module/ts/src/app/divide-routes/main.ts new file mode 100644 index 0000000000..9b4d37ebc3 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/downgrade-io/app.module.ts b/public/docs/_examples/upgrade-module/ts/src/app/downgrade-io/app.module.ts new file mode 100644 index 0000000000..1e836cfc9d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/downgrade-io/hero-detail.component.ts b/public/docs/_examples/upgrade-module/ts/src/app/downgrade-io/hero-detail.component.ts new file mode 100644 index 0000000000..94082813fd --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/downgrade-io/main.controller.ts b/public/docs/_examples/upgrade-module/ts/src/app/downgrade-io/main.controller.ts new file mode 100644 index 0000000000..d50272073f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/downgrade-static/app.module.ts b/public/docs/_examples/upgrade-module/ts/src/app/downgrade-static/app.module.ts new file mode 100644 index 0000000000..4d621d2f03 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/downgrade-static/hero-detail.component.ts b/public/docs/_examples/upgrade-module/ts/src/app/downgrade-static/hero-detail.component.ts new file mode 100644 index 0000000000..df4a705f37 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/hero-detail.directive.ts b/public/docs/_examples/upgrade-module/ts/src/app/hero-detail.directive.ts new file mode 100644 index 0000000000..e1b14d2dfc --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/hero.ts b/public/docs/_examples/upgrade-module/ts/src/app/hero.ts new file mode 100644 index 0000000000..5dcb5664eb --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/upgrade-io/app.module.ts b/public/docs/_examples/upgrade-module/ts/src/app/upgrade-io/app.module.ts new file mode 100644 index 0000000000..599e711b5e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/upgrade-io/container.component.ts b/public/docs/_examples/upgrade-module/ts/src/app/upgrade-io/container.component.ts new file mode 100644 index 0000000000..8d76085174 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/upgrade-io/hero-detail.component.ts b/public/docs/_examples/upgrade-module/ts/src/app/upgrade-io/hero-detail.component.ts new file mode 100644 index 0000000000..90c3273010 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/upgrade-static/app.module.ts b/public/docs/_examples/upgrade-module/ts/src/app/upgrade-static/app.module.ts new file mode 100644 index 0000000000..401963c35e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/upgrade-static/container.component.ts b/public/docs/_examples/upgrade-module/ts/src/app/upgrade-static/container.component.ts new file mode 100644 index 0000000000..c28e5ea42d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/upgrade-static/hero-detail.component.ts b/public/docs/_examples/upgrade-module/ts/src/app/upgrade-static/hero-detail.component.ts new file mode 100644 index 0000000000..02ddd293eb --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/app/villain.ts b/public/docs/_examples/upgrade-module/ts/src/app/villain.ts new file mode 100644 index 0000000000..ef3d014f11 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-module/ts/src/index-a-to-ajs-providers.html b/public/docs/_examples/upgrade-module/ts/src/index-a-to-ajs-providers.html new file mode 100644 index 0000000000..693655a53c --- /dev/null +++ b/public/docs/_examples/upgrade-module/ts/src/index-a-to-ajs-providers.html @@ -0,0 +1,31 @@ + + + + Angular Upgrade + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/upgrade-module/ts/src/index-a-to-ajs-transclusion.html b/public/docs/_examples/upgrade-module/ts/src/index-a-to-ajs-transclusion.html new file mode 100644 index 0000000000..43bc09c64b --- /dev/null +++ b/public/docs/_examples/upgrade-module/ts/src/index-a-to-ajs-transclusion.html @@ -0,0 +1,30 @@ + + + + Angular Upgrade + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/upgrade-module/ts/src/index-ajs-a-hybrid-bootstrap.html b/public/docs/_examples/upgrade-module/ts/src/index-ajs-a-hybrid-bootstrap.html new file mode 100644 index 0000000000..1115e931f5 --- /dev/null +++ b/public/docs/_examples/upgrade-module/ts/src/index-ajs-a-hybrid-bootstrap.html @@ -0,0 +1,28 @@ + + + + Angular Upgrade + + + + + + + + + + + + + + + + + + +
    {{ mainCtrl.message }}
    + + diff --git a/public/docs/_examples/upgrade-module/ts/src/index-ajs-to-a-projection.html b/public/docs/_examples/upgrade-module/ts/src/index-ajs-to-a-projection.html new file mode 100644 index 0000000000..3456957077 --- /dev/null +++ b/public/docs/_examples/upgrade-module/ts/src/index-ajs-to-a-projection.html @@ -0,0 +1,37 @@ + + + + Angular Upgrade + + + + + + + + + + + + + + + + + + + + +
    + + +

    {{mainCtrl.hero.description}}

    +
    +
    + +
    + + diff --git a/public/docs/_examples/upgrade-module/ts/src/index-ajs-to-a-providers.html b/public/docs/_examples/upgrade-module/ts/src/index-ajs-to-a-providers.html new file mode 100644 index 0000000000..7d1f2b7a05 --- /dev/null +++ b/public/docs/_examples/upgrade-module/ts/src/index-ajs-to-a-providers.html @@ -0,0 +1,31 @@ + + + + Angular Upgrade + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/upgrade-module/ts/src/index-bootstrap.html b/public/docs/_examples/upgrade-module/ts/src/index-bootstrap.html new file mode 100644 index 0000000000..bc4614e562 --- /dev/null +++ b/public/docs/_examples/upgrade-module/ts/src/index-bootstrap.html @@ -0,0 +1,13 @@ + + + + + + + + + + +
    {{ mainCtrl.message }}
    + + diff --git a/public/docs/_examples/upgrade-module/ts/src/index-divide-routes.html b/public/docs/_examples/upgrade-module/ts/src/index-divide-routes.html new file mode 100644 index 0000000000..ee9a56ab36 --- /dev/null +++ b/public/docs/_examples/upgrade-module/ts/src/index-divide-routes.html @@ -0,0 +1,31 @@ + + + + Angular 2 Upgrade + + + + + + + + + + + + + + + + + + + + + Loading... + + + diff --git a/public/docs/_examples/upgrade-module/ts/src/index-downgrade-io.html b/public/docs/_examples/upgrade-module/ts/src/index-downgrade-io.html new file mode 100644 index 0000000000..71e55db355 --- /dev/null +++ b/public/docs/_examples/upgrade-module/ts/src/index-downgrade-io.html @@ -0,0 +1,44 @@ + + + + Angular Upgrade + + + + + + + + + + + + + + + + + + + + +
    + + +
    + + +
    + + +
    + +
    + + diff --git a/public/docs/_examples/upgrade-module/ts/src/index-downgrade-static.html b/public/docs/_examples/upgrade-module/ts/src/index-downgrade-static.html new file mode 100644 index 0000000000..47ec27e8f4 --- /dev/null +++ b/public/docs/_examples/upgrade-module/ts/src/index-downgrade-static.html @@ -0,0 +1,32 @@ + + + + Angular Upgrade + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/upgrade-module/ts/src/index-ng-app.html b/public/docs/_examples/upgrade-module/ts/src/index-ng-app.html new file mode 100644 index 0000000000..0bd661faae --- /dev/null +++ b/public/docs/_examples/upgrade-module/ts/src/index-ng-app.html @@ -0,0 +1,15 @@ + + + + + + + + + + +
    + {{ mainCtrl.message }} +
    + + diff --git a/public/docs/_examples/upgrade-module/ts/src/index-upgrade-io.html b/public/docs/_examples/upgrade-module/ts/src/index-upgrade-io.html new file mode 100644 index 0000000000..2dca0d44d4 --- /dev/null +++ b/public/docs/_examples/upgrade-module/ts/src/index-upgrade-io.html @@ -0,0 +1,30 @@ + + + + Angular Upgrade + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/upgrade-module/ts/src/index-upgrade-static.html b/public/docs/_examples/upgrade-module/ts/src/index-upgrade-static.html new file mode 100644 index 0000000000..9134cfbc32 --- /dev/null +++ b/public/docs/_examples/upgrade-module/ts/src/index-upgrade-static.html @@ -0,0 +1,30 @@ + + + + Angular Upgrade + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/upgrade-module/ts/src/systemjs.config.1.js b/public/docs/_examples/upgrade-module/ts/src/systemjs.config.1.js new file mode 100644 index 0000000000..a8c57d1792 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/README.md b/public/docs/_examples/upgrade-phonecat-1-typescript/README.md new file mode 100644 index 0000000000..f3433b901a --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/e2e-spec.ts b/public/docs/_examples/upgrade-phonecat-1-typescript/e2e-spec.ts new file mode 100644 index 0000000000..4598a7f6dc --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/.gitignore b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/.gitignore new file mode 100644 index 0000000000..448efbe632 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/.gitignore @@ -0,0 +1 @@ +!karma.conf.ajs.js diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/app.animations.css b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/app.animations.css new file mode 100644 index 0000000000..175320b509 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/app.animations.ts b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/app.animations.ts new file mode 100644 index 0000000000..f0739b6405 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/app.config.ts b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/app.config.ts new file mode 100644 index 0000000000..1d16f67fbe --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/app.css b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/app.css new file mode 100644 index 0000000000..f4b45b02a5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/app.module.ts b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/app.module.ts new file mode 100644 index 0000000000..ab6d353eeb --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/core/checkmark/checkmark.filter.spec.ts b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/core/checkmark/checkmark.filter.spec.ts new file mode 100644 index 0000000000..1b2d77c30c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/core/checkmark/checkmark.filter.ts b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/core/checkmark/checkmark.filter.ts new file mode 100644 index 0000000000..3114dc9681 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/core/core.module.ts b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/core/core.module.ts new file mode 100644 index 0000000000..84a91dc7a6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/core/phone/phone.module.ts b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/core/phone/phone.module.ts new file mode 100644 index 0000000000..0b6b348899 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/core/phone/phone.service.spec.ts b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/core/phone/phone.service.spec.ts new file mode 100644 index 0000000000..312036d71d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/core/phone/phone.service.ts b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/core/phone/phone.service.ts new file mode 100644 index 0000000000..c6204bc896 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/.gitkeep b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.1.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.1.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.2.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.2.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.3.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.3.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.4.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/dell-streak-7.4.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-atrix-4g.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-atrix-4g.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-atrix-4g.1.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-atrix-4g.1.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-atrix-4g.2.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-atrix-4g.2.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-atrix-4g.3.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-atrix-4g.3.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.1.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.1.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.2.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.2.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.3.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.3.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.4.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.4.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.5.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom-with-wi-fi.5.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom.1.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom.1.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom.2.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/motorola-xoom.2.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/nexus-s.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/nexus-s.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/nexus-s.1.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/nexus-s.1.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/nexus-s.2.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/nexus-s.2.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/nexus-s.3.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/img/phones/nexus-s.3.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/index.html b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/index.html new file mode 100644 index 0000000000..82717fb7ee --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/index.html @@ -0,0 +1,35 @@ + + + + + Google Phone Gallery + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phone-detail/phone-detail.component.spec.ts b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phone-detail/phone-detail.component.spec.ts new file mode 100644 index 0000000000..0998b638f0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phone-detail/phone-detail.component.ts b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phone-detail/phone-detail.component.ts new file mode 100644 index 0000000000..079b31e2c2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phone-detail/phone-detail.module.ts b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phone-detail/phone-detail.module.ts new file mode 100644 index 0000000000..fd7cb3b920 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phone-detail/phone-detail.template.html b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phone-detail/phone-detail.template.html new file mode 100644 index 0000000000..f48657803c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.component.spec.ts b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.component.spec.ts new file mode 100644 index 0000000000..19e1890817 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.component.ts b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.component.ts new file mode 100644 index 0000000000..e2f2855ae6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.module.ts b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.module.ts new file mode 100644 index 0000000000..8ade7c5b88 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.template.html b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.template.html new file mode 100644 index 0000000000..90548f9f91 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phone-list/phone-list.template.html @@ -0,0 +1,36 @@ +
    +
    +
    + + +

    + Search: + +

    + +

    + Sort by: + +

    + +
    +
    + + + + +
    +
    +
    diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phones/dell-streak-7.json b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phones/dell-streak-7.json new file mode 100644 index 0000000000..a32eb6ff98 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phones/motorola-atrix-4g.json b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phones/motorola-atrix-4g.json new file mode 100644 index 0000000000..ccca00e3b2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phones/motorola-xoom-with-wi-fi.json b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phones/motorola-xoom-with-wi-fi.json new file mode 100644 index 0000000000..4ba9c8d5b5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phones/motorola-xoom.json b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phones/motorola-xoom.json new file mode 100644 index 0000000000..f0f0c8711d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phones/nexus-s.json b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phones/nexus-s.json new file mode 100644 index 0000000000..5e712e2ff8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phones/phones.json b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/app/phones/phones.json new file mode 100644 index 0000000000..339b94fbb5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/example-config.json b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/example-config.json new file mode 100644 index 0000000000..a8781b9a31 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/example-config.json @@ -0,0 +1,4 @@ +{ + "build": "build:upgrade", + "run": "serve:upgrade" +} diff --git a/public/docs/_examples/upgrade-phonecat-1-typescript/ts/karma.conf.ajs.js b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/karma.conf.ajs.js new file mode 100644 index 0000000000..dc829d1983 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/run-unit-tests.sh b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/run-unit-tests.sh new file mode 100755 index 0000000000..239e5ff7d7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/tsconfig.ajs.json b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/tsconfig.ajs.json new file mode 100644 index 0000000000..53da36ca95 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-1-typescript/ts/tsconfig.json b/public/docs/_examples/upgrade-phonecat-1-typescript/ts/tsconfig.json new file mode 100644 index 0000000000..be11d1eb49 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/README.md b/public/docs/_examples/upgrade-phonecat-2-hybrid/README.md new file mode 100644 index 0000000000..b005739148 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/e2e-spec.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/e2e-spec.ts new file mode 100644 index 0000000000..2ec8e37977 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/.gitignore b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/.gitignore new file mode 100644 index 0000000000..927d62d641 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/aot/index.html b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/aot/index.html new file mode 100644 index 0000000000..fdc9584928 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/aot/index.html @@ -0,0 +1,40 @@ + + + + + + + + + Google Phone Gallery + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/ajs-upgraded-providers.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/ajs-upgraded-providers.ts new file mode 100644 index 0000000000..f6e1654d74 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/app.animations.css b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/app.animations.css new file mode 100644 index 0000000000..175320b509 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/app.animations.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/app.animations.ts new file mode 100644 index 0000000000..f0739b6405 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/app.config.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/app.config.ts new file mode 100644 index 0000000000..458ed94250 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/app.css b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/app.css new file mode 100644 index 0000000000..f4b45b02a5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/app.module.ajs.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/app.module.ajs.ts new file mode 100644 index 0000000000..089c3c7d85 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/app.module.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/app.module.ts new file mode 100644 index 0000000000..ea570e3cbf --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/core/checkmark/checkmark.pipe.spec.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/core/checkmark/checkmark.pipe.spec.ts new file mode 100644 index 0000000000..f7485ec2ba --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/core/checkmark/checkmark.pipe.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/core/checkmark/checkmark.pipe.ts new file mode 100644 index 0000000000..888017e15c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/core/core.module.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/core/core.module.ts new file mode 100644 index 0000000000..84a91dc7a6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/core/phone/phone.module.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/core/phone/phone.module.ts new file mode 100644 index 0000000000..0b6b348899 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/core/phone/phone.service.spec.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/core/phone/phone.service.spec.ts new file mode 100644 index 0000000000..a0c1655c20 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/core/phone/phone.service.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/core/phone/phone.service.ts new file mode 100644 index 0000000000..c4673475fb --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/.gitkeep b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.1.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.1.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.2.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.2.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.3.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.3.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.4.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-streak-7.4.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-venue.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/dell-venue.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/droid-2-global-by-motorola.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/droid-2-global-by-motorola.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/droid-pro-by-motorola.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/droid-pro-by-motorola.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/lg-axis.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/lg-axis.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-atrix-4g.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-atrix-4g.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-atrix-4g.1.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-atrix-4g.1.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-atrix-4g.2.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-atrix-4g.2.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-atrix-4g.3.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-atrix-4g.3.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-bravo-with-motoblur.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-bravo-with-motoblur.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-charm-with-motoblur.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-charm-with-motoblur.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-defy-with-motoblur.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-defy-with-motoblur.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.1.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.1.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.2.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.2.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.3.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.3.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.4.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.4.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.5.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom-with-wi-fi.5.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom.1.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom.1.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom.2.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/motorola-xoom.2.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/nexus-s.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/nexus-s.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/nexus-s.1.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/nexus-s.1.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/nexus-s.2.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/nexus-s.2.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/nexus-s.3.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/nexus-s.3.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-galaxy-tab.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-galaxy-tab.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-gem.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-gem.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-transform.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/samsung-transform.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/sanyo-zio.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/sanyo-zio.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/t-mobile-g2.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/t-mobile-g2.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/t-mobile-mytouch-4g.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/img/phones/t-mobile-mytouch-4g.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/main-aot.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/main-aot.ts new file mode 100644 index 0000000000..23a741c684 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/main.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/main.ts new file mode 100644 index 0000000000..886e8ffac8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.component.ajs.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.component.ajs.ts new file mode 100644 index 0000000000..80282858c4 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.component.spec.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.component.spec.ts new file mode 100644 index 0000000000..e3b9143a94 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.component.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.component.ts new file mode 100644 index 0000000000..aa20ebb801 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.component.ts @@ -0,0 +1,41 @@ +// #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({ + 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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.module.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.module.ts new file mode 100644 index 0000000000..fd7cb3b920 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.template.html b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-detail/phone-detail.template.html new file mode 100644 index 0000000000..46a96d66c3 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.component.ajs.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.component.ajs.ts new file mode 100644 index 0000000000..81eac1cd81 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.component.spec.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.component.spec.ts new file mode 100644 index 0000000000..2bb9d2b62f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.component.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.component.ts new file mode 100644 index 0000000000..1f51c6faa2 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.component.ts @@ -0,0 +1,72 @@ +// #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({ + 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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.module.ts b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.module.ts new file mode 100644 index 0000000000..8ade7c5b88 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.template.html b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.template.html new file mode 100644 index 0000000000..8f02113985 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phone-list/phone-list.template.html @@ -0,0 +1,40 @@ +
    +
    +
    + + + +

    + Search: + +

    + +

    + Sort by: + +

    + + +
    +
    + + + + + + +
    +
    +
    diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phones/dell-streak-7.json b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phones/dell-streak-7.json new file mode 100644 index 0000000000..a32eb6ff98 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phones/motorola-atrix-4g.json b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phones/motorola-atrix-4g.json new file mode 100644 index 0000000000..ccca00e3b2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phones/motorola-xoom-with-wi-fi.json b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phones/motorola-xoom-with-wi-fi.json new file mode 100644 index 0000000000..4ba9c8d5b5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phones/motorola-xoom.json b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phones/motorola-xoom.json new file mode 100644 index 0000000000..f0f0c8711d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phones/nexus-s.json b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phones/nexus-s.json new file mode 100644 index 0000000000..5e712e2ff8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phones/phones.json b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/app/phones/phones.json new file mode 100644 index 0000000000..339b94fbb5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/bs-config.aot.json b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/bs-config.aot.json new file mode 100644 index 0000000000..e59a7403a0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/copy-dist-files.js b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/copy-dist-files.js new file mode 100644 index 0000000000..a857af085c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/example-config.json b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/example-config.json new file mode 100644 index 0000000000..401c14f835 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/example-config.json @@ -0,0 +1,5 @@ +{ + "build": "build:upgrade", + "run": "serve:upgrade", + "unittesting": true +} diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/index.html b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/index.html new file mode 100644 index 0000000000..f747e641e0 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/index.html @@ -0,0 +1,51 @@ + + + + + + + + + Google Phone Gallery + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/karma-test-shim.1.js b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/karma-test-shim.1.js new file mode 100644 index 0000000000..19fcc89fe9 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/karma-test-shim.1.js @@ -0,0 +1,89 @@ +// #docregion +// /*global jasmine, __karma__, window*/ +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 = 1000; + +var builtPath = '/base/app/'; + +__karma__.loaded = function () { }; + +function isJsFile(path) { + return path.slice(-3) == '.js'; +} + +function isSpecFile(path) { + return /\.spec\.(.*\.)?js$/.test(path); +} + +function isBuiltFile(path) { + return isJsFile(path) && (path.substr(0, builtPath.length) == builtPath); +} + +var allSpecFiles = Object.keys(window.__karma__.files) + .filter(isSpecFile) + .filter(isBuiltFile); + +System.config({ + baseURL: '/base', + // 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( + 'Warning: System.import could not load the optional "systemjs.config.extras.js". Did you omit it by accident? Continuing 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 and start karma +function initTesting () { + return Promise.all( + allSpecFiles.map(function (moduleName) { + return System.import(moduleName); + }) + ) + .then(__karma__.start, __karma__.error); +} diff --git a/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/karma.conf.ajs.js b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/karma.conf.ajs.js new file mode 100644 index 0000000000..a52abf73ce --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/package.ajs.json b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/package.ajs.json new file mode 100644 index 0000000000..54f73776dd --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/rollup-config.js b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/rollup-config.js new file mode 100644 index 0000000000..aeb227689c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/run-unit-tests.sh b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/run-unit-tests.sh new file mode 100755 index 0000000000..239e5ff7d7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/systemjs.config.1.js b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/systemjs.config.1.js new file mode 100644 index 0000000000..8197c44a89 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/tsconfig-aot.json b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/tsconfig-aot.json new file mode 100644 index 0000000000..58f9de3309 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/tsconfig.json b/public/docs/_examples/upgrade-phonecat-2-hybrid/ts/tsconfig.json new file mode 100644 index 0000000000..f267800f14 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/README.md b/public/docs/_examples/upgrade-phonecat-3-router/README.md new file mode 100644 index 0000000000..4f8e4928af --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/e2e-spec.ts b/public/docs/_examples/upgrade-phonecat-3-router/e2e-spec.ts new file mode 100644 index 0000000000..2ec8e37977 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/.gitignore b/public/docs/_examples/upgrade-phonecat-3-router/ts/.gitignore new file mode 100644 index 0000000000..7f5c313a3e --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/aot/bs-config.json b/public/docs/_examples/upgrade-phonecat-3-router/ts/aot/bs-config.json new file mode 100644 index 0000000000..7c85d6eddd --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/aot/index.html b/public/docs/_examples/upgrade-phonecat-3-router/ts/aot/index.html new file mode 100644 index 0000000000..0d6cf5946e --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-3-router/ts/aot/index.html @@ -0,0 +1,38 @@ + + + + + + + + + Google Phone Gallery + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/ajs-upgraded-providers.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/ajs-upgraded-providers.ts new file mode 100644 index 0000000000..f6e1654d74 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/app-routing.module.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/app-routing.module.ts new file mode 100644 index 0000000000..0485b1848d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/app.animations.css b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/app.animations.css new file mode 100644 index 0000000000..175320b509 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/app.animations.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/app.animations.ts new file mode 100644 index 0000000000..f0739b6405 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/app.component.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/app.component.ts new file mode 100644 index 0000000000..6ecd19ab80 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/app.config.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/app.config.ts new file mode 100644 index 0000000000..51a5d82422 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/app.css b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/app.css new file mode 100644 index 0000000000..f4b45b02a5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/app.module.ajs.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/app.module.ajs.ts new file mode 100644 index 0000000000..e493137966 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/app.module.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/app.module.ts new file mode 100644 index 0000000000..e0bb64f4e4 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/core/checkmark/checkmark.pipe.spec.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/core/checkmark/checkmark.pipe.spec.ts new file mode 100644 index 0000000000..f7485ec2ba --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/core/checkmark/checkmark.pipe.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/core/checkmark/checkmark.pipe.ts new file mode 100644 index 0000000000..888017e15c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/core/core.module.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/core/core.module.ts new file mode 100644 index 0000000000..84a91dc7a6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/core/phone/phone.module.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/core/phone/phone.module.ts new file mode 100644 index 0000000000..0b6b348899 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/core/phone/phone.service.spec.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/core/phone/phone.service.spec.ts new file mode 100644 index 0000000000..a0c1655c20 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/core/phone/phone.service.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/core/phone/phone.service.ts new file mode 100644 index 0000000000..ccbd1fdd72 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/.gitkeep b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.1.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.1.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.2.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.2.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.3.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.3.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.4.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-streak-7.4.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-venue.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/dell-venue.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/droid-2-global-by-motorola.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/droid-2-global-by-motorola.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/droid-pro-by-motorola.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/droid-pro-by-motorola.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/lg-axis.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/lg-axis.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-atrix-4g.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-atrix-4g.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-atrix-4g.1.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-atrix-4g.1.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-atrix-4g.2.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-atrix-4g.2.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-atrix-4g.3.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-atrix-4g.3.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-bravo-with-motoblur.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-bravo-with-motoblur.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-charm-with-motoblur.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-charm-with-motoblur.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-defy-with-motoblur.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-defy-with-motoblur.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.1.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.1.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.2.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.2.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.3.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.3.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.4.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.4.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.5.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom-with-wi-fi.5.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom.1.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom.1.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom.2.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/motorola-xoom.2.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/nexus-s.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/nexus-s.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/nexus-s.1.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/nexus-s.1.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/nexus-s.2.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/nexus-s.2.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/nexus-s.3.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/nexus-s.3.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-galaxy-tab.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-galaxy-tab.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-gem.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-gem.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-transform.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/samsung-transform.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/sanyo-zio.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/sanyo-zio.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/t-mobile-g2.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/t-mobile-g2.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/t-mobile-mytouch-4g.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/img/phones/t-mobile-mytouch-4g.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/main-aot.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/main-aot.ts new file mode 100644 index 0000000000..23a741c684 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/main.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/main.ts new file mode 100644 index 0000000000..51b8e4d2a8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.component.spec.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.component.spec.ts new file mode 100644 index 0000000000..e3b9143a94 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.component.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.component.ts new file mode 100644 index 0000000000..32a3d40790 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.component.ts @@ -0,0 +1,33 @@ +// #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({ + 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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.module.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.module.ts new file mode 100644 index 0000000000..fd7cb3b920 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.template.html b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phone-detail/phone-detail.template.html new file mode 100644 index 0000000000..46a96d66c3 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.component.spec.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.component.spec.ts new file mode 100644 index 0000000000..2bb9d2b62f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.component.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.component.ts new file mode 100644 index 0000000000..40f522bec8 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.component.ts @@ -0,0 +1,60 @@ +// #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({ + 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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.module.ts b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.module.ts new file mode 100644 index 0000000000..8ade7c5b88 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.template.html b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.template.html new file mode 100644 index 0000000000..2678d384c2 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phone-list/phone-list.template.html @@ -0,0 +1,38 @@ +
    +
    +
    + + +

    + Search: + +

    + +

    + Sort by: + +

    + +
    +
    + + + + + + +
    +
    +
    diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phones/dell-streak-7.json b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phones/dell-streak-7.json new file mode 100644 index 0000000000..a32eb6ff98 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phones/motorola-atrix-4g.json b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phones/motorola-atrix-4g.json new file mode 100644 index 0000000000..ccca00e3b2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phones/motorola-xoom-with-wi-fi.json b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phones/motorola-xoom-with-wi-fi.json new file mode 100644 index 0000000000..4ba9c8d5b5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phones/motorola-xoom.json b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phones/motorola-xoom.json new file mode 100644 index 0000000000..f0f0c8711d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phones/nexus-s.json b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phones/nexus-s.json new file mode 100644 index 0000000000..5e712e2ff8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phones/phones.json b/public/docs/_examples/upgrade-phonecat-3-router/ts/app/phones/phones.json new file mode 100644 index 0000000000..339b94fbb5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/bs-config.aot.json b/public/docs/_examples/upgrade-phonecat-3-router/ts/bs-config.aot.json new file mode 100644 index 0000000000..e59a7403a0 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/copy-dist-files.js b/public/docs/_examples/upgrade-phonecat-3-router/ts/copy-dist-files.js new file mode 100644 index 0000000000..a857af085c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/example-config.json b/public/docs/_examples/upgrade-phonecat-3-router/ts/example-config.json new file mode 100644 index 0000000000..401c14f835 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-3-router/ts/example-config.json @@ -0,0 +1,5 @@ +{ + "build": "build:upgrade", + "run": "serve:upgrade", + "unittesting": true +} diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/index.html b/public/docs/_examples/upgrade-phonecat-3-router/ts/index.html new file mode 100644 index 0000000000..572c80d315 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-3-router/ts/index.html @@ -0,0 +1,44 @@ + + + + + + + + + Google Phone Gallery + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/upgrade-phonecat-3-router/ts/rollup-config.js b/public/docs/_examples/upgrade-phonecat-3-router/ts/rollup-config.js new file mode 100644 index 0000000000..aeb227689c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/systemjs.config.1.js b/public/docs/_examples/upgrade-phonecat-3-router/ts/systemjs.config.1.js new file mode 100644 index 0000000000..b801d42bad --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/tsconfig-aot.json b/public/docs/_examples/upgrade-phonecat-3-router/ts/tsconfig-aot.json new file mode 100644 index 0000000000..58f9de3309 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-3-router/ts/tsconfig.json b/public/docs/_examples/upgrade-phonecat-3-router/ts/tsconfig.json new file mode 100644 index 0000000000..f267800f14 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/README.md b/public/docs/_examples/upgrade-phonecat-4-final/README.md new file mode 100644 index 0000000000..7448da44e6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/e2e-spec.ts b/public/docs/_examples/upgrade-phonecat-4-final/e2e-spec.ts new file mode 100644 index 0000000000..6f47c54d02 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/app-routing.module.ts b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/app-routing.module.ts new file mode 100644 index 0000000000..f64d82e253 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/app.component.ts b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/app.component.ts new file mode 100644 index 0000000000..c476614121 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/app.css b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/app.css new file mode 100644 index 0000000000..f4b45b02a5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/app.module.ts b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/app.module.ts new file mode 100644 index 0000000000..607ecab8c9 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/core/checkmark/checkmark.pipe.spec.ts b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/core/checkmark/checkmark.pipe.spec.ts new file mode 100644 index 0000000000..75150500a6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/core/checkmark/checkmark.pipe.ts b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/core/checkmark/checkmark.pipe.ts new file mode 100644 index 0000000000..888017e15c --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/core/phone/phone.service.spec.ts b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/core/phone/phone.service.spec.ts new file mode 100644 index 0000000000..e3a422965b --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/core/phone/phone.service.ts b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/core/phone/phone.service.ts new file mode 100644 index 0000000000..83a837afb7 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/.gitkeep b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.1.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.1.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.2.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.2.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.3.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.3.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.4.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-streak-7.4.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-venue.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/dell-venue.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/droid-2-global-by-motorola.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/droid-2-global-by-motorola.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/droid-pro-by-motorola.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/droid-pro-by-motorola.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/lg-axis.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/lg-axis.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-atrix-4g.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-atrix-4g.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-atrix-4g.1.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-atrix-4g.1.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-atrix-4g.2.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-atrix-4g.2.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-atrix-4g.3.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-atrix-4g.3.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-bravo-with-motoblur.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-bravo-with-motoblur.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-charm-with-motoblur.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-charm-with-motoblur.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-defy-with-motoblur.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-defy-with-motoblur.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.1.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.1.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.2.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.2.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.3.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.3.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.4.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.4.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.5.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom-with-wi-fi.5.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom.1.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom.1.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom.2.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/motorola-xoom.2.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/nexus-s.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/nexus-s.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/nexus-s.1.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/nexus-s.1.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/nexus-s.2.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/nexus-s.2.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/nexus-s.3.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/nexus-s.3.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-galaxy-tab.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-galaxy-tab.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-gem.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-gem.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-transform.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/samsung-transform.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/sanyo-zio.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/sanyo-zio.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/t-mobile-g2.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/t-mobile-g2.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/t-mobile-mytouch-4g.0.jpg b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/img/phones/t-mobile-mytouch-4g.0.jpg differ diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/main.ts b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/main.ts new file mode 100644 index 0000000000..08be7a99ba --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phone-detail/phone-detail.component.spec.ts b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phone-detail/phone-detail.component.spec.ts new file mode 100644 index 0000000000..e3b9143a94 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phone-detail/phone-detail.component.ts b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phone-detail/phone-detail.component.ts new file mode 100644 index 0000000000..dd47f746f6 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phone-detail/phone-detail.component.ts @@ -0,0 +1,27 @@ +// #docplaster +// #docregion +import { Component } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; + +import { Phone, PhoneData } from '../core/phone/phone.service'; + +@Component({ + 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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phone-detail/phone-detail.template.html b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phone-detail/phone-detail.template.html new file mode 100644 index 0000000000..46a96d66c3 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phone-list/phone-list.component.spec.ts b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phone-list/phone-list.component.spec.ts new file mode 100644 index 0000000000..834c93df8f --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phone-list/phone-list.component.ts b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phone-list/phone-list.component.ts new file mode 100644 index 0000000000..6cfd172027 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phone-list/phone-list.component.ts @@ -0,0 +1,53 @@ +// #docregion +import { Component } from '@angular/core'; + +import { Phone, PhoneData } from '../core/phone/phone.service'; + +@Component({ + 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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phone-list/phone-list.template.html b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phone-list/phone-list.template.html new file mode 100644 index 0000000000..b4a994b297 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phone-list/phone-list.template.html @@ -0,0 +1,40 @@ +
    +
    +
    + + + +

    + Search: + +

    + +

    + Sort by: + +

    + + +
    +
    + + + + + + +
    +
    +
    diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phones/dell-streak-7.json b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phones/dell-streak-7.json new file mode 100644 index 0000000000..a32eb6ff98 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phones/motorola-atrix-4g.json b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phones/motorola-atrix-4g.json new file mode 100644 index 0000000000..ccca00e3b2 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phones/motorola-xoom-with-wi-fi.json b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phones/motorola-xoom-with-wi-fi.json new file mode 100644 index 0000000000..4ba9c8d5b5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phones/motorola-xoom.json b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phones/motorola-xoom.json new file mode 100644 index 0000000000..f0f0c8711d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phones/nexus-s.json b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phones/nexus-s.json new file mode 100644 index 0000000000..5e712e2ff8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phones/phones.json b/public/docs/_examples/upgrade-phonecat-4-final/ts/app/phones/phones.json new file mode 100644 index 0000000000..339b94fbb5 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/example-config.json b/public/docs/_examples/upgrade-phonecat-4-final/ts/example-config.json new file mode 100644 index 0000000000..401c14f835 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-4-final/ts/example-config.json @@ -0,0 +1,5 @@ +{ + "build": "build:upgrade", + "run": "serve:upgrade", + "unittesting": true +} diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/index.html b/public/docs/_examples/upgrade-phonecat-4-final/ts/index.html new file mode 100644 index 0000000000..fee59370e6 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-4-final/ts/index.html @@ -0,0 +1,36 @@ + + + + + + + + + + Google Phone Gallery + + + + + + + + + + + + + + + + + + + diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/run-unit-tests.sh b/public/docs/_examples/upgrade-phonecat-4-final/ts/run-unit-tests.sh new file mode 100644 index 0000000000..00a5abb7bc --- /dev/null +++ b/public/docs/_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/public/docs/_examples/upgrade-phonecat-4-final/ts/systemjs.config.1.js b/public/docs/_examples/upgrade-phonecat-4-final/ts/systemjs.config.1.js new file mode 100644 index 0000000000..c48bb7ca39 --- /dev/null +++ b/public/docs/_examples/upgrade-phonecat-4-final/ts/systemjs.config.1.js @@ -0,0 +1,55 @@ +/** + * 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: { + 'ng-loader': '../src/systemjs-angular-loader.js', + 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', + '@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', + meta: { + './*.js': { + loader: 'ng-loader' + } + } + }, + rxjs: { + defaultExtension: 'js' + }, + 'angular-in-memory-web-api': { + main: './index.js', + defaultExtension: 'js' + } + } + }); +})(this); diff --git a/public/docs/_examples/upgrade-phonecat-4-final/ts/tsconfig.json b/public/docs/_examples/upgrade-phonecat-4-final/ts/tsconfig.json new file mode 100644 index 0000000000..f267800f14 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/user-input/e2e-spec.ts b/public/docs/_examples/user-input/e2e-spec.ts new file mode 100644 index 0000000000..3f178382c7 --- /dev/null +++ b/public/docs/_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 |'); + }); + + it('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/public/docs/_examples/user-input/ts/.gitignore b/public/docs/_examples/user-input/ts/.gitignore deleted file mode 100644 index 6724ce3596..0000000000 --- a/public/docs/_examples/user-input/ts/.gitignore +++ /dev/null @@ -1 +0,0 @@ -src/**/*.js \ No newline at end of file diff --git a/public/docs/_examples/user-input/ts/example-config.json b/public/docs/_examples/user-input/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/user-input/ts/package.json b/public/docs/_examples/user-input/ts/package.json deleted file mode 100644 index 245a10d30a..0000000000 --- a/public/docs/_examples/user-input/ts/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "angular2-template-syntax", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "tsc": "tsc -p src -w", - "start": "live-server --open=src" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "angular2": "2.0.0-alpha.44", - "systemjs": "0.19.2" - }, - "devDependencies": { - "live-server": "^0.8.1", - "typescript": "^1.6.2" - } -} diff --git a/public/docs/_examples/user-input/ts/plnkr.json b/public/docs/_examples/user-input/ts/plnkr.json new file mode 100644 index 0000000000..dd8f063d37 --- /dev/null +++ b/public/docs/_examples/user-input/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "User Input", + "basePath": "src/", + "files": [ + "!**/*.d.ts", + "!**/*.js" + ], + "tags": ["input"] +} diff --git a/public/docs/_examples/user-input/ts/src/app/app.component.html b/public/docs/_examples/user-input/ts/src/app/app.component.html new file mode 100644 index 0000000000..036b04c510 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/user-input/ts/src/app/app.component.ts b/public/docs/_examples/user-input/ts/src/app/app.component.ts new file mode 100644 index 0000000000..5f885d5105 --- /dev/null +++ b/public/docs/_examples/user-input/ts/src/app/app.component.ts @@ -0,0 +1,8 @@ +// #docregion +import { Component } from '@angular/core'; + +@Component({ + selector: 'my-app', + templateUrl: './app.component.html' +}) +export class AppComponent { } diff --git a/public/docs/_examples/user-input/ts/src/app/app.html b/public/docs/_examples/user-input/ts/src/app/app.html deleted file mode 100644 index 9744402d8c..0000000000 --- a/public/docs/_examples/user-input/ts/src/app/app.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - -

    keyup loop-back component

    - - - - - - - - - \ No newline at end of file diff --git a/public/docs/_examples/user-input/ts/src/app/app.module.ts b/public/docs/_examples/user-input/ts/src/app/app.module.ts new file mode 100644 index 0000000000..41f13f9f11 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/user-input/ts/src/app/app.ts b/public/docs/_examples/user-input/ts/src/app/app.ts deleted file mode 100644 index c192f7e829..0000000000 --- a/public/docs/_examples/user-input/ts/src/app/app.ts +++ /dev/null @@ -1,147 +0,0 @@ -// #docplaster - -// imports formatted for dev guide only -// #docregion little-tour-of-heroes-app -import {bootstrap, Component, CORE_DIRECTIVES} from 'angular2/angular2'; - -// #enddocregion little-tour-of-heroes-app - - -// #docregion click-me-component -@Component({ - selector: 'click-me', - template: '' -}) -class ClickMeComponent { - onClickMe(){ - alert('You are my hero!') - } -} -// #enddocregion click-me-component - -// #docregion loop-back-component -@Component({ - selector: 'loop-back', - template: '

    {{box.value}}

    ' -}) -class LoopbackComponent { -} -// #enddocregion loop-back-component - -// #docregion key-up-component -@Component({ - selector: 'key-up', - template: ` -

    Give me some keys!

    -
    -
    {{values}}
    - ` -}) -class KeyUpComponent { - values=''; - onKey(event) { - this.values += event.target.value + ' | '; - } -} -// #enddocregion key-up-component - -// #docregion key-up2-component -@Component({ - selector: 'key-up2', - template: ` -

    Give me some more keys!

    -
    -
    {{values}}
    - ` -}) -class KeyUpComponentV2 { - values=''; - onKey(value) { - this.values += value + ' | '; - } -} -// #enddocregion key-up2-component - - -// #docregion key-up3-component -@Component({ - selector: 'key-up3', - template: ` -

    Type away! Press [enter] when done.

    -
    -
    {{values}}
    - ` -}) -class KeyUpComponentV3 { - values=''; -} -// #enddocregion key-up3-component - - -// #docregion key-up4-component -@Component({ - selector: 'key-up4', - template: ` -

    Type away! Press [enter] or mouse away when done.

    -
    - -
    -
    {{values}}
    - ` -}) -class KeyUpComponentV4 { - values=''; -} -// #enddocregion key-up4-component - - -@Component({ - selector: 'my-app', - templateUrl: 'app/app.html', - directives: [ - CORE_DIRECTIVES, - ClickMeComponent, - KeyUpComponent, KeyUpComponentV2, KeyUpComponentV3, KeyUpComponentV4, - LoopbackComponent, - ] -}) -class AppComponent { - - onClickMe(event){ - let evtMsg = event ? ' Event target class is '+ event.target.className : ''; - alert('Click me.'+evtMsg) - } -} - -bootstrap(AppComponent); - -/////////////////////////////////////////////////// - -// #docregion little-tour-of-heroes-app -@Component({ - selector: 'little-tour', - template: ` -

    Little Tour of Heroes

    - - -
    • {{hero}}
    - `, - directives: [CORE_DIRECTIVES] -}) -class LittleTour { - heroes=['Windstorm', 'Bombasto', 'Magneta', 'Tornado']; - - addHero(newHero) { - if (newHero.value) { - this.heroes.push(newHero.value); - newHero.value = null; // clear the newHero textbox - } - } -} - -bootstrap(LittleTour); -// #enddocregion little-tour-of-heroes-app diff --git a/public/docs/_examples/user-input/ts/src/app/click-me.component.ts b/public/docs/_examples/user-input/ts/src/app/click-me.component.ts new file mode 100644 index 0000000000..45a4ca7e70 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/user-input/ts/src/app/click-me2.component.ts b/public/docs/_examples/user-input/ts/src/app/click-me2.component.ts new file mode 100644 index 0000000000..1e35731a82 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/user-input/ts/src/app/keyup.components.ts b/public/docs/_examples/user-input/ts/src/app/keyup.components.ts new file mode 100644 index 0000000000..94ed1ae423 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/user-input/ts/src/app/little-tour.component.ts b/public/docs/_examples/user-input/ts/src/app/little-tour.component.ts new file mode 100644 index 0000000000..5862f033d6 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/user-input/ts/src/app/loop-back.component.ts b/public/docs/_examples/user-input/ts/src/app/loop-back.component.ts new file mode 100644 index 0000000000..6c9dad1da8 --- /dev/null +++ b/public/docs/_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/public/docs/_examples/user-input/ts/src/index.html b/public/docs/_examples/user-input/ts/src/index.html index d59ff63b82..9728814107 100644 --- a/public/docs/_examples/user-input/ts/src/index.html +++ b/public/docs/_examples/user-input/ts/src/index.html @@ -2,21 +2,26 @@ User Input - - - + + + + + + + + + + + + + Loading... -
    - Loading... - \ No newline at end of file + diff --git a/public/docs/_examples/user-input/ts/src/main.ts b/public/docs/_examples/user-input/ts/src/main.ts new file mode 100644 index 0000000000..311c44b76d --- /dev/null +++ b/public/docs/_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/public/docs/_examples/user-input/ts/src/styles.css b/public/docs/_examples/user-input/ts/src/styles.css deleted file mode 100644 index b2133e5103..0000000000 --- a/public/docs/_examples/user-input/ts/src/styles.css +++ /dev/null @@ -1,9 +0,0 @@ -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/public/docs/_examples/user-input/ts/src/tsconfig.json b/public/docs/_examples/user-input/ts/src/tsconfig.json deleted file mode 100644 index 6a58b35a58..0000000000 --- a/public/docs/_examples/user-input/ts/src/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "target": "ES5", - "module": "commonjs", - "sourceMap": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "removeComments": false, - "noImplicitAny": false - } -} \ No newline at end of file diff --git a/public/docs/_examples/template-syntax/ts/src/styles.css b/public/docs/_examples/user-input/ts/src/user-input-styles.css similarity index 100% rename from public/docs/_examples/template-syntax/ts/src/styles.css rename to public/docs/_examples/user-input/ts/src/user-input-styles.css diff --git a/public/docs/_examples/webpack/e2e-spec.ts b/public/docs/_examples/webpack/e2e-spec.ts new file mode 100644 index 0000000000..9bca9810eb --- /dev/null +++ b/public/docs/_examples/webpack/e2e-spec.ts @@ -0,0 +1,21 @@ +'use strict'; // necessary for es6 output in node + +import { browser, element, by } from 'protractor'; + +describe('QuickStart E2E Tests', function () { + + let expectedMsg = 'Hello from Angular App with Webpack'; + + beforeEach(function () { + browser.get(''); + }); + + it(`should display: ${expectedMsg}`, function () { + expect(element(by.css('h1')).getText()).toEqual(expectedMsg); + }); + + it('should display an image', function () { + expect(element(by.css('img')).isPresent()).toBe(true); + }); + +}); diff --git a/public/docs/_examples/webpack/ts/.gitignore b/public/docs/_examples/webpack/ts/.gitignore new file mode 100644 index 0000000000..8628a5eef6 --- /dev/null +++ b/public/docs/_examples/webpack/ts/.gitignore @@ -0,0 +1,5 @@ +dist +!karma.webpack.conf.js +!webpack.config.js +!config/* +!public/css/styles.css diff --git a/public/docs/_examples/webpack/ts/config/helpers.js b/public/docs/_examples/webpack/ts/config/helpers.js new file mode 100644 index 0000000000..b760520f1c --- /dev/null +++ b/public/docs/_examples/webpack/ts/config/helpers.js @@ -0,0 +1,12 @@ +// #docregion +var path = require('path'); + +var _root = path.resolve(__dirname, '..'); + +function root(args) { + args = Array.prototype.slice.call(arguments, 0); + return path.join.apply(path, [_root].concat(args)); +} + +exports.root = root; +// #enddocregion \ No newline at end of file diff --git a/public/docs/_examples/webpack/ts/config/karma-test-shim.js b/public/docs/_examples/webpack/ts/config/karma-test-shim.js new file mode 100644 index 0000000000..2ea37fbd72 --- /dev/null +++ b/public/docs/_examples/webpack/ts/config/karma-test-shim.js @@ -0,0 +1,22 @@ +// #docregion +Error.stackTraceLimit = Infinity; + +require('core-js/es6'); +require('core-js/es7/reflect'); + +require('zone.js/dist/zone'); +require('zone.js/dist/long-stack-trace-zone'); +require('zone.js/dist/proxy'); +require('zone.js/dist/sync-test'); +require('zone.js/dist/jasmine-patch'); +require('zone.js/dist/async-test'); +require('zone.js/dist/fake-async-test'); + +var appContext = require.context('../src', true, /\.spec\.ts/); + +appContext.keys().forEach(appContext); + +var testing = require('@angular/core/testing'); +var browser = require('@angular/platform-browser-dynamic/testing'); + +testing.TestBed.initTestEnvironment(browser.BrowserDynamicTestingModule, browser.platformBrowserDynamicTesting()); diff --git a/public/docs/_examples/webpack/ts/config/karma.conf.js b/public/docs/_examples/webpack/ts/config/karma.conf.js new file mode 100644 index 0000000000..7bbb4617db --- /dev/null +++ b/public/docs/_examples/webpack/ts/config/karma.conf.js @@ -0,0 +1,39 @@ +// #docregion +var webpackConfig = require('./webpack.test'); + +module.exports = function (config) { + var _config = { + basePath: '', + + frameworks: ['jasmine'], + + files: [ + {pattern: './config/karma-test-shim.js', watched: false} + ], + + preprocessors: { + './config/karma-test-shim.js': ['webpack', 'sourcemap'] + }, + + webpack: webpackConfig, + + webpackMiddleware: { + stats: 'errors-only' + }, + + webpackServer: { + noInfo: true + }, + + reporters: ['kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: false, + browsers: ['Chrome'], + singleRun: true + }; + + config.set(_config); +}; +// #enddocregion diff --git a/public/docs/_examples/webpack/ts/config/webpack.common.js b/public/docs/_examples/webpack/ts/config/webpack.common.js new file mode 100644 index 0000000000..28be240e04 --- /dev/null +++ b/public/docs/_examples/webpack/ts/config/webpack.common.js @@ -0,0 +1,81 @@ +// #docplaster +// #docregion +var webpack = require('webpack'); +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var ExtractTextPlugin = require('extract-text-webpack-plugin'); +var helpers = require('./helpers'); + +module.exports = { + // #docregion entries, one-entry, two-entries + entry: { + // #enddocregion one-entry, two-entries + 'polyfills': './src/polyfills.ts', + // #docregion two-entries + 'vendor': './src/vendor.ts', + // #docregion one-entry + 'app': './src/main.ts' + }, + // #enddocregion entries, one-entry, two-entries + + // #docregion resolve + resolve: { + extensions: ['.ts', '.js'] + }, + // #enddocregion resolve + + // #docregion loaders + module: { + rules: [ + { + test: /\.ts$/, + loaders: [ + { + loader: 'awesome-typescript-loader', + options: { configFileName: helpers.root('src', 'tsconfig.json') } + } , 'angular2-template-loader' + ] + }, + { + test: /\.html$/, + loader: 'html-loader' + }, + { + test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/, + loader: 'file-loader?name=assets/[name].[hash].[ext]' + }, + { + test: /\.css$/, + exclude: helpers.root('src', 'app'), + loader: ExtractTextPlugin.extract({ fallbackLoader: 'style-loader', loader: 'css-loader?sourceMap' }) + }, + { + test: /\.css$/, + include: helpers.root('src', 'app'), + loader: 'raw-loader' + } + ] + }, + // #enddocregion loaders + + // #docregion plugins + plugins: [ + // Workaround for angular/angular#11580 + new webpack.ContextReplacementPlugin( + // The (\\|\/) piece accounts for path separators in *nix and Windows + /angular(\\|\/)core(\\|\/)@angular/, + helpers.root('./src'), // location of your src + {} // a map of your routes + ), + + new webpack.optimize.CommonsChunkPlugin({ + name: ['app', 'vendor', 'polyfills'] + }), + + new HtmlWebpackPlugin({ + template: 'src/index.html' + }) + ] + // #enddocregion plugins +}; +// #enddocregion + diff --git a/public/docs/_examples/webpack/ts/config/webpack.dev.js b/public/docs/_examples/webpack/ts/config/webpack.dev.js new file mode 100644 index 0000000000..57d29560a0 --- /dev/null +++ b/public/docs/_examples/webpack/ts/config/webpack.dev.js @@ -0,0 +1,26 @@ +// #docregion +var webpackMerge = require('webpack-merge'); +var ExtractTextPlugin = require('extract-text-webpack-plugin'); +var commonConfig = require('./webpack.common.js'); +var helpers = require('./helpers'); + +module.exports = webpackMerge(commonConfig, { + devtool: 'cheap-module-eval-source-map', + + output: { + path: helpers.root('dist'), + publicPath: '/', + filename: '[name].js', + chunkFilename: '[id].chunk.js' + }, + + plugins: [ + new ExtractTextPlugin('[name].css') + ], + + devServer: { + historyApiFallback: true, + stats: 'minimal' + } +}); +// #enddocregion diff --git a/public/docs/_examples/webpack/ts/config/webpack.prod.js b/public/docs/_examples/webpack/ts/config/webpack.prod.js new file mode 100644 index 0000000000..d6c70119bc --- /dev/null +++ b/public/docs/_examples/webpack/ts/config/webpack.prod.js @@ -0,0 +1,41 @@ +// #docregion +var webpack = require('webpack'); +var webpackMerge = require('webpack-merge'); +var ExtractTextPlugin = require('extract-text-webpack-plugin'); +var commonConfig = require('./webpack.common.js'); +var helpers = require('./helpers'); + +const ENV = process.env.NODE_ENV = process.env.ENV = 'production'; + +module.exports = webpackMerge(commonConfig, { + devtool: 'source-map', + + output: { + path: helpers.root('dist'), + publicPath: '/', + filename: '[name].[hash].js', + chunkFilename: '[id].[hash].chunk.js' + }, + + plugins: [ + new webpack.NoEmitOnErrorsPlugin(), + new webpack.optimize.UglifyJsPlugin({ // https://github.com/angular/angular/issues/10618 + mangle: { + keep_fnames: true + } + }), + new ExtractTextPlugin('[name].[hash].css'), + new webpack.DefinePlugin({ + 'process.env': { + 'ENV': JSON.stringify(ENV) + } + }), + new webpack.LoaderOptionsPlugin({ + htmlLoader: { + minimize: false // workaround for ng2 + } + }) + ] +}); + +// #enddocregion diff --git a/public/docs/_examples/webpack/ts/config/webpack.test.js b/public/docs/_examples/webpack/ts/config/webpack.test.js new file mode 100644 index 0000000000..e668504c67 --- /dev/null +++ b/public/docs/_examples/webpack/ts/config/webpack.test.js @@ -0,0 +1,55 @@ +// #docregion +var webpack = require('webpack'); +var helpers = require('./helpers'); + +module.exports = { + devtool: 'inline-source-map', + + resolve: { + extensions: ['.ts', '.js'] + }, + + module: { + rules: [ + { + test: /\.ts$/, + loaders: [ + { + loader: 'awesome-typescript-loader', + options: { configFileName: helpers.root('src', 'tsconfig.json') } + } , 'angular2-template-loader' + ] + }, + { + test: /\.html$/, + loader: 'html-loader' + + }, + { + test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/, + loader: 'null-loader' + }, + { + test: /\.css$/, + exclude: helpers.root('src', 'app'), + loader: 'null-loader' + }, + { + test: /\.css$/, + include: helpers.root('src', 'app'), + loader: 'raw-loader' + } + ] + }, + + plugins: [ + new webpack.ContextReplacementPlugin( + // The (\\|\/) piece accounts for path separators in *nix and Windows + /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/, + helpers.root('./src'), // location of your src + {} // a map of your routes + ) + ] +} + +// #enddocregion diff --git a/public/docs/_examples/webpack/ts/example-config.json b/public/docs/_examples/webpack/ts/example-config.json new file mode 100644 index 0000000000..e405d0c01a --- /dev/null +++ b/public/docs/_examples/webpack/ts/example-config.json @@ -0,0 +1,4 @@ +{ + "build": "build:webpack", + "run": "serve:cli" +} diff --git a/public/docs/_examples/webpack/ts/karma.webpack.conf.js b/public/docs/_examples/webpack/ts/karma.webpack.conf.js new file mode 100644 index 0000000000..e2a663e8de --- /dev/null +++ b/public/docs/_examples/webpack/ts/karma.webpack.conf.js @@ -0,0 +1,2 @@ +// #docregion +module.exports = require('./config/karma.conf.js'); diff --git a/public/docs/_examples/webpack/ts/package.webpack.json b/public/docs/_examples/webpack/ts/package.webpack.json new file mode 100644 index 0000000000..8f9e3954c2 --- /dev/null +++ b/public/docs/_examples/webpack/ts/package.webpack.json @@ -0,0 +1,49 @@ +{ + "name": "angular2-webpack", + "version": "1.0.0", + "description": "A webpack starter for Angular", + "scripts": { + "start": "webpack-dev-server --inline --progress --port 8080", + "test": "karma start", + "build": "rimraf dist && webpack --config config/webpack.prod.js --progress --profile --bail" + }, + "license": "MIT", + "dependencies": { + "@angular/common": "~4.0.0", + "@angular/compiler": "~4.0.0", + "@angular/core": "~4.0.0", + "@angular/forms": "~4.0.0", + "@angular/http": "~4.0.0", + "@angular/platform-browser": "~4.0.0", + "@angular/platform-browser-dynamic": "~4.0.0", + "@angular/router": "~4.0.0", + "core-js": "^2.4.1", + "rxjs": "5.0.1", + "zone.js": "^0.8.4" + }, + "devDependencies": { + "@types/node": "^6.0.45", + "@types/jasmine": "2.5.36", + "angular2-template-loader": "^0.6.0", + "awesome-typescript-loader": "^3.0.4", + "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", + "jasmine-core": "^2.4.1", + "karma": "^1.2.0", + "karma-chrome-launcher": "^2.0.0", + "karma-jasmine": "^1.0.2", + "karma-sourcemap-loader": "^0.3.7", + "karma-webpack": "^2.0.1", + "null-loader": "^0.1.1", + "raw-loader": "^0.5.1", + "rimraf": "^2.5.2", + "style-loader": "^0.13.1", + "typescript": "~2.0.10", + "webpack": "2.2.1", + "webpack-dev-server": "2.4.1", + "webpack-merge": "^3.0.0" + } +} diff --git a/public/docs/_examples/webpack/ts/src/app/app.component.css b/public/docs/_examples/webpack/ts/src/app/app.component.css new file mode 100644 index 0000000000..bb624c5aae --- /dev/null +++ b/public/docs/_examples/webpack/ts/src/app/app.component.css @@ -0,0 +1,9 @@ +/* #docregion */ +main { + padding: 1em; + font-family: Arial, Helvetica, sans-serif; + text-align: center; + margin-top: 50px; + display: block; +} +/* #enddocregion */ diff --git a/public/docs/_examples/webpack/ts/src/app/app.component.html b/public/docs/_examples/webpack/ts/src/app/app.component.html new file mode 100644 index 0000000000..9e60cd2ad5 --- /dev/null +++ b/public/docs/_examples/webpack/ts/src/app/app.component.html @@ -0,0 +1,7 @@ + +
    +

    Hello from Angular App with Webpack

    + + +
    + diff --git a/public/docs/_examples/webpack/ts/src/app/app.component.spec.ts b/public/docs/_examples/webpack/ts/src/app/app.component.spec.ts new file mode 100644 index 0000000000..a6512a11e7 --- /dev/null +++ b/public/docs/_examples/webpack/ts/src/app/app.component.spec.ts @@ -0,0 +1,16 @@ +// #docregion +import { TestBed } from '@angular/core/testing'; + +import { AppComponent } from './app.component'; + +describe('App', () => { + beforeEach(() => { + TestBed.configureTestingModule({ declarations: [AppComponent]}); + }); + + it ('should work', () => { + let fixture = TestBed.createComponent(AppComponent); + expect(fixture.componentInstance instanceof AppComponent).toBe(true, 'should create AppComponent'); + }); +}); +// #enddocregion diff --git a/public/docs/_examples/webpack/ts/src/app/app.component.ts b/public/docs/_examples/webpack/ts/src/app/app.component.ts new file mode 100644 index 0000000000..2c5eac0147 --- /dev/null +++ b/public/docs/_examples/webpack/ts/src/app/app.component.ts @@ -0,0 +1,16 @@ +// #docplaster +// #docregion +// #docregion component +import { Component } from '@angular/core'; + +// #enddocregion component +import '../assets/css/styles.css'; + +// #docregion component +@Component({ + selector: 'my-app', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'] +}) +export class AppComponent { } +// #enddocregion diff --git a/public/docs/_examples/webpack/ts/src/app/app.module.ts b/public/docs/_examples/webpack/ts/src/app/app.module.ts new file mode 100644 index 0000000000..362f3401fa --- /dev/null +++ b/public/docs/_examples/webpack/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/public/docs/_examples/webpack/ts/src/assets/css/styles.css b/public/docs/_examples/webpack/ts/src/assets/css/styles.css new file mode 100644 index 0000000000..2d404ff5b9 --- /dev/null +++ b/public/docs/_examples/webpack/ts/src/assets/css/styles.css @@ -0,0 +1,6 @@ +/* #docregion */ +body { + background: #0147A7; + color: #fff; +} +/* #enddocregion */ diff --git a/public/docs/_examples/webpack/ts/src/assets/images/angular.png b/public/docs/_examples/webpack/ts/src/assets/images/angular.png new file mode 100644 index 0000000000..c510293918 Binary files /dev/null and b/public/docs/_examples/webpack/ts/src/assets/images/angular.png differ diff --git a/public/docs/_examples/webpack/ts/src/index.html b/public/docs/_examples/webpack/ts/src/index.html new file mode 100644 index 0000000000..503ea4a950 --- /dev/null +++ b/public/docs/_examples/webpack/ts/src/index.html @@ -0,0 +1,14 @@ + + + + + + Angular With Webpack + + + + + Loading... + + + diff --git a/public/docs/_examples/webpack/ts/src/main.ts b/public/docs/_examples/webpack/ts/src/main.ts new file mode 100644 index 0000000000..e1d8cbc0fe --- /dev/null +++ b/public/docs/_examples/webpack/ts/src/main.ts @@ -0,0 +1,14 @@ +// #docregion +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { enableProdMode } from '@angular/core'; + +import { AppModule } from './app/app.module'; + +// #docregion enable-prod +if (process.env.ENV === 'production') { + enableProdMode(); +} +// #enddocregion enable-prod + +platformBrowserDynamic().bootstrapModule(AppModule); +// #enddocregion diff --git a/public/docs/_examples/webpack/ts/src/polyfills.ts b/public/docs/_examples/webpack/ts/src/polyfills.ts new file mode 100644 index 0000000000..118acd2b0c --- /dev/null +++ b/public/docs/_examples/webpack/ts/src/polyfills.ts @@ -0,0 +1,12 @@ +// #docregion +import 'core-js/es6'; +import 'core-js/es7/reflect'; +require('zone.js/dist/zone'); + +if (process.env.ENV === 'production') { + // Production +} else { + // Development and test + Error['stackTraceLimit'] = Infinity; + require('zone.js/dist/long-stack-trace-zone'); +} diff --git a/public/docs/_examples/webpack/ts/src/tsconfig.1.json b/public/docs/_examples/webpack/ts/src/tsconfig.1.json new file mode 100644 index 0000000000..544e895bce --- /dev/null +++ b/public/docs/_examples/webpack/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 + } +} \ No newline at end of file diff --git a/public/docs/_examples/webpack/ts/src/vendor.ts b/public/docs/_examples/webpack/ts/src/vendor.ts new file mode 100644 index 0000000000..8ffd09240a --- /dev/null +++ b/public/docs/_examples/webpack/ts/src/vendor.ts @@ -0,0 +1,15 @@ +// #docregion +// Angular +import '@angular/platform-browser'; +import '@angular/platform-browser-dynamic'; +import '@angular/core'; +import '@angular/common'; +import '@angular/http'; +import '@angular/router'; + +// RxJS +import 'rxjs'; + +// Other vendors for example jQuery, Lodash or Bootstrap +// You can import js, ts, css, sass, ... +// #enddocregion diff --git a/public/docs/_examples/webpack/ts/webpack.config.js b/public/docs/_examples/webpack/ts/webpack.config.js new file mode 100644 index 0000000000..66141706fe --- /dev/null +++ b/public/docs/_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 diff --git a/public/docs/_examples/webpack/ts/zipper.json b/public/docs/_examples/webpack/ts/zipper.json new file mode 100644 index 0000000000..73ea46a406 --- /dev/null +++ b/public/docs/_examples/webpack/ts/zipper.json @@ -0,0 +1,12 @@ +{ + "files":[ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[0-9].*", + "config/**/*", + "webpack.config.js", + "karma.webpack.conf.js" + ], + "removeSystemJsConfig": true, + "type": "webpack" +} diff --git a/public/docs/_includes/_help.jade b/public/docs/_includes/_help.jade index 067975e46a..20b131054a 100644 --- a/public/docs/_includes/_help.jade +++ b/public/docs/_includes/_help.jade @@ -19,7 +19,7 @@ li. Link to a live code example that demonstrates your problem or question, so you'll get an answer faster. - Use this template. + Use this template. li. If you get help, help others. Good karma rulez! diff --git a/public/docs/_includes/_hover-card.jade b/public/docs/_includes/_hover-card.jade new file mode 100644 index 0000000000..a76f9948c2 --- /dev/null +++ b/public/docs/_includes/_hover-card.jade @@ -0,0 +1,12 @@ +- var hasIcon = icon ? 'has-icon' : '' +- var iconNumber = number ? number : '' +a(class="hover-card is-button #{hasIcon}" href="#{url}" md-button) + if icon + span(class="hover-card-icon #{icon}") #{iconNumber} + + h3 #{name} + + if cta + p #{cta} + else + p View #{name} Docs diff --git a/public/docs/_includes/_see-addr-bar.jade b/public/docs/_includes/_see-addr-bar.jade new file mode 100644 index 0000000000..523b72f67a --- /dev/null +++ b/public/docs/_includes/_see-addr-bar.jade @@ -0,0 +1,11 @@ +table + tr + td + :marked + To see the URL changes in the browser address bar of the live example, + open it again in the Plunker editor by clicking the icon in the upper right, + then pop out the preview window by clicking the blue 'X' button in the upper right corner. + td + img(src='https://github.com/resources/images/devguide/plunker-switch-to-editor-button.png' width="200px" height="70px" alt="pop out the window" align="right" ) + br + img(src='https://github.com/resources/images/devguide/plunker-separate-window-button.png' width="200px" height="47px" alt="pop out the window" align="right" ) diff --git a/public/docs/_includes/_side-nav.jade b/public/docs/_includes/_side-nav.jade new file mode 100644 index 0000000000..cf73ed27d0 --- /dev/null +++ b/public/docs/_includes/_side-nav.jade @@ -0,0 +1,149 @@ +// Include this file ONLY when current.path[2] is defined +- var base = '/docs/' + current.path[1] + '/' + current.path[2] + '/'; +- var dir = current.path[4] ? current.path[3] + '/' : ''; +- var cur = current.path[4] || current.path[3]; +- cur = cur === 'index' ? '' : cur + '.html'; +- cur = base + dir + cur; + +- var sections = function(dir, selector) { +- var secondaryPath = public.docs[current.path[1]][current.path[2]] +- var data = dir ? secondaryPath[dir] && secondaryPath[dir]._data : secondaryPath._data; +- if (!data) return []; + +- var section = []; +- var selector = selector || function() { return true; }; +- var prefix = base + (dir ? dir + '/' : ''); +- for (prop in data) { +- var item = data[prop]; +- item.slug = prop; +- if (prop[0] !== '_' && !item.hide && selector(item)) { +- var file = pro +- var ext = prop.lastIndexOf('/') === prop.length-1 ? '' : '.html'; +- var file = prop === 'index' ? '' : prop + ext; +- var href = prefix + file; +- item.class = cur === href ? 'is-selected' : ''; +- item.file = file; +- item.href = href; +- item.navTitle = item.navTitle || item.title; +- item.tooltip = item.description || item.intro || item.title || ''; +- section.push(item); +- } +- } +- return section; +- } + +- var tutorial = sections('tutorial'); +- var cookbook = sections('cookbook'); +- var basics = sections('guide', function(item) { return item.basics; }); +- var guide = sections('guide', function(item) { return !item.basics; }); +- var qs = sections('', function(item) { return item.slug === 'quickstart'; })[0] || {}; +- var cliqs = sections('', function(item) { return item.slug === 'cli-quickstart'; })[0] || {}; +- var reference = sections('', function(item) { return item.reference; }); +- var anyItemSelected = function(items) { +- var selectedCount = items.filter(function(item) { return !!item.class; }).length; +- return selectedCount > 0 ? 'is-selected' : ''; +- } +- var isQuickstartSelected = function() { +- var splitted = cur.split('/'); +- var bit = splitted[splitted.length - 1].replace('.html', ''); +- return bit === 'quickstart' ? 'is-selected' : ''; +-} +- var isCLIQuickstartSelected = function() { +- var split = cur.split('/'); +- var bit = split[split.length - 1].replace('.html', ''); +- return bit === 'cli-quickstart' ? 'is-selected' : ''; +-} +- var isApiReferenceSelected = function() { +- var splitted = cur.split('/'); +- var bit = splitted[splitted.length - 2]; +- return bit === 'api' ? 'is-selected' : ''; +-} +- var isCollapsed = function(titleStyle) { +- return titleStyle === 'is-selected' ? '' : 'is-hidden'; +- } + +- var language = current.path[1] || 'ts' +- if (language !== 'ts' || language !== 'js' || language !== 'dart') { language = 'ts'; } + + +nav(class="sidenav l-pinned-left l-layer-4 l-offset-nav" data-swiftype-index="false" ng-class="appCtrl.showDocsNav ? 'is-visible' : ''") + // SEARCH BAR + header.sidenav-search.st-input-wrapper + .st-input-inner + label(for="search-io" class="is-hidden") Search Docs + input(type="text" class="st-default-search-input" placeholder="SEARCH DOCS...") + button(class="mobile-trigger button" aria-label="View Docs Menu" ng-click="appCtrl.toggleDocsMenu($event)" md-button) Docs + + ul(class="sidenav-links") + li.sidenav-section.no-border + a(class="nav-title" href="https://github.com/docs/#{current.path[1]}/latest/") Docs Home + + // CORE DOCUMENTATION + li.sidenav-section-divider + h3 Core Documentation + + li.sidenav-section + a(class="nav-title #{isQuickstartSelected(cur)}" href="#{qs.href}" title="#{qs.tooltip}") Quickstart + + li.sidenav-section + a(class="nav-title #{isCLIQuickstartSelected(cur)}" href="#{cliqs.href}" title="#{cliqs.tooltip}") CLI Quickstart + + + li.sidenav-section + a(class="nav-title is-parent #{anyItemSelected(basics)}" href="#{basics[0].href}" title="#{basics[0].tooltip}") Guide + img(class="inline-arrow-down-svg" src="https://github.com/resources/images/icons/ic_keyboard_arrow_down_black_24px.svg") + + .nav-ordered-lists(class="#{isCollapsed(anyItemSelected(basics))}") + ul + each item, index in basics + li(class="nav-list-item #{item.class}"): a(href="#{item.href}" title="#{item.tooltip}") #{index + 1}. #{item.navTitle} + li.sidenav-section.no-border + a(class="nav-title #{isApiReferenceSelected()}" href="#{reference[0].href}" title="#{reference[0].tooltip}") API Reference + + // ADVANCED DOCUMENATION + li.sidenav-section-divider + h3 Additional Documentation + + li.sidenav-section + a(class="nav-title is-parent #{anyItemSelected(tutorial)}" href="#{tutorial[0].href}" title="#{tutorial[0].tooltip}") Tutorial + img(class="inline-arrow-down-svg" src="https://github.com/resources/images/icons/ic_keyboard_arrow_down_black_24px.svg") + .nav-ordered-lists(class="#{isCollapsed(anyItemSelected(tutorial))}") + ul + each item, index in tutorial + li(class="nav-list-item #{item.class}"): a(href="#{item.href}" title="#{item.tooltip}") #{index + 1}. #{item.navTitle} + + li.sidenav-section + a(class="nav-title is-parent #{anyItemSelected(guide)}" href="#{guide[0].href}" title="#{guide[0].tooltip}") Advanced + img(class="inline-arrow-down-svg" src="https://github.com/resources/images/icons/ic_keyboard_arrow_down_black_24px.svg") + + .nav-unordered-lists(class="#{isCollapsed(anyItemSelected(guide))}") + ul + each item in guide + li(class="nav-list-item #{item.class}"): a(href="#{item.href}" title="#{item.tooltip}") #{item.navTitle} + + li.sidenav-section + a(class="nav-title is-parent #{anyItemSelected(cookbook)}" href="#{cookbook[0].href}" title="#{cookbook[0].tooltip}") Cookbook + img(class="inline-arrow-down-svg" src="https://github.com/resources/images/icons/ic_keyboard_arrow_down_black_24px.svg") + + .nav-unordered-lists(class="#{isCollapsed(anyItemSelected(cookbook))}") + ul + each item in cookbook + li(class="nav-list-item #{item.class}"): a(href="#{item.href}" title="#{item.tooltip}") #{item.navTitle} + + + + + if current.path[0] == "docs" + != partial("../../_includes/_version-dropdown") + + +script. + // Could put in appCtrl but only needed here and clear here + (function scrollToSelectedLink() { + var sideNav = document.getElementsByClassName('sidenav')[0]; + var link = sideNav.getElementsByClassName('is-selected')[0]; + if(link && link.offsetTop > window.innerHeight){ + sideNav.scrollTop = link.offsetTop - (window.innerHeight/2); + //alert("offsetTop: " + link.offsetTop + " side-nav top is " + sideNav.scrollTop); + } + })() diff --git a/public/docs/_includes/_ts-temp.jade b/public/docs/_includes/_ts-temp.jade new file mode 100644 index 0000000000..7c162672f4 --- /dev/null +++ b/public/docs/_includes/_ts-temp.jade @@ -0,0 +1,16 @@ +- var language = current.path[1] +- var lang = 'your choosen language' + +if language == 'dart' + - lang = 'Dart' +if language == 'js' + - lang = 'JavaScript' +if language == 'ts' + - lang = 'TypeScript' + +- var page = current.path[4] ? current.path[4] + '.html' : '' +- if (page === 'index.html') page = '' +- var path = '/docs/ts/latest/'+ current.path[3] + '/' + page +:marked + This page is not yet available in #{lang}. + We recommend reading [it in TypeScript](!{path}). \ No newline at end of file diff --git a/public/docs/_includes/sidenav/_primary.jade b/public/docs/_includes/sidenav/_primary.jade deleted file mode 100644 index b5d613a203..0000000000 --- a/public/docs/_includes/sidenav/_primary.jade +++ /dev/null @@ -1,25 +0,0 @@ -nav.side-nav.l-pinned-left.l-layer-4.l-offset-nav - - // SEARCH BAR - header.side-nav-search.st-input-wrapper - form.st-input-inner - label(for="search-io" class="is-hidden") Search Docs - input(type="search" id="search-io" placeholder="SEARCH DOCS...") - button(class="mobile-trigger button" aria-label="View Docs Menu" ng-click="toggleDocsMenu($event)" md-button) Docs - - - // PRIMARY NAVIGATION - ul(class="side-nav-primary" ng-class="showDocsNav ? 'is-visible' : ''") - if current.path[2] - for page, slug in public.docs[current.path[1]][current.path[2]]._data - - - var name = page.menuTitle || page.title - - var selected = current.path[3] == slug ? 'is-selected':'' - - var pathSuffix = public.docs[current.path[1]][current.path[2]][slug] ? "/" : ".html" - - li(class="#{selected}") #{name} - - - // SECONDARY NAVIGATION - if selected - != partial("_secondary") \ No newline at end of file diff --git a/public/docs/_includes/sidenav/_secondary.jade b/public/docs/_includes/sidenav/_secondary.jade deleted file mode 100644 index 0a5bb47506..0000000000 --- a/public/docs/_includes/sidenav/_secondary.jade +++ /dev/null @@ -1,40 +0,0 @@ -- var secondaryPath = public.docs[current.path[1]][current.path[2]][current.path[3]] - -if secondaryPath - - var data = secondaryPath._data - - var listType = data._listtype - - var ordered = listType == "ordered" ? "is-ordered" : "" - - var items = listType == 'api' ? secondaryPath : data - - var number = 1 - - - //SECONDARY NAVIGATION - ul(class="side-nav-secondary #{ordered}") - - for page, slug in items - - // DEFAULT LIST VALUES - - var selected = current.path[4] == slug ? 'is-selected':'' - - var name = page.title; - - var path = "/docs/" + current.path[1] + "/" + current.path[2] + "/" + current.path[3] + "/" + slug + ".html" - - - if slug != "_listtype" && slug != 'index' && slug != '_contents' && slug != '_data' - // API LIST VALUES - if listType == 'api' - - var name = public.docs[current.path[1]][current.path[2]][current.path[3]][slug]._data["index"]["title"] - - var path = "/docs/" + current.path[1] + "/" + current.path[2] + "/" + current.path[3] + "/" + slug - - // ORDERED LIST VALUES - if listType == 'ordered' - - var num = number++ - - var name = (listType == "ordered") ? num + '. ' + page.title : page.title; - - - li(class="#{selected}") #{name} - - - - // TERTIARY NAVIGATION - if selected - != partial("_tertiary") \ No newline at end of file diff --git a/public/docs/_includes/sidenav/_tertiary.jade b/public/docs/_includes/sidenav/_tertiary.jade deleted file mode 100644 index d50d306f36..0000000000 --- a/public/docs/_includes/sidenav/_tertiary.jade +++ /dev/null @@ -1,15 +0,0 @@ - -// TERTIARY NAVIGATION -- var tertiaryPath = public.docs[current.path[1]][current.path[2]][current.path[3]][current.path[4]] - -if tertiaryPath - - var data = tertiaryPath._data - - ul.side-nav-tertiary - for page, slug in data - - var name = page.title - - var selected = current.path[5] == slug ? 'is-selected':'' - - if slug != "index" - li(class="#{selected}")#{name} - diff --git a/public/docs/_includes/styleguide/_aside.jade b/public/docs/_includes/styleguide/_aside.jade index d8675b2e99..dfc4fec6d3 100644 --- a/public/docs/_includes/styleguide/_aside.jade +++ b/public/docs/_includes/styleguide/_aside.jade @@ -11,7 +11,7 @@ h3 Adding an aside - aside.is-right Did you know that hipsum is a replacment for Lorem Ipsum? To find out more visit hipsum.co + aside.is-right Did you know that hipsum is a replacement for Lorem Ipsum? To find out more visit hipsum.co p. Etsy artisan Thundercats, authentic sustainable bitters wolf roof party meditation 90's asymmetrical XOXO hoodie. Twee umami cray iPhone. Chillwave shabby chic tilde occupy sriracha squid Brooklyn street art. Selvage heirloom kogi American Apparel bicycle rights. Carles Etsy Truffaut mlkshk trust fund. Jean shorts fashion axe Williamsburg wolf cardigan beard, twee blog locavore organic. Cred skateboard dreamcatcher, taxidermy Bushwick actually aesthetic normcore fanny pack. @@ -19,7 +19,7 @@ pre.prettyprint.linenums.lang-html code. - aside.is-right Did you know that hipsum is a replacment for Lorem Ipsum? To find out more visit hipsum.co + aside.is-right Did you know that hipsum is a replacement for Lorem Ipsum? To find out more visit hipsum.co p. Etsy artisan Thundercats, authentic sustainable bitters diff --git a/public/docs/_includes/styleguide/_code-examples.jade b/public/docs/_includes/styleguide/_code-examples.jade index d3f2f19914..c92cb84cd9 100644 --- a/public/docs/_includes/styleguide/_code-examples.jade +++ b/public/docs/_includes/styleguide/_code-examples.jade @@ -9,483 +9,466 @@ include ../../../_includes/_util-fns p Below are some examples of how you can add/customize code examples in a page. .showcase-content - .l-sub-section + :marked + ### Including a code example from the `_examples` folder - :markdown - ### Including a code example from the `_examples` folder + One of the design goals for this documentation was that any code samples that appear within the documentation be 'testable'. + In practice this means that a set of standalone testable examples exist somewhere in the same repository as the rest + of the documentation. These examples will each typically consist of a collection of html, javascript and css files. - One of the design goals for this documention was that any code samples that appear within the documentation be 'testable'. - In practice this means that a set of standalone testable examples exist somewhere in the same repository as the rest - of the documentation. These examples will each typically consist of a collection of html, javascript and css files. + Clearly there also needs to be some mechanism for including fragments of these files into the jade/harp generated + html. By convention all of the 'testable' examples within this repository should be created within the `docs/_examples` folder. - Clearly there also needs to be some mechanism for including fragments of these files into the jade/harp generated - html. By convention all of the 'testable' examples within this repository should be created within the `docs/_examples` folder. + To include an example from somewhere in the `doc/_examples` folder you can use the `makeExample` mixin. + This mixin along with the `makeTabs` mixin both require that the 'included' file be marked + up with special comment markers. This markup will be described a bit later. - To include an example from somewhere in the `doc/_examples` folder you can use the `makeExample` mixin. - This mixin along with the `makeTabs` mixin both require that the 'included' file be marked - up with special comment markers. This markup will be described a bit later. + In addition there are several custom gulp tasks that must be run before any of the edits described below will actually appear + in the generated documentation. These gulp tasks are documented elsewhere. - In addition there are several custom gulp tasks that must be run before any of the edits described below will actually appear - in the generated documentation. These gulp tasks are documented elsewhere. + In order to use the `makeExample` or `makeTabs` mixins each page that references the mixins must include the '_utilFns.jade' + file on the current page. This is usually accomplished simply by adding a path to this file at the top of any + page that needs either of these mixins. - In order to use the `makeExample` or `makeTabs` mixins each page that references the mixins must include the '_utilFns.jade' - file on the current page. This is usually accomplished simply by adding a path to this file at the top of any - page that needs either of these mixins. + code-example(language="js"). + include ../_util-fns - code-example(language="js"). - include ../../../../_includes/_util-fns + :marked + The syntax for the `makeExample` mixin is: - :markdown - The syntax for the `makeExample` mixin is: + #### +makeExample(filePath, region, title, stylePattern) + - *filePath:* path to the example file under the '_examples' folder + - *region:* (optional or null) region from the example file to display + - *title:* (optional or null) title displayed above the included text. + - *stylePattern:* (optional or null) allows additional styling via regular expression ( described later). - #### +makeExample(filePath, region, title, stylePattern) - - *filePath:* path to the example file under the '_examples' folder - - *region:* (optional or null) region from the example file to display - - *title:* (optional or null) title displayed above the included text. - - *stylePattern:* (optional or null) allows additional styling via regular expression ( described later). + #### Example: - #### Example: + code-example(format="linenums" language="js"). + +makeExample('styleguide/js/src/index.html', null, 'index.html') - code-example(format="linenums" language="js"). - +makeExample('styleguide/js/index.html', null, 'index.html') + :marked + This will read the *_examples/styleguide/js/src/index.html* file and include it + with the heading 'index.html'. Note that the file will be properly escaped and + color coded according to the extension on the file ( html in this case). - :markdown - This will read the *_examples/styleguide/js/index.html* file and include it - with the heading 'index.html'. Note that the file will be properly escaped and - color coded according to the extension on the file ( html in this case). + +makeExample('styleguide/js/src/index.html', null, 'index.html') - +makeExample('styleguide/js/index.html', null, 'index.html') + :marked + The second parameter with a value of 'null' will be described later in this document. - :markdown - The second parameter with a value of 'null' will be described later in this document. + There is a similar `makeTabs` mixin that provides the same service but for multiple examples + within a tabbed interface. - There is a similar `makeTabs` mixin that provides the same service but for multiple examples - within a tabbed interface. + #### +makeTabs(filePaths, regions, titles, stylePatterns) + - *filePaths:* a comma delimited string of filePaths to example files under the '_examples' folder + - *regions:* (optional or null) region from the example file to display + - *titles:* (optional or null) a comma delimited string of titles corresponding to each of the filePaths above. + - *stylePatterns:* (optional or null) allows additional styling via regular expression( described later). - #### +makeTabs(filePaths, regions, titles, stylePatterns) - - *filePaths:* a comma delimited string of filePaths to example files under the '_examples' folder - - *regions:* (optional or null) region from the example file to display - - *titles:* (optional or null) a comma delimited string of titles corresponding to each of the filePaths above. - - *stylePatterns:* (optional or null) allows additional styling via regular expression( described later). + #### Example: - #### Example: + code-example(format="linenums" language="js"). + +makeTabs('styleguide/js/src/index.html, styleguide/js/spec.js', null, 'index.html,unit test') - code-example(format="linenums" language="js"). - +makeTabs('styleguide/js/index.html, styleguide/js/spec.js', null, 'index.html,unit test') + :marked + This will create two tabs, each with its own title and appropriately color coded. - :markdown - This will create two tabs, each with its own title and appropriately color coded. + +makeTabs('styleguide/js/src/index.html, styleguide/js/spec.js', null, 'index.html,unit test') - +makeTabs('styleguide/js/index.html, styleguide/js/spec.js', null, 'index.html,unit test') - .l-sub-section - :markdown - ### Marking up an example file for use by the `makeExample` and `makeTabs` mixins + :marked + ### Marking up an example file for use by the `makeExample` and `makeTabs` mixins - At a minimum, marking up an example file simply consists of adding a single comment line to the top of the file - containing the string `#docregion`. Following this a second string that is the 'name' of the region is also allowed - but not required. A file may have any number of `#docregion` comments with the only requirement being that the names - of each region within a single file be unique. This also means that there can only be one *blank* docregion. + At a minimum, marking up an example file simply consists of adding a single comment line to the top of the file + containing the string `#docregion`. Following this a second string that is the 'name' of the region is also allowed + but not required. A file may have any number of `#docregion` comments with the only requirement being that the names + of each region within a single file be unique. This also means that there can only be one *blank* docregion. - #### Example of a simple #docregion + #### Example of a simple #docregion - code-example(format="linenums" language="js"). - // #docregion - describe("Jasmine sample test", function() { - it("1+1 should be 2", function() { - var result = 1 + 1; - expect(result).toBe(2); - }); + code-example(format="linenums" language="js"). + // #docregion + describe("Jasmine sample test", function() { + it("1+1 should be 2", function() { + var result = 1 + 1; + expect(result).toBe(2); }); + }); + + :marked + If a file only has a single `#docregion` then the entire file AFTER the `#docregion` comment is available for inclusion + via mixin. Portions of the file can be indicated by surrounding an area of the file with + `#docregion` and an `#enddocregion` tags. These regions, each with its own name, may be nested to any level and any regions that are not 'ended' explicitly + are assumed to be ended automatically at the bottom of the file. Regions may either be ended/closed by name or if the name is left blank then the most recent + unclosed docregion defined earlier will be closed. Any individual region within the file is accessible + to the `makeExample` and `makeTabs` mixins. + + #### Example of a nested #docregion + + code-example(format="linenums" language="js" escape="html"). + (function() { + // #docregion + // #docregion class-w-annotations + var AppComponent = ng + // #docregion component + .Component({ + selector: 'my-app', + // #enddocregion component + // #docregion view + template: '

    My First Angular App

    ' + }) + // #enddocregion view + // #docregion class + .Class({ + constructor: function () { } + }); + // #enddocregion + // #enddocregion + + :marked + Multiple `#docregion` tags may be defined on a single line as shown below. In addition, anytime a file contains multiple + `#docregion` tags with the same name they will automatically be combined. Each of the individually tagged sections of the combined document + will be separated from one another by a comment consisting of '. . .'. This default separator, known + as 'plaster' can be overridden anywhere within the affected file via a `#docplaster` comment as shown below. This example creates + a separator that consists of `/* more code here */` in the output file. + + code-example(format="linenums" language="js" escape="html"). + // #docplaster more code here + + // #docregion import,twoparts + import { Component } from '@angular/core'; + import { bootstrap } from '@angular/platform-browser-dynamic'; + + // #enddocregion twoparts, import + @Component({ + selector: 'my-app', + template: '

    My first Angular App

    ' + }) + class AppComponent { + } + + // #docregion bootstrap, twoparts + bootstrap(AppComponent); + // #enddocregion twoparts + doSomethingInteresting(); + // #enddocregion + + :marked + HTML files can also contain #docregion comments: + + code-example(format="linenums" language="html"). + <!-- #docregion --> + ... + <script src="https://github.com/app.js"></script> + ... + + :marked + as can CSS files: + + code-example(format="linenums" language="css"). + /* #docregion center-global */ + .center-global { + max-width: 1020px; + margin: 0 auto; + } + + + :marked + ### Including a named #docregion via the makeExample or makeTabs mixins. + + In order to include just a portion of an example file that has been marked up with a 'named' `#docregion` + you will pass the name of the desired region as the 2nd parameter to the makeExample call. + + #### Example + code-example(format="linenums" language="js"). + +makeExample('styleguide/js/src/app.js', 'class-w-annotations', "Extracted region") + + :marked + is a request to include just the `class-w-annotations` region from the `app.js` file in the `_examples/styleguide` + folder and results in the following: + + +makeExample('styleguide/js/src/app.js', 'class-w-annotations', "Extracted region") + + + :marked + ### Additional styling + + In some cases you may want to add additional styling to an external file after it had been included in the documentation. + This styling is accomplished via the `stylePattern` and `stylePatterns` parameters in the `makeExample` and `makeTabs` + mixins. A `stylePattern` is actually just a javascript object where the keys are the names of styles to be applied to + some portion of the included text as defined by a regular expression ( or expressions). These regular expressions are the + value of each key. Each regular expression MUST specify at least a single capture group; the contents of the capture + group being what the style will actually apply to, not the entire regular expression. The idea here is that you may + need to include a contextual match in a regular expression but only want your styling to be applied to a subset + of the entire regular expression. + + Current there are only three types of highlight styles available: Outlined (otl), Pink (pnk), and Black (blk), however the + mechanism described above will work with any style defined on the page. + + #### Example + code-example(format="linenums" language="js" escape="none"). + +makeExample('styleguide/js/src/index.html', null, 'index.html', {pnk: /script (src=.*&quot;)/g}) + + :marked + Which will mark all of the quoted contents of each `script` tag within the index.html file in pink. + + .alert.is-important. + Note that expression replacement occurs AFTER the fragment has been included and html escaped. + This means that your regular expression must use escaped html text; i.e. the '&quot' in the regex above. + + +makeExample('styleguide/js/src/index.html', null, 'src/index.html', {pnk: /script (src=.*")/g}) + + :marked + A more complicated example might be: + + code-example(format="linenums" language="js"). + - var stylePattern = { pnk: /script (src=.*&quot;)/g, otl: /(\S*my-app.*$)/m }; + +makeExample('styleguide/js/src/index.html', null, 'index.html', stylePattern ) - :markdown - If a file only has a single `#docregion` then the entire file AFTER the `#docregion` comment is available for inclusion - via mixin. Portions of the file can be indicated by surrounding an area of the file with - `#docregion` and an `#enddocregion` tags. These regions, each with its own name, may be nested to any level and any regions that are not 'ended' explicitly - are assumed to be ended automatically at the bottom of the file. Regions may either be ended/closed by name or if the name is left blank then the most recent - unclosed docregion defined earlier will be closed. Any individual region within the file is accessible - to the `makeExample` and `makeTabs` mixins. - - #### Example of a nested #docregion - - code-example(format="linenums" language="js" escape="html"). - (function() { - // #docregion - // #docregion class-w-annotations - var AppComponent = ng - // #docregion component - .Component({ - selector: 'my-app' - }) - // #enddocregion component - // #docregion view - .View({ - template: '

    My First Angular 2 App

    ' - }) - // #enddocregion view - // #docregion class - .Class({ - constructor: function () { } - }); - // #enddocregion - // #enddocregion - - :markdown - Multiple `#docregion` tags may be defined on a single line as shown below. In addition, anytime a file contains multiple - `#docregion` tags with the same name they will automatically be combined. Each of the individually tagged sections of the combined document - will be separated from one another by a comment consisting or '. . .'. This default separator, known - as 'plaster' can be overriden anywhere within the affected file via a `#docplaster` comment as shown below. This example creates - a separator that consists of `/* more code here */` in the output file. - - code-example(format="linenums" language="js" escape="html"). - // #docplaster more code here - - // #docregion import,twoparts - import {Component, View, bootstrap} from 'angular2/angular2'; - // #enddocregion twoparts, import - @Component({ - selector: 'my-app' - }) - @View({ - template: '

    My first Angular 2 App

    ' - }) - class AppComponent { - } - - // #docregion bootstrap, twoparts - bootstrap(AppComponent); - // #enddocregion twoparts - doSomethingInteresting(); - // #enddocregion - - :markdown - HTML files can also contain #docregion comments: - - code-example(format="linenums" language="html" escape="html"). - - ... - - - ... - - :markdown - as can CSS files: - - code-example(format="linenums" language="css"). - /* #docregion bar */ - .center-global { - max-width: 1020px; - margin: 0 auto; - } - - .l-sub-section - :markdown - ### Including a named #docregion via the makeExample or makeTabs mixins. - - In order to include just a portion of an example file that has been marked up with a 'named' `#docregion` - you will pass the name of the desired region as the 2nd parameter to the makeExample call. - - #### Example - code-example(format="linenums" language="js"). - +makeExample('styleguide/js/app.js', 'class-w-annotations', "Extracted region") - - :markdown - is a request to include just the `class-w-annotations` region from the `app.js` file in the `_examples/styleguide` - folder and results in the following: - - +makeExample('styleguide/js/app.js', 'class-w-annotations', "Extracted region") - - .l-sub-section - :markdown - ### Additional styling - - In some cases you may want to add additional styling to an external file after it had been included in the documentation. - This styling is accomplished via the `stylePattern` and `stylePatterns` parameters in the `makeExample` and `makeTabs` - mixins. A `stylePattern` is actually just a javascript object where the keys are the names of styles to be applied to - some portion of the included text as defined by a regular expression ( or expressions). These regular expressions are the - value of each key. Each regular expression MUST specify at least a single capture group; the contents of the capture - group being what the style will actually apply to, not the entire regular expression. The idea here is that you may - need to include a contextual match in a regular expression but only want your styling to be applied to a subset - of the entire regular expression. - - Current there are only three types of highlight styles available: Outlined (otl), Pink (pnk), and Black (blk), however the - mechanism described above will work with any style defined on the page. - - #### Example - code-example(format="linenums" language="js" escape="none"). - +makeExample('styleguide/js/index.html', null, 'index.html', {pnk: /script (src=.*&quot)/g}) - - :markdown - Which will mark all of the quoted contents of each `script` tag within the index.html file in pink. - - .alert.is-important. - Note that expression replacement occurs AFTER the fragment has been included and html escaped. - This means that your regular expression must use escaped html text; i.e. the '&quot' in the regex above. - - +makeExample('styleguide/js/index.html', null, 'index.html', {pnk: /script (src=.*")/g}) + :marked + Which applies multiple styles and uses an intermediate javascript object as opposed to a literal. - :markdown - A more complicated example might be: + - var stylePattern = { pnk: /script (src=.*")/g, otl: /(\S*my-app.*$)/m }; + +makeExample('styleguide/js/src/index.html', null, 'index.html', stylePattern ) - code-example(format="linenums" language="js"). - - var stylePattern = { pnk: /script (src=.*&quot;)/g, otl: /(\S*my-app.*$)/m }; - +makeExample('styleguide/js/index.html', null, 'index.html', stylePattern ) + :marked + `makeTabs` support for `stylePatterns` is slightly different from the `makeExample` mixin in that you can also + pass in an array of stylePattern objects where each is paired with its corresponding 'tab'. If only a single stylePattern + object is passed in then it is assumed to apply to all of the tabs. - :markdown - Which applies multiple styles and uses an intermediate javascript object as opposed to a literal. + code-example(format="linenums" language="js"). + -var stylePatterns = [{ pnk: /script (src=.*&quot;)/g }, {pnk: /(result)/ }]; + +makeTabs('styleguide/js/src/index.html, styleguide/js/spec.js', null, 'index.html,unit test', stylePatterns) - - var stylePattern = { pnk: /script (src=.*")/g, otl: /(\S*my-app.*$)/m }; - +makeExample('styleguide/js/index.html', null, 'index.html', stylePattern ) + -var stylePatterns = [{ pnk: /script (src=.*")/g }, {pnk: /(result)/ }]; + +makeTabs('styleguide/js/src/index.html, styleguide/js/spec.js', null, 'index.html,unit test', stylePatterns) - :markdown - `makeTabs` support for `stylePatterns` is slightly different from the `makeExample` mixin in that you can also - pass in an array of stylePattern objects where each is paired with its corresponding 'tab'. If only a single stylePattern - object is passed in then it is assumed to apply to all of the tabs. - code-example(format="linenums" language="js"). - -var stylePatterns = [{ pnk: /script (src=.*&quot;)/g }, {pnk: /(result)/ }]; - +makeTabs('styleguide/js/index.html, styleguide/js/spec.js', null, 'index.html,unit test', stylePatterns) + :marked + ### Including a JSON file or just parts of one - -var stylePatterns = [{ pnk: /script (src=.*")/g }, {pnk: /(result)/ }]; - +makeTabs('styleguide/js/index.html, styleguide/js/spec.js', null, 'index.html,unit test', stylePatterns) + To include an '.json' file from somewhere in the `doc\_examples` folder you can use the `makeJson` mixin. The `makeExample` + and `makeTabs` mixins cannot be used for this purpose because there is no standard 'comment' marker in a json file. - .l-sub-section - :markdown - ### Including a JSON file or just parts of one + The `makeJson` mixin does however provide a similar capability to selectively pick which portions of the '.json' file + to display. - To include an '.json' file from somewhere in the `doc\_examples` folder you can use the `makeJson` mixin. The `makeExample` - and `makeTabs` mixins cannot be used for this purpose because there is no standard 'comment' marker in a json file. + The syntax for the `makeJson` mixin is: - The `makeJson` mixin does however provide a similar capability to selectively pick which portions of the '.json' file - to display. + #### +makeJson(filePath, jsonConfig, title, stylePattern) + - *filePath:* path to the example file under the '_examples' folder + - *jsonConfig:* (optional) an object that defines which portions of the .json file to select for display. + - *rootPath:* (optional default=null) a json property path at which the 'paths' parameter below should start. + - *paths:* a comma delimited list of property paths for those elements to be selected. + - *space:* (optional default=" " [2 spaces]) a String or Number object that's used to insert white space into the output JSON + - *title:* (optional) title displayed above the included text. + - *stylePattern:* (optional) allows additional styling via regular expression ( described above). - The syntax for the `makeJson` mixin is: + #### Example: - #### +makeJson(filePath, jsonConfig, title, stylePattern) - - *filePath:* path to the example file under the '_examples' folder - - *jsonConfig:* (optional) an object that defines which portions of the .json file to select for display. - - *rootPath:* (optional default=null) a json property path at which the 'paths' parameter below should start. - - *paths:* a comma delimited list of property paths for those elements to be selected. - - *space:* (optional default=" " [2 spaces]) a String or Number object that's used to insert white space into the output JSON - - *title:* (optional) title displayed above the included text. - - *stylePattern:* (*optional) allows additional styling via regular expression ( described above). + code-example(format="" language="js"). + +makeJson('styleguide/package.1.json', null, "Entire package.json file") - #### Example: + +makeJson('styleguide/package.1.json', null, "Entire package.json file") - code-example(format="linenums" language="js"). - +makeJson('styleguide/package.json', null, "Entire package.json file") + :marked + A subset of the '.json' file can also be selected. - +makeJson('styleguide/package.json', null, "Entire package.json file") + code-example(format="" language="js"). + +makeJson('styleguide/package.1.json', { paths: 'version, scripts.tsc, scripts.start '}, "Selected parts of the package.json file" ) - :markdown - A subset of the '.json' file can also be selected. + +makeJson('styleguide/package.1.json', { paths: 'version, scripts.tsc, scripts.start '}, "Selected parts of the package.json file" ) - code-example(format="linenums" language="js"). - +makeJson('styleguide/package.json', { paths: 'version, scripts.tsc, scripts.start '}, "Selected parts of the package.json file" ) + :marked + Styling selected portions of the json is also supported. - +makeJson('styleguide/package.json', { paths: 'version, scripts.tsc, scripts.start '}, "Selected parts of the package.json file" ) + code-example(format="" language="js"). + +makeJson('styleguide/package.1.json', {paths: 'dependencies'}, "package.json dependencies", { pnk: [/(\S*zone.*)/, /(\Score-js.*)/, /(\Ssystem.*)/ ]}) - :markdown - Styling selected portions of the json is also supported. + +makeJson('styleguide/package.1.json', {paths: 'dependencies'}, "package.json dependencies", { pnk: [/(\S*zone.*)/, /(\Score-js.*)/, /(\Ssystem.*)/ ]}) - code-example(format="linenums" language="js"). - +makeJson('styleguide/package.json', {paths: 'dependencies'}, "package.json dependencies", { pnk: [/(\S*traceur.*)/, /(\Sangular2.*)/, /(\Ssystem.*)/ ]}) + :marked + ### Inline code and code examples provided directly i.e. not from an example file. - +makeJson('styleguide/package.json', {paths: 'dependencies'}, "package.json dependencies", { pnk: [/(\S*traceur.*)/, /(\Sangular2.*)/, /(\Ssystem.*)/ ]}) + The `makeExample` and `makeTabs` mixins are both both built on top of a custom jade 'style'; `code-example`. + In those cases where you want to include code directly inline i.e. not from some external file; you can use + this style. + This style has several named attributes - :markdown - As well as styling across multiple lines. + #### code-example attributes + * *name:* Name displayed in Tab (required for tabs) + * *language:* javascript, html, etc. + * *escape:* html (escapes html, woot!) + * *format:* linenums (or linenums:4 specify starting line) - code-example(format="linenums" language="js"). - +makeJson('styleguide/package.json', {paths: 'name, version, dependencies '}, "Foo", styles ) + #### Example - - var styles = { pnk: /(^.*dependencies[\s\S]* \})/gm }; - +makeJson('styleguide/package.json', {paths: 'name, version, dependencies '}, "Foo", styles ) + code-example(format="linenums" language="html"). + code-example(format="linenums" language="javascript"). + //SOME CODE - .l-sub-section - :markdown - ### Inline code and code examples provided directly i.e. not from an example file. - The `makeExample` and `makeTabs` mixins are both both built on top of a custom jade 'style'; `code-example`. - In those cases where you want to include code directly inline i.e. not from some external file; you can use - this style. - This style has several named attributes + h3 Specify starting line number - #### code-example attributes - - *name:" Name displayed in Tab (required for tabs) - - *language* javascript, html, etc. - - *escape:* html (escapes html, woot!) - - *format:* linenums (or linenums:4 specify starting line) + code-example(language="javascript" format="linenums:4"). + code-example(language="html" format="linenums:4"). + var title = "This starts on line four"; - #### Example - code-example(format="linenums" language="html"). - code-example(format="linenums" language="javascript"). - //SOME CODE + h3 Specify a language - .l-sub-section - h3 Specify starting line number + p. + Prettify makes a best effort to guess the language but + works best with C-like and HTML-like languages. For + others, there are special language handlers that are + chosen based on language hints. Add a class that matches + your desired language (example below uses .lang-html) - code-example(language="javascript" format="linenums:4"). - code-example(language="html" format="linenums:4"). - var title = "This starts on line four"; + code-example(language="html" format="linenums"). + <h1>Title</h1> + <p>This is some copy...</p> - .l-sub-section - h3 Specify a language - p. - Prettify makes a best effort to guess the language but - works best with C-like and HTML-like languages. For - others, there are special language handlers that are - chosen based on language hints. Add a class that matches - your desired language (example below uses .lang-html) + h3 Code Highlighting + p. + There are three types of highlights available + Outlined, Pink, and + Black. You can see examples below and + the class names needed for each type. - code-example(language="html" format="linenums"). - h1 Title - p This is some copy... + code-example(format="linenums"). + // Pink Background Version + // class="pnk" + var elephants = "The pink elephants were marching..."; - .l-sub-section - h3 Code Highlighting - p. - There are three types of highlights avialable - Outlined, Pink, and - Black. You can see examples below and - the class names needed for each type. + // Black Background Version + // class="blk" + var night = "The night was pitch black."; - code-example(format="linenums"). - // Pink Background Version - // class="pnk" - var elephants = "The pink elephants were marching..."; + // Outlined Version + // class="otl" + var match = "The bird ate bird seed near the bird bath "; - // Black Background Version - // class="blk" - var night = "The night was pitch black."; + h3 Code Tabs + p. + Code Tabs are a great way to show different languages and versions + of a particular piece of code. When using these tabs make sure the + content is always related. - // Outlined Version - // class="otl" - var match = "The bird ate bird seed near the bird bath "; + code-tabs + code-pane(language="javascript" format="linenums" name="ES5"). + // ES5 + var hello = 'blah'; - .l-sub-section - h3 Code Tabs - p. - Code Tabs are a great way to show different languages and versions - of a particular piece of code. When using these tabs make sure the - content is always related. + code-pane(language="javascript" format="linenums" name="TypeScript"). + // TypeScript + var hello = 'blah'; + p To create code tabs simply use the directives below + code-example(format="linenums"). code-tabs - code-pane(language="javascript" format="linenums" name="ES5"). - // ES5 - var hello = 'blah'; - - code-pane(language="javascript" format="linenums" name="TypeScript"). - // TypeScript - var hello = 'blah'; - - p To create code tabs simply use the directives below - code-example(format="linenums"). - code-tabs - code-pane(format="linenums" name="Tab 1"). - // TAB 1 CONTENT - code-pane(format="linenums" name="Tab 2"). - // TAB 2 CONTENT - - .l-sub-section - :markdown - ### Combining makeExample, makeTabs mixins with code-example style attributes - As mentioned above the `makeExample` and `makeTabs` mixins are built on top of the `code-example` style. By default - the mixins automatically determine a language based on the example file's extensions and always include line numbers. - - You can override this behavior by including code-example attributes within parentheses after the mixin parameters. - - #### Example - - code-example(). - +makeExample('styleguide/js/app.js', "class-w-annotations")(format="linenums:15") - - :markdown - Starts the numbering of the example at line 15. - - +makeExample('styleguide/js/app.js', "class-w-annotations")(format="linenums:15") - - :markdown - Or to suppress line numbering completely you can use - - code-example(). - +makeExample('styleguide/js/app.js', 'class-w-annotations')(format=".") - - +makeExample('styleguide/js/app.js', 'class-w-annotations')(format=".") - - .l-sub-section - :markdown - ### Code examples in angular/angular source code - - References to embedded example code in the angular/angular source make use of the same mixins as defined above, but with a slightly different - syntax. Inline tags in source code comments like {@example ...} and {@exampleTabs ...} actually generate 'makeExample' and 'makeTabs' mixins - calls in the documentation. The order of 'arguments' in the inline tags is also the same as that of the mixins defined above. However, optional - parameters can also be specified via name (optionally prefixed with a '-'), as will be shown by example below. Parameters that include spaces should - be enclosed in either single or double quotes. This syntax is intended to mirror standard command line argument patterns. - - .alert.is-important. - The '@example' and '@exampleTabs' inline tags MUST always appear at the beginning of a line. - - Example files referenced by inline tags are all assumed to be in the 'modules/angular2' folder in the angular/angular repo. - - :markdown - #### @example inline tag parameters - - *filePath:* path to the example file under the '_examples' folder - - *region:* (optional or null) region from the example file to display - - *title:* (optional or null) title displayed above the included text. - - *stylePattern:* (optional or null) allows additional styling via regular expression ( described later). - - #### Examples - - code-example(format="linenums" language="js"). - /** - * An example with no region - * {@example core/directives/ng_if_spec.ts -title='Whole other component' } - * - * An example with a region and a title both specified by name - * {@example core/directives/ng_if_spec.ts region='ng-if' title='Partial' } - * - * Another example with a region and a title with only the title specified explicitly. - * {@example core/directives/ng_if_spec.ts foo title='Foo' } - **/ - - :markdown - #### @exampleTabs inline tag parameters - - *filePaths:* a comma delimited string of filePaths to example files under the '_examples' folder - - *regions:* (optional or null) region from the example file to display - - *titles:* (optional or null) a comma delimited string of titles corresponding to each of the filePaths above. - - *stylePatterns:* (optional or null) allows additional styling via regular expression( described later). - - #### Examples - - code-example(format="linenums" language="js"). - /** - * An example with multiple tabs each with its own region and title. - * {@exampleTabs core/directives/test1_spec.ts,core/directives/test2_spec.ts regions='aaa,bbb,' -titles='Test 1,Test 2' } - * - **/ - - .l-sub-section - :markdown - ### Cross references to Developer guide pages in angular/angular source comments. - - The '{@linkDevGuide ... }' inline tag is intended to be used to create links from api documentation to dev guide - documentation. - - #### @linkDevGuide inline tag parameters - - *filePath:* a filePath that points to a jade page in the DevGuide without the .jade extension ( under public/docs ). - - *title:* The title of link. If the title is omitted an attempt will be made to determine the title of the jade page - being pointed to. If not found the then title will simply be the link. - #### Examples - - code-example(format="linenums" language="js"). - /** - * An link to the Developer guide example with a link title - * This can appear anywhere in a comment line: {@linkDevGuide /js/latest/guide/gettingStarted 'Getting Started' } - * and the same link can also be expressed with an explicit 'title' param - * {@linkDevGuide /js/latest/guide/gettingStarted title='Getting Started' } - * Or... an attempt will be made to infer the title if it is omitted. - * {@linkDevGuide /js/latest/guide/gettingStarted } - **/ \ No newline at end of file + code-pane(format="linenums" name="Tab 1"). + // TAB 1 CONTENT + code-pane(format="linenums" name="Tab 2"). + // TAB 2 CONTENT + + :marked + ### Combining makeExample, makeTabs mixins with code-example style attributes + As mentioned above the `makeExample` and `makeTabs` mixins are built on top of the `code-example` style. By default + the mixins automatically determine a language based on the example file's extensions and always include line numbers. + + You can override this behavior by including code-example attributes within parentheses after the mixin parameters. + + #### Example + + code-example(). + +makeExample('styleguide/js/src/app.js', "class-w-annotations")(format="linenums:15") + + :marked + Starts the numbering of the example at line 15. + + +makeExample('styleguide/js/src/app.js', "class-w-annotations")(format="linenums:15") + + :marked + Or to suppress line numbering completely you can use + + code-example(). + +makeExample('styleguide/js/src/app.js', 'class-w-annotations')(format=".") + + +makeExample('styleguide/js/src/app.js', 'class-w-annotations')(format=".") + + + :marked + ### Code examples in angular/angular source code + + References to embedded example code in the angular/angular source make use of the same mixins as defined above, but with a slightly different + syntax. Inline tags in source code comments like {@example ...} and {@exampleTabs ...} actually generate 'makeExample' and 'makeTabs' mixins + calls in the documentation. The order of 'arguments' in the inline tags is also the same as that of the mixins defined above. However, optional + parameters can also be specified via name (optionally prefixed with a '-'), as will be shown by example below. Parameters that include spaces should + be enclosed in either single or double quotes. This syntax is intended to mirror standard command line argument patterns. + + .alert.is-important. + The '@example' and '@exampleTabs' inline tags MUST always appear at the beginning of a line. + + Example files referenced by inline tags are all assumed to be in the 'modules/@angular' folder in the angular/angular repo. + + :marked + #### @example inline tag parameters + - *filePath:* path to the example file under the '_examples' folder + - *region:* (optional or null) region from the example file to display + - *title:* (optional or null) title displayed above the included text. + - *stylePattern:* (optional or null) allows additional styling via regular expression ( described later). + + #### Examples + + code-example(format="linenums" language="js"). + /** + * An example with no region + * {@example core/directives/ng_if_spec.ts -title='Whole other component' } + * + * An example with a region and a title both specified by name + * {@example core/directives/ng_if_spec.ts region='ng-if' title='Partial' } + * + * Another example with a region and a title with only the title specified explicitly. + * {@example core/directives/ng_if_spec.ts foo title='Foo' } + **/ + + :marked + #### @exampleTabs inline tag parameters + - *filePaths:* a comma delimited string of filePaths to example files under the '_examples' folder + - *regions:* (optional or null) region from the example file to display + - *titles:* (optional or null) a comma delimited string of titles corresponding to each of the filePaths above. + - *stylePatterns:* (optional or null) allows additional styling via regular expression( described later). + + #### Examples + + code-example(format="linenums" language="js"). + /** + * An example with multiple tabs each with its own region and title. + * {@exampleTabs core/directives/test1_spec.ts,core/directives/test2_spec.ts regions='aaa,bbb,' -titles='Test 1,Test 2' } + * + **/ + + + :marked + ### Cross references to Developer guide pages in angular/angular source comments. + + The '{@linkDevGuide ... }' inline tag is intended to be used to create links from api documentation to dev guide + documentation. + + #### @linkDevGuide inline tag parameters + - *filePath:* a filePath that points to a jade page in the DevGuide without the .jade extension ( under public/docs ). + - *title:* The title of link. If the title is omitted an attempt will be made to determine the title of the jade page + being pointed to. If not found the then title will simply be the link. + #### Examples + + code-example(format="linenums" language="js"). + /** + * An link to the Developer guide example with a link title + * This can appear anywhere in a comment line: {@linkDevGuide /js/latest/guide/gettingStarted 'Getting Started' } + * and the same link can also be expressed with an explicit 'title' param + * {@linkDevGuide /js/latest/guide/gettingStarted title='Getting Started' } + * Or... an attempt will be made to infer the title if it is omitted. + * {@linkDevGuide /js/latest/guide/gettingStarted } + **/ diff --git a/public/docs/_includes/styleguide/_images.jade b/public/docs/_includes/styleguide/_images.jade new file mode 100644 index 0000000000..9643229235 --- /dev/null +++ b/public/docs/_includes/styleguide/_images.jade @@ -0,0 +1,10 @@ +#sg-images.showcase.shadow-1 + header.showcase-header + h2 Images + p. + To maintain visual consistency across documentation chapters, please follow the best + practices for authors outlined in the Image + Guide. + p. + The browser background template used for outlining screenshots is here. diff --git a/public/docs/_includes/styleguide/_layouts.jade b/public/docs/_includes/styleguide/_layouts.jade index 904e8de0aa..7fc023ae0d 100644 --- a/public/docs/_includes/styleguide/_layouts.jade +++ b/public/docs/_includes/styleguide/_layouts.jade @@ -2,7 +2,7 @@ header.showcase-header h2 Basic Layouts p. - You will use the following layouts throughout your documenation + You will use the following layouts throughout your documentation to specify sections and sub-sections of content. .showcase-content @@ -23,4 +23,4 @@ code-example(language="html" format="linenums"). .l-sub-section h3 Sub Section Title - p sub section content... \ No newline at end of file + p sub section content... diff --git a/public/docs/_includes/styleguide/_styleguide.jade b/public/docs/_includes/styleguide/_styleguide.jade new file mode 100644 index 0000000000..36684a8841 --- /dev/null +++ b/public/docs/_includes/styleguide/_styleguide.jade @@ -0,0 +1,11 @@ +.grid-fluid + .c10 + include _layouts + include _code-examples + include _alerts + include _callouts + include _tables + include _aside + include _images + +//include _jump-nav diff --git a/public/docs/_includes/styleguide/_tables.jade b/public/docs/_includes/styleguide/_tables.jade index e06e05559b..738a8dc873 100644 --- a/public/docs/_includes/styleguide/_tables.jade +++ b/public/docs/_includes/styleguide/_tables.jade @@ -2,7 +2,7 @@ header.showcase-header h2 Tables p. - Tables can be used to present tablular data as it relates + Tables can be used to present tabular data as it relates to each other. .showcase-content @@ -15,15 +15,15 @@ th Task th Speed tr - td Angular 1.3 + td AngularJS v.1.3 td Routing td fast tr - td Angular 1.4 + td AngularJS v.1.4 td Routing td faster tr - td Angular 2 + td Angular td Routing td fastest :) @@ -35,6 +35,6 @@ th Task th Speed tr - td Angular 1.3 + td AngularJS v.1.3 td Routing - td fast \ No newline at end of file + td fast diff --git a/public/docs/_layout-dart-api.jade b/public/docs/_layout-dart-api.jade new file mode 100644 index 0000000000..cded545a26 --- /dev/null +++ b/public/docs/_layout-dart-api.jade @@ -0,0 +1,52 @@ +//- WARNING: _layout.jade and _layout-dart-api.jade should match in terms of content +//- except that one uses Harp partial/yield and the other uses Jade extends/include. +if jade2ng + .side-nav--offset + link(rel="stylesheet" href="https://github.com/assets/css/vendor/dartdoc/bootstrap.min.css") + link(rel="stylesheet" href="https://github.com/assets/css/vendor/dartdoc/styles.css") + include ../_includes/_hero + include ../_includes/_banner + .l-content-small.grid-fluid.docs-content + block main-content +else + doctype + html(lang="en" ng-app="angularIOApp" itemscope itemtype="http://schema.org/Framework") + // template: public/docs/_layout-dart-api + head + include ../_includes/_head-include + link(rel="stylesheet" href="https://github.com/resources/css/vendor/dartdoc/bootstrap.min.css") + link(rel="stylesheet" href="https://github.com/resources/css/vendor/dartdoc/styles.css") + block head-extra + + block var-def + body(class="l-offset-nav l-offset-side-nav" ng-controller="AppCtrl as appCtrl") + include ../_includes/_main-nav + if current.path[2] + include _includes/_side-nav + include ../_includes/_hero + include ../_includes/_banner + + if current.path[3] == 'api' + if current.path[4] == 'index' + block main-content + else + article(class="l-content-small grid-fluid docs-content") + block main-content + else if current.path.indexOf('cheatsheet') > 0 + block main-content + else + if current.path[3] == 'index' || current.path[3] == 'styleguide' + article(class="l-content-small grid-fluid docs-content") + block main-content + else + article(class="l-content-small grid-fluid docs-content") + div(class="c10") + .showcase + .showcase-content + block main-content + if (current.path[3] == 'guide' || current.path[3] == 'tutorial') && current.path[4] + include ../_includes/_next-item + + include ../_includes/_footer + include ../_includes/_scripts-include + include ../_includes/_scripts-minimum \ No newline at end of file diff --git a/public/docs/_layout.jade b/public/docs/_layout.jade index 5b1f09e395..a59a28af1d 100644 --- a/public/docs/_layout.jade +++ b/public/docs/_layout.jade @@ -1,28 +1,64 @@ -doctype -html(lang="en" ng-app="angularIOApp" itemscope itemtype="http://schema.org/Framework") - head - != partial("../_includes/_head-include") +//- WARNING: _layout.jade and _layout-dart-api.jade should match in terms of content +//- except that one uses Harp partial/yield and the other uses Jade extends/include. - body(class="l-offset-nav l-offset-side-nav" ng-controller="AppCtrl") - != partial("../_includes/_main-nav") - != partial("_includes/sidenav/_primary") +- function tsApiHrefToDart(match, hrefApi, dontcare1, urlRest) { +- // Simple argument values: +- // hrefApi: href="../api/ +- // urlRest: core/index/ViewChild-var.html" +- // console.log(`got match on ${match}, 1: ${hrefApi}, 3: ${urlRest}`); +- var matches = urlRest.match(/^(\w*)\/index\/(\w*)-(\w*)(\.html")$/); +- // console.log(`urlRest matches ${matches}`); +- if (!matches) return match; // leave unchanged +- var i = 1; // matches[0] corresponds to the fully matched result +- var libName = matches[i++]; +- var apiPageEntryName = matches[i++]; +- var apiEntryKind = matches[i++]; +- var suffix = matches[i++]; +- return hrefApi + 'angular2.' + libName + '/' + apiPageEntryName + '-class' + suffix; +- } + +if jade2ng + .side-nav--offset != partial("../_includes/_hero") != partial("../_includes/_banner") + .l-content-small.grid-fluid.docs-content + != yield +else + doctype + html(lang="en" ng-app="angularIOApp" itemscope itemtype="http://schema.org/Framework") + // template: public/docs/_layout + head + != partial("../_includes/_head-include") + block head-extra - if current.path[3] == 'api' - if current.path[4] == 'index' - != yield - else - article(class="l-content-small grid-fluid docs-content") + //- + body(class="l-offset-nav l-offset-side-nav" ng-controller="AppCtrl as appCtrl") + != partial("../_includes/_main-nav") + if current.path[2] + != partial("_includes/_side-nav") + != partial("../_includes/_hero") + != partial("../_includes/_banner") + + if current.path[3] == 'api' + if current.path[4] == 'index' != yield - else if current.path.indexOf('cheatsheet') > 0 - != yield - else - article(class="l-content-small grid-fluid docs-content") + else + article(class="l-content-small grid-fluid docs-content") + != yield + else if current.path.indexOf('cheatsheet') > 0 != yield + else + if current.path[3] == 'index' || current.path[3] == 'styleguide' + article(class="l-content-small grid-fluid docs-content") + != yield + else + - var isDart = current.path[1] === 'dart'; + - var regex = /(href=\"(\.?\.\/)*api\/)(.*")/g; + article(class="l-content-small grid-fluid docs-content") + != !isDart ? yield : yield.replace(regex, tsApiHrefToDart) + if (current.path[3] == 'guide' || current.path[3] == 'tutorial') && current.path[4] + != partial("../_includes/_next-item") - if current.path[3] == 'guide' && current.path[4] - != partial("../_includes/_next-item") - - != partial("../_includes/_footer") - != partial("../_includes/_scripts-include") \ No newline at end of file + != partial("../_includes/_footer") + != partial("../_includes/_scripts-include") + != partial("../_includes/_scripts-minimum") \ No newline at end of file diff --git a/public/docs/dart/latest/_data.json b/public/docs/dart/latest/_data.json deleted file mode 100644 index 38c95d62d6..0000000000 --- a/public/docs/dart/latest/_data.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "index": { - "icon": "home", - "title": "Angular Docs", - "menuTitle": "Docs Home", - "banner": "Angular 2 is currently in Developer Preview. We recommend using Angular for Dart for production applications." - }, - - "quickstart": { - "icon": "query-builder", - "title": "5 Min Quickstart" - }, - - "guide": { - "icon": "list", - "title": "Step By Step Guide", - "banner": "Angular 2 is currently in Developer Preview. For AngularDart 1.X resources, visit angulardart.org." - }, - - "api": { - "icon": "book", - "title": "API Proposal" - }, - - "resources": { - "icon": "play-circle-fill", - "title": "Angular Resources", - "banner": "Angular 2 is currently in Developer Preview. For AngularDart 1.X resources, visit angulardart.org." - }, - - "help": { - "icon": "chat", - "title": "Help & Support" - } -} diff --git a/public/docs/dart/latest/api/_data.json b/public/docs/dart/latest/api/_data.json deleted file mode 100644 index 4b375156fc..0000000000 --- a/public/docs/dart/latest/api/_data.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "index" : { - "title" : "API 2.0 Preview" - } -} \ No newline at end of file diff --git a/public/docs/dart/latest/api/index.jade b/public/docs/dart/latest/api/index.jade deleted file mode 100644 index 367ee3c38d..0000000000 --- a/public/docs/dart/latest/api/index.jade +++ /dev/null @@ -1,16 +0,0 @@ -.l-main-section - h2 Developer Preview - - p. - The proposed Angular 2 API does not yet have Dart-specific documentation. - However, because the Dart and JavaScript APIs are generated from the same source, - you might find the JavaScript API docs helpful: - - p.text-center - Angular 2 API Preview (JavaScript) - - .l-sub-section - h3 Angular 1 for Production - p. - If you're building a production app today, please - use AngularDart 1.X. diff --git a/public/docs/dart/latest/guide/_data.json b/public/docs/dart/latest/guide/_data.json deleted file mode 100644 index cbdd28acde..0000000000 --- a/public/docs/dart/latest/guide/_data.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "_listtype": "ordered", - - "index": { - "title": "Step By Step Guide" - }, - - "setup": { - "title": "Getting Started" - }, - - "displaying-data": { - "title": "Displaying Data", - "intro": "Displaying data is job number one for any good application. In Angular, you bind data to elements in HTML templates and Angular automatically updates the UI as data changes." - }, - - "user-input": { - "title": "User Input", - "intro": "DOM events drive user input in Angular. You can use the native events like click, mouseover, and keyup. Angular uses a special syntax to register events to DOM elements. This section covers all the ins and outs of using the event syntax." - } -} diff --git a/public/docs/dart/latest/guide/displaying-data.jade b/public/docs/dart/latest/guide/displaying-data.jade deleted file mode 100644 index c32896c66f..0000000000 --- a/public/docs/dart/latest/guide/displaying-data.jade +++ /dev/null @@ -1,469 +0,0 @@ -.l-main-section - p. - Let's walk through how to display a property and a list of properties, - and then to conditionally show content - based on state. The final UI looks like this: - - figure.image-display - img(src='https://github.com/resources/images/examples/displaying-data-example1.png' alt="Example of Todo App") - -.l-main-section - h2#section-create-an-entry-point Create entry points and pubspec - - p. - Open your favorite editor and create a directory with - a web/main.dart file, - a web/index.html file, and - a pubspec.yaml file: - - code-tabs - code-pane(language="dart" name="web/main.dart" format="linenums"). - import 'package:angular2/bootstrap.dart'; - import 'package:displaying_data/show_properties.dart'; - - main() { - bootstrap(DisplayComponent); - } - code-pane(language="html" name="web/index.html" format="linenums"). - <!DOCTYPE html> - <html> - <head> - <title>Displaying Data</title> - <link rel="stylesheet" href="style.css"> - <script async src="main.dart" type="application/dart"></script> - <script async src="packages/browser/dart.js"></script> - </head> - <body> - <display></display> - </body> - </html> - code-pane(language="yaml" name="pubspec.yaml" format="linenums"). - name: displaying_data - description: Dart version of Angular 2 example, Displaying Data - version: 0.0.1 - dependencies: - angular2: 2.0.0-alpha.39 - browser: ^0.10.0 - transformers: - - angular2: - entry_points: web/main.dart - p. - All of this code should look familiar from the previous page, - except for the imports in main.dart. - The import of show_properties.dart - lets you implement part of the app in a different Dart file. - You've previously seen angular2.dart imported, - but that's unnecessary here because this version of main.dart - doesn't implement any components or other injectable types. - - p. - All three of these files remain similar in the rest of the examples, - so we'll focus on what changes. - -.l-main-section - h2#section-showing-properties-with-interpolation Showing properties with interpolation - p. - The simple method for binding text into templates is through interpolation, - where you put the name of a property - inside {{ }}. - - p. - To see this working, first create a lib directory. - Under it, put a Dart file named show_properties.dart - that contains the following code: - - code-example(language="dart" format="linenums" escape="html"). - // lib/show_properties.dart - library displaying_data.show_properties; - - import 'package:angular2/angular2.dart'; - - @Component(selector: 'display') - @View(template: ''' -

    My name: {{ myName }}

    - ''') - class DisplayComponent { - String myName = 'Alice'; - } - - p. - You've just defined a component that encompasses a view and controller for the app. The view - defines a template: - - code-example(language="html" escape="html"). -

    My name: {{ myName }}

    - - p. - Angular will automatically pull the value of myName and - insert it into the browser, - automatically updating it whenever it changes. - - .callout.is-helpful - header Note - p. - While you've used template: to specify an inline view, for larger templates you'd - want to move them to a separate file and load them with templateUrl: instead. - - p. - One thing to notice is that although you've written - your DisplayComponent class, you haven't - used new to instantiate it. - Because your class is associated with <display> elements in - the DOM, Angular automatically calls new on - DisplayComponent and bind its properties to - that part of the template. - - p. - When you're building templates, data bindings like these have access to - the same scope of - properties as your controller class does. - Here your class is DisplayComponent, which has - just one property, myName. - - p. - Add a second line to the template, - so you can see Angular dynamically update content: - - code-example(language="html"). - <p>Current time: {{ time }}</p> - - p. - Then import dart:async so you can use a Timer, - and give the DisplayComponent a starting value for time and - create a periodic Timer call to update the time: - - code-example(language="dart"). - import 'dart:async'; - ... - class DisplayComponent { - String myName = 'Alice'; - String time; - Timer _timer; - - DisplayComponent() { - _updateTime(null); - _timer = new Timer.periodic(new Duration(seconds: 1), _updateTime); - } - - _updateTime(Timer _) { - time = new DateTime.now().toString(); - } - } - - p Reload the app, and you'll now see the seconds updating automatically. - -.l-main-section - h2#Create-an-array Display an iterable using *ng-for - p Moving up from a single value, create a property that's a list of values. - - code-example(language="dart"). - class DisplayComponent { - String myName = 'Alice'; - List<String> friendNames = const [ - 'Aarav', - 'Martín', - 'Shannon', - 'Ariana', - 'Kai' - ]; - } - - p. - You can then use this list in your template with - the ng-for directive to create copies of DOM elements - with one for each item in the list. - - code-example(language="dart"). - @View(template: ''' - <p>My name: {{ myName }}</p> - <p>Friends:</p> - <ul> - <li *ng-for="#name of friendNames"> - {{ name }} - </li> - </ul> - ''', directives: const [NgFor]) - - p. - To make ng-for work, - you need to add the Angular NgFor directive, - so that Angular knows to include it. - Add NgFor using the optional directives parameter. - - p Reload and you've got your list of friends! - p. - Again, Angular will mirror changes you make to this list over in the DOM. - Add a new item and it appears in your list. - Delete one and Angular deletes the <li>. - Reorder items and Angular makes the corresponding reorder of the DOM list. - - p Let's look at the few lines that do the work again: - - code-example(language="html"). - <li *ng-for="#name of friendNames"> - {{ name }} - </li> - - p The way to read this is: - ul - li. - *ng-for: Create a DOM element for each item in an - iterable - such as a list. - li #name: Refer to individual values of the iterable as name. - li of friendNames: The iterable to use is called friendNames in the current controller. - - p Using this syntax, you can build UI lists from any iterable object. - -.l-main-section - h2#Create-a-class Create a model and inject it - p. - Before we get too much further, we should mention that - putting the model (list) directly into the controller isn't proper form. - We should separate the concerns by having another class - serve the role of model and inject it into the controller. - - p. - Make a FriendsService class to implement a model - containing a list of friends. - Put this in a new file under lib/ - named friends_service.dart. Here's what the class looks like: - - code-example(language="dart" format="linenums"). - // lib/friends_service.dart - library displaying_data.friends_service; - - import 'package:angular2/angular2.dart'; - - @Injectable() - class FriendsService { - List<String> names = ['Aarav', 'Martín', 'Shannon', 'Ariana', 'Kai']; - } - - p. - Now you can replace the current list of friends in DisplayComponent. - Import friends_service.dart, - and add a FriendsService parameter to the constructor. - Then set friendNames to the names provided by the service. - - code-example(language="dart"). - // In lib/show_properties.dart - ... - import 'package:displaying_data/friends_service.dart'; - ... - class DisplayComponent { - String myName = 'Alice'; - List<String> friendNames; - - DisplayComponent(FriendsService friendsService) { - friendNames = friendsService.names; - } - } - - p. - Next, make FriendsService available to dependency injection - by adding a viewBindings parameter to DisplayComponent's - @Component annotation: - - code-example(language="dart"). - @Component(selector: 'display', viewBindings: const [FriendsService]) - -.l-main-section - h2#Conditionally-displaying-data-with-NgIf Conditionally display data using *ng-if - p. - Lastly, before we move on, let's handle showing parts of our UI conditionally with *ng-if. The - NgIf directive adds or removes elements from the DOM based on the expression you provide. - - p See it in action by adding a paragraph at the end of your template: - - code-example(language="html"). - <p *ng-if="friendNames.length > 3">You have many friends!</p> - - p. - Also add NgIf to the list of directives, - so Angular knows to include it: - - code-example(language="dart"). - directives: const[NgFor, NgIf] - p. - The list currently has 5 items, so if you run the app you'll see the message - congratulating you on your many friends. - Remove two items from the list, reload your browser, - and see that the message no longer displays. - - p Here's the final code. - - code-tabs - code-pane(language="dart" name="lib/show_properties.dart" format="linenums"). - library displaying_data.show_properties; - - import 'package:angular2/angular2.dart'; - import 'package:displaying_data/friends_service.dart'; - - @Component(selector: 'display', viewBindings: const [FriendsService]) - @View(template: ''' - <p>My name: {{ myName }}</p> - <p>Friends:</p> - <ul> - <li *ng-for="#name of friendNames"> - {{ name }} - </li> - </ul> - <p *ng-if="friendNames.length > 3">You have many friends!</p> - ''', directives: const [NgFor, NgIf]) - class DisplayComponent { - String myName = 'Alice'; - List<String> friendNames; - - DisplayComponent(FriendsService friendsService) { - friendNames = friendsService.names; - } - } - code-pane(language="dart" name="lib/friends_service.dart" format="linenums"). - library displaying_data.friends_service; - - import 'package:angular2/angular2.dart'; - - @Injectable() - class FriendsService { - List<String> names = ['Aarav', 'Martín', 'Shannon', 'Ariana', 'Kai']; - } - code-pane(language="dart" name="web/main.dart" format="linenums"). - import 'package:angular2/bootstrap.dart'; - import 'package:displaying_data/show_properties.dart'; - - main() { - bootstrap(DisplayComponent); - } - code-pane(language="html" name="web/index.html" format="linenums"). - <!DOCTYPE html> - <html> - <head> - <title>Displaying Data</title> - <link rel="stylesheet" href="style.css"> - <script async src="main.dart" type="application/dart"></script> - <script async src="packages/browser/dart.js"></script> - </head> - <body> - <display></display> - </body> - </html> - code-pane(language="yaml" name="pubspec.yaml" format="linenums"). - name: displaying_data - description: Displaying Data example - version: 0.0.1 - dependencies: - angular2: 2.0.0-alpha.39 - browser: ^0.10.0 - transformers: - - angular2: - entry_points: web/main.dart -.l-main-section - h2#section-explanations Explanations - - .l-sub-section - h3 Using multiple Dart files in an Angular app - - p. - Dart offers a few ways to implement an app in multiple files. - In this guide, each example is in a single package, - and each Dart file implements a separate library. - For a bigger project, you might split the code into libraries - in two or more packages. - - p. - To use the API defined in show_properties.dart, - main.dart must import that file. - The import statement uses the package name - (defined in pubspec.yaml to be displaying_data) - and the path to show_properties.dart - (starting at the app's top directory, - but omitting the lib/ directory). - - code-example(language="dart"). - // In web/main.dart: - ... - import 'package:displaying_data/show_properties.dart'; - ... - - // In lib/show_properties.dart: - library displaying_data.show_properties; - ... - - p. - The name that show_properties.dart specifies for its library - is similar to the path used to import the library, - but with no ".dart" suffix and - with dots (.) instead of slashes (/). - Naming - conventions for libraries, - along with lots of other helpful information, are in the - Dart Style Guide. - - p. - Another import lets show_properties.dart - use the API defined in friends_service.dart: - - code-example(language="dart"). - // In lib/show_properties.dart: - library displaying_data.show_properties; - ... - import 'package:displaying_data/friends_service.dart'; - ... - - // In lib/friends_service.dart: - library displaying_data.friends_service; - ... - - p. - Because both show_properties.dart and friends_service.dart - are under lib, - the import could instead use a relative path: - - code-example(language="dart"). - // In lib/show_properties.dart: - library displaying_data.show_properties; - ... - import 'friends_service.dart'; - ... - - // In lib/friends_service.dart: - library displaying_data.friends_service; - ... - - p. - The app's entry point—main.dart—imports - bootstrap.dart so that it can call bootstrap(). - Both show_properties.dart and friends_service.dart - import angular2.dart - so that they can define Angular components and models. - - code-example(language="dart"). - // In web/main.dart: - import 'package:angular2/bootstrap.dart'; - ... - bootstrap(...) - ... - - // In lib/show_properties.dart: - ... - import 'package:angular2/angular2.dart'; - ... - @Component(...) - @View(...) - ... - - // In lib/friends_service.dart: - ... - import 'package:angular2/angular2.dart'; - ... - @Injectable() - ... - - - p. - For more information on implementing Dart libraries, see - Libraries and visibility - in the - Dart language tour. - diff --git a/public/docs/dart/latest/guide/index.jade b/public/docs/dart/latest/guide/index.jade deleted file mode 100644 index cbcf5addb2..0000000000 --- a/public/docs/dart/latest/guide/index.jade +++ /dev/null @@ -1,10 +0,0 @@ -- var number = 1; -ul.is-plain - for page, slug in public.docs[current.path[1]][current.path[2]].guide._data - - if slug != '_listtype' && slug != 'index' - - var url = "/docs/" + current.path[1] + "/" + current.path[2] + "/" + current.path[3] + "/" + slug + ".html" - - var num = number++ - - li - != partial("../../../../_includes/_hover-card", { icon: "icon-number", number: num, name: page.title, url: url }) diff --git a/public/docs/dart/latest/guide/setup.jade b/public/docs/dart/latest/guide/setup.jade deleted file mode 100644 index a24bed3b93..0000000000 --- a/public/docs/dart/latest/guide/setup.jade +++ /dev/null @@ -1,168 +0,0 @@ -.l-main-section - - p. - As long as you already - have the Dart SDK, - getting started with Angular 2 is simple: - - ol - li Depend on the angular2 pub package. - li Create a Dart file that defines (directly or indirectly) a - root component and bootstraps Angular. - li Create an HTML file that uses the root component and points to the Dart file - - p. - You can use whichever editor or IDE you like, - or just use the command-line tools that the Dart SDK provides. - See Dart Tools - for more information. - - - h2#section-install Depend on angular2 - - p. - To use Angular2 in your app, include angular2 as a dependency in - your app's pubspec.yaml file. For example: - - code-example(language="yaml"). - # pubspec.yaml - name: getting_started - description: Getting Started example - version: 0.0.1 - dependencies: - angular2: 2.0.0-alpha.39 - browser: ^0.10.0 - transformers: - - angular2: - entry_points: web/main.dart - p. - Run pub get to download the packages your app depends on. - (Dart-savvy editors and IDEs - typically run pub get for you.) - - -.l-main-section - h2#section-set-up-the-starting-component Write the Dart code - - p. - Next to your pubspec.yaml file, - create a web subdirectory containing a Dart file - (main.dart). - Edit main.dart, adding a component class (AppComponent), - configuring it to bind to the <my-app> element, - and creating a top-level main() function that calls - Angular's bootstrap() function. - - code-example(language="dart" escape="html"). - // web/main.dart - import 'package:angular2/angular2.dart'; - import 'package:angular2/bootstrap.dart'; - - @Component(selector: 'my-app') - @View(template: '

    My first Angular 2 App

    ') - class AppComponent {} - - main() { - bootstrap(AppComponent); - } - -.l-main-section - h2#section-create-an-entry-point Create an HTML file - p. - In the web/ directory of your app, - create an HTML file (index.html). - Edit index.html to add a <my-app> element - and call main.dart. - - code-example(language="html"). - <!-- web/index.html --> - <!DOCTYPE html> - <html> - <head> - <title>Getting Started</title> - <link rel="stylesheet" href="style.css"> - <script async src="main.dart" type="application/dart"></script> - <script async src="packages/browser/dart.js"></script> - </head> - <body> - <my-app></my-app> - </body> - </html> - -.l-main-section - h2#section-run-it Run the app! - - p. - Now run the app. How you do this depends on your tools. - - ul - li. - If you're using WebStorm or IntelliJ IDEA, - right-click web/index.html, - and choose Run 'index.html'. - - li. - If you're using the command line and don't have Dartium, - serve the app using pub serve, - and then run it by visiting http://localhost:8080 in a browser. - Generating the JavaScript takes a few seconds when you first visit the page, - and the generated JavaScript is currently large. - The generated JavaScript will be smaller once - Angular's transformer becomes available. - - p. - You should see something like this: - - figure.image-display - img(src='https://github.com/resources/images/examples/setup-example1.png' alt="Example of Todo App") - -.l-main-section - h2#section-explanations Explanations - - p This basic Angular app contains the structure for any app you'll build. - - .l-sub-section - h3 It's all a tree - - p. - You can think of an Angular app as a tree of components. - The root component acts as the top-level container for the rest of your application. - You've named this one AppComponent, but there's - nothing special about the name; you can use whatever makes sense to you. - - p. - The root component's job is to give a location in the HTML file where - your application can - render through its element—in this case, <my-app>. - There's nothing special about the HTML filename or the element name; - you can pick whatever you like. - - p. - The root component loads the initial template for the application, - which loads other components to perform - whatever functions your application needs—menu bars, views, forms, and so on. - We'll walk through examples of all of - these in the following pages. - - .l-sub-section - h3 @Component and @View annotations - - p. - A component annotation describes details about the component. - An annotation can be identified by its at-sign (@). - p. - The @Component annotation defines the HTML tag for - the component by specifying the component's CSS selector. - p. - The @View annotation defines the HTML that - represents the component. - The component you wrote uses an inline template, - but you can also have an external template. - To use an external template, - specify a templateUrl property and - give it the path to the HTML file. - - - p. - Exciting! Not excited yet? - Let's move on to Displaying Data. diff --git a/public/docs/dart/latest/guide/user-input.jade b/public/docs/dart/latest/guide/user-input.jade deleted file mode 100644 index 50a313d442..0000000000 --- a/public/docs/dart/latest/guide/user-input.jade +++ /dev/null @@ -1,198 +0,0 @@ -.l-main-section - p. - Use the event syntax (eventName) to - make your application respond to user input. - p. - You can specify the event handler—a method in the component controller—like this: - - code-example(language="html"). - <input (keyup)="myControllerMethod()"> - p. - As in previous examples, you can make element references available to - other parts of the template as a local - variable using the # syntax. - Using # and events, - you can write the old "update text as you type" example: - - - code-example(language="html"). - <input #myname (keyup)> - <p>{{myname.value}}</p> - - p.text-body(ng-non-bindable). - In that example, #myname creates a local variable in the template that - the <p> element can refer to. - The (keyup) tells Angular to trigger updates when it gets a keyup - event. And {{myname.value}} binds the text node of the - <p> element to the - input's value property. - - p. - Let's do something a little more complex, where the user enters items - that the app adds to a list: - figure.image-display - img(src='https://github.com/resources/images/examples/user-input-example1.png' alt="Example of Todo App") - - -.l-main-section - h2#section-create-an-array-property Create a list property - p. - With the default files in place, - create a TodoController class to manage interactions with the - list. Inside TodoController, add a list with some initial items. - Then add a method that adds new items - to the list. - - code-example(language="dart"). - class TodoList { - List<String> todos =[ - 'Eat breakfast', - 'Walk dog', - 'Breathe', - 'Learn Angular' - ]; - - addTodo(String todo) { - todos.add(todo); - } - } - -.callout.is-helpful - header Production Best Practice - p. - As shown in the previous example, a production application you would - separate the model out into another class - and inject it into TodoController. - We've omitted that here for brevity. - -

    - -.l-main-section - h2#section-display-the-list-of-todos Display the list of todos - p. - Using the *ng-for iterator, create an <li> for each item in the todos list and set - its text to the value. - - code-example(language="html"). - <ul> - <li *ng-for="#todo of todos"> - {{ todo }} - </li> - </ul> - -.l-main-section - h2#section-add-todos-to-the-list Add todos to the list via button click - p. - Now, add a text input and a button for users to add items to the list. As you saw above, you can create a local - variable reference in your template with #varname. Call it #todotext here. - - code-example(language="html"). - <input #todotext> - p. - Specify the target of the click event binding as your controller's - addTodo() method and pass - it the value. Since you created a reference called todotext, - you can get the value with todotext.value. - - code-example(language="html"). - <button (click)="addTodo(todotext.value)">Add Todo</button> - - p. - To make pressing Enter do something useful, - you can add a keyup event handler to the input field. - This event handler uses APIs defined in - dart:html, - so be sure to import that library. - - code-example(language="dart"). - import 'dart:html'; - ... - - // In the template: - <input #todotext (keyup)="doneTyping(\$event)"> - ... - - // In the component controller class: - doneTyping(KeyboardEvent event) { - if (event.keyCode == KeyCode.ENTER) { - InputElement e = event.target; - addTodo(e.value); - e.value = null; - } - } - -.l-main-section - h2#section-final-code Final code - - code-tabs - code-pane(language="dart" name="lib/todo_list.dart" format="linenums"). - library user_input.todo_list; - - import 'dart:html'; - import 'package:angular2/angular2.dart'; - - @Component(selector: 'todo-list') - @View( - // An alternative to using \$event is to use a raw string instead. - // For example, change "template: '''" to "template: r'''". - template: ''' - <ul> - <li *ng-for="#todo of todos"> - {{ todo }} - </li> - </ul> - - <input #todotext (keyup)="doneTyping(\$event)"> - <button (click)="addTodo(todotext.value)">Add Todo</button> - ''', directives: const [NgFor]) - class TodoList { - List<String> todos = [ - 'Eat breakfast', - 'Walk dog', - 'Breathe', - 'Learn Angular' - ]; - - addTodo(String todo) { - todos.add(todo); - } - - doneTyping(KeyboardEvent event) { - if (event.keyCode == KeyCode.ENTER) { - InputElement e = event.target; - addTodo(e.value); - e.value = null; - } - } - } - code-pane(language="dart" name="web/main.dart" format="linenums"). - import 'package:angular2/bootstrap.dart'; - import 'package:user_input/todo_list.dart'; - - main() { - bootstrap(TodoList); - } - code-pane(language="html" name="web/index.html" format="linenums"). - <!DOCTYPE html> - <html> - <head> - <title>User Input</title> - <link rel="stylesheet" href="style.css"> - <script async src="main.dart" type="application/dart"></script> - <script async src="packages/browser/dart.js"></script> - </head> - <body> - <todo-list></todo-list> - </body> - </html> - code-pane(language="yaml" name="pubspec.yaml" format="linenums"). - name: user_input - description: User Input example - version: 0.0.1 - dependencies: - angular2: 2.0.0-alpha.39 - browser: ^0.10.0 - transformers: - - angular2: - entry_points: web/main.dart diff --git a/public/docs/dart/latest/help.jade b/public/docs/dart/latest/help.jade deleted file mode 100644 index 8421f25772..0000000000 --- a/public/docs/dart/latest/help.jade +++ /dev/null @@ -1 +0,0 @@ -!= partial("../../_includes/_help") \ No newline at end of file diff --git a/public/docs/dart/latest/index.jade b/public/docs/dart/latest/index.jade deleted file mode 100644 index 37f877df95..0000000000 --- a/public/docs/dart/latest/index.jade +++ /dev/null @@ -1,30 +0,0 @@ -div.card-row(layout='row') - div(flex) - md-card.card - md-card-content - h3.text-headline.text-uppercase Quickstart - p.text-body Learn in 5 minutes - - footer - a(href="https://github.com/docs/#{current.path[1]}/#{current.path[2]}/quickstart.html" class="button" md-button) View Quickstart - - - div(flex) - md-card.card - md-card-content - h3.text-headline.text-uppercase Guide - p.text-body Step by Step Guide - - footer - a(href="https://github.com/docs/#{current.path[1]}/#{current.path[2]}/guide/" class="button button-primary" md-button) View Guide - - - div(flex) - md-card.card - md-card-content - h3.text-headline.text-uppercase API - p.text-body API 2.0 Preview - - footer - a(href="https://github.com/docs/#{current.path[1]}/#{current.path[2]}/api/" class="button" md-button) View API - diff --git a/public/docs/dart/latest/quickstart.jade b/public/docs/dart/latest/quickstart.jade deleted file mode 100644 index 4358382a9c..0000000000 --- a/public/docs/dart/latest/quickstart.jade +++ /dev/null @@ -1,239 +0,0 @@ -include ../../../_includes/_util-fns - -:markdown - Let's start from zero and build a super simple Angular 2 application in Dart. - -.callout.is-helpful - header Don't want Dart? - :markdown - Although we're getting started in Dart, you can also write Angular 2 apps - in TypeScript and JavaScript. - Just select either of those languages from the combo-box in the banner. - -p. - These instructions assume that you already have the - Dart SDK - and any tools you like to use with Dart. - If you don't have a favorite editor already, try - WebStorm, - which comes with a Dart plugin. - You can also download - Dart plugins for - other IDEs and editors. - Once you have the Dart SDK and any other tools you want, return here. - - -//- ########################## -.l-main-section - h2#section-install-angular Set up a new app directory - - :markdown - Create a new directory, - and put a file named `pubspec.yaml` in it. - - code-example(language="sh"). - > mkdir angular2_getting_started - > cd angular2_getting_started - > vim pubspec.yaml # Use your favorite editor! - - p. - In pubspec.yaml, - specify the angular2 and browser packages as dependencies, - as well as the angular2 transformer. - Angular 2 is changing rapidly, so provide an exact version: - 2.0.0-alpha.44. - - +makeExample('quickstart/dart/ex1/pubspec.yaml', null, 'pubspec.yaml') - - p. - In the same directory, create a web directory, and then - run pub get to install the angular2 and browser packages - (along with the packages they depend on). - - code-example(language="sh"). - > mkdir web - > pub get - Resolving dependencies... - - //- PENDING: Create template? Link to pub/pubspec docs? - - -//- ########################## -.l-main-section - h2#section-transpile Create a Dart file - - p. - Create a file under web named main.dart. - - code-example(language="sh"). - > vim web/main.dart # Use your favorite editor! - - p. - Paste the following code into web/main.dart: - - +makeExample('quickstart/dart/ex1/web/main.dart', null, 'web/main.dart') - - :markdown - You've just defined an Angular 2 **component**, - one of the most important Angular 2 features. - Components are the primary way to create application views - and support them with application logic. - - This component is an empty, do-nothing class class named `AppComponent`. - You can add properties and application logic to it later, - when you're ready to build a substantive application. - - Above the class is the `@Component` annotation, - which tells Angular that this class *is an Angular component*. - The call to the `@Component` constructor has two - named parameters, `selector` and `template`. - - The `selector` parameter specifies a CSS selector for - a host HTML element named `my-app`. - Angular creates and displays an instance of `AppComponent` - wherever it encounters a `my-app` element. - - The `template` parameter is the component's companion template - that tells Angular how to render a view. - In this case, the template is a single line of HTML announcing - "My First Angular 2 App". - - The main() function - calls Angular's bootstrap() function, - which tells Angular to start the application with `AppComponent` - at the application root. - Someday the application will - consist of more components arising in tree-like fashion from this root. - - The top lines import two libraries. - *All* Dart files that use Angular APIs import `angular2.dart`. - Only files that call `bootstrap()` import `bootstrap.dart`. - -//- ########################## -.l-main-section - - - h2#section-angular-create-account Create an HTML file - - p. - Create a file named web/index.html that contains - the following code: - - +makeExample('quickstart/dart/ex1/web/index.html', null, 'web/index.html') - - :markdown - The `` tag in the `` is - the custom HTML element defined in the Dart file. - - -//- ########################## -.l-main-section - - h2#section-angular-run-app Run the app - - p. - You have a few options for running your app. - One is to launch a local HTTP server - and then view the app in - Dartium. - You can use whatever server you like, such as WebStorm's server - or Python's SimpleHTTPServer. - - p. - Another option is to build and serve the app using pub serve, - and then run it by visiting http://localhost:8080 in any modern browser. - Pub serve generates the JavaScript on the fly, - which can take a while when you first visit the page. - - p. - Once the app is running, - you should see My First Angular 2 App in your browser window. - - :markdown - If you don't see that, make sure you've entered all the code correctly - and run `pub get`. - -//- ########################## -.l-main-section - - h2#section-angular-run-app Generate JavaScript - - :markdown - Before you can deploy your app, you need to generate JavaScript files. - Pub build makes that easy. - To improve your app's performance, convert the - HTML file to directly include the generated JavaScript; - one way to do that is with dart_to_js_script_rewriter. - - :markdown - Add the dart_to_js_script_rewriter package to your pubspec, - in both the `dependencies` and `transformers` sections. - - - var stylePattern = { pnk: /(dart_to_js_script_rewriter.*$)|(- dart_to_js_script_rewriter.*$)/gm, otl: /(dependencies:)|(transformers:)/g }; - +makeExample('quickstart/dart/ex2/pubspec.yaml', null, 'pubspec.yaml', stylePattern) - - p. - Then compile your Dart code to JavaScript, - using pub build. - - code-example(language="basic"). - > pub build - Loading source assets... - - p. - The generated JavaScript appears, along with supporting files, - under the build directory. - - p. - When you generate JavaScript for an Angular app, - be sure to use the Angular transformer. - It analyzes your code, - converting reflection-using code to static code - that Dart's build tools can compile to faster, smaller JavaScript. - The highlighted lines in pubspec.yaml - configure the Angular transformer: - - - var stylePattern = { otl: /(transformers:)|(- angular2:)|(entry_points.*$)/gm }; - +makeExample('quickstart/dart/ex2/pubspec.yaml', null, 'pubspec.yaml', stylePattern) - - p. - The entry_points item - identifies the Dart file in your app - that has a main() function. - For more information, see the - Angular - transformer wiki page. - - - #performance.l-sub-section - h3 Performance, the transformer, and Angular 2 libraries - - p. - When you import bootstrap.dart, - you also get dart:mirrors, - a reflection library that - causes performance problems when compiled to JavaScript. - Don't worry, - the Angular transformer converts your entry points - (entry_points in pubspec.yaml) - so that they don't use mirrors. - - -//- WHAT'S NEXT... ########################## -.l-main-section - h2#section-transpile Great job! Next step... - - - - p. - Follow the developer guide - to continue playing with Angular 2 for Dart. - - p. - Or read more about Angular or Dart: - - ul - li - Angular resources - li - dartlang.org diff --git a/public/docs/dart/latest/resources.jade b/public/docs/dart/latest/resources.jade deleted file mode 100644 index ad95688925..0000000000 --- a/public/docs/dart/latest/resources.jade +++ /dev/null @@ -1,40 +0,0 @@ -// TODO: don't duplicate text from /docs/js/latest/resources.jade -.l-main-section - h2 Victor Savkin's Blog Posts - ul - li Two Phases of Angular 2 Applications - li Forms in Angular 2 - li Change detection - li Functional programming - li Dependency injection - -.l-main-section - h2 Videos - - h4 Intro Vidoes - ul - li Building a Todo App by David East - li Angular 2 Forms by David East - - h4 ng-conf - ul - li Playlist of ng-conf 2015 videos. - li Day 1 Keynote: a broad overview of Angular 2, migration, and where we are headed. - li Day 2 Keynote: Misko and Rado do a deep-dive on Angular 2 details. - li Creating Container Components with Web Components in Angular: Kara Erickson & Rachael L Moore. - li Change Detection Reinvented: Why Angular 2 change detection is fast out of the box and options for developers to make it even faster. - - h4 ng-europe - ul - li Oct 2014 playlist of ng-europe videos on Angular 2 and the future of Angular. - - -.l-main-section - h2 API Design Docs & Notes - ul - li Best Practices - li Best Practices - li API Design Docs - li Meeting Notes - li Presentations - li More... diff --git a/public/docs/index.jade b/public/docs/index.jade index 9d71df89fc..75750720a8 100644 --- a/public/docs/index.jade +++ b/public/docs/index.jade @@ -1,12 +1,13 @@ .l-main-section .l-sub-section - h3 JavaScript + h3 TypeScript - ul - li Angular 2 JS - Latest Version + p: Angular TS - Latest Version + + h3 JavaScript + p Angular JS - Latest Version h3 Dart - ul - li Angular 2 Dart - Latest Version \ No newline at end of file + p: Angular Dart - Latest Version \ No newline at end of file diff --git a/public/docs/js/latest/_data.json b/public/docs/js/latest/_data.json index 82f6a689f3..5158d78b27 100644 --- a/public/docs/js/latest/_data.json +++ b/public/docs/js/latest/_data.json @@ -2,34 +2,74 @@ "index": { "icon": "home", "title": "Angular Docs", + "subtitle": "JavaScript", "menuTitle": "Docs Home", - "banner": "Angular 2 is currently in Developer Preview. We recommend using Angular 1.X for production applications." + "banner": "The latest Angular release is 4.0. Learn about the latest updates to the documentation. View the Angular change log for enhancements, fixes, and breaking changes in Angular itself. If you’re looking for Angular v2 documents, you’ll find them at v2.angular.io." }, "quickstart": { "icon": "query-builder", - "title": "5 Min Quickstart" + "title": "Quickstart", + "subtitle": "JavaScript", + "description": "Get up and running with Angular", + "banner": "This QuickStart guide demonstrates how to build and run a simple Angular application." + }, + + "tutorial": { + "icon": "list", + "title": "Tutorial", + "subtitle": "JavaScript" }, "guide": { "icon": "list", - "title": "Developer Guide", - "banner": "Angular 2 is currently in Developer Preview. For Angular 1.X Resources please visit Angularjs.org." + "title": "Guide", + "subtitle": "JavaScript" + }, + + "cookbook": { + "icon": "list", + "title": "Cookbook", + "subtitle": "JavaScript" }, - "api": { + "api/": { "icon": "book", - "title": "API Preview" + "title": "API Reference", + "subtitle": "JavaScript", + "reference": true + }, + + "cheatsheet": { + "title": "Cheat Sheet", + "subtitle": "JavaScript", + "reference": false + }, + + "glossary": { + "title": "Glossary", + "subtitle": "JavaScript", + "intro": "Brief definitions of the most important words in the Angular vocabulary", + "reference": false }, "resources": { "icon": "play-circle-fill", "title": "Angular Resources", - "banner": "Angular 2 is currently in Developer Preview. For Angular 1.X Resources please visit Angularjs.org." + "subtitle": "JavaScript", + "resources": true }, "help": { "icon": "chat", - "title": "Help & Support" + "title": "Help & Support", + "subtitle": "From our team & community", + "resources": true + }, + + "styleguide": { + "title": "Docs Style Guide", + "subtitle": "JavaScript", + "intro": "Design & Layout Patterns For Documentation" } } diff --git a/public/docs/js/latest/api/_data.json b/public/docs/js/latest/api/_data.json index 2f96733bfa..a732590638 100644 --- a/public/docs/js/latest/api/_data.json +++ b/public/docs/js/latest/api/_data.json @@ -1,6 +1,6 @@ { "_listtype": "ordered", "index" : { - "title" : "API 2.0 Preview" + "title" : "API Reference" } } \ No newline at end of file diff --git a/public/docs/js/latest/api/index.jade b/public/docs/js/latest/api/index.jade index ab88700ddb..d672174832 100644 --- a/public/docs/js/latest/api/index.jade +++ b/public/docs/js/latest/api/index.jade @@ -1,18 +1 @@ -div(ng-cloak).banner - dl.api-key - dt Display: - dd.directive(ng-class='{ active: apiType === "directive" }' ng-click='setType("directive")') Directive - dd.class(ng-class='{ active: apiType === "class" }' ng-click='setType("class")') Class - dd.interface(ng-class='{ active: apiType === "interface" }' ng-click='setType("interface")') Interface - dd.function(ng-class='{ active: apiType === "function" }' ng-click='setType("function")') Function - dd.const(ng-class='{ active: apiType === "const" }' ng-click='setType("const")') Const or Enum - dd.var(ng-class='{ active: apiType === "var" }' ng-click='setType("var")') Variable - input.api-filter(placeholder='Filter', ng-model='apiFilter') -article(class="l-content-small grid-fluid docs-content") - div(ng-repeat='section in apiSections' ng-if="(apiList[section.name] | filter: { title: apiFilter, docType: apiType }).length" ng-cloak) - h3 {{ section.title }} - ul.api-list - li.api-item(ng-repeat='item in apiList[section.name] | filter: { title: apiFilter, docType: apiType }') - a(ng-href='https://github.com/%7B%7B%20section.name%20%7D%7D/%7B%7B%20item.title%20%7D%7D-%7B%7B%20item.docType%20==="directive" ? "class" : item.docType }}.html') - span(class='symbol {{ item.docType }}') - | {{ item.title }} +api-list(src="https://github.com/api-list.json") \ No newline at end of file diff --git a/public/docs/js/latest/cookbook/_data.json b/public/docs/js/latest/cookbook/_data.json new file mode 100644 index 0000000000..b8bcbf545c --- /dev/null +++ b/public/docs/js/latest/cookbook/_data.json @@ -0,0 +1,61 @@ +{ + "index": { + "title": "Cookbook", + "navTitle": "Overview", + "intro": "A collection of recipes for common Angular application scenarios" + }, + + "ajs-quick-reference": { + "title": "AngularJS to Angular Quick Reference", + "navTitle": "AngularJS to Angular Quick Ref", + "intro": "Learn how AngularJS concepts and techniques map to Angular" + }, + + "ngmodule-faq": { + "title": "Angular Module FAQs", + "intro": "Answers to frequently asked questions about @NgModule", + "hide": true + }, + + "component-communication": { + "title": "Component Interaction", + "intro": "Share information between different directives and components" + }, + + "dependency-injection": { + "title": "Dependency Injection", + "intro": "Techniques for Dependency Injection" + }, + + "dynamic-form": { + "title": "Dynamic Forms", + "intro": "Render dynamic forms with NgFormModel" + }, + + "form-validation": { + "title": "Form Validation", + "intro": "Validate user's form entries" + }, + + "i18n": { + "title": "Internationalization (i18n)", + "intro": "Translate the app's template text into multiple languages", + "hide": true + }, + + "set-document-title": { + "title": "Set the Document Title", + "intro": "Setting the document or window title using the Title service." + }, + + "ts-to-js": { + "title": "TypeScript to JavaScript", + "intro": "Convert Angular TypeScript examples into ES6 and ES5 JavaScript" + }, + + "visual-studio-2015": { + "title": "Visual Studio 2015 QuickStart", + "intro": "Use Visual Studio 2015 with the QuickStart files" + } + +} diff --git a/public/docs/js/latest/cookbook/ajs-quick-reference.jade b/public/docs/js/latest/cookbook/ajs-quick-reference.jade new file mode 100644 index 0000000000..c743361ac8 --- /dev/null +++ b/public/docs/js/latest/cookbook/ajs-quick-reference.jade @@ -0,0 +1 @@ +include ../../../_includes/_ts-temp diff --git a/public/docs/js/latest/cookbook/aot-compiler.jade b/public/docs/js/latest/cookbook/aot-compiler.jade new file mode 100644 index 0000000000..c743361ac8 --- /dev/null +++ b/public/docs/js/latest/cookbook/aot-compiler.jade @@ -0,0 +1 @@ +include ../../../_includes/_ts-temp diff --git a/public/docs/js/latest/cookbook/component-communication.jade b/public/docs/js/latest/cookbook/component-communication.jade new file mode 100644 index 0000000000..c743361ac8 --- /dev/null +++ b/public/docs/js/latest/cookbook/component-communication.jade @@ -0,0 +1 @@ +include ../../../_includes/_ts-temp diff --git a/public/docs/js/latest/cookbook/component-relative-paths.jade b/public/docs/js/latest/cookbook/component-relative-paths.jade new file mode 100644 index 0000000000..c743361ac8 --- /dev/null +++ b/public/docs/js/latest/cookbook/component-relative-paths.jade @@ -0,0 +1 @@ +include ../../../_includes/_ts-temp diff --git a/public/docs/js/latest/cookbook/dependency-injection.jade b/public/docs/js/latest/cookbook/dependency-injection.jade new file mode 100644 index 0000000000..4782ba81d3 --- /dev/null +++ b/public/docs/js/latest/cookbook/dependency-injection.jade @@ -0,0 +1 @@ +include ../../../_includes/_ts-temp \ No newline at end of file diff --git a/public/docs/js/latest/cookbook/dynamic-form-deprecated.jade b/public/docs/js/latest/cookbook/dynamic-form-deprecated.jade new file mode 100644 index 0000000000..4782ba81d3 --- /dev/null +++ b/public/docs/js/latest/cookbook/dynamic-form-deprecated.jade @@ -0,0 +1 @@ +include ../../../_includes/_ts-temp \ No newline at end of file diff --git a/public/docs/js/latest/cookbook/dynamic-form.jade b/public/docs/js/latest/cookbook/dynamic-form.jade new file mode 100644 index 0000000000..4782ba81d3 --- /dev/null +++ b/public/docs/js/latest/cookbook/dynamic-form.jade @@ -0,0 +1 @@ +include ../../../_includes/_ts-temp \ No newline at end of file diff --git a/public/docs/js/latest/cookbook/form-validation.jade b/public/docs/js/latest/cookbook/form-validation.jade new file mode 100644 index 0000000000..c743361ac8 --- /dev/null +++ b/public/docs/js/latest/cookbook/form-validation.jade @@ -0,0 +1 @@ +include ../../../_includes/_ts-temp diff --git a/public/docs/js/latest/cookbook/i18n.jade b/public/docs/js/latest/cookbook/i18n.jade new file mode 100644 index 0000000000..c743361ac8 --- /dev/null +++ b/public/docs/js/latest/cookbook/i18n.jade @@ -0,0 +1 @@ +include ../../../_includes/_ts-temp diff --git a/public/docs/js/latest/cookbook/index.jade b/public/docs/js/latest/cookbook/index.jade new file mode 100644 index 0000000000..ee19ecaf9f --- /dev/null +++ b/public/docs/js/latest/cookbook/index.jade @@ -0,0 +1 @@ +include ../../../ts/latest/cookbook/index.jade diff --git a/public/docs/js/latest/cookbook/ngmodule-faq.jade b/public/docs/js/latest/cookbook/ngmodule-faq.jade new file mode 100644 index 0000000000..c743361ac8 --- /dev/null +++ b/public/docs/js/latest/cookbook/ngmodule-faq.jade @@ -0,0 +1 @@ +include ../../../_includes/_ts-temp diff --git a/public/docs/js/latest/cookbook/set-document-title.jade b/public/docs/js/latest/cookbook/set-document-title.jade new file mode 100644 index 0000000000..4782ba81d3 --- /dev/null +++ b/public/docs/js/latest/cookbook/set-document-title.jade @@ -0,0 +1 @@ +include ../../../_includes/_ts-temp \ No newline at end of file diff --git a/public/docs/js/latest/cookbook/ts-to-js.jade b/public/docs/js/latest/cookbook/ts-to-js.jade new file mode 100644 index 0000000000..77e3f9b7d6 --- /dev/null +++ b/public/docs/js/latest/cookbook/ts-to-js.jade @@ -0,0 +1 @@ +include ../../../ts/latest/cookbook/ts-to-js diff --git a/public/docs/js/latest/cookbook/visual-studio-2015.jade b/public/docs/js/latest/cookbook/visual-studio-2015.jade new file mode 100644 index 0000000000..4782ba81d3 --- /dev/null +++ b/public/docs/js/latest/cookbook/visual-studio-2015.jade @@ -0,0 +1 @@ +include ../../../_includes/_ts-temp \ No newline at end of file diff --git a/public/docs/js/latest/glossary.jade b/public/docs/js/latest/glossary.jade new file mode 100644 index 0000000000..17e889224f --- /dev/null +++ b/public/docs/js/latest/glossary.jade @@ -0,0 +1,4 @@ +extends ../../ts/latest/glossary.jade + +block includes + include _util-fns diff --git a/public/docs/js/latest/guide/_data.json b/public/docs/js/latest/guide/_data.json index 0bcaee890c..f4f95cb12c 100644 --- a/public/docs/js/latest/guide/_data.json +++ b/public/docs/js/latest/guide/_data.json @@ -1,17 +1,156 @@ { - "_listtype": "ordered", - "index": { - "title": "Developer Guide" + "title": "Documentation Overview", + "navTitle": "Overview", + "description": "How to read and use this documentation", + "nextable": true, + "basics": true + }, + + "architecture": { + "title": "Architecture Overview", + "navTitle": "Architecture", + "intro": "The basic building blocks of Angular applications", + "nextable": true, + "basics": true }, "displaying-data": { "title": "Displaying Data", - "intro": "Displaying data is job number one for any good application. In Angular, you bind data to elements in HTML templates and Angular automatically updates the UI as data changes." + "intro": "Interpolation and other forms of property binding help us show app data in the UI.", + "nextable": true, + "basics": true }, "user-input": { "title": "User Input", - "intro": "DOM events drive user input in Angular. You can use the native events like click, mouseover, and keyup. Angular uses a special syntax to register events to DOM elements. This section covers all the ins and outs of using the event syntax." + "intro": "User input triggers DOM events. We listen to those events with event bindings that funnel updated values back into our components and models.", + "nextable": true, + "basics": true + }, + + "forms": { + "title": "Forms", + "intro": "A form creates a cohesive, effective, and compelling data entry experience. An Angular form coordinates a set of data-bound user controls, tracks changes, validates input, and presents errors.", + "nextable": true, + "basics": true + }, + + "dependency-injection": { + "title": "Dependency Injection", + "intro": "Angular's dependency injection system creates and delivers dependent services \"just-in-time\".", + "nextable": true, + "basics": true + }, + + "template-syntax": { + "title": "Template Syntax", + "intro": "Learn how to write templates that display data and consume user events with the help of data binding.", + "nextable": true, + "basics": true + }, + + "cheatsheet": { + "title": "Cheat Sheet", + "subtitle": "JavaScript", + "nextable": true, + "basics": true + }, + + "style-guide": { + "title": "Style Guide", + "intro": "Write Angular with style.", + "basics": true + }, + + "ngmodule": { + "title": "Angular Modules (NgModule)", + "intro": "Define application modules with @NgModule", + "hide": true + }, + + "attribute-directives": { + "title": "Attribute Directives", + "intro": "Attribute directives attach behavior to elements." + }, + + "browser-support": { + "title": "Browser support", + "intro": "Browser support and polyfills guide." + }, + + "component-styles": { + "title": "Component Styles", + "intro": "Learn how to apply CSS styles to components." + }, + + "glossary": { + "title": "Glossary", + "intro": "Brief definitions of the most important words in the Angular vocabulary", + "basics": true + }, + + "hierarchical-dependency-injection": { + "title": "Hierarchical Dependency Injectors", + "navTitle": "Hierarchical Injectors", + "intro": "Angular's hierarchical dependency injection system supports nested injectors in parallel with the component tree." + }, + + "server-communication": { + "title": "HTTP Client", + "intro": "Talk to a remote server with an HTTP Client." + }, + + "lifecycle-hooks": { + "title": "Lifecycle Hooks", + "intro": "Angular calls lifecycle hook methods on directives and components as it creates, changes, and destroys them." + }, + + "npm-packages": { + "title": "Npm Packages", + "intro": "Recommended npm packages, and how to specify package dependencies" + }, + + "pipes": { + "title": "Pipes", + "intro": "Pipes transform displayed values within a template." + }, + + "router": { + "title": "Routing & Navigation", + "intro": "Discover the basics of screen navigation with the Angular router." + }, + + "security": { + "title": "Security", + "intro": "Developing for content security in Angular applications" + }, + + "structural-directives": { + "title": "Structural Directives", + "intro": "Angular has a powerful template engine that lets us easily manipulate the DOM structure of our elements." + }, + + "testing": { + "title": "Testing", + "intro": "Techniques and practices for testing an Angular app", + "hide": true + }, + + "typescript-configuration": { + "title": "TypeScript Configuration", + "intro": "TypeScript configuration for Angular developers", + "hide": true + }, + + "upgrade": { + "title": "Upgrading to Angular", + "intro": "AngularJS applications can be incrementally upgraded to Angular." + }, + + "webpack": { + "title": "Webpack: an introduction", + "intro": "Create your Angular applications with a Webpack based tooling", + "hide": true } } diff --git a/public/docs/js/latest/guide/animations.jade b/public/docs/js/latest/guide/animations.jade new file mode 100644 index 0000000000..c743361ac8 --- /dev/null +++ b/public/docs/js/latest/guide/animations.jade @@ -0,0 +1 @@ +include ../../../_includes/_ts-temp diff --git a/public/docs/js/latest/guide/architecture.jade b/public/docs/js/latest/guide/architecture.jade new file mode 100644 index 0000000000..c743361ac8 --- /dev/null +++ b/public/docs/js/latest/guide/architecture.jade @@ -0,0 +1 @@ +include ../../../_includes/_ts-temp diff --git a/public/docs/js/latest/guide/attribute-directives.jade b/public/docs/js/latest/guide/attribute-directives.jade new file mode 100644 index 0000000000..c743361ac8 --- /dev/null +++ b/public/docs/js/latest/guide/attribute-directives.jade @@ -0,0 +1 @@ +include ../../../_includes/_ts-temp diff --git a/public/docs/js/latest/guide/browser-support.jade b/public/docs/js/latest/guide/browser-support.jade new file mode 100644 index 0000000000..c743361ac8 --- /dev/null +++ b/public/docs/js/latest/guide/browser-support.jade @@ -0,0 +1 @@ +include ../../../_includes/_ts-temp diff --git a/public/docs/js/latest/guide/cheatsheet.jade b/public/docs/js/latest/guide/cheatsheet.jade new file mode 100644 index 0000000000..f89f522a0c --- /dev/null +++ b/public/docs/js/latest/guide/cheatsheet.jade @@ -0,0 +1 @@ +extends ../../../ts/latest/guide/cheatsheet diff --git a/public/docs/js/latest/guide/component-styles.jade b/public/docs/js/latest/guide/component-styles.jade new file mode 100644 index 0000000000..4782ba81d3 --- /dev/null +++ b/public/docs/js/latest/guide/component-styles.jade @@ -0,0 +1 @@ +include ../../../_includes/_ts-temp \ No newline at end of file diff --git a/public/docs/js/latest/guide/dependency-injection.jade b/public/docs/js/latest/guide/dependency-injection.jade new file mode 100644 index 0000000000..c743361ac8 --- /dev/null +++ b/public/docs/js/latest/guide/dependency-injection.jade @@ -0,0 +1 @@ +include ../../../_includes/_ts-temp diff --git a/public/docs/js/latest/guide/displaying-data.jade b/public/docs/js/latest/guide/displaying-data.jade index a234a0c091..4782ba81d3 100644 --- a/public/docs/js/latest/guide/displaying-data.jade +++ b/public/docs/js/latest/guide/displaying-data.jade @@ -1,347 +1 @@ -.l-main-section - - h2#section-displaying-controller-properties Displaying controller properties - - - p. - Let's walk through how we'd display a property, a list of properties, and then conditionally show content - based on state. We'll end up with a UI that looks like this: - - figure.image-display - img(src='https://github.com/resources/images/examples/displaying-data-example1.png' alt="Example of Todo App") - -.callout.is-helpful - header Typescript vs ES5 - p. - Although we work through the examples in TypeScript, you can also use - regular ES5. Click the ES5 link in any code box to see the ES5 JavaScript - version. Note that in ES5, you'd want to name your files .js rather than - .ts. - -.l-main-section - h2#section-create-an-entry-point Create an entry point - - p Open your favorite editor and create a show-properties.html file with the content: - - code-example(language="html" escape="html"). - - - p - | The <display> component here acts as the site where you'll insert your application. - | We'll assume a structure like this for the rest of the examples here and just focus on the parts that - | are different. - -.l-main-section - h2#section-showing-properties-with-interpolation Showing properties with interpolation - p.text-body - | The simple method for binding text into templates is through interpolation where you put the name of a property - | inside {{ }}. - - p To see this working, create another file, show-properties.ts, and add the following: - - code-tabs - code-pane(language="javascript" name="TypeScript" format="linenums"). - // TypeScript - import {Component, View, bootstrap} from 'angular2/angular2'; - - @Component({ - selector: 'display' - }) - @View({ - template: ` - <p>My name: {{ myName }}</p> - ` - }) - class DisplayComponent { - myName: string; - - constructor() { - this.myName = "Alice"; - } - } - code-pane(language="javascript" name="ES5" format="linenums"). - // ES5 - function DisplayComponent() { - this.myName = "Alice"; - } - DisplayComponent.annotations = [ - new angular.ComponentAnnotation({ - selector: "display" - }), - new angular.ViewAnnotation({ - template: - '<p>My name: {{ myName }}</p>' - }) - ]; - - - p. - You've just defined a component that encompasses a view and controller for the app. The view - defines a template: - - code-example(language="html" escape="html"). -

    My name: {{ myName }}

    - - p. - Angular will automatically pull the value of myName and insert it into the browser and - update it whenever it changes without work on your part. - - p. - One thing to notice here is that though you've written your DisplayComponent class, you haven't - called new to create one anywhere. By associating your class with elements named 'display' in - the DOM, Angular knows to automatically call new on DisplayComponent and bind its properties to - that part of the template. - - p. - When you're building templates, data bindings like these have access to the same scope of - properties as your controller class does. Here, your class is the DisplayComponent that has - just one property, myName. - - .callout.is-helpful - header Note - p. - While you've used template: to specify an inline view, for larger templates you'd - want to move them to a separate file and load them with templateUrl: instead. - -.l-main-section - h2#Create-an-array Create an array property and use NgFor on the view - p Moving up from a single property, create an array to display as a list. - - code-tabs - code-pane(language="javascript" name="TypeScript" format="linenums"). - //Typescript - class DisplayComponent { - myName: string; - names: Array<string>; - - constructor() { - this.myName = "Alice"; - this.names = ["Aarav", "Martín", "Shannon", "Ariana", "Kai"]; - } - } - - code-pane(language="javascript" name="Javascript (ES5)" format="linenums"). - //ES5 - function DisplayComponent() { - this.myName = "Alice"; - this.names = ["Aarav", "Martín", "Shannon", "Ariana", "Kai"]; - } - p. - You can then use this array in your template with the NgFor directive to create copies of DOM elements - with one for each item in the array. - - code-tabs - code-pane(language="javascript" name="TypeScript" format="linenums"). - //Typescript - template: ` - <p>My name: {{ myName }}</p> - <p>Friends:</p> - <ul> - <li *ng-for="#name of names"> - {{ name }} - </li> - </ul> - `, - code-pane(language="javascript" name="ES5" format="linenums"). - //ES5 - template: - '<p>My name: {{ myName }}</p>' + - '<p>Friends:</p>' + - '<ul>' + - '<li *ng-for="#name of names">' + - '{{ name }}' + - '</li>' + - '</ul>', - - p. - To make this work, you'll also need to add the NgFor directive used by the template so - that Angular knows to include it: - - code-tabs - code-pane(language="javascript" name="TypeScript" format="linenums"). - //Typescript - import {Component, View, bootstrap, NgFor} from 'angular2/angular2'; - @View({ - ... - directives: [NgFor] - }) - - - code-pane(language="javascript" name="ES5" format="linenums"). - //ES5 - DisplayComponent.annotations = [ - ... - new angular.ViewAnnotation({ - ... - directives: [angular.NgFor] - }) - ]; - - p Reload and you've got your list of friends! - p. - Angular will mirror changes you make to this list over in the DOM. Add a new item and it appears in your - list. Delete one and Angular deletes the <li>. Reorder items and Angular makes the corresponding reorder of - the DOM list. - p Let's look at the few lines that do the work again: - code-example(language="html" format="linenums"). - //HTML - <li *ng-for="#name of names"> - {{ name }} - </li> - p The way to read this is: - ul - li. - *ng-for : create a DOM element for each item in an - iterable - like an array - li #name : refer to individual values of the iterable as 'name' - li of names : the iterable to use is called 'names' in the current controller - p Using this syntax, you can build UI lists from any iterable object. -.l-main-section - h2#Create-a-class Create a class for the array property and inject into component - - p. - Before we get too much further, we should mention that putting our model (array) directly in our controller isn't - proper form. We should separate the concerns by having another class serve the role of model and inject it into - the controller. - - p Make a FriendsService class to provide the model with the list of friends. - - code-tabs - code-pane(language="javascript" name="TypeScript" format="linenums"). - class FriendsService { - names: Array<string>; - constructor() { - this.names = ["Alice", "Aarav", "Martín", "Shannon", "Ariana", "Kai"]; - } - } - - code-pane(language="javascript" name="ES5" format="linenums"). - function FriendsService() { - this.names = ["Aarav", "Martín", "Shannon", "Ariana", "Kai"]; - } - - p. - Now replace the current list of friends in DisplayComponent by including the FriendsService in the injectables list, - then including the service in the constructor, and finally setting the list of - names in DisplayComponent to the names provided by the service you passed in. - - .callout.is-helpful - header ES5 Note - p. - The dependency injection syntax here is using the low-level API and is...well...not very nice. We're - working on sugaring the syntax to match the way it works in Angular 1. Expect this to change soon. - - code-tabs - code-pane(language="javascript" name="TypeScript" format="linenums"). - @Component({ - ... - appInjector: [FriendsService] - }) - class DisplayComponent { - myName: string; - names: Array<string>; - constructor(friendsService: FriendsService) { - this.myName = 'Alice'; - this.names = friendsService.names; - } - } - - - code-pane(language="javascript" name="ES5" format="linenums"). - //ES5 - function DisplayComponent(friends) { - this.myName = "Alice"; - this.names = friends.names; - } - DisplayComponent.annotations = [ - new angular.ComponentAnnotation({ - selector: "display", - appInjector: [FriendsService] - }), - new angular.ViewAnnotation({ - template: '{{ myName }} <ul> <li *for="#name of names">{{ name }}</li> </ul>', - directives: [angular.NgFor] - }) - ]; - DisplayComponent.parameters = [[FriendsService]]; - document.addEventListener("DOMContentLoaded", function() { - angular.bootstrap(DisplayComponent); - }); -.l-main-section - h2#Conditionally-displaying-data-with-NgIf Conditionally displaying data with NgIf - p. - Lastly, before we move on, let's handle showing parts of our UI conditionally with NgIf. The - NgIf directive adds or removes elements from the DOM based on the expression you provide. - p See it in action by adding a paragraph at the end of your template - pre.prettyprint.lang-html - code. - <p *ng-if="names.length > 3">You have many friends!</p> - p You'll also need to add the NgIf directive so Angular knows to include it. - - code-tabs - code-pane(language="javascript" name="TypeScript" format="linenums"). - //Typescript - import {Component, View, bootstrap, NgFor, NgIf} from 'angular2/angular2'; - ... - directives: [NgFor, NgIf] - code-pane(language="javascript" name="ES5" format="linenums"). - //ES5 - directives: [angular.NgFor, angular.NgIf] - p. - As there are currently 6 items in the list, you'll see the message congratulating you on your many friends. - Remove three items from the list, reload your browser, and see that the message no longer displays. - - code-tabs - code-pane(language="javascript" name="TypeScript" format="linenums"). - //TypeScript - import {Component, View, bootstrap, NgFor, NgIf} from 'angular2/angular2'; - ... - @View({ - template: ` - <p>My name: {{ myName }}</p> - <p>Friends:</p> - <ul> - <li *ng-for="#name of names"> - {{ name }} - </li> - </ul> - <p *ng-if="names.length > 3">You have many friends!</p> - `, - directives: [NgFor, NgIf] - }) - class DisplayComponent { - ... - } - - class FriendsService { - names: Array<string>; - constructor() { - this.names = ["Aarav", "Martín", "Shannon"]; - } - } - code-pane(language="javascript" name="ES5" format="linenums"). - //ES5 - function DisplayComponent(friends) { - this.myName = "Alice"; - this.names = friends.names; - } - DisplayComponent.annotations = [ - ... - new angular.ViewAnnotation({ - template: ' - '<p>My name: {{ myName }}</p>' + - '<p>Friends:</p>' + - '<ul>' + - '<li *ng-for="#name of names">' + - '{{ name }}' + - '</li>' + - '</ul>' + - '<p *ng-if="names.length > 3">You have many friends!</p>'', - directives: [angular.NgFor, angular.NgIf] - }) - ]; - - function FriendsService () { - this.names = ["Aarav", "Martín", "Shannon"]; - } +include ../../../_includes/_ts-temp \ No newline at end of file diff --git a/public/docs/js/latest/guide/forms.jade b/public/docs/js/latest/guide/forms.jade new file mode 100644 index 0000000000..a0831d8a15 --- /dev/null +++ b/public/docs/js/latest/guide/forms.jade @@ -0,0 +1,650 @@ +include ../_util-fns + +:marked + We’ve all used a form to login, submit a help request, place an order, book a flight, + schedule a meeting and perform countless other data entry tasks. + Forms are the mainstay of business applications. + + Any seasoned web developer can slap together an HTML form with all the right tags. + It's more challenging to create a cohesive data entry experience that guides the + user efficiently and effectively through the workflow behind the form. + + *That* takes design skills that are, to be frank, well out of scope for this chapter. + + It also takes framework support for + **two-way data binding, change tracking, validation, and error handling** + ... which we shall cover in this chapter on Angular forms. + + We will build a simple form from scratch, one step at a time. Along the way we'll learn + + * How to build an Angular form with a component and template + + * The `ngModel` two-way data binding syntax for reading and writing values to input controls + + * The `ngModel` directive in combination with a form to track the change state and validity of form controls + + * The Special CSS classes that follow the state of the controls and can be used to provide strong visual feedback + + * How to display validation errors to users and enable/disable form controls + + * How to share information across controls with template local variables + + Live Example + +.l-main-section +:marked + ## Bootstrap + + We start by showing how to bootstrap the application and add the necessary dependencies to use forms. + + During bootstrap we have to register the new forms module by calling `provideForms()` and pass the result to the provider array. ++makeExample('forms/js/src/main.js','','src/main.js') + +:marked + The old forms API is going through a deprecation phase. During this transition Angular is supporting both form modules. + + To remind us that the old API is deprecated, Angular will print a warning message to the console. + + Since we are converting to the new API, and no longer need the old API, we call `disableDeprecatedForms()` to disable the old form functionality and the warning message. + +.l-main-section +:marked + ## Template-Driven Forms + + Many of us will build forms by writing templates in the Angular [template syntax](./template-syntax.html) with + the form-specific directives and techniques described in this chapter. +.l-sub-section + :marked + That's not the only way to create a form but it's the way we'll cover in this chapter. +:marked + We can build almost any form we need with an Angular template — login forms, contact forms ... pretty much any business forms. + We can lay out the controls creatively, bind them to data, specify validation rules and display validation errors, + conditionally enable or disable specific controls, trigger built-in visual feedback, and much more. + + It will be pretty easy because Angular handles many of the repetitive, boiler plate tasks we'd + otherwise wrestle with ourselves. + + We'll discuss and learn to build the following template-driven form: + +figure.image-display + img(src="https://github.com/resources/images/devguide/forms/hero-form-1.png" width="400px" alt="Clean Form") + +:marked + Here at the *Hero Employment Agency* we use this form to maintain personal information about the + heroes in our stable. Every hero needs a job. It's our company mission to match the right hero with the right crisis! + + Two of the three fields on this form are required. Required fields have a green bar on the left to make them easy to spot. + + If we delete the hero name, the form displays a validation error in an attention grabbing style: + +figure.image-display + img(src="https://github.com/resources/images/devguide/forms/hero-form-2.png" width="400px" alt="Invalid, Name Required") + +:marked + Note that the submit button is disabled and the "required" bar to the left of the input control changed from green to red. + +.l-sub-section + p We'll' customize the colors and location of the "required" bar with standard CSS. + +:marked + We will build this form in the following sequence of small steps + + 1. Create the `Hero` model class + 1. Create the component that controls the form + 1. Create a template with the initial form layout + 1. Add the **ngModel** directive to each form input control + 1. Add the **#name** attribute to each form input control + 1. Add custom CSS to provide visual feedback + 1. Show and hide validation error messages + 1. Handle form submission with **ngSubmit** + 1. Disable the form’s submit button until the form is valid + +:marked + ## Setup + Create a new project folder (`angular-forms`) and follow the steps in the [QuickStart](../quickstart.html). + + ## Create the Hero Model Class + + As users enter form data, we capture their changes and update an instance of a model. + We can't layout the form until we know what the model looks like. + + A model can be as simple as a "property bag" that holds facts about a thing of application importance. + That describes well our `Hero` class with its three required fields (`id`, `name`, `power`) + and one optional field (`alterEgo`). + + Create a new file in the app folder called `hero.js` and give it the following constructor: + ++makeExample('forms/js/src/app/hero.js', null, 'src/app/hero.js') + +:marked + It's an anemic model with few requirements and no behavior. Perfect for our demo. + + The `alterEgo` is optional and the constructor lets us omit it by being the last argument. + + We can create a new hero like this: +code-example(format=""). + var 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" +:marked + We update the `` of the `index.html` to include this javascript file. + ++makeExample('forms/js/src/index.html', 'scripts-hero', 'src/index.html (excerpt)')(format=".") + +.l-main-section +:marked + ## Create a Form component + + An Angular form has two parts: an HTML-based template and a code-based Component to handle data and user interactions. + + We begin with the Component because it states, in brief, what the Hero editor can do. + + Create a new file called `hero-form.component.js` and give it the following definition: + ++makeExample('forms/js/src/app/hero-form.component.js', 'first', 'src/app/hero-form.component.js') + +:marked + There’s nothing special about this component, nothing form-specific, nothing to distinguish it from any component we've written before. + + Understanding this component requires only the Angular concepts we’ve learned in previous chapters + + * We use the `ng.core` object from the Angular library as we usually do. + + * The `Component()` selector value of "hero-form" means we can drop this form in a parent template with a `` tag. + + * The `templateUrl` property points to a separate file for template HTML called `hero-form.component.html`. + + * We defined dummy data for `model` and `powers` as befits a demo. + + Down the road, we can inject a data service to get and save real data + or perhaps expose these properties as [inputs and outputs](./template-syntax.html#inputs-outputs) for binding to a + parent component. None of this concerns us now and these future changes won't affect our form. + + * We threw in a `diagnostic` method at the end to return a JSON representation of our model. + It'll help us see what we're doing during our development; we've left ourselves a cleanup note to discard it later. + + Why don't we write the template inline in the component file as we often do + elsewhere in the Developer Guide? + + There is no “right” answer for all occasions. We like inline templates when they are short. + Most form templates won't be short. TypeScript and JavaScript files generally aren't the best place to + write (or read) large stretches of HTML and few editors are much help with files that have a mix of HTML and code. + We also like short files with a clear and obvious purpose like this one. + + We made a good choice to put the HTML template elsewhere. + We'll write that template in a moment. Before we do, we'll take a step back + and revise the `app.component.js` to make use of our new `HeroFormComponent`. + +:marked + Again we update the `` of the `index.html` to include the new javascript file. + ++makeExample('forms/js/src/index.html', 'scripts-hero-form', 'src/index.html (excerpt)')(format=".") + +.l-main-section +:marked + ## Revise the *app.component.js* + + `app.component.js` is the application's root component. It will host our new `HeroFormComponent`. + + Replace the contents of the "QuickStart" version with the following: ++makeExample('forms/js/src/app/app.component.js', null, 'src/app/app.component.js') + +:marked +.l-sub-section + :marked + There are only two changes: + + 1. The `template` is simply the new element tag identified by the component's `select` property. + + 1. The `directives` array tells Angular that our template depends upon the `HeroFormComponent` + which is itself a Directive (as are all Components). + +.l-main-section +:marked + ## Create an initial HTML Form Template + + Create a new template file called `hero-form.component.html` and give it the following definition: + ++makeExample('forms/js/src/app/hero-form.component.html', 'start', 'src/app/hero-form.component.html') + +:marked + That is plain old HTML 5. We're presenting two of the `Hero` fields, `name` and `alterEgo`, and + opening them up for user input in input boxes. + + The *Name* `` control has the HTML5 `required` attribute; + the *Alter Ego* `` control does not because `alterEgo` is optional. + + We've got a *Submit* button at the bottom with some classes on it. + + **We are not using Angular yet**. There are no bindings. No extra directives. Just layout. + + The `container`,`form-group`, `form-control`, and `btn` classes + come from [Twitter Bootstrap](http://getbootstrap.com/css/). Purely cosmetic. + We're using Bootstrap to gussy up our form. + Hey, what's a form without a little style! + +.callout.is-important + header Angular Forms Do Not Require A Style Library + :marked + Angular makes no use of the `container`, `form-group`, `form-control`, and `btn` classes or + the styles of any external library. Angular apps can use any CSS library + ... or none at all. + +:marked + Let's add the stylesheet. Open index.html and add the following link to the <head>. ++makeExample('forms/js/src/index.html', 'bootstrap')(format=".") + +:marked +.l-main-section +:marked + ## Add Powers with ***ngFor** + Our hero may choose one super power from a fixed list of Agency-approved powers. + We maintain that list internally (in `HeroFormComponent`). + + We'll add a `select` to our + form and bind the options to the `powers` list using `NgFor`, + a technique we might have seen before in the [Displaying Data](./displaying-data.html) chapter. + + Add the following HTML *immediately below* the *Alter Ego* group. ++makeExample('forms/js/src/app/hero-form.component.html', 'powers', 'src/app/hero-form.component.html (excerpt)')(format=".") + +:marked + We are repeating the `` tag for each power in the list of Powers. + The `#p` local template variable is a different power in each iteration; + we display its name using the interpolation syntax with the double-curly-braces. + +.l-main-section +:marked + ## Two-way data binding with ***ngModel** + Running the app right now would be disappointing. + +figure.image-display + img(src="https://github.com/resources/images/devguide/forms/hero-form-3.png" width="400px" alt="Early form with no binding") +:marked + We don't see hero data because we are not binding to the `Hero` yet. + We know how to do that from earlier chapters. + [Displaying Data](./displaying-data.html) taught us Property Binding. + [User Input](./user-input.html) showed us how to listen for DOM events with an + Event Binding and how to update a component property with the displayed value. + + Now we need to display, listen, and extract at the same time. + + We could use those techniques again in our form. + Instead we'll introduce something new, the `NgModel` directive, that + makes binding our form to the model super-easy. + + Find the `` tag for the "Name" and update it like this + ++makeExample('forms/js/src/app/hero-form.component.html', 'ngModel-1','src/app/hero-form.component.html (excerpt)')(format=".") + +.l-sub-section + :marked + We appended a diagnostic interpolation after the input tag + so we can see what we're doing. + We left ourselves a note to throw it way when we're done. + +:marked + Focus on the binding syntax: `[(ngModel)]="..."`. + + If we ran the app right now and started typing in the *Name* input box, + adding and deleting characters, we'd see them appearing and disappearing + from the interpolated text. + At some point it might look like this. +figure.image-display + img(src="https://github.com/resources/images/devguide/forms/ng-model-in-action.png" width="400px" alt="ngModel in action") +:marked + The diagnostic is evidence that we really are flowing values from the input box to the model and + back again. **That's two-way data binding!** + + Notice that we also added a `name` attribute to our `` tag. This is a requirement when using `[(ngModel)]` in combination with a form, so that we can easily refer to it in the aggregate form value and validity state. + + Let's add similar `[(ngModel)]` bindings to *Alter Ego* and *Hero Power*. + We'll ditch the input box binding message + and add a new binding at the top to the component's `diagnostic` method. + Then we can confirm that two-way data binding works *for the entire Hero model*. + + After revision the core of our form should have three `[(ngModel)]` bindings that + look much like this: + ++makeExample('forms/js/src/app/hero-form.component.html', 'ngModel-2', 'src/app/hero-form.component.html (excerpt)') + +:marked + If we ran the app right now and changed every Hero model property, the form might display like this: +figure.image-display + img(src="https://github.com/resources/images/devguide/forms/ng-model-in-action-2.png" width="400px" alt="ngModel in super action") +:marked + The diagnostic near the top of the form + confirms that all of our changes are reflected in the model. + + **Delete** the `{{diagnostic()}}` binding at the top as it has served its purpose. + +.l-sub-section + :marked + ### Inside [(ngModel)] + *This section is an optional deep dive into [(ngModel)]. Not interested? Skip ahead!* + + The punctuation in the binding syntax, [()], is a good clue to what's going on. + + In a Property Binding, a value flows from the model to a target property on screen. + We identify that target property by surrounding its name in brackets, []. + This is a one-way data binding **from the model to the view**. + + In an Event Binding, we flow the value from the target property on screen to the model. + We identify that target property by surrounding its name in parentheses, (). + This is a one-way data binding in the opposite direction **from the view to the model**. + + No wonder Angular chose to combine the punctuation as [()] + to signify a two-way data binding and a **flow of data in both directions**. + + In fact, we can break the `NgModel` binding into its two separate modes + as we do in this re-write of the "Name" `` binding: + +makeExample('forms/js/src/app/hero-form.component.html', 'ngModel-3','src/app/hero-form.component.html (excerpt)')(format=".") + + :marked +
    The Property Binding should feel familiar. The Event Binding might seem strange. + + The `ngModelChange` is not an `` element event. + It is actually an event property of the `NgModel` directive. + When Angular sees a binding target in the form [(x)], + it expects the `x` directive to have an `x` input property and an `xChange` output property. + + The other oddity is the template expression, `model.name = $event`. + We're used to seeing an `$event` object coming from a DOM event. + The `ngModelChange` property doesn't produce a DOM event; it's an Angular `EventEmitter` + property that returns the input box value when it fires — which is precisely what + we should assign to the model's `name' property. + + Nice to know but is it practical? We almost always prefer `[(ngModel)]`. + We might split the binding if we had to do something special in + the event handling such as debounce or throttle the key strokes. + + Learn more about `NgModel` and other template syntax in the + [Template Syntax](./template-syntax.html) chapter. + +.l-main-section +:marked + ## Track change-state and validity with **ngModel** + + A form isn't just about data binding. We'd also like to know the state of the controls on our form. + + Using `ngModel` in a form gives us more than just two way data binding. It also tells us if the user touched the control, if the value changed, or if the value became invalid. + + `ngModel` doesn't just track state; it updates the control with special Angular CSS classes from the set we listed above. + We can leverage those class names to change the appearance of the + control and make messages appear or disappear. + + We'll explore those effects soon. Right now + let's make sure we have `ngModel` and the corresponding name attribute on all three form controls, + starting with the *Name* input box. ++makeExample('forms/js/src/app/hero-form.component.html', 'ngModelName-1', 'src/app/hero-form.component.html (excerpt)')(format=".") +:marked + We set the `name` attribute to "name" which makes sense for our app. Any unique value will do. + +.l-sub-section + :marked + Internally Angular creates `FormControls` and registers them with an `NgForm` directive that Angular attached to the `
    ` tag. Each `FormControl` is registered under the name we assigned to the `name` attribute. + We'll talk about `NgForm` [later in the chapter](#ngForm). + +.l-main-section +:marked + ## Add Custom CSS for Visual Feedback + + `NgModel` doesn't just track state. + It updates the control with three classes that reflect the state. + +table + tr + th State + th Class if true + th Class if false + tr + td Control has been visited + td ng-touched + td ng-untouched + tr + td Control's value has changed + td ng-dirty + td ng-pristine + tr + td Control's value is valid + td ng-valid + td ng-invalid +:marked + Let's add a temporary [local template variable](./template-syntax.html#local-vars) named **spy** + to the "Name" `` tag and use the spy to display those classes. + ++makeExample('forms/js/src/app/hero-form.component.html', 'ngModelName-2','src/app/hero-form.component.html (excerpt)')(format=".") + +:marked + Now run the app and focus on the *Name* input box. + Follow the next four steps *precisely* + + 1. Look but don't touched + 1. Click in the input box, then click outside the text input box + 1. Add slashes to the end of the name + 1. Erase the name + + The actions and effects are as follows: +figure.image-display + img(src="https://github.com/resources/images/devguide/forms/control-state-transitions-anim.gif" alt="Control State Transition") +:marked + We should be able to see the following four sets of class names and their transitions: +figure.image-display + img(src="https://github.com/resources/images/devguide/forms/ng-control-class-changes.png" width="400px" alt="Control State Transitions") + +:marked + The (`ng-valid` | `ng-invalid`) pair are most interesting to us. We want to send a + strong visual signal when the data are invalid and we want to mark required fields. + + We realize we can do both at the same time with a colored bar on the left of the input box: + +figure.image-display + img(src="https://github.com/resources/images/devguide/forms/validity-required-indicator.png" width="400px" alt="Invalid Form") + +:marked + We achieve this effect by adding two styles to a new `forms.css` file + that we add to our project as a sibling to `index.html`. + ++makeExample('forms/js/src/forms.css',null,'src/forms.css')(format=".") +:marked + These styles select for the two Angular validity classes and the HTML 5 "required" attribute. + + We update the `` of the `index.html` to include this style sheet. ++makeExample('forms/js/src/index.html', 'styles', 'src/index.html (excerpt)')(format=".") +:marked + ## Show and Hide Validation Error messages + + We can do better. + + The "Name" input box is required. Clearing it turns the bar red. That says *something* is wrong but we + don't know *what* is wrong or what to do about it. + We can leverage the `ng-invalid` class to reveal a helpful message. + + Here's the way it should look when the user deletes the name: +figure.image-display + img(src="https://github.com/resources/images/devguide/forms/name-required-error.png" width="400px" alt="Name required") + +:marked + To achieve this effect we extend the `` tag with + 1. a [local template variable](./template-syntax.html#local-vars) + 1. the "*is required*" message in a nearby `
    ` which we'll display only if the control is invalid. + + Here's how we do it for the *name* input box: +-var stylePattern = { otl: /(#name="form")|(.*div.*$)|(Name is required)/gm }; ++makeExample('forms/js/src/app/hero-form.component.html', + 'name-with-error-msg', + 'src/app/hero-form.component.html (excerpt)', + stylePattern) +:marked + We need a template reference variable to access the input box's Angular control from within the template. + Here we created a variable called `name` and gave it the value "ngModel". +.l-sub-section + :marked + Why "ngModel"? + A directive's [exportAs](../api/core/index/Directive-decorator.html) property + tells Angular how to link the reference variable to the directive. + We set `name` to `ngModel` because the `ngModel` directive's `exportAs` property happens to be "ngModel". + + Now we can control visibility of the "name" error message by binding properties of the `name` control to the message `
    ` element's `hidden` property. + + The message is hidden while the control is valid; the message is revealed when the control becomes invalid. + +.l-sub-section + :marked + ### The NgForm directive + We just set a template local variable with the value of an `NgForm` directive. + Why did that work? We didn't add the **[`NgForm`](../api/forms/index/NgForm-directive.html) directive** explicitly. + + Angular added it surreptitiously, wrapping it around the `` element + + The `NgForm` directive supplements the `form` element with additional features. + It holds the controls we created for the elements with `ngModel` directive and `name` attribute + and monitors their properties including their validity. + It also has its own `valid` property which is true only if every contained + control is valid. +:marked + The Hero *Alter Ego* is optional so we can leave that be. + + Hero *Power* selection is required. + We can add the same kind of error handling to the `` elements as shown below: - code-example(format="linenums"). - template:` - <h1>{{title}}</h1> - <h2>{{hero.name}} details!</h2> - <div><label>id: </label>{{hero.id}}</div> - <div> - <label>name: </label> - <div><input value="{{hero.name}}" placeholder="name"></div> - </div> - ` - :markdown - We see in the browser that the hero’s name does appear in the `` textbox. - But something doesn’t feel right. - When we change the name, we notice that our change - is not reflected in the `

    `. We won't get the desired behavior - with a one-way binding to ``. - - ### Two-Way Binding - - We intend to display the name of the hero in the ``, change it, - and see those changes wherever we bind to the hero’s name. - In short, we want two-way data binding. - - Let’s update the template to use the **`ng-model`** built-in directive for two-way binding. - - .l-sub-section - :markdown - Learn more about `ng-model` in the [Template Syntax](../guide/template-syntax.html#ng-model) - :markdown - Replace the `` with the following HTML - - code-example(language="html"). - <input [(ng-model)]="hero.name" placeholder="name"> - - :markdown - Unfortunately, that change broke our application and we're no longer displaying the hero in the browser. - Let’s fix that next. ++makeExample('toh-1/ts/src/app/app.component.ts', 'hero-property-1', 'src/app/app.component.ts (hero property)')(format=".") + +:marked + Because you changed the hero from a string to an object, + update the binding in the template to refer to the hero's `name` property. + ++makeExample('toh-1/ts/app/app.component.1.ts', 'show-hero-2') +:marked + The browser refreshes and continues to display the hero's name. + + ### Adding HTML with multi-line template strings + + To show all of the hero's properties, + add a `
    ` for the hero's `id` property and another `
    ` for the hero's `name`. + To keep the template readable, place each `
    ` on its own line. + + The backticks around the component template let you put the `

    `, `

    `, and `
    ` elements on their own lines, + thanks to the template literals feature in ES2015 and TypeScript. For more information, see + Template literals. + + ++makeExample('toh-1/ts/app/app.component.1.ts', 'multi-line-strings', 'app.component.ts (AppComponent\'s template)')(format='.') .l-main-section - :markdown - ## Declaring Template Directives - - We added the `ng-model` directive but we didn't tell Angular about it. - A component must disclose every directive that appears in its template. - - Let’s first gain access to the `NgModel` directive class by importing it from Angular as shown below: - - ```` - import {bootstrap, Component, NgModel} from 'angular2/angular2'; - ``` - - Now tell the component that we will use the `ng-model` directive in the template - by adding the `directives` property to the `@Component` decoration - immediately below the `template` string: - - ``` - directives: [NgModel] - ``` - - The `directives` property is an array holding all directive classes that - are used by the component’s template. - - Unfortunately when we view the app in the browser we still have an error: - - code-example(language="html"). - EXCEPTION: No value accessor for ' ' in [null] - - :markdown - Apparently declaring the `NgModel` is not quite enough. - - ## Declare Multiple Form Directives - - We learned from our latest error message that we can’t the import `NgModel` alone. - We need additional directives to enable two-way data binding with `NgModel`. - - We could hunt them down and add each of them to the `directives` array one by one. - That's painful. No one wants to remember all of the necessary directives and - type them correctly. Fortunately, there is a shortcut. - - The `ng-model` directive is one of many Forms directives which happen to be - bundled in a convenient array called `FORM_DIRECTIVES`. - - Let’s forget about importing `NgModel` and import the `FORM_DIRECTIVES` array instead: - ``` - import {bootstrap, Component, FORM_DIRECTIVES} from 'angular2/angular2'; - ``` - Now we tell the component that our template can use `FORM_DIRECTIVES` - by updating the `directives` property of the `@Component` decorator. - ``` - directives: [FORM_DIRECTIVES] - ``` - The browser refreshes. We see our hero again. We can edit the hero’s name and - see the changes reflected immediately in the `

    `. - - ### Bundled directives - Angular bundled the Form-related directives together in a convenient `FORM_DIRECTIVES` array. - That's all we need to remember to light up our template. - - We may wish to use this trick ourselves someday. - We too can bundle a collection of directives in an array, give it a catchy name, - and plug that array into the `directives` property. +:marked + ## Edit the hero name + + Users should be able to edit the hero name in an `` textbox. + The textbox should both _display_ the hero's `name` property + and _update_ that property as the user types. + + You need a two-way binding between the `` form element and the `hero.name` property. + + ### Two-way binding + + Refactor the hero name in the template so it looks like this: ++makeExample('toh-1/ts/app/app.component.1.ts', 'name-input')(format='.') + +:marked + `[(ngModel)]` is the Angular syntax to bind the `hero.name` property + to the textbox. + Data flows _in both directions:_ from the property to the textbox, + and from the textbox back to the property. + + Unfortunately, immediately after this change, the application breaks. + If you looked in the browser console, you'd see Angular complaining that + "`ngModel` ... isn't a known property of `input`." + + Although `NgModel` is a valid Angular directive, it isn't available by default. + It belongs to the optional `FormsModule`. + You must opt-in to using that module. + + ### Import the _FormsModule_ + + Open the `app.module.ts` file and import the `FormsModule` symbol from the `@angular/forms` library. + Then add the `FormsModule` to the `@NgModule` metadata's `imports` array, which contains the list + of external modules that the app uses. + + The updated `AppModule` looks like this: ++makeExample('toh-1/ts/src/app/app.module.ts', '', 'app.module.ts (FormsModule import)') + +.l-sub-section + :marked + Read more about `FormsModule` and `ngModel` in the + [Two-way data binding with ngModel](../guide/forms.html#ngModel) section of the + [Forms](../guide/forms.html) guide and the + [Two-way binding with NgModel](../guide/template-syntax.html#ngModel) section of the + [Template Syntax](../guide/template-syntax.html) guide. + +:marked + When the browser refreshes, the app should work again. + You can edit the hero's name and see the changes reflected immediately in the `

    ` above the textbox. .l-main-section - :markdown - ## The Road We’ve Travelled - Let’s take stock of what we’ve built. - - * Our Tour of Heroes uses the double curly braces of interpolation (a form of one-way data binding) - to display the application title and properties of a `Hero` object. - * We wrote a multi-line template using ES2015’s template strings to make our template readable. - * We can both display and change the hero’s name after adding a two-way data binding to the `` element - using the built-in `ng-model` directive. - * The `ng-model` directive also propagates changes to every other binding of the `hero.name`. - * We declared our use of `NgModel` and other Form directives - by setting the component's `directives` metadata property to the `FORMS_DIRECTIVES` array. - - Here's the complete `app.ts` as it stands now: - - code-example(format="linenums"). - import {bootstrap, Component, FORM_DIRECTIVES} from 'angular2/angular2'; - - class Hero { - id: number; - name: string; - } - - @Component({ - selector: 'my-app', - template:` - <h1>{{title}}</h1> - <h2>{{hero.name}} details!</h2> - <div><label>id: </label>{{hero.id}}</div> - <div> - <label>name: </label> - <div><input [(ng-model)]="hero.name" placeholder="name"></div> - </div> - `, - directives: [FORM_DIRECTIVES] - }) - class AppComponent { - public title = 'Tour of Heroes'; - public hero: Hero = { - id: 1, - name: 'Windstorm' - }; - } - - bootstrap(AppComponent); - +:marked + ## The road you've travelled + + Take stock of what you've built. + + * The Tour of Heroes app uses the double curly braces of interpolation (a type of one-way data binding) + to display the app title and properties of a `Hero` object. + * You wrote a multi-line template using ES2015's template literals to make the template readable. + * You added a two-way data binding to the `` element + using the built-in `ngModel` directive. This binding both displays the hero's name and allows users to change it. + * The `ngModel` directive propagates changes to every other binding of the `hero.name`. + + Your app should look like this . + + Here's the complete `app.component.ts` as it stands now: + ++makeExample('toh-1/ts/src/app/app.component.ts', '', 'src/app/app.component.ts') + + .l-main-section - :markdown - ## The Road Ahead - Our Tour of Heroes only displays one hero and we really want to display a list of heroes. - We also want to allow the user to select a hero and display their details. - We’ll learn more about how to retrieve lists, bind them to the - template, and allow a user to select it in the - [next tutorial chapter](./toh-pt2.html). +:marked + ## The road ahead + In the [next tutorial page](./toh-pt2.html), you'll build on the Tour of Heroes app to display a list of heroes. + You'll also allow the user to select heroes and display their details. + You'll learn more about how to retrieve lists and bind them to the template. diff --git a/public/docs/ts/latest/tutorial/toh-pt2.jade b/public/docs/ts/latest/tutorial/toh-pt2.jade index 05210f7840..25cbb7e1e8 100644 --- a/public/docs/ts/latest/tutorial/toh-pt2.jade +++ b/public/docs/ts/latest/tutorial/toh-pt2.jade @@ -1,442 +1,292 @@ -include ../../../../_includes/_util-fns +include ../_util-fns + +:marked + In this page, you'll expand the Tour of Heroes app to display a list of heroes, and + allow users to select a hero and display the hero's details. + + When you're done with this page, the app should look like this . .l-main-section - :markdown - # It Takes Many Heroes - Our story needs more heroes. - We’ll expand our Tour of Heroes app to display a list of heroes, - allow the user to select a hero, and display the hero’s details. - - Let’s take stock of what we’ll need to display a list of heroes. - First, we need a list of heroes. We want to display those heroes in the view’s template, - so we’ll need a way to do that. - -.l-main-section - :markdown - ## Where We Left Off - Before we continue with Part 2 of the Tour of Heroes, - let’s verify we have the following structure after [Part 1](./toh-pt1.html). - If not, we’ll need to go back to Part 1 and figure out what we missed. - - code-example. - angular2-tour-of-heroes - ├── node_modules - ├── src - | ├── app - | | └── app.ts - | ├── index.html - | └── tsconfig.json - └── package.json - :markdown - ### Keep the app running - Start the TypeScript compiler and have it watch for changes in one terminal window by typing - - pre.prettyprint.lang-bash - code npm run tsc - - :markdown - Now open another terminal window and start the server by typing - - pre.prettyprint.lang-bash - code npm start - - :markdown - This will keep the application running while we continue to build the Tour of Heroes. +:marked + ## Where you left off + Before you continue with this page of the Tour of Heroes, + verify that you have the following structure after [The Hero Editor](./toh-pt1.html) page. + If your structure doesn't match, go back to that page to figure out what you missed. + +.filetree + .file angular-tour-of-heroes + .children + .file src + .children + .file app + .children + .file app.component.ts + .file app.module.ts + .file main.ts + .file index.html + .file styles.css + .file systemjs.config.js + .file tsconfig.json + .file node_modules ... + .file package.json +:marked + ## Keep the app transpiling and running + Enter the following command in the terminal window: + +code-example(language="sh" class="code-shell"). + npm start + +:marked + This command runs the TypeScript compiler in "watch mode", recompiling automatically when the code changes. + The command simultaneously launches the app in a browser and refreshes the browser when the code changes. + + You can keep building the Tour of Heroes without pausing to recompile or refresh the browser. .l-main-section - :markdown - ## Displaying Our Heroes - ### Creating heroes - Let’s create an array of ten heroes at the bottom of `app.ts`. - ``` - 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" } - ]; - ``` - The `HEROES` array is of type `Hero`. - We are taking advantage of the `Hero` class we coded previously to create an array of our heroes. - We aspire to get this list of heroes from a web service, but let’s take small steps - on this road and start by displaying these mock heroes in the browser. - - ### Exposing heroes - Let’s create a public property in `AppComponent` that exposes the heroes for binding. - ``` - public heroes = HEROES; - ``` - We did not have to define the `heroes` type. TypeScript can infer it from the `HEROES` array. - .l-sub-section - :markdown - We could have defined the heroes list here in this component class. - But we know that we’ll get the heroes from a data service. - Because we know where we are heading, it makes sense to separate the hero data - from the class implementation from the start. - :markdown - ### Displaying heroes in a template - Our component has`heroes`. Let’s create an unordered list in our template to display them. - We’ll insert the following chunk of HTML below the title and above the hero details. - ``` -

    My Heroes

    -
      -
    • - -
    • -
    - ``` - Now we have a template that we can fill with our heroes. - - ### Listing heroes with ng-for - - We want to bind the array of `heroes` in our component to our template, iterate over them, - and display them individually. - We’ll need some help from Angular to do this. Let’s do this step by step. - - First modify the `
  • ` tag by adding the built-in directive `*ng-for`. - ``` -
  • - ``` - .alert.is-critical - :markdown - The leading asterisk (`*`) in front of `ng-for` is a critical part of this syntax. - - .l-sub-section - :markdown - The (`*`) prefix to `ng-for` indicates that the `
  • ` element and its children - constitute a master template. - - The `ng-for` directive iterates over the `heroes` array returned by the `AppComponent.heroes` property - and stamps out instances of this template. - - The quoted text assigned to `ng-for` means - “*take each hero in the `heroes` array, store it in the local `hero` variable, - and make it available to the corresponding template instance*”. - - The `#` prefix before "hero" identifies the `hero` as a local template variable. - We can reference this variable within the template to access a hero’s properties. - - Learn more about `ng-for` and local template variables in the - [Template Syntax](../guide/template-syntax.html#ng-for) chapter. - - :markdown - With this background in mind, we now insert some content between the `
  • ` tags - that uses the `hero` template variable to display the hero’s properties. - - code-example(format="linenums" language="html"). - <li *ng-for="#hero of heroes"> - <span class="badge">{{hero.id}}</span> {{hero.name}} - </li> - - :markdown - ### Declaring ng-for - When we view the running app in the browser we see nothing … no heroes. - We open the developer tools and see an error in the console. - - code-example(language="html" ). - EXCEPTION: - Can't bind to 'ngForOf' since it isn't a known property of the '<template>' element and - there are no matching directives with a corresponding property - - :markdown - Thankfully we have a clear error message that indicates where we went wrong. - We used `ng-for` in the template but we didn’t tell the component about it. - From Angular's perspective, `ng-for` is a meaningless attribute. - When it tries to render the view, it doesn’t recognize `ng-for` and gives up. - - We need to say “*hey component, I’m going to use this NgFor directive. OK?*” - - To that end, we first import the `NgFor` symbol - ``` - import {bootstrap, Component, FORM_DIRECTIVES, NgFor} from 'angular2/angular2'; - ``` - and then declare `NgFor` to be one of the view’s directives in the `@Component` decorator. - ``` - directives: [FORM_DIRECTIVES, NgFor] - ``` - After the browser refreshes, we see a list of heroes! - - ### Styling our heroes - Our list of heroes looks pretty bland. - We want to make it visually obvious to a user which hero we are hovering over and which hero is selected. - - Let’s add some styles to our component by setting the `styles` property on the `@Component` decorator - to the following CSS classes: - ``` - styles:[` - .heroes {list-style-type: none; margin-left: 1em; padding: 0; width: 10em;} - .heroes li { cursor: pointer; position: relative; left: 0; transition: all 0.2s ease; } - .heroes li:hover {color: #369; background-color: #EEE; left: .2em;} - .heroes .badge { - font-size: small; - color: white; - padding: 0.1em 0.7em; - background-color: #369; - line-height: 1em; - position: relative; - left: -1px; - top: -1px; - } - .selected { background-color: #EEE; color: #369; } - `], - ``` - Notice that we again use the back-tick notation for multi-line strings. - - When we assign styles to a component they are scoped to that specific component. - Our styles will only apply to our `AppComponent` and won't "leak" to the outer HTML. - - Our template for displaying the heroes should now look like this: - - code-example(format="linenums"). - <h2>My Heroes</h2> - <ul class="heroes"> - <li *ng-for="#hero of heroes"> - <span class="badge">{{hero.id}}</span> {{hero.name}} - </li> - </ul> - :markdown - Our styled list of heroes should look like this: - - figure.image-display - img(src='https://github.com/resources/images/devguide/toh/heroes-list-2.png' alt="Output of heroes list app (no selection color)") +:marked + ## Displaying heroes + To display a list of heroes, you'll add heroes to the view's template. + + ### Create heroes + Create an array of ten heroes. + ++makeExample('toh-2/ts/src/app/app.component.ts', 'hero-array', 'src/app/app.component.ts (hero array)') + +:marked + The `HEROES` array is of type `Hero`, the class defined in the previous page. + Eventually this app will fetch the list of heroes from a web service, but for now + you can display mock heroes. + + ### Expose heroes + Create a public property in `AppComponent` that exposes the heroes for binding. + + ++makeExample('toh-2/ts/src/app/app.component.1.html', 'hero-array-1', 'app.component.ts (hero array property)') + +:marked + The `heroes` type isn't defined because TypeScript infers it from the `HEROES` array. + +.l-sub-section + :marked + The hero data is separated from the class implementation + because ultimately the hero names will come from a data service. + +:marked + ### Display hero names in a template + To display the hero names in an unordered list, + insert the following chunk of HTML below the title and above the hero details. + + ++makeExample('toh-2/ts/src/app/app.component.1.html', 'heroes-template-1', 'app.component.ts (heroes template)')(format='.') + +:marked + Now you can fill the template with hero names. + + ### List heroes with ngFor + + The goal is to bind the array of heroes in the component to the template, iterate over them, + and display them individually. + + Modify the `
  • ` tag by adding the built-in directive `*ngFor`. + ++makeExample('toh-2/ts/src/app/app.component.1.html', 'heroes-ngfor-1', 'app.component.ts (ngFor)') + + +.l-sub-section + :marked + The (`*`) prefix to `ngFor` is a critical part of this syntax. + It indicates that the `
  • ` element and its children + constitute a master template. + + The `ngFor` directive iterates over the component's `heroes` array + and renders an instance of this template for each hero in that array. + + The `let hero` part of the expression identifies `hero` as the template input variable, + which holds the current hero item for each iteration. + You can reference this variable within the template to access the current hero's properties. + + Read more about `ngFor` and template input variables in the + [Showing an array property with *ngFor](../guide/displaying-data.html#ngFor) section of the + [Displaying Data](../guide/displaying-data.html) page and the + [ngFor](../guide/template-syntax.html#ngFor) section of the + [Template Syntax](../guide/template-syntax.html) page. + +:marked + Within the `
  • ` tags, add content + that uses the `hero` template variable to display the hero's properties. + + ++makeExample('toh-2/ts/src/app/app.component.1.html', 'ng-for', 'app.component.ts (ngFor template)')(format=".") + +:marked + When the browser refreshes, a list of heroes appears. + + ### Style the heroes + Users should get a visual cue of which hero they are hovering over and which hero is selected. + + To add styles to your component, set the `styles` property on the `@Component` decorator + to the following CSS classes: + ++makeExample('toh-2/ts/src/app/app.component.ts', 'styles', 'src/app/app.component.ts (styles)')(format=".") + +:marked + Remember to use the backtick notation for multi-line strings. + + Adding these styles makes the file much longer. In a later page you'll move the styles to a separate file. + + When you assign styles to a component, they are scoped to that specific component. + These styles apply only to the `AppComponent` and don't affect the outer HTML. + + The template for displaying heroes should look like this: + + ++makeExample('toh-2/ts/src/app/app.component.1.html', 'heroes-styled', 'src/app/app.component.ts (styled heroes)')(format='.') .l-main-section - :markdown - ## Selecting a Hero - We have a list of heroes and we have a single hero displayed in our app. - The list and the single hero are not connected in any way. - We want the user to select a hero from our list, and have the selected hero appear in the details view. - This UI pattern is widely known as “master-detail”. - In our case, the master is the heroes list and the detail is the selected hero. - - Let’s connect the master to the detail through a `selectedHero` component property bound to a click event. - - ### Click event - We modify the `
  • ` by inserting an Angular event binding to its click event. - - code-example(format="linenums"). - <li *ng-for="#hero of heroes" (click)="onSelect(hero)"> - <span class="badge">{{hero.id}}</span> {{hero.name}} - </li> - :markdown - Focus on the event binding - pre.prettyprint.lang-bash - code (click)="onSelect(hero)"> - :markdown - The parenthesis identify the `
  • ` element’s `click` event as the target. - The expression to the right of the equal sign calls the `AppComponent` method, `onSelect()`, - passing the local template variable `hero` as an argument. - That’s the same `hero` variable we defined previously in the `ng-for`. + :marked + ## Selecting a hero + The app now displays a list of heroes as well as a single hero in the details view. But + the list and the details view are not connected. + When users select a hero from the list, the selected hero should appear in the details view. + This UI pattern is known as "master/detail." + In this case, the _master_ is the heroes list and the _detail_ is the selected hero. + + Next you'll connect the master to the detail through a `selectedHero` component property, + which is bound to a click event. + + ### Handle click events + Add a click event binding to the `
  • ` like this: + + + +makeExample('toh-2/ts/src/app/app.component.1.html', 'selectedHero-click', 'app.component.ts (template excerpt)')(format='.') + + :marked + The parentheses identify the `
  • ` element's `click` event as the target. + The `onSelect(hero)` expression calls the `AppComponent` method, `onSelect()`, + passing the template input variable `hero`, as an argument. + That's the same `hero` variable you defined previously in the `ngFor` directive. .l-sub-section - :markdown - Learn more about Event Binding in the [Templating Syntax](../guide/template-syntax.html#event-binding) chapter. - :markdown - ### Add the click handler - Our event binding refers to an `onSelect` method that doesn’t exist yet. - We’ll add that method to our component now. - - What should that method do? It should set the component’s selected hero to the hero that the user clicked. - - Our component doesn’t have a “selected hero” yet either. We’ll start there. - - ### Expose the selected hero - We no longer need the static `hero` property of the `AppComponent`. - **Replace** it with this simple `selectedHero` property: - ``` - public selectedHero: Hero; - ``` - We’ve decided that none of the heroes should be selected before the user picks a hero so - we won’t initialize the `selectedHero` as we were doing with `hero`. - - Now **add an `onSelect` method** that sets the `selectedHero` property to the `hero` the user clicked. - ``` - onSelect(hero: Hero) { this.selectedHero = hero; } - ``` - - We will be showing the selected hero's details in our template. - At the moment, it is still referring to the old `hero` property. - Let’s fix the template to bind to the new `selectedHero` property. - - code-example(format="linenums"). - <h2>{{selectedHero.name}} details!</h2> - <div><label>id: </label>{{selectedHero.id}}</div> - <div> - <label>name: </label> - <input [(ng-model)]="selectedHero.name" placeholder="name"></input> - </div> - - :markdown - ### Hide the empty detail with ng-if - - When our app loads we see a list of heroes, but a hero is not selected. - The `selectedHero` is `undefined`. - That’s why we'll see the following error in the browser’s console: - - code-example(language="html"). + :marked + Learn more about event binding at the + [User Input](../guide/user-input.html) page and the + [Event binding](../guide/template-syntax.html#event-binding) section of the + [Template Syntax](../guide/template-syntax.html) page. + + :marked + ### Add a click handler to expose the selected hero + You no longer need the `hero` property because you're no longer displaying a single hero; you're displaying a list of heroes. + But the user will be able to select one of the heroes by clicking on it. + So replace the `hero` property with this simple `selectedHero` property: + + +makeExample('toh-2/ts/src/app/app.component.ts', 'selected-hero', 'src/app/app.component.ts (selectedHero)') + :marked + The hero names should all be unselected before the user picks a hero, so + you won't initialize the `selectedHero` as you did with `hero`. + + Add an `onSelect()` method that sets the `selectedHero` property to the `hero` that the user clicks. + +makeExample('toh-2/ts/src/app/app.component.ts', 'on-select', 'src/app/app.component.ts (onSelect)')(format='.') + + :marked + The template still refers to the old `hero` property. + Bind to the new `selectedHero` property instead as follows: + + + +makeExample('toh-2/ts/src/app/app.component.1.html', 'selectedHero-details', 'app.component.ts (template excerpt)')(format='.') + + :marked + ### Hide the empty detail with ngIf + + When the app loads, `selectedHero` is undefined. + The selected hero is initialized when the user clicks a hero's name. + Angular can't display properties of the undefined `selectedHero` and throws the following error, + visible in the browser's console: + + code-example(format="nocode"). EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null] - - :markdown - Remember that we are displaying `selectedHero.name` in the template. - This name property does not exist because `selectedHero`itself is undefined. - - We'll address this problem by keeping the hero detail out of the DOM until there is a selected hero. - - We wrap the HTML hero detail content of our template with a `
    `. - Then we add the `ng-if` built-in directive and set it to the `selectedHero` property of our component. - - code-example(format="linenums"). - <div *ng-if="selectedHero"> - <h2>{{selectedHero.name}} details!</h2> - <div><label>id: </label>{{selectedHero.id}}</div> - <div> - <label>name: </label> - <input [(ng-model)]="selectedHero.name" placeholder="name"></input> - </div> - </div> + + :marked + Although `selectedHero.name` is displayed in the template, + you must keep the hero detail out of the DOM until there is a selected hero. + + Wrap the HTML hero detail content of the template with a `
    `. + Then add the `ngIf` built-in directive and set it to the `selectedHero` property of the component. + + +makeExample('toh-2/ts/src/app/app.component.1.html', 'ng-if', 'src/app/app.component.ts (ngIf)')(format='.') + .alert.is-critical - :markdown - Remember that the leading asterisk (`*`) in front of `ng-if` is - a critical part of this syntax. - :markdown - When there is no `selectedHero`, the `ng-if` directive removes the hero detail HTML from the DOM. - There will be no hero detail elements and no bindings to worry about. - - When the user picks a hero, `selectedHero` becomes "truthy" and - `ng-if` puts the hero detail content into the DOM and evaluates the nested bindings. + :marked + Don't forget the asterisk (`*`) in front of `ngIf`. + + :marked + The app no longer fails and the list of names displays again in the browser. + + :marked + When there is no selected hero, the `ngIf` directive removes the hero detail HTML from the DOM. + There are no hero detail elements or bindings to worry about. + + When the user picks a hero, `selectedHero` becomes defined and + `ngIf` puts the hero detail content into the DOM and evaluates the nested bindings. + .l-sub-section - :markdown - `ng-if` and `ng-for` are called “structural directives” because they can change the - structure of portions of the DOM. - In other words, they give structure to the way Angular displays content in the DOM. - - Learn more about `ng-if`, `ng-for` and other structural directives in the - [Template Syntax](../guide/template-syntax.html#directives) chapter - - :markdown - We learned previously with `NgFor` that we must declare every directive we use in the component’s `@Component` decorator. - Let’s do that again for `NgIf`. - - Add the `NgIf` symbol to our imports at the top of our `app.ts` file, keeping them sorted - alphabetically to make them easier to find: - ``` - import {bootstrap, Component, FORM_DIRECTIVES, NgFor, NgIf} from 'angular2/angular2'; - ``` - :markdown - Now add `NgIf` to the directives array in the `@Component` decorator: - ``` - directives: [FORM_DIRECTIVES, NgFor, NgIf] - ``` - The browser refreshes and we see the list of heroes but not the selected hero detail. - The `ng-if` keeps it out of the DOM as long as the `selectedHero` is undefined. - When we click on a hero in the list, the selected hero displays in the hero details. - Everything is working as we expect. - - ### Styling the selection - - We see the selected hero in the details area below but we can’t quickly locate that hero in the list above. - We can fix that by applying the `selected` CSS class to the appropriate `
  • ` in the master list. - For example, when we select Magneta from the heroes list, - we can make it pop out visually by giving it a subtle background color as shown here. - + :marked + Read more about `ngIf` and `ngFor` in the + [Structural Directives](../guide/structural-directives.html) page and the + [Built-in directives](../guide/template-syntax.html#directives) section of the + [Template Syntax](../guide/template-syntax.html) page. + + + :marked + ### Style the selected hero + + While the selected hero details appear below the list, it's difficult to identify the selected hero within the list itself. + + In the `styles` metadata that you added above, there is a custom CSS class named `selected`. + To make the selected hero more visible, you'll apply this `selected` class to the `
  • ` when the user clicks on a hero name. + For example, when the user clicks "Magneta", it should render with a distinctive but subtle background color + like this: + figure.image-display img(src='https://github.com/resources/images/devguide/toh/heroes-list-selected.png' alt="Selected hero") - :markdown - First we’ll add a `getSelectedClass` method to the component that compares the current `selectedHero` to a hero parameter - and returns an object with a single key/value pair. - - The key is the name of the CSS class (`selected`). The value is `true` if the two heroes match and `false` otherwise. - We’re saying “*apply the `selected` class if the heroes match, remove it if they don’t*”. - Here is that method. - ``` - getSelectedClass(hero: Hero) { - return { 'selected': hero === this.selectedHero }; - } - ``` - What do we do with this method and its peculiar result? - - ### ng-class - We’ll add the `ng-class`built-in directive to the `
  • ` element in our template and bind it to `getSelectedClass`. - It’s no coincidence that the value returned by `getSelectedClass` is exactly what the `ng-class` requires - to add or remove the `selected` class to each hero’s display. - code-example(format="linenums"). - <li *ng-for="#hero of heroes" - [ng-class]="getSelectedClass(hero)" - (click)="onSelect(hero)"> - <span class="badge">{{hero.id}}</span> {{hero.name}} - </li> - - :markdown - Notice in the template that the `ng-class` name is surrounded in square brackets (`[]`). - This is the syntax for a Property Binding, a binding in which data flows one way - from the data source (the `getSelectedClass`) to a property of the `ng-class` directive. - + :marked + In the template, add the following `[class.selected]` binding to the `
  • `: + +makeExample('toh-2/ts/src/app/app.component.1.html', 'class-selected-1', 'app.component.ts (setting the CSS class)')(format=".") + :marked + When the expression (`hero === selectedHero`) is `true`, Angular adds the `selected` CSS class. + When the expression is `false`, Angular removes the `selected` class. + + .l-sub-section - :markdown - Learn more about [ng-class](../guide/template-syntax.html#ng-class) - and [Property Binding](../guide/template-syntax.html#property-binding) - in the Template Syntax chapter - :markdown - We've added yet another new directive to our template that we have to import and declare - in the component’s `directives` array as we’ve done twice before. - ``` - import {bootstrap, Component, - FORM_DIRECTIVES, NgClass, NgFor, NgIf} from 'angular2/angular2'; - ``` - - ``` - directives: [FORM_DIRECTIVES, NgClass, NgFor, NgIf] - ``` - The browser reloads our app. - We select a hero and the selection is clearly identified by the background color. - + :marked + Read more about the `[class]` binding in the [Template Syntax](../guide/template-syntax.html#ngClass "Template syntax: NgClass") guide. + + :marked + The final version of the `
  • ` looks like this: + +makeExample('toh-2/ts/src/app/app.component.1.html', 'class-selected-2', 'app.component.ts (styling each hero)')(format=".") + + :marked + After clicking "Magneta", the list should look like this: + figure.image-display img(src='https://github.com/resources/images/devguide/toh/heroes-list-1.png' alt="Output of heroes list app") - - :markdown - We select a different hero and the tell-tale color switches to that hero. - - ## Declaring Built-In Directives - - Every time we used a directive, we imported it and declared it in the component. - We only used three directives but we can easily envision a component that uses many more. - The `directives` array grows quickly and the process of importing the directive and adding it to the array is tedious. - We can make this easier. - - Remember how we imported the `FORM_DIRECTIVES` array to help us apply `ng-model`to our template in the previous chapter? - The `FORM_DIRECTIVES` array held all the directives we needed for `ng-model` (and a few more). - We didn’t have to list them. We simply added the `FORM_DIRECTIVES` array to the component’s `directives` array. - - The `NgClass`, `NgFor`, and `NgIf` are extremely common directives used by many components in many applications. - Fortunately they are all exported from Angular as part of the `CORE_DIRECTIVES` array. - - Let’s replace all of those separate import variables with `CORE_DIRECTIVES`: - ``` - import {bootstrap, Component, CORE_DIRECTIVES, FORM_DIRECTIVES} from 'angular2/angular2'; - ``` - Then replace `NgClass`, `NgFor`, and `NgIf` in the `directives` array with `CORE_DIRECTIVES`: - ``` - directives: [CORE_DIRECTIVES, FORM_DIRECTIVES] - ``` - Everything still works and we have a convenient way to import and declare the most commonly used directives. - Cleaner code for the win! - + + :marked + Here's the complete `app.component.ts` as of now: + + +makeExample('toh-2/ts/src/app/app.component.ts', '', 'src/app/app.component.ts') + .l-main-section - :markdown - ## The Road We’ve Travelled - Here’s what we achieved in this chapter: - - * Our Tour of Heroes now displays a list of selectable heroes - * We added the ability to select a hero and show the hero’s details - * We learned how to use the built-in directives `ng-if`, `ng-for` and `ng-class` in a component’s template - - ### The Road Ahead - Our Tour of Heroes has grown, but it’s far from complete. - We want to get data from an asynchronous source using promises, use shared services, and create reusable components. - We’ll learn more about these tasks in the coming tutorial chapters. - \ No newline at end of file +:marked + ## The road you've travelled + + Here's what you achieved in this page: + + * The Tour of Heroes app displays a list of selectable heroes. + * You added the ability to select a hero and show the hero's details. + * You learned how to use the built-in directives `ngIf` and `ngFor` in a component's template. + + Your app should look like this . + + ## The road ahead + You've expanded the Tour of Heroes app, but it's far from complete. + An app shouldn't be one monolithic component. + In the [next page](toh-pt3.html), you'll split the app into subcomponents and make them work together. diff --git a/public/docs/ts/latest/tutorial/toh-pt3.jade b/public/docs/ts/latest/tutorial/toh-pt3.jade new file mode 100644 index 0000000000..0501497f41 --- /dev/null +++ b/public/docs/ts/latest/tutorial/toh-pt3.jade @@ -0,0 +1,266 @@ +include ../_util-fns + +:marked + The `AppComponent` is doing _everything_ at the moment. + In the beginning, it showed details of a single hero. + Then it became a master/detail form with both a list of heroes and the hero detail. + Soon there will be new requirements and capabilities. + You can't keep piling features on top of features in one component; that's not maintainable. + + You'll need to break it up into sub-components, each focused on a specific task or workflow. + Eventually, the `AppComponent` could become a simple shell that hosts those sub-components. + + In this page, you'll take the first step in that direction by carving out the hero details into a separate, reusable component. + When you're done, the app should look like this . + +.l-main-section +:marked + ## Where you left off + Before getting started on this page, verify that you have the following structure from earlier in the Tour of Heroes. + If not, go back to the previous pages. + +.filetree + .file angular-tour-of-heroes + .children + .file src + .children + .file app + .children + .file app.component.ts + .file app.module.ts + .file main.ts + .file index.html + .file styles.css + .file systemjs.config.js + .file tsconfig.json + .file node_modules ... + .file package.json +:marked + Keep the app transpiling and running while you build the Tour of Heroes + by entering the `npm start` command in a terminal window + [as you did before](toh-pt1.html#keep-transpiling "Keep the app running"). + + ## Make a hero detail component + Add a file named `hero-detail.component.ts` to the `app/` folder. + This file will hold the new `HeroDetailComponent`. + + The file and component names follow the standard described in the Angular + [style guide](../guide/style-guide.html#naming). + + * The component _class_ name should be written in _upper camel case_ and end in the word "Component". + The hero detail component class is `HeroDetailComponent`. + + * The component _file_ name should be spelled in [_lower dash case_](../guide/glossary.html#dash-case), + each word separated by dashes, and end in `.component.ts`. + The `HeroDetailComponent` class goes in the `hero-detail.component.ts` file. + + Start writing the `HeroDetailComponent` as follows: + ++makeExample('toh-3/ts/app/hero-detail.component.1.ts', 'v1', 'app/hero-detail.component.ts (initial version)')(format=".") +a#selector +:marked + To define a component, you always import the `Component` symbol. + + The `@Component` decorator provides the Angular metadata for the component. + The CSS selector name, `hero-detail`, will match the element tag + that identifies this component within a parent component's template. + [Near the end of this tutorial page](#add-hero-detail "Add the HeroDetailComponent to the AppComponent"), + you'll add a `` element to the `AppComponent` template. + + Always `export` the component class because you'll always `import` it elsewhere. +:marked + ### Hero detail template + To move the hero detail view to the `HeroDetailComponent`, + cut the hero detail _content_ from the bottom of the `AppComponent` template + and paste it into a new `template` property in the `@Component` metadata. + + The `HeroDetailComponent` has a _hero_, not a _selected hero_. + Replace the word, "selectedHero", with the word, "hero", everywhere in the template. + When you're done, the new template should look like this: + ++makeExample('toh-3/ts/src/app/hero-detail.component.ts', 'template', 'src/app/hero-detail.component.ts (template)')(format=".") + + +:marked + ### Add the *hero* property + + The `HeroDetailComponent` template binds to the component's `hero` property. + Add that property to the `HeroDetailComponent` class like this: ++makeExample('toh-3/ts/app/hero-detail.component.1.ts', 'hero', 'src/app/hero-detail.component.ts (hero property)') +:marked + The `hero` property is typed as an instance of `Hero`. + The `Hero` class is still in the `app.component.ts` file. + Now there are two components that need to reference the `Hero` class. + The Angular [style guide](../guide/style-guide.html#rule-of-one "Style guide: rule of one") recommends one class per file anyway. + + Move the `Hero` class from `app.component.ts` to its own `hero.ts` file. + ++makeExample('toh-3/ts/src/app/hero.ts', '', 'src/app/hero.ts')(format=".") + +:marked + Now that the `Hero` class is in its own file, the `AppComponent` and the `HeroDetailComponent` have to import it. + Add the following `import` statement near the top of _both_ the `app.component.ts` and the `hero-detail.component.ts` files. ++makeExample('toh-3/ts/app/hero-detail.component.1.ts', 'hero-import') + +:marked + ### The *hero* property is an *input* property + + [Later in this page](#add-hero-detail "Add the HeroDetailComponent to the AppComponent"), + the parent `AppComponent` will tell the child `HeroDetailComponent` which hero to display + by binding its `selectedHero` to the `hero` property of the `HeroDetailComponent`. + The binding will look like this: ++makeExample('toh-3/ts/app/app.component.1.html', 'hero-detail-binding')(format='.') +:marked + Putting square brackets around the `hero` property, to the left of the equal sign (=), + makes it the *target* of a property binding expression. + You must declare a *target* binding property to be an *input* property. + Otherwise, Angular rejects the binding and throws an error. + + First, amend the `@angular/core` import statement to include the `Input` symbol. ++makeExample('toh-3/ts/src/app/hero-detail.component.ts', 'import-input', 'src/app/hero-detail.component.ts (excerpt)')(format='.') + +:marked + Then declare that `hero` is an *input* property by + preceding it with the `@Input` decorator that you imported earlier. ++makeExample('toh-3/ts/src/app/hero-detail.component.ts', 'hero', 'src/app/hero-detail.component.ts (excerpt)')(format='.') +.l-sub-section + :marked + Read more about _input_ properties in the + [Attribute Directives](../guide/attribute-directives.html#why-input) page. + +:marked + That's it. The `hero` property is the only thing in the `HeroDetailComponent` class. ++makeExample('toh-3/ts/src/app/hero-detail.component.ts', 'class')(format='.') +:marked + All it does is receive a hero object through its `hero` input property and then bind to that property with its template. + + Here's the complete `HeroDetailComponent`. ++makeExample('toh-3/ts/src/app/hero-detail.component.ts', '', 'src/app/hero-detail.component.ts') + +.l-main-section +:marked + ## Declare _HeroDetailComponent_ in the _AppModule_ + Every component must be declared in one—and only one—Angular module. + + Open `app.module.ts` in your editor and import the `HeroDetailComponent` so you can refer to it. ++makeExample('toh-3/ts/src/app/app.module.ts', 'hero-detail-import', 'src/app/app.module.ts') + +:marked + Add `HeroDetailComponent` to the module's `declarations` array. + ++makeExample('toh-3/ts/src/app/app.module.ts', 'declarations', 'src/app/app.module.ts')(format='.') +:marked + In general, the `declarations` array contains a list of application components, pipes, and directives that belong to the module. + A component must be declared in a module before other components can reference it. + This module declares only the two application components, `AppComponent` and `HeroDetailComponent`. +.l-sub-section + :marked + Read more about Angular modules in the [NgModules](../guide/ngmodule.html "Angular Modules (NgModule)") guide. + +a#add-hero-detail +.l-main-section +:marked + ## Add the _HeroDetailComponent_ to the _AppComponent_ +:marked + The `AppComponent` is still a master/detail view. + It used to display the hero details on its own, before you cut out that portion of the template. + Now it will delegate to the `HeroDetailComponent`. + + + Recall that `hero-detail` is the CSS [`selector`](#selector "HeroDetailComponent selector") + in the `HeroDetailComponent` metadata. + That's the tag name of the element that represents the `HeroDetailComponent`. + + Add a `` element near the bottom of the `AppComponent` template, + where the hero detail view used to be. + + Coordinate the master `AppComponent` with the `HeroDetailComponent` + by binding the `selectedHero` property of the `AppComponent` + to the `hero` property of the `HeroDetailComponent`. ++makeExample('toh-3/ts/app/app.component.1.html', 'hero-detail-binding', 'app.component.ts (excerpt)')(format='.') +:marked + Now every time the `selectedHero` changes, the `HeroDetailComponent` gets a new hero to display. + + The revised `AppComponent` template should look like this: + ++makeExample('toh-3/ts/src/app/app.component.ts', 'hero-detail-template', 'app.component.ts (excerpt)')(format='.') + +.l-main-section +:marked + ## What changed? + As [before](./toh-pt2.html), whenever a user clicks on a hero name, + the hero detail appears below the hero list. + But now the `HeroDetailView` is presenting those details. + + Refactoring the original `AppComponent` into two components yields benefits, both now and in the future: + + 1. You simplified the `AppComponent` by reducing its responsibilities. + + 1. You can evolve the `HeroDetailComponent` into a rich hero editor + without touching the parent `AppComponent`. + + 1. You can evolve the `AppComponent` without touching the hero detail view. + + 1. You can re-use the `HeroDetailComponent` in the template of some future parent component. + + ### Review the app structure + Verify that you have the following structure: + +.filetree + .file angular-tour-of-heroes + .children + .file src + .children + .file app + .children + .file app.component.ts + .file app.module.ts + .file hero.ts + .file hero-detail.component.ts + .file main.ts + .file index.html + .file styles.css + .file systemjs.config.js + .file tsconfig.json + .file node_modules ... + .file package.json + +:marked + Here are the code files discussed in this page. + ++makeTabs(` + toh-3/ts/src/app/hero-detail.component.ts, + toh-3/ts/src/app/app.component.ts, + toh-3/ts/src/app/hero.ts, + toh-3/ts/src/app/app.module.ts + `,'',` + src/app/hero-detail.component.ts, + src/app/app.component.ts, + src/app/hero.ts, + src/app/app.module.ts + `) + +.l-main-section +:marked + ## The road you’ve travelled + + Here's what you achieved in this page: + + * You created a reusable component. + * You learned how to make a component accept input. + * You learned to declare the required application directives in an Angular module. You + listed the directives in the `NgModule` decorator's `declarations` array. + * You learned to bind a parent component to a child component. + + Your app should look like this . + +.l-main-section +:marked + ## The road ahead + The Tour of Heroes app is more reusable with shared components, + but its (mock) data is still hard coded within the `AppComponent`. + That's not sustainable. + Data access should be refactored to a separate service + and shared among the components that need data. + + You’ll learn to create services in the [next tutorial](toh-pt4.html) page. diff --git a/public/docs/ts/latest/tutorial/toh-pt4.jade b/public/docs/ts/latest/tutorial/toh-pt4.jade new file mode 100644 index 0000000000..acdabe9c4b --- /dev/null +++ b/public/docs/ts/latest/tutorial/toh-pt4.jade @@ -0,0 +1,356 @@ +include ../_util-fns + +:marked + As the Tour of Heroes app evolves, you'll add more components that need access to hero data. + + Instead of copying and pasting the same code over and over, + you'll create a single reusable data service and + inject it into the components that need it. + Using a separate service keeps components lean and focused on supporting the view, + and makes it easy to unit-test components with a mock service. + + Because data services are invariably asynchronous, + you'll finish the page with a *Promise*-based version of the data service. + + When you're done with this page, the app should look like this . + +.l-main-section +:marked + ## Where you left off + Before continuing with the Tour of Heroes, verify that you have the following structure. + If not, go back to the previous pages. + +.filetree + .file angular-tour-of-heroes + .children + .file src + .children + .file app + .children + .file app.component.ts + .file app.module.ts + .file hero.ts + .file hero-detail.component.ts + .file main.ts + .file index.html + .file styles.css + .file systemjs.config.js + .file tsconfig.json + .file node_modules ... + .file package.json + +:marked + ## Keep the app transpiling and running + Enter the following command in the terminal window: + +code-example(language="sh" class="code-shell"). + npm start + +:marked + This command runs the TypeScript compiler in "watch mode", recompiling automatically when the code changes. + The command simultaneously launches the app in a browser and refreshes the browser when the code changes. + + You can keep building the Tour of Heroes without pausing to recompile or refresh the browser. + + ## Creating a hero service + The stakeholders want to show the heroes in various ways on different pages. + Users can already select a hero from a list. + Soon you'll add a dashboard with the top performing heroes and create a separate view for editing hero details. + All three views need hero data. + + At the moment, the `AppComponent` defines mock heroes for display. + However, defining heroes is not the component's job, + and you can't easily share the list of heroes with other components and views. + In this page, you'll move the hero data acquisition business to a single service that provides the data and + share that service with all components that need the data. + + ### Create the HeroService + Create a file in the `app` folder called `hero.service.ts`. +.l-sub-section + :marked + The naming convention for service files is the service name in lowercase followed by `.service`. + For a multi-word service name, use lower [dash-case](../guide/glossary.html#!#dash-case). + For example, the filename for `SpecialSuperHeroService` is `special-super-hero.service.ts`. +:marked + Name the class `HeroService` and export it for others to import. + ++makeExample('toh-4/ts/src/app/hero.service.1.ts', 'empty-class', 'src/app/hero.service.ts (starting point)')(format=".") + +:marked + ### Injectable services + Notice that you imported the Angular `Injectable` function and applied that function as an `@Injectable()` decorator. +.callout.is-helpful + :marked + Don't forget the parentheses. Omitting them leads to an error that's difficult to diagnose. +:marked + The `@Injectable()` decorator tells TypeScript to emit metadata about the service. + The metadata specifies that Angular may need to inject other dependencies into this service. + + Although the `HeroService` doesn't have any dependencies at the moment, + applying the `@Injectable()` decorator ​from the start ensures + consistency and future-proofing. + +:marked + ### Getting hero data + Add a `getHeroes()` method stub. + ++makeExample('toh-4/ts/src/app/hero.service.1.ts', 'getHeroes-stub', 'src/app/hero.service.ts (getHeroes stub)')(format=".") + + +:marked + The `HeroService` could get `Hero` data from anywhere—a + web service, local storage, or a mock data source. + Removing data access from the component means + you can change your mind about the implementation anytime, + without touching the components that need hero data. + + ### Move the mock hero data + Cut the `HEROES` array from `app.component.ts` and paste it to a new file in the `app` folder named `mock-heroes.ts`. + Additionally, copy the `import {Hero} ...` statement because the heroes array uses the `Hero` class. + ++makeExample('toh-4/ts/src/app/mock-heroes.ts', null, 'src/app/mock-heroes.ts') +:marked + The `HEROES` constant is exported so it can be imported elsewhere, such as the `HeroService`. + + In `app.component.ts`, where you cut the `HEROES` array, + add an uninitialized `heroes` property: ++makeExample('toh-4/ts/src/app/app.component.1.ts', 'heroes-prop', 'src/app/app.component.ts (heroes property)')(format=".") +:marked + ### Return mocked hero data + Back in the `HeroService`, import the mock `HEROES` and return it from the `getHeroes()` method. + The `HeroService` looks like this: ++makeExample('toh-4/ts/src/app/hero.service.1.ts', 'full', 'src/app/hero.service.ts')(format=".") + +:marked + ### Import the hero service + You're ready to use the `HeroService` in other components, starting with `AppComponent`. + + Import the `HeroService` so that you can reference it in the code. ++makeExcerpt('toh-4/ts/src/app/app.component.ts', 'hero-service-import', 'src/app/app.component.ts') +:marked + ### Don't use *new* with the *HeroService* + How should the `AppComponent` acquire a runtime concrete `HeroService` instance? + + You could create a new instance of the `HeroService` with `new` like this: ++makeExample('toh-4/ts/src/app/app.component.1.ts', 'new-service')(format=".") + +:marked + However, this option isn't ideal for the following reasons: + + * The component has to know how to create a `HeroService`. + If you change the `HeroService` constructor, + you must find and update every place you created the service. + Patching code in multiple places is error prone and adds to the test burden. + * You create a service each time you use `new`. + What if the service caches heroes and shares that cache with others? + You couldn't do that. + * With the `AppComponent` locked into a specific implementation of the `HeroService`, + switching implementations for different scenarios, such as operating offline or using + different mocked versions for testing, would be difficult. + + + ### Inject the *HeroService* + + Instead of using the *new* line, you'll add two lines. + + * Add a constructor that also defines a private property. + * Add to the component's `providers` metadata. + + Add the constructor: ++makeExample('toh-4/ts/src/app/app.component.1.ts', 'ctor', 'src/app/app.component.ts (constructor)') +:marked + The constructor itself does nothing. The parameter simultaneously + defines a private `heroService` property and identifies it as a `HeroService` injection site. +:marked + Now Angular knows to supply an instance of the `HeroService` when it creates an `AppComponent`. + +.l-sub-section + :marked + Read more about dependency injection in the [Dependency Injection](../guide/dependency-injection.html) page. +:marked + The *injector* doesn't know yet how to create a `HeroService`. + If you ran the code now, Angular would fail with this error: +code-example(format="nocode"). + EXCEPTION: No provider for HeroService! (AppComponent -> HeroService) +:marked + To teach the injector how to make a `HeroService`, + add the following `providers` array property to the bottom of the component metadata + in the `@Component` call. + + ++makeExcerpt('toh-4/ts/src/app/app.component.1.ts', 'providers', 'src/app/app.component.ts') + +:marked + The `providers` array tells Angular to create a fresh instance of the `HeroService` when it creates an `AppComponent`. + The `AppComponent`, as well as its child components, can use that service to get hero data. +a#child-component +:marked + ### *getHeroes()* in the *AppComponent* + The service is in a `heroService` private variable. + + You could call the service and get the data in one line. ++makeExample('toh-4/ts/src/app/app.component.1.ts', 'get-heroes')(format=".") +:marked + You don't really need a dedicated method to wrap one line. Write it anyway: + ++makeExcerpt('toh-4/ts/src/app/app.component.1.ts', 'getHeroes', 'src/app/app.component.ts') + +:marked + ### The *ngOnInit* lifecycle hook + `AppComponent` should fetch and display hero data with no issues. + + You might be tempted to call the `getHeroes()` method in a constructor, but + a constructor should not contain complex logic, + especially a constructor that calls a server, such as as a data access method. + The constructor is for simple initializations, like wiring constructor parameters to properties. + + To have Angular call `getHeroes()`, you can implement the Angular *ngOnInit lifecycle hook*. + Angular offers interfaces for tapping into critical moments in the component lifecycle: + at creation, after each change, and at its eventual destruction. + + Each interface has a single method. When the component implements that method, Angular calls it at the appropriate time. +.l-sub-section + :marked + Read more about lifecycle hooks in the [Lifecycle Hooks](../guide/lifecycle-hooks.html) page. +:marked + Here's the essential outline for the `OnInit` interface (don't copy this into your code): ++makeExample('toh-4/ts/src/app/app.component.1.ts', 'on-init')(format=".") + +:marked + Add the implementation for the `OnInit` interface to your export statement: +code-example(format="nocode"). + export class AppComponent implements OnInit {} +:marked + Write an `ngOnInit` method with the initialization logic inside. Angular will call it + at the right time. In this case, initialize by calling `getHeroes()`. ++makeExcerpt('toh-4/ts/src/app/app.component.1.ts', 'ng-on-init', 'app/app.component.ts') +:marked + The app should run as expected, showing a list of heroes and a hero detail view + when you click on a hero name. + + +:marked + ## Async services and Promises + The `HeroService` returns a list of mock heroes immediately; + its `getHeroes()` signature is synchronous. ++makeExample('toh-4/ts/src/app/app.component.1.ts', 'get-heroes')(format=".") +:marked + Eventually, the hero data will come from a remote server. + When using a remote server, users don't have to wait for the server to respond; + additionally, you aren't able to block the UI during the wait. + +:marked + To coordinate the view with the response, + you can use *Promises*, which is an asynchronous + technique that changes the signature of the `getHeroes()` method. + + ### The hero service makes a Promise + + A *Promise* essentially promises to call back when the results are ready. + You ask an asynchronous service to do some work and give it a callback function. + The service does that work and eventually calls the function with the results or an error. +.l-sub-section + :marked + This is a simplified explanation. Read more about ES2015 Promises in the + [Promises for asynchronous programming](http://exploringjs.com/es6/ch_promises.html) page of + [Exploring ES6](http://http://exploringjs.com/es6.html). + +:marked + Update the `HeroService` with this Promise-returning `getHeroes()` method: ++makeExample('toh-4/ts/src/app/hero.service.ts', 'get-heroes', 'src/app/hero.service.ts (excerpt)')(format=".") +:marked + You're still mocking the data. You're simulating the behavior of an ultra-fast, zero-latency server, + by returning an *immediately resolved Promise* with the mock heroes as the result. + + ### Act on the Promise + + As a result of the change to `HeroService`, `this.heroes` is now set to a `Promise` rather than an array of heroes. ++makeExample('toh-4/ts/src/app/app.component.1.ts', 'getHeroes', 'src/app/app.component.ts (getHeroes - old)')(format=".") + +:marked + You have to change the implementation to *act on the `Promise` when it resolves*. + When the `Promise` resolves successfully, you'll have heroes to display. + + Pass the callback function as an argument to the Promise's `then()` method: ++makeExample('toh-4/ts/src/app/app.component.ts', 'get-heroes', 'src/app/app.component.ts (getHeroes - revised)')(format=".") +.l-sub-section + :marked + As described in [Arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions), + the ES2015 arrow function + in the callback is more succinct than the equivalent function expression and gracefully handles `this`. +:marked + The callback sets the component's `heroes` property to the array of heroes returned by the service. + + The app is still running, showing a list of heroes, and + responding to a name selection with a detail view. +.l-sub-section + :marked + At the end of this page, [Appendix: take it slow](#slow) describes what the app might be like with a poor connection. +:marked + ## Review the app structure + Verify that you have the following structure after all of your refactoring: + +.filetree + .file angular-tour-of-heroes + .children + .file src + .children + .file app + .children + .file app.component.ts + .file app.module.ts + .file hero.ts + .file hero-detail.component.ts + .file hero.service.ts + .file mock-heroes.ts + .file main.ts + .file index.html + .file styles.css + .file systemjs.config.js + .file tsconfig.json + .file node_modules ... + .file package.json +:marked + Here are the code files discussed in this page. + ++makeTabs(` + toh-4/ts/src/app/hero.service.ts, + toh-4/ts/src/app/app.component.ts, + toh-4/ts/src/app/mock-heroes.ts + `,'',` + src/app/hero.service.ts, + src/app/app.component.ts, + src/app/mock-heroes.ts + `) +:marked + ## The road you've travelled + Here's what you achieved in this page: + + * You created a service class that can be shared by many components. + * You used the `ngOnInit` lifecycle hook to get the hero data when the `AppComponent` activates. + * You defined the `HeroService` as a provider for the `AppComponent`. + * You created mock hero data and imported them into the service. + * You designed the service to return a Promise and the component to get the data from the Promise. + + Your app should look like this . + + ## The road ahead + The Tour of Heroes has become more reusable using shared components and services. + The next goal is to create a dashboard, add menu links that route between the views, and format data in a template. + As the app evolves, you'll discover how to design it to make it easier to grow and maintain. + + Read about the Angular component router and navigation among the views in the [next tutorial](toh-pt5.html) page. + +.l-main-section + +:marked + ## Appendix: Take it slow + To simulate a slow connection, + import the `Hero` symbol and add the following `getHeroesSlowly()` method to the `HeroService`. ++makeExample('toh-4/ts/src/app/hero.service.ts', 'get-heroes-slowly', 'app/hero.service.ts (getHeroesSlowly)')(format=".") +:marked + Like `getHeroes()`, it also returns a `Promise`. + But this Promise waits two seconds before resolving the Promise with mock heroes. + + Back in the `AppComponent`, replace `getHeroes()` with `getHeroesSlowly()` + and see how the app behaves. diff --git a/public/docs/ts/latest/tutorial/toh-pt5.jade b/public/docs/ts/latest/tutorial/toh-pt5.jade new file mode 100644 index 0000000000..4af761ea60 --- /dev/null +++ b/public/docs/ts/latest/tutorial/toh-pt5.jade @@ -0,0 +1,900 @@ +block includes + include ../_util-fns + +:marked + There are new requirements for the Tour of Heroes app: + + * Add a *Dashboard* view. + * Add the ability to navigate between the *Heroes* and *Dashboard* views. + * When users click a hero name in either view, navigate to a detail view of the selected hero. + * When users click a *deep link* in an email, open the detail view for a particular hero. + + When you’re done, users will be able to navigate the app like this: + +figure.image-display + img(src='https://github.com/resources/images/devguide/toh/nav-diagram.png' alt="View navigations") + +:marked + To satisfy these requirements, you'll add Angular’s router to the app. + +.l-sub-section + :marked + For more information about the router, read the [Routing and Navigation](../guide/router.html) page. + +:marked + When you're done with this page, the app should look like this . + +include ../../../_includes/_see-addr-bar + +.l-main-section +:marked + ## Where you left off + Before continuing with the Tour of Heroes, verify that you have the following structure. + + +.filetree + .file angular-tour-of-heroes + .children + .file src + .children + .file app + .children + .file app.component.ts + .file app.module.ts + .file hero.service.ts + .file hero.ts + .file hero-detail.component.ts + .file mock-heroes.ts + .file main.ts + .file index.html + .file styles.css + .file systemjs.config.js + .file tsconfig.json + .file node_modules ... + .file package.json + +:marked + ## Keep the app transpiling and running + Enter the following command in the terminal window: + +code-example(language="sh" class="code-shell"). + npm start + +:marked + This command runs the TypeScript compiler in "watch mode", recompiling automatically when the code changes. + The command simultaneously launches the app in a browser and refreshes the browser when the code changes. + +:marked + You can keep building the Tour of Heroes without pausing to recompile or refresh the browser. + + ## Action plan + + Here's the plan: + + * Turn `AppComponent` into an application shell that only handles navigation. + * Relocate the *Heroes* concerns within the current `AppComponent` to a separate `HeroesComponent`. + * Add routing. + * Create a new `DashboardComponent`. + * Tie the *Dashboard* into the navigation structure. + +.l-sub-section + :marked + *Routing* is another name for *navigation*. The router is the mechanism for navigating from view to view. + +.l-main-section +:marked + ## Splitting the *AppComponent* + + The current app loads `AppComponent` and immediately displays the list of heroes. + + The revised app should present a shell with a choice of views (*Dashboard* and *Heroes*) + and then default to one of them. + + The `AppComponent` should only handle navigation, so you'll + move the display of *Heroes* out of `AppComponent` and into its own `HeroesComponent`. + + ### *HeroesComponent* + + `AppComponent` is already dedicated to *Heroes*. + Instead of moving the code out of `AppComponent`, rename it to `HeroesComponent` + and create a separate `AppComponent` shell. + + Do the following: + + * Rename the app.component.ts file to heroes.component.ts. + * Rename the `AppComponent` class as `HeroesComponent` (rename locally, _only_ in this file). + * Rename the selector `my-app` as `my-heroes`. + ++makeExample('toh-5/ts/src/app/heroes.component.ts', 'renaming', 'src/app/heroes.component.ts (showing renamings only)') + +:marked + ### Create *AppComponent* + + The new `AppComponent` is the application shell. + It will have some navigation links at the top and a display area below. + + Perform these steps: + + * Create the file src/app/app.component.ts. + * Define an exported `AppComponent` class. + * Add an `@Component` decorator above the class with a `my-app` selector. + * Move the following from `HeroesComponent` to `AppComponent`: + + * `title` class property. + * `@Component` template `

    ` element, which contains a binding to `title`. + + * Add a `` element to the app template just below the heading so you still see the heroes. + * Add `HeroesComponent` to the `declarations` array of `AppModule` so Angular recognizes the `` tags. + * Add `HeroService` to the `providers` array of `AppModule` because you'll need it in every other view. + * Remove `HeroService` from the `HeroesComponent` `providers` array since it was promoted. + * Add the supporting `import` statements for `AppComponent`. + + The first draft looks like this: + ++makeTabs( + `toh-5/ts/src/app/app.component.1.ts, + toh-5/ts/src/app/app.module.1.ts`, + ',', + `src/app/app.component.ts (v1), + src/app/app.module.ts (v1)`) + +:marked + The app still runs and displays heroes. + +:marked + ## Add routing + + Instead of displaying automatically, heroes should display after users click a button. + In other words, users should be able to navigate to the list of heroes. + + Use the Angular router to enable navigation. + +:marked + The Angular router is an external, optional Angular NgModule called `RouterModule`. + The router is a combination of multiple provided services (`RouterModule`), + multiple directives (`RouterOutlet, RouterLink, RouterLinkActive`), + and a configuration (`Routes`). You'll configure the routes first. + +:marked + ### *<base href>* + + Open `index.html` and ensure there is a `` element + (or a script that dynamically sets this element) + at the top of the `` section. + ++makeExample('toh-5/ts/src/index.html', 'base-href', 'src/index.html (base-href)') + +.callout.is-important + header base href is essential + :marked + For more information, see the [Set the base href](../guide/router.html#!#base-href) + section of the [Routing and Navigation](../guide/router.html) page. + + +a#configure-routes +:marked + ### Configure routes + + Create a configuration file for the app routes. + +:marked + *Routes* tell the router which views to display when a user clicks a link or + pastes a URL into the browser address bar. + + Define the first route as a route to the heroes component. + ++makeExample('toh-5/ts/src/app/app.module.2.ts', 'heroes', 'src/app/app.module.ts (heroes route)') + +:marked + The `Routes` are an array of *route definitions*. + + This route definition has the following parts: + + * *Path*: The router matches this route's path to the URL in the browser address bar (`heroes`). + * *Component*: The component that the router should create when navigating to this route (`HeroesComponent`). + + +.l-sub-section + :marked + Read more about defining routes with `Routes` in the [Routing & Navigation](../guide/router.html) page. + +:marked + ### Make the router available + + Import the `RouterModule` and add it to the `AppModule` imports array. + ++makeExample('toh-5/ts/src/app/app.module.2.ts', '', 'src/app/app.module.ts (app routing)') + +.l-sub-section + :marked + The `forRoot()` method is called because a configured router is provided at the app's root. + The `forRoot()` method supplies the Router service providers and directives needed for routing, and + performs the initial navigation based on the current browser URL. + +:marked + ### Router outlet + + If you paste the path, `/heroes`, into the browser address bar at the end of the URL, + the router should match it to the `heroes` route and display the `HeroesComponent`. + However, you have to tell the router where to display the component. + To do this, you can add a `` element at the end of the template. + `RouterOutlet` is one of the directives provided by the `RouterModule`. + The router displays each component immediately below the `` as users navigate through the app. + + ### Router links + + Users shouldn't have to paste a route URL into the address bar. + Instead, add an anchor tag to the template that, when clicked, triggers navigation to the `HeroesComponent`. + + The revised template looks like this: + ++makeExample('toh-5/ts/src/app/app.component.1.ts', 'template-v2', 'src/app/app.component.ts (template-v2)') + +:marked + Note the `routerLink` binding in the anchor tag. + The `RouterLink` directive (another of the `RouterModule` directives) is bound to a string + that tells the router where to navigate when the user clicks the link. + + Since the link is not dynamic, a routing instruction is defined with a one-time binding to the route path. + Looking back at the route configuration, you can confirm that `'/heroes'` is the path of the route to the `HeroesComponent`. +.l-sub-section + :marked + Read more about dynamic router links and the link parameters array + in the [Appendix: Link Parameters Array](../guide/router.html#link-parameters-array) section of the + [Routing & Navigation](../guide/router.html#) page. + +:marked + Refresh the browser. The browser displays the app title and heroes link, but not the heroes list. + +.l-sub-section + :marked + The browser's address bar shows `/`. + The route path to `HeroesComponent` is `/heroes`, not `/`. + Soon you'll add a route that matches the path `/`. + +:marked + Click the *Heroes* navigation link. The address bar updates to `/heroes` + and the list of heroes displays. + + `AppComponent` now looks like this: + ++makeExample('toh-5/ts/src/app/app.component.1.ts', 'v2', 'src/app/app.component.ts (v2)') + +:marked + The *AppComponent* is now attached to a router and displays routed views. + For this reason, and to distinguish it from other kinds of components, + this component type is called a *router component*. + +:marked + ## Add a dashboard + + Routing only makes sense when multiple views exist. + To add another view, create a placeholder `DashboardComponent`, which users can navigate to and from. + ++makeExample('toh-5/ts/src/app/dashboard.component.1.ts', '', 'src/app/dashboard.component.ts (v1)') + +:marked + You'll make this component more useful later. + + ### Configure the dashboard route + + To teach `app.module.ts` to navigate to the dashboard, + import the dashboard component and + add the following route definition to the `Routes` array of definitions. + ++makeExample('toh-5/ts/src/app/app.module.3.ts', 'dashboard', 'src/app/app.module.ts (Dashboard route)') + +:marked + Also import and add `DashboardComponent` to the `AppModule`'s `declarations`. + ++makeExample('toh-5/ts/src/app/app.module.ts', 'dashboard', 'src/app/app.module.ts (dashboard)') + +:marked + ### Add a redirect route + + Currently, the browser launches with `/` in the address bar. + When the app starts, it should show the dashboard and + display a `/dashboard` URL in the browser address bar. + +:marked + To make this happen, use a redirect route. Add the following + to the array of route definitions: + ++makeExample('toh-5/ts/src/app/app.module.3.ts','redirect', 'src/app/app.module.ts (redirect)') + +.l-sub-section + :marked + Read more about *redirects* in the [Redirecting routes](../guide/router.html#!#redirect) section + of the [Routing & Navigation](../guide/router.html#) page. + +:marked + ### Add navigation to the template + + Add a dashboard navigation link to the template, just above the *Heroes* link. + ++makeExample('toh-5/ts/src/app/app.component.1.ts', 'template-v3', 'src/app/app.component.ts (template-v3)') + +.l-sub-section + :marked + The `