Compare commits
132 Commits
4.2.0-rc.1
...
4.2.2
Author | SHA1 | Date | |
---|---|---|---|
bd58e129cf | |||
e55bb7ba13 | |||
ed73d4f3ac | |||
6ca46929fa | |||
185075d870 | |||
451257a2fd | |||
3d5f520ff0 | |||
a1065bfaf0 | |||
d6f345cc3c | |||
d961911a98 | |||
e543272eb3 | |||
c8c0ceeee9 | |||
afbc2b0082 | |||
69ec1e2e31 | |||
ff15509bc7 | |||
7e22a2fc0b | |||
0a48c92d2c | |||
991ca92a9d | |||
c550f885a4 | |||
851038c3a8 | |||
df7774c018 | |||
15090a8ad4 | |||
8575e3f71c | |||
90b0713e32 | |||
d56b7ed96d | |||
db5e5067a0 | |||
0020dad595 | |||
c2d31fb01e | |||
65d49d5c94 | |||
3a99af2696 | |||
503fd1fbaf | |||
4c74dba2f3 | |||
33df13ad65 | |||
ce18c68eca | |||
2d5623911a | |||
a0b30e5dfb | |||
76a920651c | |||
ef6609a723 | |||
c0dcb342f3 | |||
8a9a5ecdd7 | |||
12c5ead39c | |||
3db6b6ca7a | |||
e894f5c399 | |||
8524187869 | |||
f7422a9607 | |||
cf0a9e0730 | |||
8e5beb024f | |||
576d6d8f86 | |||
41a765d715 | |||
f370fd36e0 | |||
a222c3e609 | |||
5a71df0cb3 | |||
6a46cabb10 | |||
74673545c0 | |||
75a311f250 | |||
391ed6334d | |||
0940e6d6ed | |||
4d2ee51bb0 | |||
680128bc09 | |||
5364b51979 | |||
67ef0f0c8f | |||
e9886d701d | |||
022835bab2 | |||
8be2e4c325 | |||
33ba3e31ed | |||
cba2b3c72d | |||
a4a2901294 | |||
bb46f54ad7 | |||
c9b930dd82 | |||
a39f7d63bb | |||
e317f7d51c | |||
3ddd28d37d | |||
fe6b39d585 | |||
d837bfc2d7 | |||
4759975be6 | |||
078a4b00a7 | |||
b00b80a45b | |||
269bbe0e7d | |||
bb2fc6b8da | |||
d4e196035c | |||
b8979c8701 | |||
bfdd3398f6 | |||
3a121a621f | |||
c06f4fc702 | |||
95f1ea2f12 | |||
784347f61f | |||
42176a7ac4 | |||
76b4b80a23 | |||
7c78282ce8 | |||
47c2a2e411 | |||
5faf520067 | |||
02d74cafba | |||
734f30d14c | |||
f2d810febc | |||
819514aeba | |||
b55adee982 | |||
4c32cb952f | |||
35f714e438 | |||
d5ce086089 | |||
82ec02daf8 | |||
8e7a3f031b | |||
7822187b17 | |||
068133ec85 | |||
598fdad089 | |||
ad6a57e0a3 | |||
230255f887 | |||
1338995ee9 | |||
47e4fca7fd | |||
665e7071fa | |||
c20f60b144 | |||
1408357198 | |||
1f9a3dd1e6 | |||
11505fa0d8 | |||
9326e062d8 | |||
cb2cb7c3bd | |||
92d99ba279 | |||
dfbbbb5e3e | |||
89f317915d | |||
4d5fa5c855 | |||
2f35392cd8 | |||
b056adc032 | |||
0d894a18fc | |||
e5138081ec | |||
8ffa483bb6 | |||
ea8a43def0 | |||
535d9da6b6 | |||
18bf77204e | |||
160221c815 | |||
6220b49463 | |||
aa683a765d | |||
b8b91d3418 | |||
0018acfede |
6
.bazelrc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Disable sandboxing because it's too slow.
|
||||||
|
# https://github.com/bazelbuild/bazel/issues/2424
|
||||||
|
build --spawn_strategy=standalone
|
||||||
|
|
||||||
|
# Performance: avoid stat'ing input files
|
||||||
|
build --watchfs
|
20
.circleci/config.yml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
version: 2
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
working_directory: ~/ng
|
||||||
|
docker:
|
||||||
|
- image: alexeagle/ngcontainer
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- restore_cache:
|
||||||
|
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||||
|
- run: npm install
|
||||||
|
- run: npm run postinstall
|
||||||
|
- run: ./node_modules/.bin/gulp lint
|
||||||
|
# Build twice, workaround for
|
||||||
|
# https://github.com/bazelbuild/bazel/issues/3114
|
||||||
|
- run: bazel build ... || bazel build ...
|
||||||
|
- save_cache:
|
||||||
|
key: angular-{{ .Branch }}-{{ checksum "npm-shrinkwrap.json" }}
|
||||||
|
paths:
|
||||||
|
- "node_modules"
|
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
|||||||
.DS_STORE
|
.DS_STORE
|
||||||
|
|
||||||
/dist/
|
/dist/
|
||||||
|
bazel-*
|
||||||
node_modules
|
node_modules
|
||||||
bower_components
|
bower_components
|
||||||
|
|
||||||
|
@ -97,15 +97,15 @@ groups:
|
|||||||
- vicb
|
- vicb
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
|
|
||||||
compiler/animations:
|
animations:
|
||||||
conditions:
|
conditions:
|
||||||
files:
|
files:
|
||||||
- "packages/compiler/src/animation/*"
|
- "packages/animation/*"
|
||||||
|
- "packages/platform-browser/animations/*"
|
||||||
users:
|
users:
|
||||||
- matsko #primary
|
- matsko #primary
|
||||||
- tbosch
|
|
||||||
- IgorMinar #fallback
|
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
|
- IgorMinar #fallback
|
||||||
|
|
||||||
compiler/i18n:
|
compiler/i18n:
|
||||||
conditions:
|
conditions:
|
||||||
|
18
BUILD
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
exports_files(["tsconfig.json"])
|
||||||
|
|
||||||
|
# This rule belongs in node_modules/BUILD
|
||||||
|
# It's here as a workaround for
|
||||||
|
# https://github.com/bazelbuild/bazel/issues/374#issuecomment-296217940
|
||||||
|
filegroup(
|
||||||
|
name = "node_modules",
|
||||||
|
srcs = glob([
|
||||||
|
# Performance workaround: list individual files
|
||||||
|
# This won't scale in the general case.
|
||||||
|
# TODO(alexeagle): figure out what to do
|
||||||
|
"node_modules/typescript/**",
|
||||||
|
"node_modules/zone.js/**/*.d.ts",
|
||||||
|
"node_modules/rxjs/**/*.d.ts",
|
||||||
|
"node_modules/@types/**/*.d.ts",
|
||||||
|
]),
|
||||||
|
)
|
73
CHANGELOG.md
@ -1,3 +1,74 @@
|
|||||||
|
<a name="4.2.2"></a>
|
||||||
|
## [4.2.2](https://github.com/angular/angular/compare/4.2.1...4.2.2) (2017-06-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **animations:** compute removal node height correctly ([185075d](https://github.com/angular/angular/commit/185075d))
|
||||||
|
* **animations:** do not treat a `0` animation state as `void` ([451257a](https://github.com/angular/angular/commit/451257a))
|
||||||
|
* **animations:** properly collect :enter nodes in a partially updated collection ([6ca4692](https://github.com/angular/angular/commit/6ca4692)), closes [#17440](https://github.com/angular/angular/issues/17440)
|
||||||
|
* **compiler:** don’t always compile `.ngfactory.ts` files by default ([ed73d4f](https://github.com/angular/angular/commit/ed73d4f3ac6b542bf5ea3eb73fbe91e2ceabcdb4))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="4.2.1"></a>
|
||||||
|
## [4.2.1](https://github.com/angular/angular/compare/4.2.0-rc.2...4.2.1) (2017-06-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
|
||||||
|
* **compiler:** don’t write summaries for jit by default ([d3a5f1a](https://github.com/angular/angular/commit/d3a5f1a))
|
||||||
|
* **http:** move destructuring inside {Request,Response}Options ctor ([c2d31fb](https://github.com/angular/angular/commit/c2d31fb)), closes [#16663](https://github.com/angular/angular/issues/16663)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="4.2.0"></a>
|
||||||
|
# [4.2.0](https://github.com/angular/angular/compare/4.2.0-rc.2...4.2.0) salubrious-stratagem (2017-06-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **animations:** ensure web-animations understands a numeric CSS perspective value ([819514a](https://github.com/angular/angular/commit/819514a)), closes [#14007](https://github.com/angular/angular/issues/14007)
|
||||||
|
* **animations:** evaluate substitutions on option param values ([e9886d7](https://github.com/angular/angular/commit/e9886d7))
|
||||||
|
* **forms:** fix min and max validator behavior on non-numbers ([a222c3e](https://github.com/angular/angular/commit/a222c3e))
|
||||||
|
* **router:** opening links in new window ([4c32cb9](https://github.com/angular/angular/commit/4c32cb9))
|
||||||
|
* **upgrade:** call setInterval outside the Angular zone ([269bbe0](https://github.com/angular/angular/commit/269bbe0))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **compiler-cli:** introduce synchronous codegen API ([b00b80a](https://github.com/angular/angular/commit/b00b80a))
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **animations:** do not create a closure each time a node is removed ([fe6b39d](https://github.com/angular/angular/commit/fe6b39d))
|
||||||
|
* **animations:** only apply `:leave` flags if animations are set to run ([b55adee](https://github.com/angular/angular/commit/b55adee))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="4.2.0-rc.2"></a>
|
||||||
|
# [4.2.0-rc.2](https://github.com/angular/angular/compare/4.2.0-rc.1...4.2.0-rc.2) (2017-06-01)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **animations:** always change to desired animation state even if no transition fires ([#17025](https://github.com/angular/angular/issues/17025)) ([665e707](https://github.com/angular/angular/commit/665e707)), closes [#16947](https://github.com/angular/angular/issues/16947)
|
||||||
|
* **animations:** do not retain deleted nodes during an non-removal animation ([#17153](https://github.com/angular/angular/issues/17153)) ([068133e](https://github.com/angular/angular/commit/068133e)), closes [#17086](https://github.com/angular/angular/issues/17086)
|
||||||
|
* **common:** always use 'other' case for locales with no plural rules ([#16990](https://github.com/angular/angular/issues/16990)) ([535d9da](https://github.com/angular/angular/commit/535d9da))
|
||||||
|
* **compiler:** enableLegacyTemplate should not be ignored ([#17051](https://github.com/angular/angular/issues/17051)) ([8ffa483](https://github.com/angular/angular/commit/8ffa483)), closes [#15555](https://github.com/angular/angular/issues/15555)
|
||||||
|
* **router:** make remove trailing slash consistent with URL params ([c20f60b](https://github.com/angular/angular/commit/c20f60b)), closes [#16069](https://github.com/angular/angular/issues/16069)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **compiler:** emit typescript nodes from an output ast ([#16823](https://github.com/angular/angular/issues/16823)) ([18bf772](https://github.com/angular/angular/commit/18bf772))
|
||||||
|
* **compiler-cli:** produce template diagnostics error messages ([#17125](https://github.com/angular/angular/issues/17125)) ([230255f](https://github.com/angular/angular/commit/230255f))
|
||||||
|
* **tsc-wrapped:** always convert shorthand imports ([#16898](https://github.com/angular/angular/issues/16898)) ([ea8a43d](https://github.com/angular/angular/commit/ea8a43d))
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **animations:** do not place enterId values on elements for querying purposes ([#17150](https://github.com/angular/angular/issues/17150)) ([ad6a57e](https://github.com/angular/angular/commit/ad6a57e))
|
||||||
|
|
||||||
<a name="4.2.0-rc.1"></a>
|
<a name="4.2.0-rc.1"></a>
|
||||||
# [4.2.0-rc.1](https://github.com/angular/angular/compare/4.2.0-rc.0...4.2.0-rc.1) (2017-05-26)
|
# [4.2.0-rc.1](https://github.com/angular/angular/compare/4.2.0-rc.0...4.2.0-rc.1) (2017-05-26)
|
||||||
|
|
||||||
@ -342,7 +413,9 @@ Note: 4.1.0-beta.0 release also contains all the changes present in the 4.0.1 re
|
|||||||
* **router:** should pass new data to Observable when query params change ([#15387](https://github.com/angular/angular/issues/15387)) ([08f2f08](https://github.com/angular/angular/commit/08f2f08)), closes [#15290](https://github.com/angular/angular/issues/15290)
|
* **router:** should pass new data to Observable when query params change ([#15387](https://github.com/angular/angular/issues/15387)) ([08f2f08](https://github.com/angular/angular/commit/08f2f08)), closes [#15290](https://github.com/angular/angular/issues/15290)
|
||||||
* prevent strictNullChecks support until [#15432](https://github.com/angular/angular/issues/15432) is fixed ([#15434](https://github.com/angular/angular/issues/15434)) ([b800a0c](https://github.com/angular/angular/commit/b800a0c))
|
* prevent strictNullChecks support until [#15432](https://github.com/angular/angular/issues/15432) is fixed ([#15434](https://github.com/angular/angular/issues/15434)) ([b800a0c](https://github.com/angular/angular/commit/b800a0c))
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
From 4.0.0 @angular/core uses a [`WeakMap`](https://github.com/angular/angular/commit/52b21275f4c2c26c46627f5648b41a33bb5c8283), a polyfill needs to be included for [browsers that do not support it natively](http://kangax.github.io/compat-table/es6/#test-WeakMap).
|
||||||
|
|
||||||
<a name="4.0.0-rc.6"></a>
|
<a name="4.0.0-rc.6"></a>
|
||||||
# [4.0.0-rc.6](https://github.com/angular/angular/compare/4.0.0-rc.5...4.0.0-rc.6) (2017-03-23)
|
# [4.0.0-rc.6](https://github.com/angular/angular/compare/4.0.0-rc.5...4.0.0-rc.6) (2017-03-23)
|
||||||
|
12
WORKSPACE
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
|
||||||
|
|
||||||
|
git_repository(
|
||||||
|
name = "io_bazel_rules_typescript",
|
||||||
|
remote = "https://github.com/bazelbuild/rules_typescript.git",
|
||||||
|
tag = "0.0.3",
|
||||||
|
)
|
||||||
|
|
||||||
|
load("@io_bazel_rules_typescript//:defs.bzl", "node_repositories", "yarn_install")
|
||||||
|
|
||||||
|
node_repositories()
|
||||||
|
yarn_install(package_json = "//:package.json")
|
4
aio/content/examples/.gitignore
vendored
@ -45,12 +45,16 @@ dist/
|
|||||||
# aot
|
# aot
|
||||||
**/*.ngfactory.ts
|
**/*.ngfactory.ts
|
||||||
**/*.ngsummary.json
|
**/*.ngsummary.json
|
||||||
|
**/*.ngsummary.ts
|
||||||
**/*.shim.ngstyle.ts
|
**/*.shim.ngstyle.ts
|
||||||
**/*.metadata.json
|
**/*.metadata.json
|
||||||
!aot/bs-config.json
|
!aot/bs-config.json
|
||||||
!aot/index.html
|
!aot/index.html
|
||||||
!rollup-config.js
|
!rollup-config.js
|
||||||
|
|
||||||
|
# i18n
|
||||||
|
!i18n/src/systemjs-text-plugin.js
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
!testing/src/browser-test-shim.js
|
!testing/src/browser-test-shim.js
|
||||||
!testing/karma*.js
|
!testing/karma*.js
|
||||||
|
1
aio/content/examples/animations/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
**/*.js
|
|
@ -44,7 +44,7 @@ import { Heroes } from './hero.service';
|
|||||||
animate('0.2s ease-in')
|
animate('0.2s ease-in')
|
||||||
]),
|
]),
|
||||||
transition('* => void', [
|
transition('* => void', [
|
||||||
animate('0.2s 10 ease-out', style({
|
animate('0.2s 0.1s ease-out', style({
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
transform: 'translateX(100%)'
|
transform: 'translateX(100%)'
|
||||||
}))
|
}))
|
||||||
|
7
aio/content/examples/aot-compiler/.gitignore
vendored
@ -1,7 +0,0 @@
|
|||||||
**/*.ngfactory.ts
|
|
||||||
**/*.ngsummary.json
|
|
||||||
**/*.shim.ngstyle.ts
|
|
||||||
**/*.metadata.json
|
|
||||||
dist
|
|
||||||
!app/tsconfig.json
|
|
||||||
!rollup-config.js
|
|
45
aio/content/examples/cli-quickstart/.gitignore
vendored
@ -1,45 +0,0 @@
|
|||||||
# 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
|
|
@ -1,116 +0,0 @@
|
|||||||
/* #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;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
**/*.js
|
|
1
aio/content/examples/deployment/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
!systemjs.config.server.js
|
|
6
aio/content/examples/i18n/.gitignore
vendored
@ -1,6 +0,0 @@
|
|||||||
**/*.ngfactory.ts
|
|
||||||
**/*.metadata.json
|
|
||||||
dist
|
|
||||||
!app/tsconfig.json
|
|
||||||
!rollup.js
|
|
||||||
!src/systemjs-text-plugin.js
|
|
47
aio/content/examples/router/src/app.css
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/* 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;
|
||||||
|
}
|
@ -10,6 +10,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<link rel="stylesheet" href="app.css">
|
||||||
|
|
||||||
<!-- Polyfills -->
|
<!-- Polyfills -->
|
||||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||||
|
2
aio/content/examples/styleguide/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
*.js
|
|
||||||
!systemjs.custom.js
|
|
@ -803,6 +803,15 @@ The null hero's name is {{nullHero && nullHero.name}}
|
|||||||
<!-- #enddocregion safe-6 -->
|
<!-- #enddocregion safe-6 -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<!-- #docregion non-null-assertion-1 -->
|
||||||
|
<!--No hero, no text -->
|
||||||
|
<div *ngIf="hero">
|
||||||
|
The hero's name is {{hero!.name}}
|
||||||
|
</div>
|
||||||
|
<!-- #enddocregion non-null-assertion-1 -->
|
||||||
|
</div>
|
||||||
|
|
||||||
<a class="to-toc" href="#toc">top</a>
|
<a class="to-toc" href="#toc">top</a>
|
||||||
|
|
||||||
<!-- TODO: discuss this in the Style binding section -->
|
<!-- TODO: discuss this in the Style binding section -->
|
||||||
|
1
aio/content/examples/testing/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
!src/browser-test-shim.js
|
|
22
aio/content/examples/toh-pt5/src/styles.1.css
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/* 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;
|
||||||
|
}
|
||||||
|
body, input[text], button {
|
||||||
|
color: #888;
|
||||||
|
font-family: Cambria, Georgia;
|
||||||
|
}
|
||||||
|
/* everywhere else */
|
||||||
|
* {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
8
aio/content/examples/universal/.gitignore
vendored
@ -1,8 +0,0 @@
|
|||||||
aot/**/*.ts
|
|
||||||
**/*.ngfactory.ts
|
|
||||||
**/*.ngsummary.json
|
|
||||||
**/*.metadata.json
|
|
||||||
**/*.js
|
|
||||||
dist
|
|
||||||
!app/tsconfig.json
|
|
||||||
!/*.js
|
|
@ -1 +0,0 @@
|
|||||||
!karma.conf.ajs.js
|
|
@ -1,8 +0,0 @@
|
|||||||
aot/**/*
|
|
||||||
!aot/index.html
|
|
||||||
dist
|
|
||||||
!app/tsconfig.json
|
|
||||||
!rollup-config.js
|
|
||||||
!karma.conf.ajs.js
|
|
||||||
!copy-dist-files.js
|
|
||||||
!systemjs.config.1.js
|
|
@ -1,11 +1,6 @@
|
|||||||
// #docregion bootstrap
|
// #docregion bootstrap
|
||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
import { UpgradeModule } from '@angular/upgrade/static';
|
|
||||||
|
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
|
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||||
const upgrade = platformRef.injector.get(UpgradeModule) as UpgradeModule;
|
|
||||||
upgrade.bootstrap(document.documentElement, ['phonecatApp']);
|
|
||||||
});
|
|
||||||
// #enddocregion bootstrap
|
// #enddocregion bootstrap
|
||||||
|
@ -7,12 +7,6 @@ import { browser, element, by } from 'protractor';
|
|||||||
|
|
||||||
describe('PhoneCat Application', function() {
|
describe('PhoneCat Application', function() {
|
||||||
|
|
||||||
beforeAll(function () {
|
|
||||||
// Set protractor to hybrid mode.
|
|
||||||
browser.rootEl = 'body';
|
|
||||||
browser.ng12Hybrid = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should redirect `index.html` to `index.html#!/phones', function() {
|
it('should redirect `index.html` to `index.html#!/phones', function() {
|
||||||
browser.get('index.html');
|
browser.get('index.html');
|
||||||
expect(browser.getLocationAbsUrl()).toBe('/phones');
|
expect(browser.getLocationAbsUrl()).toBe('/phones');
|
||||||
@ -70,7 +64,7 @@ describe('PhoneCat Application', function() {
|
|||||||
query.sendKeys('nexus');
|
query.sendKeys('nexus');
|
||||||
|
|
||||||
element.all(by.css('.phones li a')).first().click();
|
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.
|
browser.sleep(1000); // Not sure why this is needed but it is. The route change works fine.
|
||||||
expect(browser.getLocationAbsUrl()).toBe('/phones/nexus-s');
|
expect(browser.getLocationAbsUrl()).toBe('/phones/nexus-s');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
!tsconfig.json
|
|
5
aio/content/examples/webpack/.gitignore
vendored
@ -1,5 +0,0 @@
|
|||||||
dist
|
|
||||||
!karma.webpack.conf.js
|
|
||||||
!webpack.config.js
|
|
||||||
!config/*
|
|
||||||
!public/css/styles.css
|
|
@ -9,14 +9,14 @@
|
|||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/common": "~4.0.0",
|
"@angular/common": "~4.2.0",
|
||||||
"@angular/compiler": "~4.0.0",
|
"@angular/compiler": "~4.2.0",
|
||||||
"@angular/core": "~4.0.0",
|
"@angular/core": "~4.2.0",
|
||||||
"@angular/forms": "~4.0.0",
|
"@angular/forms": "~4.2.0",
|
||||||
"@angular/http": "~4.0.0",
|
"@angular/http": "~4.2.0",
|
||||||
"@angular/platform-browser": "~4.0.0",
|
"@angular/platform-browser": "~4.2.0",
|
||||||
"@angular/platform-browser-dynamic": "~4.0.0",
|
"@angular/platform-browser-dynamic": "~4.2.0",
|
||||||
"@angular/router": "~4.0.0",
|
"@angular/router": "~4.2.0",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
"rxjs": "5.0.1",
|
"rxjs": "5.0.1",
|
||||||
"zone.js": "^0.8.4"
|
"zone.js": "^0.8.4"
|
||||||
|
@ -38,7 +38,7 @@ This page introduces modules; the [Angular modules](guide/ngmodule) page covers
|
|||||||
|
|
||||||
<br class="clear">
|
<br class="clear">
|
||||||
|
|
||||||
Every Angular app has at least one Angular module class, [the _root module_](guide/appmodule "AppModule: the root module"),
|
Every Angular app has at least one Angular module class, [the _root module_](guide/bootstrapping "AppModule: the root module"),
|
||||||
conventionally named `AppModule`.
|
conventionally named `AppModule`.
|
||||||
|
|
||||||
While the _root module_ may be the only module in a small application, most apps have many more
|
While the _root module_ may be the only module in a small application, most apps have many more
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# AppModule: the root module
|
# Bootstrapping
|
||||||
|
|
||||||
An Angular module class describes how the application parts fit together.
|
An Angular module class describes how the application parts fit together.
|
||||||
Every application has at least one Angular module, the _root_ module
|
Every application has at least one Angular module, the _root_ module
|
||||||
that you [bootstrap](guide/appmodule#main) to launch the application.
|
that you [bootstrap](guide/bootstrapping#main) to launch the application.
|
||||||
You can call it anything you want. The conventional name is `AppModule`.
|
You can call it anything you want. The conventional name is `AppModule`.
|
||||||
|
|
||||||
The [setup](guide/setup) instructions produce a new project with the following minimal `AppModule`.
|
The [setup](guide/setup) instructions produce a new project with the following minimal `AppModule`.
|
||||||
@ -10,7 +10,6 @@ You'll evolve this module as your application grows.
|
|||||||
|
|
||||||
|
|
||||||
<code-example path="setup/src/app/app.module.ts" title="src/app/app.module.ts" linenums="false">
|
<code-example path="setup/src/app/app.module.ts" title="src/app/app.module.ts" linenums="false">
|
||||||
|
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
@ -111,7 +110,7 @@ Do not put any other kind of class in `declarations`; _not_ `NgModule` classes,
|
|||||||
|
|
||||||
### The _bootstrap_ array
|
### The _bootstrap_ array
|
||||||
|
|
||||||
You launch the application by [_bootstrapping_](guide/appmodule#main) the root `AppModule`.
|
You launch the application by [_bootstrapping_](guide/bootstrapping#main) the root `AppModule`.
|
||||||
Among other things, the _bootstrapping_ process creates the component(s) listed in the `bootstrap` array
|
Among other things, the _bootstrapping_ process creates the component(s) listed in the `bootstrap` array
|
||||||
and inserts each one into the browser DOM.
|
and inserts each one into the browser DOM.
|
||||||
|
|
@ -68,7 +68,7 @@ Angular supports most recent browsers. This includes the following specific vers
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
Marshmallow (6.0)
|
Nougat (7.0)<br>Marshmallow (6.0)
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
|
@ -1,617 +0,0 @@
|
|||||||
# CLI QuickStart
|
|
||||||
|
|
||||||
Good tools make application development quicker and easier to maintain than
|
|
||||||
if you did everything by hand.
|
|
||||||
|
|
||||||
The [**Angular CLI**](https://cli.angular.io/) is a **_command line interface_** tool
|
|
||||||
that can create a project, add files, and perform a variety of ongoing development tasks such
|
|
||||||
as testing, bundling, and deployment.
|
|
||||||
|
|
||||||
The goal in this guide is to build and run a simple Angular
|
|
||||||
application in TypeScript, using the Angular CLI
|
|
||||||
while adhering to the [Style Guide](guide/styleguide) recommendations that
|
|
||||||
benefit _every_ Angular project.
|
|
||||||
|
|
||||||
By the end of the chapter, you'll have a basic understanding of development with the CLI
|
|
||||||
and a foundation for both these documentation samples and for real world applications.
|
|
||||||
|
|
||||||
<!--
|
|
||||||
|
|
||||||
You'll pursue these ends in the following high-level steps:
|
|
||||||
|
|
||||||
1. [Set up](guide/cli-quickstart#devenv) the development environment.
|
|
||||||
2. [Create](guide/cli-quickstart#create-proj) a new project and skeleton application.
|
|
||||||
3. [Serve](guide/cli-quickstart#serve) the application.
|
|
||||||
4. [Edit](guide/cli-quickstart#first-component) the application.
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
And you can also <a href="generated/zips/cli-quickstart/cli-quickstart.zip" target="_blank">download the example.</a>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h2 id='devenv'>
|
|
||||||
Step 1. Set up the Development Environment
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
You need to set up your development environment before you can do anything.
|
|
||||||
|
|
||||||
Install **[Node.js® and npm](https://nodejs.org/en/download/)**
|
|
||||||
if they are not already on your machine.
|
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Verify that you are running at least node `6.9.x` and npm `3.x.x`**
|
|
||||||
by running `node -v` and `npm -v` in a terminal/console window.
|
|
||||||
Older versions produce errors, but newer versions are fine.
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Then **install the [Angular CLI](https://github.com/angular/angular-cli)** globally.
|
|
||||||
|
|
||||||
|
|
||||||
<code-example language="sh" class="code-shell">
|
|
||||||
npm install -g @angular/cli
|
|
||||||
|
|
||||||
</code-example>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h2 id='create-proj'>
|
|
||||||
Step 2. Create a new project
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Open a terminal window.
|
|
||||||
|
|
||||||
|
|
||||||
Generate a new project and skeleton application by running the following commands:
|
|
||||||
|
|
||||||
|
|
||||||
<code-example language="sh" class="code-shell">
|
|
||||||
ng new my-app
|
|
||||||
|
|
||||||
</code-example>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Patience please.
|
|
||||||
It takes time to set up a new project, most of it spent installing npm packages.
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h2 id='serve'>
|
|
||||||
Step 3: Serve the application
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Go to the project directory and launch the server.
|
|
||||||
|
|
||||||
|
|
||||||
<code-example language="sh" class="code-shell">
|
|
||||||
cd my-app
|
|
||||||
ng serve --open
|
|
||||||
|
|
||||||
</code-example>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The `ng serve` command launches the server, watches your files,
|
|
||||||
and rebuilds the app as you make changes to those files.
|
|
||||||
|
|
||||||
Using the `--open` (or just `-o`) option will automatically open your browser
|
|
||||||
on `http://localhost:4200/`.
|
|
||||||
|
|
||||||
Your app greets you with a message:
|
|
||||||
|
|
||||||
|
|
||||||
<figure>
|
|
||||||
<img src='generated/images/guide/cli-quickstart/app-works.png' alt="The app works!">
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h2 id='first-component'>
|
|
||||||
Step 4: Edit your first Angular component
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The CLI created the first Angular component for you.
|
|
||||||
This is the _root component_ and it is named `app-root`.
|
|
||||||
You can find it in `./src/app/app.component.ts`.
|
|
||||||
|
|
||||||
|
|
||||||
Open the component file and change the `title` property from _app works!_ to _My First Angular App_:
|
|
||||||
|
|
||||||
|
|
||||||
<code-example path="cli-quickstart/src/app/app.component.ts" region="title" title="src/app/app.component.ts" linenums="false"></code-example>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The browser reloads automatically with the revised title. That's nice, but it could look better.
|
|
||||||
|
|
||||||
Open `src/app/app.component.css` and give the component some style.
|
|
||||||
|
|
||||||
|
|
||||||
<code-example path="cli-quickstart/src/app/app.component.css" title="src/app/app.component.css" linenums="false"></code-example>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<figure>
|
|
||||||
<img src='generated/images/guide/cli-quickstart/my-first-app.png' alt="Output of QuickStart app">
|
|
||||||
</figure>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Looking good!
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## What's next?
|
|
||||||
That's about all you'd expect to do in a "Hello, World" app.
|
|
||||||
|
|
||||||
You're ready to take the [Tour of Heroes Tutorial](tutorial) and build
|
|
||||||
a small application that demonstrates the great things you can build with Angular.
|
|
||||||
|
|
||||||
Or you can stick around a bit longer to learn about the files in your brand new project.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Project file review
|
|
||||||
|
|
||||||
An Angular CLI project is the foundation for both quick experiments and enterprise solutions.
|
|
||||||
|
|
||||||
The first file you should check out is `README.md`.
|
|
||||||
It has some basic information on how to use CLI commands.
|
|
||||||
Whenever you want to know more about how Angular CLI works make sure to visit
|
|
||||||
[the Angular CLI repository](https://github.com/angular/angular-cli) and
|
|
||||||
[Wiki](https://github.com/angular/angular-cli/wiki).
|
|
||||||
|
|
||||||
Some of the generated files might be unfamiliar to you.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### The `src` folder
|
|
||||||
Your app lives in the `src` folder.
|
|
||||||
All Angular components, templates, styles, images, and anything else your app needs go here.
|
|
||||||
Any files outside of this folder are meant to support building your app.
|
|
||||||
|
|
||||||
|
|
||||||
<div class='filetree'>
|
|
||||||
<div class='file'>src</div>
|
|
||||||
<div class='children'>
|
|
||||||
<div class='file'>app</div>
|
|
||||||
<div class='children'>
|
|
||||||
<div class='file'>app.component.css</div>
|
|
||||||
<div class='file'>app.component.html</div>
|
|
||||||
<div class="file">app.component.spec.ts</div>
|
|
||||||
<div class="file">app.component.ts</div>
|
|
||||||
<div class="file">app.module.ts</div>
|
|
||||||
</div>
|
|
||||||
<div class="file">assets</div>
|
|
||||||
<div class='children'>
|
|
||||||
<div class="file">.gitkeep</div>
|
|
||||||
</div>
|
|
||||||
<div class="file">environments</div>
|
|
||||||
<div class='children'>
|
|
||||||
<div class="file">environment.prod.ts</div>
|
|
||||||
<div class="file">environment.ts</div>
|
|
||||||
</div>
|
|
||||||
<div class="file">favicon.ico</div>
|
|
||||||
<div class="file">index.html</div>
|
|
||||||
<div class="file">main.ts</div>
|
|
||||||
<div class="file">polyfills.ts</div>
|
|
||||||
<div class="file">styles.css</div>
|
|
||||||
<div class="file">test.ts</div>
|
|
||||||
<div class="file">tsconfig.app.json</div>
|
|
||||||
<div class="file">tsconfig.spec.json</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<style>
|
|
||||||
td, th {vertical-align: top}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<table width="100%">
|
|
||||||
<col width="20%">
|
|
||||||
</col>
|
|
||||||
<col width="80%">
|
|
||||||
</col>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
File
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
Purpose
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`app/app.component.{ts,html,css,spec.ts}`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
Defines the `AppComponent` along with an HTML template, CSS stylesheet, and a unit test.
|
|
||||||
It is the **root** component of what will become a tree of nested components
|
|
||||||
as the application evolves.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`app/app.module.ts`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
Defines `AppModule`, the [root module](guide/appmodule "AppModule: the root module") that tells Angular how to assemble the application.
|
|
||||||
Right now it declares only the `AppComponent`.
|
|
||||||
Soon there will be more components to declare.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`assets/*`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
A folder where you can put images and anything else to be copied wholesale
|
|
||||||
when you build your application.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`environments/*`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
This folder contains one file for each of your destination environments,
|
|
||||||
each exporting simple configuration variables to use in your application.
|
|
||||||
The files are replaced on-the-fly when you build your app.
|
|
||||||
You might use a different API endpoint for development than you do for production
|
|
||||||
or maybe different analytics tokens.
|
|
||||||
You might even use some mock services.
|
|
||||||
Either way, the CLI has you covered.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`favicon.ico`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
Every site wants to look good on the bookmark bar.
|
|
||||||
Get started with your very own Angular icon.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`index.html`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
The main HTML page that is served when someone visits your site.
|
|
||||||
Most of the time you'll never need to edit it.
|
|
||||||
The CLI automatically adds all `js` and `css` files when building your app so you
|
|
||||||
never need to add any `<script>` or `<link>` tags here manually.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`main.ts`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
The main entry point for your app.
|
|
||||||
Compiles the application with the [JIT compiler](guide/glossary#jit)
|
|
||||||
and bootstraps the application's root module (`AppModule`) to run in the browser.
|
|
||||||
You can also use the [AOT compiler](guide/glossary#ahead-of-time-aot-compilation)
|
|
||||||
without changing any code by passing in `--aot` to `ng build` or `ng serve`.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`polyfills.ts`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
Different browsers have different levels of support of the web standards.
|
|
||||||
Polyfills help normalize those differences.
|
|
||||||
You should be pretty safe with `core-js` and `zone.js`, but be sure to check out
|
|
||||||
the [Browser Support guide](guide/browser-support) for more information.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`styles.css`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
Your global styles go here.
|
|
||||||
Most of the time you'll want to have local styles in your components for easier maintenance,
|
|
||||||
but styles that affect all of your app need to be in a central place.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`test.ts`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
This is the main entry point for your unit tests.
|
|
||||||
It has some custom configuration that might be unfamiliar, but it's not something you'll
|
|
||||||
need to edit.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`tsconfig.{app|spec}.json`
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
TypeScript compiler configuration for the Angular app (`tsconfig.app.json`)
|
|
||||||
and for the unit tests (`tsconfig.spec.json`).
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
### The root folder
|
|
||||||
|
|
||||||
The `src/` folder is just one of the items inside the project's root folder.
|
|
||||||
Other files help you build, test, maintain, document, and deploy the app.
|
|
||||||
These files go in the root folder next to `src/`.
|
|
||||||
|
|
||||||
|
|
||||||
<div class='filetree'>
|
|
||||||
<div class="file">my-app</div>
|
|
||||||
<div class='children'>
|
|
||||||
<div class="file">e2e</div>
|
|
||||||
<div class='children'>
|
|
||||||
<div class="file">app.e2e-spec.ts</div>
|
|
||||||
<div class="file">app.po.ts</div>
|
|
||||||
<div class="file">tsconfig.e2e.json</div>
|
|
||||||
</div>
|
|
||||||
<div class="file">node_modules/...</div>
|
|
||||||
<div class="file">src/...</div>
|
|
||||||
<div class="file">.angular-cli.json</div>
|
|
||||||
<div class="file">.editorconfig</div>
|
|
||||||
<div class="file">.gitignore</div>
|
|
||||||
<div class="file">karma.conf.js</div>
|
|
||||||
<div class="file">package.json</div>
|
|
||||||
<div class="file">protractor.conf.js</div>
|
|
||||||
<div class="file">README.md</div>
|
|
||||||
<div class="file">tsconfig.json</div>
|
|
||||||
<div class="file">tslint.json</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
td, th {vertical-align: top}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<table width="100%">
|
|
||||||
<col width="20%">
|
|
||||||
</col>
|
|
||||||
<col width="80%">
|
|
||||||
</col>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
File
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
Purpose
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`e2e/`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
Inside `e2e/` live the End-to-End tests.
|
|
||||||
They shouldn't be inside `src/` because e2e tests are really a separate app that
|
|
||||||
just so happens to test your main app.
|
|
||||||
That's also why they have their own `tsconfig.e2e.json`.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`node_modules/`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`Node.js` creates this folder and puts all third party modules listed in
|
|
||||||
`package.json` inside of it.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`.angular-cli.json`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
Configuration for Angular CLI.
|
|
||||||
In this file you can set several defaults and also configure what files are included
|
|
||||||
when your project is build.
|
|
||||||
Check out the official documentation if you want to know more.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`.editorconfig`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
Simple configuration for your editor to make sure everyone that uses your project
|
|
||||||
has the same basic configuration.
|
|
||||||
Most editors support an `.editorconfig` file.
|
|
||||||
See http://editorconfig.org for more information.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`.gitignore`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
Git configuration to make sure autogenerated files are not commited to source control.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`karma.conf.js`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
Unit test configuration for the [Karma test runner](https://karma-runner.github.io),
|
|
||||||
used when running `ng test`.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`package.json`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`npm` configuration listing the third party packages your project uses.
|
|
||||||
You can also add your own [custom scripts](https://docs.npmjs.com/misc/scripts) here.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`protractor.conf.js`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
End-to-end test configuration for [Protractor](http://www.protractortest.org/),
|
|
||||||
used when running `ng e2e`.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`README.md`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
Basic documentation for your project, pre-filled with CLI command information.
|
|
||||||
Make sure to enhance it with project documentation so that anyone
|
|
||||||
checking out the repo can build your app!
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`tsconfig.json`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
TypeScript compiler configuration for your IDE to pick up and give you helpful tooling.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`tslint.json`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
Linting configuration for [TSLint](https://palantir.github.io/tslint/) together with
|
|
||||||
[Codelyzer](http://codelyzer.com/), used when running `ng lint`.
|
|
||||||
Linting helps keep your code style consistent.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
### Next Step
|
|
||||||
|
|
||||||
If you're new to Angular, continue with the
|
|
||||||
[tutorial](tutorial "Tour of Heroes tutorial").
|
|
||||||
You can skip the "Setup" step since you're already using the Angular CLI setup.
|
|
||||||
|
|
||||||
</div>
|
|
@ -19,26 +19,14 @@ unexpected definitions.
|
|||||||
|
|
||||||
## Ahead-of-time (AOT) compilation
|
## Ahead-of-time (AOT) compilation
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
You can compile Angular applications at build time.
|
You can compile Angular applications at build time.
|
||||||
By compiling your application using the compiler-cli, `ngc`, you can bootstrap directly
|
By compiling your application using the compiler-cli, `ngc`, you can bootstrap directly
|
||||||
to a module factory, meaning you don't need to include the Angular compiler in your JavaScript bundle.
|
to a module factory, meaning you don't need to include the Angular compiler in your JavaScript bundle.
|
||||||
Ahead-of-time compiled applications also benefit from decreased load time and increased performance.
|
Ahead-of-time compiled applications also benefit from decreased load time and increased performance.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Angular module
|
## Angular module
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Helps you organize an application into cohesive blocks of functionality.
|
Helps you organize an application into cohesive blocks of functionality.
|
||||||
An Angular module identifies the components, directives, and pipes that the application uses along with the list of external Angular modules that the application needs, such as `FormsModule`.
|
An Angular module identifies the components, directives, and pipes that the application uses along with the list of external Angular modules that the application needs, such as `FormsModule`.
|
||||||
|
|
||||||
@ -48,23 +36,11 @@ called `AppModule` and resides in a file named `app.module.ts`.
|
|||||||
For details and examples, see the [Angular Modules (NgModule)](guide/ngmodule) page.
|
For details and examples, see the [Angular Modules (NgModule)](guide/ngmodule) page.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Annotation
|
## Annotation
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
In practice, a synonym for [Decoration](guide/glossary#decorator).
|
In practice, a synonym for [Decoration](guide/glossary#decorator).
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{@a attribute-directive}
|
{@a attribute-directive}
|
||||||
|
|
||||||
|
|
||||||
@ -73,10 +49,6 @@ In practice, a synonym for [Decoration](guide/glossary#decorator).
|
|||||||
|
|
||||||
## Attribute directives
|
## Attribute directives
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A category of [directive](guide/glossary#directive) that can listen to and modify the behavior of
|
A category of [directive](guide/glossary#directive) that can listen to and modify the behavior of
|
||||||
other HTML elements, attributes, properties, and components. They are usually represented
|
other HTML elements, attributes, properties, and components. They are usually represented
|
||||||
as HTML attributes, hence the name.
|
as HTML attributes, hence the name.
|
||||||
@ -86,18 +58,10 @@ For example, you can use the `ngClass` directive to add and remove CSS class nam
|
|||||||
Learn about them in the [_Attribute Directives_](guide/attribute-directives) guide.
|
Learn about them in the [_Attribute Directives_](guide/attribute-directives) guide.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{@a B}
|
{@a B}
|
||||||
|
|
||||||
## Barrel
|
## Barrel
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A way to *roll up exports* from several ES2015 modules into a single convenient ES2015 module.
|
A way to *roll up exports* from several ES2015 modules into a single convenient ES2015 module.
|
||||||
The barrel itself is an ES2015 module file that re-exports *selected* exports of other ES2015 modules.
|
The barrel itself is an ES2015 module file that re-exports *selected* exports of other ES2015 modules.
|
||||||
|
|
||||||
@ -157,17 +121,8 @@ You can often achieve the same result using [Angular modules](guide/glossary#ang
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Binding
|
## Binding
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Usually refers to [data binding](guide/glossary#data-binding) and the act of
|
Usually refers to [data binding](guide/glossary#data-binding) and the act of
|
||||||
binding an HTML object property to a data object property.
|
binding an HTML object property to a data object property.
|
||||||
|
|
||||||
@ -175,16 +130,8 @@ Sometimes refers to a [dependency-injection](guide/glossary#dependency-injection
|
|||||||
between a "token"—also referred to as a "key"—and a dependency [provider](guide/glossary#provider).
|
between a "token"—also referred to as a "key"—and a dependency [provider](guide/glossary#provider).
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Bootstrap
|
## Bootstrap
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
You launch an Angular application by "bootstrapping" it using the application root Angular module (`AppModule`).
|
You launch an Angular application by "bootstrapping" it using the application root Angular module (`AppModule`).
|
||||||
Bootstrapping identifies an application's top level "root" [component](guide/glossary#component),
|
Bootstrapping identifies an application's top level "root" [component](guide/glossary#component),
|
||||||
which is the first component that is loaded for the application.
|
which is the first component that is loaded for the application.
|
||||||
@ -193,17 +140,10 @@ For more information, see the [Setup](guide/setup) page.
|
|||||||
You can bootstrap multiple apps in the same `index.html`, each app with its own top-level root.
|
You can bootstrap multiple apps in the same `index.html`, each app with its own top-level root.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{@a C}
|
{@a C}
|
||||||
|
|
||||||
## camelCase
|
## camelCase
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The practice of writing compound words or phrases such that each word or abbreviation begins with a capital letter
|
The practice of writing compound words or phrases such that each word or abbreviation begins with a capital letter
|
||||||
_except the first letter, which is lowercase_.
|
_except the first letter, which is lowercase_.
|
||||||
|
|
||||||
@ -213,19 +153,11 @@ camelCase is also known as *lower camel case* to distinguish it from *upper came
|
|||||||
In Angular documentation, "camelCase" always means *lower camel case*.
|
In Angular documentation, "camelCase" always means *lower camel case*.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{@a component}
|
{@a component}
|
||||||
|
|
||||||
|
|
||||||
## Component
|
## Component
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
An Angular class responsible for exposing data to a [view](guide/glossary#view) and handling most of the view’s display and user-interaction logic.
|
An Angular class responsible for exposing data to a [view](guide/glossary#view) and handling most of the view’s display and user-interaction logic.
|
||||||
|
|
||||||
The *component* is one of the most important building blocks in the Angular system.
|
The *component* is one of the most important building blocks in the Angular system.
|
||||||
@ -240,17 +172,10 @@ Those familiar with "MVC" and "MVVM" patterns will recognize
|
|||||||
the component in the role of "controller" or "view model".
|
the component in the role of "controller" or "view model".
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{@a D}
|
{@a D}
|
||||||
|
|
||||||
## dash-case
|
## dash-case
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The practice of writing compound words or phrases such that each word is separated by a dash or hyphen (`-`).
|
The practice of writing compound words or phrases such that each word is separated by a dash or hyphen (`-`).
|
||||||
This form is also known as kebab-case.
|
This form is also known as kebab-case.
|
||||||
|
|
||||||
@ -259,16 +184,8 @@ the root of filenames (such as `hero-list.component.ts`) are often
|
|||||||
spelled in dash-case.
|
spelled in dash-case.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Data binding
|
## Data binding
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Applications display data values to a user and respond to user
|
Applications display data values to a user and respond to user
|
||||||
actions (such as clicks, touches, and keystrokes).
|
actions (such as clicks, touches, and keystrokes).
|
||||||
|
|
||||||
@ -292,10 +209,6 @@ operations and supporting declaration syntax.
|
|||||||
* [Two-way data binding with ngModel](guide/template-syntax#ngModel).
|
* [Two-way data binding with ngModel](guide/template-syntax#ngModel).
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{@a decorator}
|
{@a decorator}
|
||||||
|
|
||||||
|
|
||||||
@ -304,10 +217,6 @@ operations and supporting declaration syntax.
|
|||||||
|
|
||||||
## Decorator | decoration
|
## Decorator | decoration
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A *function* that adds metadata to a class, its members (properties, methods) and function arguments.
|
A *function* that adds metadata to a class, its members (properties, methods) and function arguments.
|
||||||
|
|
||||||
Decorators are an experimental (stage 2), JavaScript language [feature](https://github.com/wycats/javascript-decorators). TypeScript adds support for decorators.
|
Decorators are an experimental (stage 2), JavaScript language [feature](https://github.com/wycats/javascript-decorators). TypeScript adds support for decorators.
|
||||||
@ -340,17 +249,8 @@ Always include parentheses `()` when applying a decorator.
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Dependency injection
|
## Dependency injection
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A design pattern and mechanism
|
A design pattern and mechanism
|
||||||
for creating and delivering parts of an application to other
|
for creating and delivering parts of an application to other
|
||||||
parts of an application that request them.
|
parts of an application that request them.
|
||||||
@ -401,10 +301,6 @@ You can register your own providers.
|
|||||||
Read more in the [Dependency Injection](guide/dependency-injection) page.
|
Read more in the [Dependency Injection](guide/dependency-injection) page.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{@a directive}
|
{@a directive}
|
||||||
|
|
||||||
|
|
||||||
@ -413,10 +309,6 @@ Read more in the [Dependency Injection](guide/dependency-injection) page.
|
|||||||
|
|
||||||
## Directive
|
## Directive
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
An Angular class responsible for creating, reshaping, and interacting with HTML elements
|
An Angular class responsible for creating, reshaping, and interacting with HTML elements
|
||||||
in the browser DOM. The directive is Angular's most fundamental feature.
|
in the browser DOM. The directive is Angular's most fundamental feature.
|
||||||
|
|
||||||
@ -447,17 +339,10 @@ shaping or reshaping HTML layout, typically by adding, removing, or manipulating
|
|||||||
elements and their children.
|
elements and their children.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{@a E}
|
{@a E}
|
||||||
|
|
||||||
## ECMAScript
|
## ECMAScript
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The [official JavaScript language specification](https://en.wikipedia.org/wiki/ECMAScript).
|
The [official JavaScript language specification](https://en.wikipedia.org/wiki/ECMAScript).
|
||||||
|
|
||||||
The latest approved version of JavaScript is
|
The latest approved version of JavaScript is
|
||||||
@ -473,47 +358,21 @@ to ES5 JavaScript.
|
|||||||
Angular developers can write in ES5 directly.
|
Angular developers can write in ES5 directly.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## ES2015
|
## ES2015
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Short hand for [ECMAScript](guide/glossary#ecmascript) 2015.
|
Short hand for [ECMAScript](guide/glossary#ecmascript) 2015.
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## ES5
|
## ES5
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Short hand for [ECMAScript](guide/glossary#ecmascript) 5, the version of JavaScript run by most modern browsers.
|
Short hand for [ECMAScript](guide/glossary#ecmascript) 5, the version of JavaScript run by most modern browsers.
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## ES6
|
## ES6
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Short hand for [ECMAScript](guide/glossary#ecmascript) 2015.
|
Short hand for [ECMAScript](guide/glossary#ecmascript) 2015.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{@a F}
|
{@a F}
|
||||||
|
|
||||||
|
|
||||||
@ -526,25 +385,13 @@ Short hand for [ECMAScript](guide/glossary#ecmascript) 2015.
|
|||||||
|
|
||||||
## Injector
|
## Injector
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
An object in the Angular [dependency-injection system](guide/glossary#dependency-injection)
|
An object in the Angular [dependency-injection system](guide/glossary#dependency-injection)
|
||||||
that can find a named dependency in its cache or create a dependency
|
that can find a named dependency in its cache or create a dependency
|
||||||
with a registered [provider](guide/glossary#provider).
|
with a registered [provider](guide/glossary#provider).
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Input
|
## Input
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A directive property that can be the *target* of a
|
A directive property that can be the *target* of a
|
||||||
[property binding](guide/template-syntax#property-binding) (explained in detail in the [Template Syntax](guide/template-syntax) page).
|
[property binding](guide/template-syntax#property-binding) (explained in detail in the [Template Syntax](guide/template-syntax) page).
|
||||||
Data values flow *into* this property from the data source identified
|
Data values flow *into* this property from the data source identified
|
||||||
@ -553,16 +400,8 @@ in the template expression to the right of the equal sign.
|
|||||||
See the [Input and output properties](guide/template-syntax#inputs-outputs) section of the [Template Syntax](guide/template-syntax) page.
|
See the [Input and output properties](guide/template-syntax#inputs-outputs) section of the [Template Syntax](guide/template-syntax) page.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Interpolation
|
## Interpolation
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A form of [property data binding](guide/glossary#data-binding) in which a
|
A form of [property data binding](guide/glossary#data-binding) in which a
|
||||||
[template expression](guide/glossary#template-expression) between double-curly braces
|
[template expression](guide/glossary#template-expression) between double-curly braces
|
||||||
renders as text. That text may be concatenated with neighboring text
|
renders as text. That text may be concatenated with neighboring text
|
||||||
@ -581,9 +420,6 @@ Read more about [interpolation](guide/template-syntax#interpolation) in the
|
|||||||
[Template Syntax](guide/template-syntax) page.
|
[Template Syntax](guide/template-syntax) page.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{@a J}
|
{@a J}
|
||||||
|
|
||||||
{@a jit}
|
{@a jit}
|
||||||
@ -591,40 +427,22 @@ Read more about [interpolation](guide/template-syntax#interpolation) in the
|
|||||||
|
|
||||||
## Just-in-time (JIT) compilation
|
## Just-in-time (JIT) compilation
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A bootstrapping method of compiling components and modules in the browser
|
A bootstrapping method of compiling components and modules in the browser
|
||||||
and launching the application dynamically. Just-in-time mode is a good choice during development.
|
and launching the application dynamically. Just-in-time mode is a good choice during development.
|
||||||
Consider using the [ahead-of-time](guide/glossary#aot) mode for production apps.
|
Consider using the [ahead-of-time](guide/glossary#aot) mode for production apps.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{@a K}
|
{@a K}
|
||||||
|
|
||||||
## kebab-case
|
## kebab-case
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
See [dash-case](guide/glossary#dash-case).
|
See [dash-case](guide/glossary#dash-case).
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{@a L}
|
{@a L}
|
||||||
|
|
||||||
## Lifecycle hooks
|
## Lifecycle hooks
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[Directives](guide/glossary#directive) and [components](guide/glossary#component) have a lifecycle
|
[Directives](guide/glossary#directive) and [components](guide/glossary#component) have a lifecycle
|
||||||
managed by Angular as it creates, updates, and destroys them.
|
managed by Angular as it creates, updates, and destroys them.
|
||||||
|
|
||||||
@ -648,21 +466,13 @@ Angular calls these hook methods in the following order:
|
|||||||
Read more in the [Lifecycle Hooks](guide/lifecycle-hooks) page.
|
Read more in the [Lifecycle Hooks](guide/lifecycle-hooks) page.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{@a M}
|
{@a M}
|
||||||
|
|
||||||
## Module
|
## Module
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="alert is-important">
|
<div class="alert is-important">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Angular has the following types of modules:
|
Angular has the following types of modules:
|
||||||
|
|
||||||
* [Angular modules](guide/glossary#angular-module).
|
* [Angular modules](guide/glossary#angular-module).
|
||||||
@ -699,20 +509,12 @@ class belongs to a feature module named `date_pipe` in the file `date_pipe.ts`.
|
|||||||
You rarely access Angular feature modules directly. You usually import them from an Angular [scoped package](guide/glossary#scoped-package) such as `@angular/core`.
|
You rarely access Angular feature modules directly. You usually import them from an Angular [scoped package](guide/glossary#scoped-package) such as `@angular/core`.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{@a N}
|
{@a N}
|
||||||
|
|
||||||
{@a O}
|
{@a O}
|
||||||
|
|
||||||
## Observable
|
## Observable
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
An array whose items arrive asynchronously over time.
|
An array whose items arrive asynchronously over time.
|
||||||
Observables help you manage asynchronous data, such as data coming from a backend service.
|
Observables help you manage asynchronous data, such as data coming from a backend service.
|
||||||
Observables are used within Angular itself, including Angular's event system and its HTTP client service.
|
Observables are used within Angular itself, including Angular's event system and its HTTP client service.
|
||||||
@ -721,14 +523,8 @@ To use observables, Angular uses a third-party library called Reactive Extension
|
|||||||
Observables are a proposed feature for ES2016, the next version of JavaScript.
|
Observables are a proposed feature for ES2016, the next version of JavaScript.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
## Output
|
## Output
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A directive property that can be the *target* of event binding
|
A directive property that can be the *target* of event binding
|
||||||
(read more in the [event binding](guide/template-syntax#event-binding)
|
(read more in the [event binding](guide/template-syntax#event-binding)
|
||||||
section of the [Template Syntax](guide/template-syntax) page).
|
section of the [Template Syntax](guide/template-syntax) page).
|
||||||
@ -738,17 +534,10 @@ in the template expression to the right of the equal sign.
|
|||||||
See the [Input and output properties](guide/template-syntax#inputs-outputs) section of the [Template Syntax](guide/template-syntax) page.
|
See the [Input and output properties](guide/template-syntax#inputs-outputs) section of the [Template Syntax](guide/template-syntax) page.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{@a P}
|
{@a P}
|
||||||
|
|
||||||
## PascalCase
|
## PascalCase
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The practice of writing individual words, compound words, or phrases such that each word or abbreviation begins with a capital letter.
|
The practice of writing individual words, compound words, or phrases such that each word or abbreviation begins with a capital letter.
|
||||||
Class names are typically spelled in PascalCase. For example, `Person` and `HeroDetailComponent`.
|
Class names are typically spelled in PascalCase. For example, `Person` and `HeroDetailComponent`.
|
||||||
|
|
||||||
@ -756,16 +545,8 @@ This form is also known as *upper camel case* to distinguish it from *lower came
|
|||||||
In this documentation, "PascalCase" means *upper camel case* and "camelCase" means *lower camel case*.
|
In this documentation, "PascalCase" means *upper camel case* and "camelCase" means *lower camel case*.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Pipe
|
## Pipe
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
An Angular pipe is a function that transforms input values to output values for
|
An Angular pipe is a function that transforms input values to output values for
|
||||||
display in a [view](guide/glossary#view).
|
display in a [view](guide/glossary#view).
|
||||||
Here's an example that uses the built-in `currency` pipe to display
|
Here's an example that uses the built-in `currency` pipe to display
|
||||||
@ -783,35 +564,19 @@ You can also write your own custom pipes.
|
|||||||
Read more in the page on [pipes](guide/pipes).
|
Read more in the page on [pipes](guide/pipes).
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Provider
|
## Provider
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A _provider_ creates a new instance of a dependency for the
|
A _provider_ creates a new instance of a dependency for the
|
||||||
[dependency injection](guide/glossary#dependency-injection) system.
|
[dependency injection](guide/glossary#dependency-injection) system.
|
||||||
It relates a lookup token to code—sometimes called a "recipe"—that can create a dependency value.
|
It relates a lookup token to code—sometimes called a "recipe"—that can create a dependency value.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{@a Q}
|
{@a Q}
|
||||||
|
|
||||||
{@a R}
|
{@a R}
|
||||||
|
|
||||||
## Reactive forms
|
## Reactive forms
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A technique for building Angular forms through code in a component.
|
A technique for building Angular forms through code in a component.
|
||||||
The alternative technique is [template-driven forms](guide/glossary#template-driven-forms).
|
The alternative technique is [template-driven forms](guide/glossary#template-driven-forms).
|
||||||
|
|
||||||
@ -825,16 +590,8 @@ When building reactive forms:
|
|||||||
Reactive forms are powerful, flexible, and a good choice for more complex data-entry form scenarios, such as dynamic generation of form controls.
|
Reactive forms are powerful, flexible, and a good choice for more complex data-entry form scenarios, such as dynamic generation of form controls.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Router
|
## Router
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Most applications consist of many screens or [views](guide/glossary#view).
|
Most applications consist of many screens or [views](guide/glossary#view).
|
||||||
The user navigates among them by clicking links and buttons,
|
The user navigates among them by clicking links and buttons,
|
||||||
and performing other similar actions that cause the application to
|
and performing other similar actions that cause the application to
|
||||||
@ -855,47 +612,24 @@ directives that users can click to navigate.
|
|||||||
For more information, see the [Routing & Navigation](guide/router) page.
|
For more information, see the [Routing & Navigation](guide/router) page.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Router module
|
## Router module
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A separate [Angular module](guide/glossary#angular-module) that provides the necessary service providers and directives for navigating through application views.
|
A separate [Angular module](guide/glossary#angular-module) that provides the necessary service providers and directives for navigating through application views.
|
||||||
|
|
||||||
For more information, see the [Routing & Navigation](guide/router) page.
|
For more information, see the [Routing & Navigation](guide/router) page.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Routing component
|
## Routing component
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
An Angular [component](guide/glossary#component) with a `RouterOutlet` that displays views based on router navigations.
|
An Angular [component](guide/glossary#component) with a `RouterOutlet` that displays views based on router navigations.
|
||||||
|
|
||||||
For more information, see the [Routing & Navigation](guide/router) page.
|
For more information, see the [Routing & Navigation](guide/router) page.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{@a S}
|
{@a S}
|
||||||
|
|
||||||
## Scoped package
|
## Scoped package
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A way to group related *npm* packages.
|
A way to group related *npm* packages.
|
||||||
Read more at the [npm-scope](https://docs.npmjs.com/misc/scope) page.
|
Read more at the [npm-scope](https://docs.npmjs.com/misc/scope) page.
|
||||||
|
|
||||||
@ -912,17 +646,8 @@ is that the scoped package name begins with the Angular *scope name*, `@angular`
|
|||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Service
|
## Service
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
For data or logic that is not associated
|
For data or logic that is not associated
|
||||||
with a specific view or that you want to share across components, build services.
|
with a specific view or that you want to share across components, build services.
|
||||||
|
|
||||||
@ -938,27 +663,15 @@ Applications often require services such as a data service or a logging service.
|
|||||||
For more information, see the [Services](tutorial/toh-pt4) page of the [Tour of Heroes](tutorial) tutorial.
|
For more information, see the [Services](tutorial/toh-pt4) page of the [Tour of Heroes](tutorial) tutorial.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{@a snake-case}
|
{@a snake-case}
|
||||||
|
|
||||||
|
|
||||||
## snake_case
|
## snake_case
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The practice of writing compound words or phrases such that an
|
The practice of writing compound words or phrases such that an
|
||||||
underscore (`_`) separates one word from the next. This form is also known as *underscore case*.
|
underscore (`_`) separates one word from the next. This form is also known as *underscore case*.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{@a structural-directive}
|
{@a structural-directive}
|
||||||
|
|
||||||
|
|
||||||
@ -967,10 +680,6 @@ underscore (`_`) separates one word from the next. This form is also known as *u
|
|||||||
|
|
||||||
## Structural directives
|
## Structural directives
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A category of [directive](guide/glossary#directive) that can
|
A category of [directive](guide/glossary#directive) that can
|
||||||
shape or reshape HTML layout, typically by adding and removing elements in the DOM.
|
shape or reshape HTML layout, typically by adding and removing elements in the DOM.
|
||||||
The `ngIf` "conditional element" directive and the `ngFor` "repeater" directive are well-known examples.
|
The `ngIf` "conditional element" directive and the `ngFor` "repeater" directive are well-known examples.
|
||||||
@ -978,32 +687,17 @@ The `ngIf` "conditional element" directive and the `ngFor` "repeater" directive
|
|||||||
Read more in the [Structural Directives](guide/structural-directives) page.
|
Read more in the [Structural Directives](guide/structural-directives) page.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{@a T}
|
{@a T}
|
||||||
|
|
||||||
## Template
|
## Template
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A chunk of HTML that Angular uses to render a [view](guide/glossary#view) with
|
A chunk of HTML that Angular uses to render a [view](guide/glossary#view) with
|
||||||
the support and guidance of an Angular [directive](guide/glossary#directive),
|
the support and guidance of an Angular [directive](guide/glossary#directive),
|
||||||
most notably a [component](guide/glossary#component).
|
most notably a [component](guide/glossary#component).
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Template-driven forms
|
## Template-driven forms
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A technique for building Angular forms using HTML forms and input elements in the view.
|
A technique for building Angular forms using HTML forms and input elements in the view.
|
||||||
The alternate technique is [Reactive Forms](guide/glossary#reactive-forms).
|
The alternate technique is [Reactive Forms](guide/glossary#reactive-forms).
|
||||||
|
|
||||||
@ -1020,16 +714,8 @@ Read about how to build template-driven forms
|
|||||||
in the [Forms](guide/forms) page.
|
in the [Forms](guide/forms) page.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Template expression
|
## Template expression
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A TypeScript-like syntax that Angular evaluates within
|
A TypeScript-like syntax that Angular evaluates within
|
||||||
a [data binding](guide/glossary#data-binding).
|
a [data binding](guide/glossary#data-binding).
|
||||||
|
|
||||||
@ -1038,30 +724,14 @@ in the [Template expressions](guide/template-syntax#template-expressions) sectio
|
|||||||
of the [Template Syntax](guide/template-syntax) page.
|
of the [Template Syntax](guide/template-syntax) page.
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Transpile
|
## Transpile
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The process of transforming code written in one form of JavaScript
|
The process of transforming code written in one form of JavaScript
|
||||||
(such as TypeScript) into another form of JavaScript (such as [ES5](guide/glossary#es5)).
|
(such as TypeScript) into another form of JavaScript (such as [ES5](guide/glossary#es5)).
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## TypeScript
|
## TypeScript
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A version of JavaScript that supports most [ECMAScript 2015](guide/glossary#es2015)
|
A version of JavaScript that supports most [ECMAScript 2015](guide/glossary#es2015)
|
||||||
language features such as [decorators](guide/glossary#decorator).
|
language features such as [decorators](guide/glossary#decorator).
|
||||||
|
|
||||||
@ -1076,20 +746,12 @@ you can use other JavaScript dialects such as [ES5](guide/glossary#es5).
|
|||||||
Read more about TypeScript at [typescriptlang.org](http://www.typescriptlang.org/).
|
Read more about TypeScript at [typescriptlang.org](http://www.typescriptlang.org/).
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{@a U}
|
{@a U}
|
||||||
|
|
||||||
{@a V}
|
{@a V}
|
||||||
|
|
||||||
## View
|
## View
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A portion of the screen that displays information and responds
|
A portion of the screen that displays information and responds
|
||||||
to user actions such as clicks, mouse moves, and keystrokes.
|
to user actions such as clicks, mouse moves, and keystrokes.
|
||||||
|
|
||||||
@ -1103,10 +765,6 @@ dynamically as the user navigates through the application, typically
|
|||||||
under the control of a [router](guide/glossary#router).
|
under the control of a [router](guide/glossary#router).
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{@a W}
|
{@a W}
|
||||||
|
|
||||||
|
|
||||||
@ -1120,10 +778,6 @@ under the control of a [router](guide/glossary#router).
|
|||||||
|
|
||||||
## Zone
|
## Zone
|
||||||
|
|
||||||
<div class="l-sub-section">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
A mechanism for encapsulating and intercepting
|
A mechanism for encapsulating and intercepting
|
||||||
a JavaScript application's asynchronous activity.
|
a JavaScript application's asynchronous activity.
|
||||||
|
|
||||||
@ -1142,6 +796,3 @@ the information it displays via [data bindings](guide/glossary#data-binding).
|
|||||||
|
|
||||||
Learn more about zones in this
|
Learn more about zones in this
|
||||||
[Brian Ford video](https://www.youtube.com/watch?v=3IqtmUscE_U).
|
[Brian Ford video](https://www.youtube.com/watch?v=3IqtmUscE_U).
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ making some of them public so external components can use them.
|
|||||||
And there are many more options covered here.
|
And there are many more options covered here.
|
||||||
|
|
||||||
Before reading this page, read the
|
Before reading this page, read the
|
||||||
[The Root Module](guide/appmodule) page, which introduces NgModules and the essentials
|
[The Root Module](guide/bootstrapping) page, which introduces NgModules and the essentials
|
||||||
of creating and maintaining a single root `AppModule` for the entire application.
|
of creating and maintaining a single root `AppModule` for the entire application.
|
||||||
|
|
||||||
This page covers NgModules in greater depth.
|
This page covers NgModules in greater depth.
|
||||||
|
@ -1,79 +1,617 @@
|
|||||||
<h1 class="no-toc">QuickStart</h1>
|
# QuickStart
|
||||||
|
|
||||||
Angular applications are made up of _components_.
|
Good tools make application development quicker and easier to maintain than
|
||||||
A _component_ is the combination of an HTML template and a component class that controls a portion of the screen. Here is an example of a component that displays a simple string:
|
if you did everything by hand.
|
||||||
|
|
||||||
|
The [**Angular CLI**](https://cli.angular.io/) is a **_command line interface_** tool
|
||||||
|
that can create a project, add files, and perform a variety of ongoing development tasks such
|
||||||
|
as testing, bundling, and deployment.
|
||||||
|
|
||||||
|
The goal in this guide is to build and run a simple Angular
|
||||||
|
application in TypeScript, using the Angular CLI
|
||||||
|
while adhering to the [Style Guide](guide/styleguide) recommendations that
|
||||||
|
benefit _every_ Angular project.
|
||||||
|
|
||||||
|
By the end of the chapter, you'll have a basic understanding of development with the CLI
|
||||||
|
and a foundation for both these documentation samples and for real world applications.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|
You'll pursue these ends in the following high-level steps:
|
||||||
|
|
||||||
|
1. [Set up](guide/cli-quickstart#devenv) the development environment.
|
||||||
|
2. [Create](guide/cli-quickstart#create-proj) a new project and skeleton application.
|
||||||
|
3. [Serve](guide/cli-quickstart#serve) the application.
|
||||||
|
4. [Edit](guide/cli-quickstart#first-component) the application.
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
And you can also <a href="generated/zips/cli-quickstart/cli-quickstart.zip" target="_blank">download the example.</a>
|
||||||
|
|
||||||
|
|
||||||
<code-example path="quickstart/src/app/app.component.ts" title="src/app/app.component.ts" linenums="false">
|
|
||||||
|
<h2 id='devenv'>
|
||||||
|
Step 1. Set up the Development Environment
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
You need to set up your development environment before you can do anything.
|
||||||
|
|
||||||
|
Install **[Node.js® and npm](https://nodejs.org/en/download/)**
|
||||||
|
if they are not already on your machine.
|
||||||
|
|
||||||
|
<div class="l-sub-section">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Verify that you are running at least node `6.9.x` and npm `3.x.x`**
|
||||||
|
by running `node -v` and `npm -v` in a terminal/console window.
|
||||||
|
Older versions produce errors, but newer versions are fine.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Then **install the [Angular CLI](https://github.com/angular/angular-cli)** globally.
|
||||||
|
|
||||||
|
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
npm install -g @angular/cli
|
||||||
|
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<h2 id='create-proj'>
|
||||||
|
Step 2. Create a new project
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Open a terminal window.
|
||||||
|
|
||||||
|
|
||||||
|
Generate a new project and skeleton application by running the following commands:
|
||||||
|
|
||||||
|
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
ng new my-app
|
||||||
|
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="l-sub-section">
|
<div class="l-sub-section">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Try this **<live-example noDownload>QuickStart example on Plunker</live-example>** without installing anything.
|
Patience please.
|
||||||
Try it locally with the [***QuickStart seed***](guide/setup "Setup for local development with the QuickStart seed")
|
It takes time to set up a new project, most of it spent installing npm packages.
|
||||||
and prepare for development of a real Angular application.
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Every component begins with an `@Component` [decorator](guide/glossary#decorator '"decorator" explained')
|
|
||||||
function that takes a _metadata_ object. The metadata object describes how the HTML template and component class work together.
|
|
||||||
|
|
||||||
The `selector` property tells Angular to display the component inside a custom `<my-app>` tag in the `index.html`.
|
<h2 id='serve'>
|
||||||
|
Step 3: Serve the application
|
||||||
|
</h2>
|
||||||
|
|
||||||
<code-example path="quickstart/src/index.html" region="my-app" title="index.html (inside <body>)" linenums="false">
|
|
||||||
|
|
||||||
|
Go to the project directory and launch the server.
|
||||||
|
|
||||||
|
|
||||||
|
<code-example language="sh" class="code-shell">
|
||||||
|
cd my-app
|
||||||
|
ng serve --open
|
||||||
|
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The `template` property defines a message inside an `<h1>` header.
|
The `ng serve` command launches the server, watches your files,
|
||||||
The message starts with "Hello" and ends with `{{name}}`,
|
and rebuilds the app as you make changes to those files.
|
||||||
which is an Angular [interpolation binding](guide/displaying-data) expression.
|
|
||||||
At runtime, Angular replaces `{{name}}` with the value of the component's `name` property.
|
Using the `--open` (or just `-o`) option will automatically open your browser
|
||||||
Interpolation binding is one of many Angular features you'll discover in this documentation.
|
on `http://localhost:4200/`.
|
||||||
|
|
||||||
|
Your app greets you with a message:
|
||||||
|
|
||||||
|
|
||||||
In the example, change the component class's `name` property from `'Angular'` to `'World'` and see what happens.
|
<figure>
|
||||||
|
<img src='generated/images/guide/cli-quickstart/app-works.png' alt="The app works!">
|
||||||
|
</figure>
|
||||||
<div class="callout is-helpful">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<header>
|
|
||||||
A word about TypeScript
|
<h2 id='first-component'>
|
||||||
</header>
|
Step 4: Edit your first Angular component
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<p>
|
The CLI created the first Angular component for you.
|
||||||
This example is written in <a href="http://www.typescriptlang.org/" title="TypeScript">TypeScript</a>, a superset of JavaScript. Angular
|
This is the _root component_ and it is named `app-root`.
|
||||||
uses TypeScript because its types make it easy to support developer productivity with tooling. You can also write Angular code in JavaScript; [this guide](guide/ts-to-js] explains how.
|
You can find it in `./src/app/app.component.ts`.
|
||||||
|
|
||||||
</p>
|
|
||||||
|
Open the component file and change the `title` property from _app works!_ to _My First Angular App_:
|
||||||
|
|
||||||
|
|
||||||
|
<code-example path="cli-quickstart/src/app/app.component.ts" region="title" title="src/app/app.component.ts" linenums="false"></code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
The browser reloads automatically with the revised title. That's nice, but it could look better.
|
||||||
|
|
||||||
|
Open `src/app/app.component.css` and give the component some style.
|
||||||
|
|
||||||
|
|
||||||
|
<code-example path="cli-quickstart/src/app/app.component.css" title="src/app/app.component.css" linenums="false"></code-example>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<img src='generated/images/guide/cli-quickstart/my-first-app.png' alt="Output of QuickStart app">
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Looking good!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## What's next?
|
||||||
|
That's about all you'd expect to do in a "Hello, World" app.
|
||||||
|
|
||||||
|
You're ready to take the [Tour of Heroes Tutorial](tutorial) and build
|
||||||
|
a small application that demonstrates the great things you can build with Angular.
|
||||||
|
|
||||||
|
Or you can stick around a bit longer to learn about the files in your brand new project.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Project file review
|
||||||
|
|
||||||
|
An Angular CLI project is the foundation for both quick experiments and enterprise solutions.
|
||||||
|
|
||||||
|
The first file you should check out is `README.md`.
|
||||||
|
It has some basic information on how to use CLI commands.
|
||||||
|
Whenever you want to know more about how Angular CLI works make sure to visit
|
||||||
|
[the Angular CLI repository](https://github.com/angular/angular-cli) and
|
||||||
|
[Wiki](https://github.com/angular/angular-cli/wiki).
|
||||||
|
|
||||||
|
Some of the generated files might be unfamiliar to you.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### The `src` folder
|
||||||
|
Your app lives in the `src` folder.
|
||||||
|
All Angular components, templates, styles, images, and anything else your app needs go here.
|
||||||
|
Any files outside of this folder are meant to support building your app.
|
||||||
|
|
||||||
|
|
||||||
|
<div class='filetree'>
|
||||||
|
<div class='file'>src</div>
|
||||||
|
<div class='children'>
|
||||||
|
<div class='file'>app</div>
|
||||||
|
<div class='children'>
|
||||||
|
<div class='file'>app.component.css</div>
|
||||||
|
<div class='file'>app.component.html</div>
|
||||||
|
<div class="file">app.component.spec.ts</div>
|
||||||
|
<div class="file">app.component.ts</div>
|
||||||
|
<div class="file">app.module.ts</div>
|
||||||
|
</div>
|
||||||
|
<div class="file">assets</div>
|
||||||
|
<div class='children'>
|
||||||
|
<div class="file">.gitkeep</div>
|
||||||
|
</div>
|
||||||
|
<div class="file">environments</div>
|
||||||
|
<div class='children'>
|
||||||
|
<div class="file">environment.prod.ts</div>
|
||||||
|
<div class="file">environment.ts</div>
|
||||||
|
</div>
|
||||||
|
<div class="file">favicon.ico</div>
|
||||||
|
<div class="file">index.html</div>
|
||||||
|
<div class="file">main.ts</div>
|
||||||
|
<div class="file">polyfills.ts</div>
|
||||||
|
<div class="file">styles.css</div>
|
||||||
|
<div class="file">test.ts</div>
|
||||||
|
<div class="file">tsconfig.app.json</div>
|
||||||
|
<div class="file">tsconfig.spec.json</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
td, th {vertical-align: top}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<table width="100%">
|
||||||
|
<col width="20%">
|
||||||
|
</col>
|
||||||
|
<col width="80%">
|
||||||
|
</col>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
File
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Purpose
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`app/app.component.{ts,html,css,spec.ts}`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Defines the `AppComponent` along with an HTML template, CSS stylesheet, and a unit test.
|
||||||
|
It is the **root** component of what will become a tree of nested components
|
||||||
|
as the application evolves.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`app/app.module.ts`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Defines `AppModule`, the [root module](guide/bootstrapping "AppModule: the root module") that tells Angular how to assemble the application.
|
||||||
|
Right now it declares only the `AppComponent`.
|
||||||
|
Soon there will be more components to declare.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`assets/*`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
A folder where you can put images and anything else to be copied wholesale
|
||||||
|
when you build your application.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`environments/*`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
This folder contains one file for each of your destination environments,
|
||||||
|
each exporting simple configuration variables to use in your application.
|
||||||
|
The files are replaced on-the-fly when you build your app.
|
||||||
|
You might use a different API endpoint for development than you do for production
|
||||||
|
or maybe different analytics tokens.
|
||||||
|
You might even use some mock services.
|
||||||
|
Either way, the CLI has you covered.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`favicon.ico`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Every site wants to look good on the bookmark bar.
|
||||||
|
Get started with your very own Angular icon.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`index.html`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
The main HTML page that is served when someone visits your site.
|
||||||
|
Most of the time you'll never need to edit it.
|
||||||
|
The CLI automatically adds all `js` and `css` files when building your app so you
|
||||||
|
never need to add any `<script>` or `<link>` tags here manually.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`main.ts`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
The main entry point for your app.
|
||||||
|
Compiles the application with the [JIT compiler](guide/glossary#jit)
|
||||||
|
and bootstraps the application's root module (`AppModule`) to run in the browser.
|
||||||
|
You can also use the [AOT compiler](guide/glossary#ahead-of-time-aot-compilation)
|
||||||
|
without changing any code by passing in `--aot` to `ng build` or `ng serve`.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`polyfills.ts`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Different browsers have different levels of support of the web standards.
|
||||||
|
Polyfills help normalize those differences.
|
||||||
|
You should be pretty safe with `core-js` and `zone.js`, but be sure to check out
|
||||||
|
the [Browser Support guide](guide/browser-support) for more information.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`styles.css`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Your global styles go here.
|
||||||
|
Most of the time you'll want to have local styles in your components for easier maintenance,
|
||||||
|
but styles that affect all of your app need to be in a central place.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`test.ts`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
This is the main entry point for your unit tests.
|
||||||
|
It has some custom configuration that might be unfamiliar, but it's not something you'll
|
||||||
|
need to edit.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`tsconfig.{app|spec}.json`
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
TypeScript compiler configuration for the Angular app (`tsconfig.app.json`)
|
||||||
|
and for the unit tests (`tsconfig.spec.json`).
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
### The root folder
|
||||||
|
|
||||||
|
The `src/` folder is just one of the items inside the project's root folder.
|
||||||
|
Other files help you build, test, maintain, document, and deploy the app.
|
||||||
|
These files go in the root folder next to `src/`.
|
||||||
|
|
||||||
|
|
||||||
|
<div class='filetree'>
|
||||||
|
<div class="file">my-app</div>
|
||||||
|
<div class='children'>
|
||||||
|
<div class="file">e2e</div>
|
||||||
|
<div class='children'>
|
||||||
|
<div class="file">app.e2e-spec.ts</div>
|
||||||
|
<div class="file">app.po.ts</div>
|
||||||
|
<div class="file">tsconfig.e2e.json</div>
|
||||||
|
</div>
|
||||||
|
<div class="file">node_modules/...</div>
|
||||||
|
<div class="file">src/...</div>
|
||||||
|
<div class="file">.angular-cli.json</div>
|
||||||
|
<div class="file">.editorconfig</div>
|
||||||
|
<div class="file">.gitignore</div>
|
||||||
|
<div class="file">karma.conf.js</div>
|
||||||
|
<div class="file">package.json</div>
|
||||||
|
<div class="file">protractor.conf.js</div>
|
||||||
|
<div class="file">README.md</div>
|
||||||
|
<div class="file">tsconfig.json</div>
|
||||||
|
<div class="file">tslint.json</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
td, th {vertical-align: top}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<table width="100%">
|
||||||
|
<col width="20%">
|
||||||
|
</col>
|
||||||
|
<col width="80%">
|
||||||
|
</col>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
File
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Purpose
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`e2e/`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Inside `e2e/` live the End-to-End tests.
|
||||||
|
They shouldn't be inside `src/` because e2e tests are really a separate app that
|
||||||
|
just so happens to test your main app.
|
||||||
|
That's also why they have their own `tsconfig.e2e.json`.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`node_modules/`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`Node.js` creates this folder and puts all third party modules listed in
|
||||||
|
`package.json` inside of it.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`.angular-cli.json`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Configuration for Angular CLI.
|
||||||
|
In this file you can set several defaults and also configure what files are included
|
||||||
|
when your project is build.
|
||||||
|
Check out the official documentation if you want to know more.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`.editorconfig`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Simple configuration for your editor to make sure everyone that uses your project
|
||||||
|
has the same basic configuration.
|
||||||
|
Most editors support an `.editorconfig` file.
|
||||||
|
See http://editorconfig.org for more information.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`.gitignore`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Git configuration to make sure autogenerated files are not commited to source control.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`karma.conf.js`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Unit test configuration for the [Karma test runner](https://karma-runner.github.io),
|
||||||
|
used when running `ng test`.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`package.json`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`npm` configuration listing the third party packages your project uses.
|
||||||
|
You can also add your own [custom scripts](https://docs.npmjs.com/misc/scripts) here.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`protractor.conf.js`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
End-to-end test configuration for [Protractor](http://www.protractortest.org/),
|
||||||
|
used when running `ng e2e`.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`README.md`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Basic documentation for your project, pre-filled with CLI command information.
|
||||||
|
Make sure to enhance it with project documentation so that anyone
|
||||||
|
checking out the repo can build your app!
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`tsconfig.json`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
TypeScript compiler configuration for your IDE to pick up and give you helpful tooling.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`tslint.json`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Linting configuration for [TSLint](https://palantir.github.io/tslint/) together with
|
||||||
|
[Codelyzer](http://codelyzer.com/), used when running `ng lint`.
|
||||||
|
Linting helps keep your code style consistent.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
<div class="l-sub-section">
|
<div class="l-sub-section">
|
||||||
|
|
||||||
|
### Next Step
|
||||||
|
|
||||||
|
If you're new to Angular, continue with the
|
||||||
### Next step
|
[tutorial](tutorial "Tour of Heroes tutorial").
|
||||||
|
You can skip the "Setup" step since you're already using the Angular CLI setup.
|
||||||
Start the [**tutorial**](tutorial "Tour of Heroes tutorial").
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -253,7 +253,7 @@ The following are all in `src/`
|
|||||||
<td>
|
<td>
|
||||||
|
|
||||||
|
|
||||||
Defines `AppModule`, the [root module](guide/appmodule "AppModule: the root module") that tells Angular how to assemble the application.
|
Defines `AppModule`, the [root module](guide/bootstrapping "AppModule: the root module") that tells Angular how to assemble the application.
|
||||||
Right now it declares only the `AppComponent`.
|
Right now it declares only the `AppComponent`.
|
||||||
Soon there will be more components to declare.
|
Soon there will be more components to declare.
|
||||||
</td>
|
</td>
|
||||||
@ -270,7 +270,7 @@ The following are all in `src/`
|
|||||||
|
|
||||||
|
|
||||||
Compiles the application with the [JIT compiler](guide/glossary#jit) and
|
Compiles the application with the [JIT compiler](guide/glossary#jit) and
|
||||||
[bootstraps](guide/appmodule#main "bootstrap the application")
|
[bootstraps](guide/bootstrapping#main "bootstrap the application")
|
||||||
the application's main module (`AppModule`) to run in the browser.
|
the application's main module (`AppModule`) to run in the browser.
|
||||||
The JIT compiler is a reasonable choice during the development of most projects and
|
The JIT compiler is a reasonable choice during the development of most projects and
|
||||||
it's the only viable choice for a sample running in a _live-coding_ environment like Plunker.
|
it's the only viable choice for a sample running in a _live-coding_ environment like Plunker.
|
||||||
|
@ -104,7 +104,7 @@ including:
|
|||||||
Other notable differences from JavaScript syntax include:
|
Other notable differences from JavaScript syntax include:
|
||||||
|
|
||||||
* no support for the bitwise operators `|` and `&`
|
* no support for the bitwise operators `|` and `&`
|
||||||
* new [template expression operators](guide/template-syntax#expression-operators), such as `|` and `?.`
|
* new [template expression operators](guide/template-syntax#expression-operators), such as `|`, `?.` and `!`.
|
||||||
|
|
||||||
{@a expression-context}
|
{@a expression-context}
|
||||||
|
|
||||||
@ -1931,6 +1931,27 @@ It works perfectly with long property paths such as `a?.b?.c?.d`.
|
|||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
|
{@a non-null-assertion-operator}
|
||||||
|
|
||||||
|
### The non-null assertion operator ( <span class="syntax">!</span> )
|
||||||
|
|
||||||
|
The Angular **non-null assertion operator (`!`)** is a post-fix operator that asserts that the preceeding property path
|
||||||
|
will never be null or undefined.
|
||||||
|
|
||||||
|
Unlike the [_safe navigation operator_](guide/template-syntax#safe-navigation-operator "Safe naviation operator (?.)")
|
||||||
|
the **non-null assertion operator** does not guard against a null or undefined; rather, it informs the TypeScript type
|
||||||
|
checker that there is something it might be unaware of that ensures that this property path is defined. This prevents
|
||||||
|
TypeScript from reporting that the path is possibly null or undefined when strict null checking is enabled.
|
||||||
|
|
||||||
|
For example, if you use [*ngIf](guide/template-syntax#ngIf) to check if `hero` is defined, you can assert the uses of
|
||||||
|
`hero` are defined in the body of the template.
|
||||||
|
|
||||||
|
<code-example path="template-syntax/src/app/app.component.html" region="non-null-assertion-1" title="src/app/app.component.html" linenums="false">
|
||||||
|
</code-example>
|
||||||
|
|
||||||
|
The Angular **non-null assertion operator (`!`)** is like TypeScript's _non-null assertion operator (!)_
|
||||||
|
introduced in [TypeScript 2.0](http://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html).
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
You've completed this survey of template syntax.
|
You've completed this survey of template syntax.
|
||||||
Now it's time to put that knowledge to work on your own components and directives.
|
Now it's time to put that knowledge to work on your own components and directives.
|
||||||
|
@ -66,7 +66,7 @@ Anything you can import from `@angular` is a nested member of this `ng` object:
|
|||||||
<code-tabs>
|
<code-tabs>
|
||||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/app.module.ts" region="ng2import">
|
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/app.module.ts" region="ng2import">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/app.module.es6" region="ng2import">
|
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/app.module.es6" region="ng2import">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/app.module.es6" region="ng2import">
|
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/app.module.es6" region="ng2import">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
@ -104,7 +104,7 @@ Here is a `HeroComponent` as it might be defined and "exported" in each of the f
|
|||||||
<code-tabs>
|
<code-tabs>
|
||||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero.component.ts" region="appexport">
|
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero.component.ts" region="appexport">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero.component.es6" region="appexport">
|
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero.component.es6" region="appexport">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero.component.es6" region="appexport">
|
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero.component.es6" region="appexport">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
@ -121,7 +121,7 @@ In _ES5_ you use the shared namespace object to access "exported" entities from
|
|||||||
<code-tabs>
|
<code-tabs>
|
||||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/app.module.ts" region="appimport">
|
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/app.module.ts" region="appimport">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/app.module.es6" region="appimport">
|
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/app.module.es6" region="appimport">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/app.module.es6" region="appimport">
|
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/app.module.es6" region="appimport">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
@ -167,7 +167,7 @@ Use the constructor function pattern instead, adding methods to the prototype.
|
|||||||
<code-tabs>
|
<code-tabs>
|
||||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero.component.ts" region="class">
|
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero.component.ts" region="class">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero.component.es6" region="class">
|
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero.component.es6" region="class">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero.component.es6" region="class">
|
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero.component.es6" region="class">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
@ -193,7 +193,7 @@ See these variations side-by-side:
|
|||||||
<code-tabs>
|
<code-tabs>
|
||||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero.component.ts" region="metadata">
|
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero.component.ts" region="metadata">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero.component.es6" region="metadata">
|
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero.component.es6" region="metadata">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero.component.es6" region="metadata">
|
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero.component.es6" region="metadata">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
@ -213,7 +213,7 @@ The component, `HeroTitleComponent` in this case, then references the template f
|
|||||||
<code-tabs>
|
<code-tabs>
|
||||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-title.component.ts" region="templateUrl">
|
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-title.component.ts" region="templateUrl">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero-title.component.es6" region="templateUrl">
|
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-title.component.es6" region="templateUrl">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-title.component.es6" region="templateUrl">
|
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-title.component.es6" region="templateUrl">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
@ -322,7 +322,7 @@ Just implement the methods and ignore interfaces when translating code samples f
|
|||||||
<code-tabs>
|
<code-tabs>
|
||||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-lifecycle.component.ts">
|
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-lifecycle.component.ts">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero-lifecycle.component.es6">
|
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-lifecycle.component.es6">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-lifecycle.component.es6">
|
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-lifecycle.component.es6">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
@ -354,7 +354,7 @@ combined in the metadata `inputs` and `outputs` _arrays_.
|
|||||||
<code-tabs>
|
<code-tabs>
|
||||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/confirm.component.ts">
|
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/confirm.component.ts">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/confirm.component.es6">
|
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/confirm.component.es6">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/confirm.component.es6">
|
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/confirm.component.es6">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
@ -412,7 +412,7 @@ This format should be familiar to AngularJS developers.
|
|||||||
<code-tabs>
|
<code-tabs>
|
||||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-di.component.ts">
|
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-di.component.ts">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero-di.component.es6">
|
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-di.component.es6">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-di.component.es6">
|
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-di.component.es6">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
@ -430,8 +430,8 @@ In _TypeScript_ and _ES6-with-decorators_, you precede the class constructor par
|
|||||||
by calling the `@Inject()` decorator with the injection token.
|
by calling the `@Inject()` decorator with the injection token.
|
||||||
In the following example, the token is the string `'heroName'`.
|
In the following example, the token is the string `'heroName'`.
|
||||||
|
|
||||||
The other JavaScript dialects add a `parameters` array to the class contructor function.
|
The other JavaScript dialects add a `parameters` array to the class constructor function.
|
||||||
Each item constains a new instance of `Inject`:
|
Each item constrains a new instance of `Inject`:
|
||||||
|
|
||||||
* _Plain ES6_—each item is a new instance of `Inject(token)` in a sub-array.
|
* _Plain ES6_—each item is a new instance of `Inject(token)` in a sub-array.
|
||||||
* _ES5_—simply list the string tokens.
|
* _ES5_—simply list the string tokens.
|
||||||
@ -442,7 +442,7 @@ array as before. Create a new instance of `ng.core.Inject(token)` for each param
|
|||||||
<code-tabs>
|
<code-tabs>
|
||||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-di-inject.component.ts">
|
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-di-inject.component.ts">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero-di-inject.component.es6">
|
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-di-inject.component.es6">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-di-inject.component.es6">
|
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-di-inject.component.es6">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
@ -475,7 +475,7 @@ array as before. Use a nested array to define a parameter's complete injection s
|
|||||||
<code-tabs>
|
<code-tabs>
|
||||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-title.component.ts">
|
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-title.component.ts">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero-title.component.es6">
|
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-title.component.es6">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-title.component.es6">
|
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-title.component.es6">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
@ -522,7 +522,7 @@ The `host` value is an object whose properties are host property and listener b
|
|||||||
<code-tabs>
|
<code-tabs>
|
||||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-host.component.ts">
|
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-host.component.ts">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero-host.component.es6">
|
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-host.component.es6">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-host.component.es6">
|
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-host.component.es6">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
@ -545,7 +545,7 @@ These particular _TypeScript_ and _ES6_ code snippets happen to be identical.
|
|||||||
<code-tabs>
|
<code-tabs>
|
||||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-host-meta.component.ts">
|
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-host-meta.component.ts">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero-host-meta.component.es6">
|
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-host-meta.component.es6">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
</code-tabs>
|
</code-tabs>
|
||||||
|
|
||||||
@ -578,7 +578,7 @@ The `queries` property value is a hash map.
|
|||||||
<code-tabs>
|
<code-tabs>
|
||||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-queries.component.ts" region="view">
|
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-queries.component.ts" region="view">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6" region="view">
|
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6" region="view">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-queries.component.es6" region="view">
|
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-queries.component.es6" region="view">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
@ -597,7 +597,7 @@ They can be added in the same way as [`@ViewChild`](api/core/ViewChild) and
|
|||||||
<code-tabs>
|
<code-tabs>
|
||||||
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-queries.component.ts" region="content">
|
<code-pane title="TypeScript" path="ts-to-js/ts/src/app/hero-queries.component.ts" region="content">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript with decorators" path="ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6" region="content">
|
<code-pane title="ES6 + Decorators" path="ts-to-js/js-es6-decorators/src/app/hero-queries.component.es6" region="content">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-queries.component.es6" region="content">
|
<code-pane title="ES6 JavaScript" path="ts-to-js/js-es6/src/app/hero-queries.component.es6" region="content">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 7.4 KiB |
BIN
aio/content/images/marketing/angular-mix.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
34
aio/content/marketing/contribute.html
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<header class="marketing-banner">
|
||||||
|
<h1 class="banner-headline no-toc no-anchor">Contribute to Angular</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<article class="contribute-container">
|
||||||
|
<h2 class="no-anchor">Angular Projects</h2>
|
||||||
|
|
||||||
|
<p>We'd love for you to contribute to our source code and to make Angular projects even better.</p>
|
||||||
|
|
||||||
|
<div class="l-sub-section">
|
||||||
|
<h3 class="no-anchor">Angular</h3>
|
||||||
|
|
||||||
|
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</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="l-sub-section">
|
||||||
|
<h3 class="no-anchor">Angular Material</h3>
|
||||||
|
|
||||||
|
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/material2/blob/master/CONTRIBUTING.md" class="button" md-button>Contribute to Angular Material</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="l-sub-section">
|
||||||
|
<h3 class="no-anchor">AngularFire</h3>
|
||||||
|
|
||||||
|
AngularFire is the officially supported Angular binding for Firebase. Firebase is a full backend so you don't need servers to build your Angular app.
|
||||||
|
|
||||||
|
<a href="https://github.com/angular/angularfire2" class="button" md-button> Contribute to AngularFire</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</article>
|
@ -1,37 +0,0 @@
|
|||||||
<h1 class="no-toc">Contribute to Angular</h1>
|
|
||||||
|
|
||||||
Help us build the framework of the future!
|
|
||||||
|
|
||||||
## Angular Projects
|
|
||||||
|
|
||||||
We'd love for you to contribute to our source code and to make Angular projects even better.
|
|
||||||
|
|
||||||
~~~ {.l-sub-section}
|
|
||||||
|
|
||||||
### Angular
|
|
||||||
|
|
||||||
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</a>
|
|
||||||
|
|
||||||
~~~
|
|
||||||
|
|
||||||
~~~ {.l-sub-section}
|
|
||||||
|
|
||||||
### Angular Material
|
|
||||||
|
|
||||||
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/.github/CONTRIBUTING.md" class="button" md-button>Contribute to Angular Material</a>
|
|
||||||
|
|
||||||
~~~
|
|
||||||
|
|
||||||
~~~ {.l-sub-section}
|
|
||||||
|
|
||||||
### AngularFire
|
|
||||||
|
|
||||||
AngularFire is the officially supported Angular binding for Firebase. Firebase is a full backend so you don't need servers to build your Angular app.
|
|
||||||
|
|
||||||
<a href="https://github.com/firebase/angularFire" class="button" md-button> Contribute to AngularFire</a>
|
|
||||||
|
|
||||||
~~~
|
|
@ -3,25 +3,22 @@
|
|||||||
Angular is a platform that makes it easy to build applications with the web. Angular combines declarative templates, dependency injection, end to end tooling, and integrated best practices to solve development challenges. Angular empowers developers to build applications that live on the web, mobile, or the desktop
|
Angular is a platform that makes it easy to build applications with the web. Angular combines declarative templates, dependency injection, end to end tooling, and integrated best practices to solve development challenges. Angular empowers developers to build applications that live on the web, mobile, or the desktop
|
||||||
|
|
||||||
<div class="card-container">
|
<div class="card-container">
|
||||||
<a href="generated/live-examples/quickstart/eplnkr.html" target="_blank" class="card"
|
<a href="generated/live-examples/quickstart/eplnkr.html" target="_blank" class="docs-card"
|
||||||
title="Experience Angular in a live coding environment">
|
title="Experience Angular in a live coding environment">
|
||||||
<section>Experience Angular</section>
|
<section>Get a Glimpse of Angular</section>
|
||||||
<p>A quick look at an Angular application.</p>
|
<p>A quick look at an Angular "hello world" application.</p>
|
||||||
<p class="card-footer">Angular in Action</p>
|
<p class="card-footer">Angular in Action</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="card">
|
<div class="docs-card">
|
||||||
<section>Get Going with Angular</section>
|
<section>Get Going with Angular</section>
|
||||||
<p>Get going on your own environment with the Quickstart and Tutorial</p>
|
<p>Get going on your own environment with the Quickstart.</p>
|
||||||
<p class="card-footer center" >
|
<p class="card-footer" >
|
||||||
<a href="guide/quickstart" title="Angular Quickstart">Quickstart</a>
|
<a href="guide/quickstart" title="Angular Quickstart">Quickstart</a>
|
||||||
<a href="tutorial" title="Angular Tutorial">Tutorial</a>
|
|
||||||
</p>
|
</p>
|
||||||
<!--<p class="card-footer"><a href="guide/quickstart">Quickstart</a></p>
|
|
||||||
<p class="card-footer"><a href="guide/tutorial">Tutorial</a></p>-->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href="guide/architecture" class="card" title="Angular Architecture">
|
<a href="guide/architecture" class="docs-card" title="Angular Architecture">
|
||||||
<section>Fundamentals</section>
|
<section>Fundamentals</section>
|
||||||
<p>Learn Angular application fundamentals, starting with an architecture overview.</p>
|
<p>Learn Angular application fundamentals, starting with an architecture overview.</p>
|
||||||
<p class="card-footer">Architecture</p>
|
<p class="card-footer">Architecture</p>
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
<h1 class="no-toc">Events</h1>
|
<header class="marketing-banner">
|
||||||
|
<h1 class="banner-headline no-toc no-anchor">Events</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
<h3>Where we'll be presenting:</h3>
|
|
||||||
<article class="l-content ">
|
<article class="l-content ">
|
||||||
|
<p>Where we'll be presenting:</p>
|
||||||
<table class="is-full-width">
|
<table class="is-full-width">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -11,30 +13,18 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<!-- DEVOXX - UK -->
|
|
||||||
<tr>
|
|
||||||
<th><a href="http://www.devoxx.co.uk/" title="DEVOXX - UK">DEVOXX - UK</a></th>
|
|
||||||
<td>London, United Kingdom</td>
|
|
||||||
<td>May 11, 2017</td>
|
|
||||||
</tr>
|
|
||||||
<!-- Google IO -->
|
|
||||||
<tr>
|
|
||||||
<th><a href="https://events.google.com/io" title="Google IO">Google IO</a></th>
|
|
||||||
<td>Mountain View, California</td>
|
|
||||||
<td>May 17, 2017</td>
|
|
||||||
</tr>
|
|
||||||
<!-- AngleBrackets-->
|
|
||||||
<tr>
|
|
||||||
<th><a href="https://anglebrackets.org" title="AngleBrackets">AngleBrackets</a></th>
|
|
||||||
<td>Orlando, Florida</td>
|
|
||||||
<td>May 21, 2017</td>
|
|
||||||
</tr>
|
|
||||||
<!-- ngJapan -->
|
<!-- ngJapan -->
|
||||||
<tr>
|
<tr>
|
||||||
<th><a href="http://ngjapan.org/" title="ng-Japan">ng-Japan</a></th>
|
<th><a href="http://ngjapan.org/" title="ng-Japan">ng-Japan</a></th>
|
||||||
<td>Tokyo, Japan</td>
|
<td>Tokyo, Japan</td>
|
||||||
<td>June 17, 2017</td>
|
<td>June 17, 2017</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<!-- AngularMix -->
|
||||||
|
<tr>
|
||||||
|
<th><a href="https://angularmix.com/" title="AngularMix">AngularMix</a></th>
|
||||||
|
<td>Universal Studios, Orlando, Florida</td>
|
||||||
|
<td>October 8, 2017</td>
|
||||||
|
</tr>
|
||||||
<!-- ReactiveConf -->
|
<!-- ReactiveConf -->
|
||||||
<tr>
|
<tr>
|
||||||
<th><a href="https://reactiveconf.com/" title="ReactiveConf">ReactiveConf</a></th>
|
<th><a href="https://reactiveconf.com/" title="ReactiveConf">ReactiveConf</a></th>
|
||||||
@ -45,7 +35,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th><a href="http://angularconnect.com" title="AngularConnect">AngularConnect</a></th>
|
<th><a href="http://angularconnect.com" title="AngularConnect">AngularConnect</a></th>
|
||||||
<td>London, United Kingdom</td>
|
<td>London, United Kingdom</td>
|
||||||
<td>Nov. 07, 2017</td>
|
<td>November 07, 2017</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -1,88 +1,113 @@
|
|||||||
<h1 class="no-toc">Features & Benefit</h1>
|
<header class="marketing-banner">
|
||||||
|
<h1 class="banner-headline no-toc no-anchor">Features & Benefits</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
<article class="l-content ">
|
<article class="l-content ">
|
||||||
<div class="flex-center">
|
<div class="flex-center">
|
||||||
<div><h2 class="text-headline">Cross Platform</h2>
|
<div>
|
||||||
<div class="feature-row">
|
<div class="feature-section">
|
||||||
|
<div class="feature-header">
|
||||||
<div class="feature">
|
<div class="text-headline">Cross Platform</div>
|
||||||
<h5>Progressive Web Apps</h5>
|
<img src="../assets/images/icons/feature-icon.svg" height="70px">
|
||||||
<p class="text-body">Use modern web platform capabilities to deliver app-like experiences.
|
|
||||||
High performance, offline, and zero-step installation.</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="feature-row">
|
||||||
|
|
||||||
<div class="feature">
|
<div class="feature">
|
||||||
<h5>Native</h5>
|
<div class="feature-title">Progressive Web Apps</div>
|
||||||
<p class="text-body">Build native mobile apps with strategies from Ionic Framework, NativeScript, and React Native.</p>
|
<p class="text-body">Use modern web platform capabilities to deliver app-like experiences.
|
||||||
</div>
|
High performance, offline, and zero-step installation.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="feature">
|
<div class="feature">
|
||||||
<h5>Desktop</h5>
|
<div class="feature-title">Native</div>
|
||||||
<p class="text-body">Create desktop-installed apps across Mac, Windows, and Linux using the same Angular methods you've learned for the web plus the ability to access native OS APIs.</p>
|
<p class="text-body">Build native mobile apps with strategies from Ionic Framework, NativeScript, and React Native.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature">
|
||||||
|
<div class="feature-title">Desktop</div>
|
||||||
|
<p class="text-body">Create desktop-installed apps across Mac, Windows, and Linux using the same Angular methods you've learned for the web plus the ability to access native OS APIs.</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="text-headline">Speed and Performance</h2>
|
<div class="feature-section">
|
||||||
<div class="feature-row">
|
<div class="feature-header">
|
||||||
|
<div class="text-headline">Speed and Performance</div>
|
||||||
<div class="feature">
|
<img src="../assets/images/icons/feature-icon.svg" height="70px">
|
||||||
<h5>Code Generation</h5>
|
|
||||||
<p class="text-body">Angular turns your templates into code that's highly optimized for today's JavaScript virtual machines, giving you all the benefits of hand-written code with the productivity of a framework.</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="feature-row">
|
||||||
|
|
||||||
<div class="feature">
|
<div class="feature">
|
||||||
<h5>Universal</h5>
|
<div class="feature-title">Code Generation</div>
|
||||||
<p class="text-body">Serve the first view of your application on node.js, .NET, PHP, and other servers for near-instant rendering in just HTML and CSS. Also paves the way for sites that optimize for SEO.</p>
|
<p class="text-body">Angular turns your templates into code that's highly optimized for today's JavaScript virtual machines, giving you all the benefits of hand-written code with the productivity of a framework.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="feature">
|
<div class="feature">
|
||||||
<h5>Code Splitting</h5>
|
<div class="feature-title">Universal</div>
|
||||||
<p class="text-body">Angular apps load quickly with the new Component Router, which delivers automatic code-splitting so users only load code required to render the view they request.</p>
|
<p class="text-body">Serve the first view of your application on node.js, .NET, PHP, and other servers for near-instant rendering in just HTML and CSS. Also paves the way for sites that optimize for SEO.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
<div class="feature">
|
||||||
|
<div class="feature-title">Code Splitting</div>
|
||||||
<h2 class="text-headline">Productivity</h2>
|
<p class="text-body">Angular apps load quickly with the new Component Router, which delivers automatic code-splitting so users only load code required to render the view they request.</p>
|
||||||
<div class="feature-row">
|
</div>
|
||||||
|
|
||||||
<div class="feature">
|
|
||||||
<h5>Templates</h5>
|
|
||||||
<p class="text-body">Quickly create UI views with simple and powerful template syntax.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="feature">
|
|
||||||
<h5>Angular CLI</h5>
|
|
||||||
<p class="text-body">Command line tools: start building fast, add components and tests, then instantly deploy.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="feature">
|
|
||||||
<h5>IDEs</h5>
|
|
||||||
<p class="text-body">Get intelligent code completion, instant errors, and other feedback in popular editors and IDEs.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="text-headline">Full Development Story</h2>
|
<div class="feature-section">
|
||||||
<div class="feature-row">
|
<div class="feature-header">
|
||||||
|
<div class="text-headline">Productivity</div>
|
||||||
<div class="feature">
|
<img src="../assets/images/icons/feature-icon.svg" height="70px">
|
||||||
<h5>Testing</h5>
|
|
||||||
<p class="text-body">With Karma for unit tests, you can know if you've broken things every time you save. And Protractor makes your scenario tests run faster and in a stable manner.</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="feature-row">
|
||||||
|
|
||||||
<div class="feature">
|
<div class="feature">
|
||||||
<h5>Animation</h5>
|
<div class="feature-title">Templates</div>
|
||||||
<p class="text-body">Create high-performance, complex choreographies and animation timelines with very little code through Angular's intuitive API.</p>
|
<p class="text-body">Quickly create UI views with simple and powerful template syntax.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature">
|
||||||
|
<div class="feature-title">Angular CLI</div>
|
||||||
|
<p class="text-body">Command line tools: start building fast, add components and tests, then instantly deploy.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature">
|
||||||
|
<div class="feature-title">IDEs</div>
|
||||||
|
<p class="text-body">Get intelligent code completion, instant errors, and other feedback in popular editors and IDEs.</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="feature">
|
<div class="feature-section">
|
||||||
<h5>Accessibility</h5>
|
<div class="feature-header">
|
||||||
<p class="text-body">Create accessible applications with ARIA-enabled components, developer guides, and built-in a11y test infrastructure.</p>
|
<div class="text-headline">Full Development Story</div>
|
||||||
|
<img src="../assets/images/icons/feature-icon.svg" height="70px">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="feature-row">
|
||||||
|
|
||||||
|
<div class="feature">
|
||||||
|
<div class="feature-title">Testing</div>
|
||||||
|
<p class="text-body">With Karma for unit tests, you can know if you've broken things every time you save. And Protractor makes your scenario tests run faster and in a stable manner.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature">
|
||||||
|
<div class="feature-title">Animation</div>
|
||||||
|
<p class="text-body">Create high-performance, complex choreographies and animation timelines with very little code through Angular's intuitive API.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="feature">
|
||||||
|
<div class="feature-title">Accessibility</div>
|
||||||
|
<p class="text-body">Create accessible applications with ARIA-enabled components, developer guides, and built-in a11y test infrastructure.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="cta-bar"><a href="guide/quickstart" md-button="md-button" class="button button-large button-shield mat-raised mat-primary">Get Started</a></div>
|
<div class="cta-bar announcement-bar">
|
||||||
|
<button class="button">
|
||||||
|
<a href="guide/quickstart">Get Started</a>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
</article>
|
</article>
|
||||||
|
@ -1,46 +1,67 @@
|
|||||||
<header class="background-sky l-relative">
|
<!--FULL HEADER BLOCK-->
|
||||||
|
<header class="l-relative">
|
||||||
|
|
||||||
<div class="hero background-superhero-paper is-large">
|
<!--BACKGROUND IMAGE-->
|
||||||
|
<div class="background-sky hero"></div>
|
||||||
|
|
||||||
<img src="assets/images/logos/angular/angular.svg" class="hero-logo"/>
|
<!--INTRO SECTION -->
|
||||||
<h1 class="text-headline no-toc">One framework.<br>Mobile & desktop.</h1>
|
<section id="intro">
|
||||||
<a href="guide/quickstart" md-button="md-button" class="hero-cta mat-raised button button-large button-plain">Get Started</a>
|
|
||||||
|
<!-- LOGO -->
|
||||||
|
<div class="hero-logo">
|
||||||
|
<img src="assets/images/logos/angular/angular.svg"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CONTAINER -->
|
||||||
|
<div class="homepage-container">
|
||||||
|
<!-- container content starts -->
|
||||||
|
|
||||||
|
<div class="hero-headline no-toc">One framework.<br>Mobile & desktop.</div>
|
||||||
|
<button class="button button-large button-plain hero-cta">
|
||||||
|
<a href="guide/quickstart">Get Started</a>
|
||||||
|
</button>
|
||||||
|
</div><!-- CONTAINER END -->
|
||||||
|
</section>
|
||||||
|
|
||||||
<announcement-bar class="announcement-bar">
|
|
||||||
<div class="announcement-bar-slide cleafix is-visible">
|
|
||||||
<img src="assets/images/logos/angular/angular-banner-logo-grey.png" width="64"/>
|
|
||||||
<p>Angular v4.0 is out! Smaller, faster, no biggie...</p>
|
|
||||||
<a href="http://angularjs.blogspot.com/2017/03/angular-400-now-available.html" target="_blank" class="button mat-button">Learn More</a>
|
|
||||||
</div>
|
|
||||||
</announcement-bar>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<article class="l-content ">
|
<article class="l-content">
|
||||||
|
<div class="home-rows">
|
||||||
|
|
||||||
<div class="home-rows"><!-- Group 1-->
|
<!--Announcement Bar-->
|
||||||
<div layout="row" layout-xs="column" class="home-row">
|
<div class="homepage-container">
|
||||||
|
<div class="announcement-bar">
|
||||||
|
<img src="generated/images/marketing/angular-mix.png" height="40" width="151">
|
||||||
|
<p>Join us at our newest event, October 2017</p>
|
||||||
|
<button class="button">
|
||||||
|
<a href="https://angularmix.com/">Learn More</a>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Group 1-->
|
||||||
|
<div layout="row" layout-xs="column" class="home-row homepage-container">
|
||||||
<div class="promo-img-container promo-1">
|
<div class="promo-img-container promo-1">
|
||||||
<div>
|
<div>
|
||||||
<img height="222" src="assets/images/home/responsive-framework.svg" alt="responsive framework">
|
<img height="222" width="340" src="assets/images/home/responsive-framework.svg" alt="responsive framework">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-container">
|
<div class="text-container">
|
||||||
<div class="text-block promo-1-desc l-pad-top-2">
|
<div class="text-block promo-1-desc l-pad-top-2">
|
||||||
<h2 class="text-headline">Develop Across All Platforms</h2>
|
<div class="text-headline">Develop Across All Platforms</div>
|
||||||
<p class="text-body">Learn one way to build applications with Angular and reuse your code and abilities to build apps for any deployment target. For web, mobile web, native mobile and native desktop.
|
<p class="text-body">Learn one way to build applications with Angular and reuse your code and abilities to build apps for any deployment target. For web, mobile web, native mobile and native desktop.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div><!-- Group 2-->
|
</div>
|
||||||
|
<hr>
|
||||||
|
<!-- Group 2-->
|
||||||
<div layout="row" layout-xs="column" class="home-row">
|
<div layout="row" layout-xs="column" class="home-row">
|
||||||
<div class="text-container">
|
<div class="text-container">
|
||||||
<div class="text-block">
|
<div class="text-block">
|
||||||
<h2 class="text-headline">Speed & Performance</h2>
|
<div class="text-headline">Speed & Performance</div>
|
||||||
<p class="text-body">Achieve the maximum speed possible on the Web Platform today, and take it further, via Web Workers and server-side rendering.</p>
|
<p class="text-body">Achieve the maximum speed possible on the Web Platform today, and take it further, via Web Workers and server-side rendering.</p>
|
||||||
<p class="text-body">Angular puts you in control over scalability. Meet huge data requirements by building data models on RxJS, Immutable.js or another push-model.</p>
|
<p class="text-body">Angular puts you in control over scalability. Meet huge data requirements by building data models on RxJS, Immutable.js or another push-model.</p>
|
||||||
</div>
|
</div>
|
||||||
@ -48,11 +69,13 @@
|
|||||||
|
|
||||||
<div class="promo-img-container promo-2">
|
<div class="promo-img-container promo-2">
|
||||||
<div>
|
<div>
|
||||||
<img height="222" src="assets/images/home/speed-performance.svg" alt="speed and performance">
|
<img height="222" width="323" src="assets/images/home/speed-performance.svg" alt="speed and performance">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div><!-- Group 3-->
|
</div>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<!-- Group 3-->
|
||||||
<div layout="row" layout-xs="column" class="home-row">
|
<div layout="row" layout-xs="column" class="home-row">
|
||||||
<div class="promo-img-container promo-3">
|
<div class="promo-img-container promo-3">
|
||||||
<div><img src="assets/images/home/joyful-development.png" alt="IDE example"></div>
|
<div><img src="assets/images/home/joyful-development.png" alt="IDE example"></div>
|
||||||
@ -60,18 +83,21 @@
|
|||||||
|
|
||||||
<div class="text-container">
|
<div class="text-container">
|
||||||
<div class="text-block promo-3-desc">
|
<div class="text-block promo-3-desc">
|
||||||
<h2 class="text-headline">Incredible Tooling</h2>
|
<div class="text-headline">Incredible Tooling</div>
|
||||||
<p class="text-body">Build features quickly with simple, declarative templates. Extend the template language with your own components and use a wide array of existing components. Get immediate Angular-specific help and feedback with nearly every IDE and editor. All this comes together so you can focus on building amazing apps rather than trying to make the code work.
|
<p class="text-body">Build features quickly with simple, declarative templates. Extend the template language with your own components and use a wide array of existing components. Get immediate Angular-specific help and feedback with nearly every IDE and editor. All this comes together so you can focus on building amazing apps rather than trying to make the code work.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div><!-- Group 4-->
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<!-- Group 4-->
|
||||||
<div layout="row" layout-xs="column" class="home-row">
|
<div layout="row" layout-xs="column" class="home-row">
|
||||||
|
|
||||||
<div class="text-container">
|
<div class="text-container">
|
||||||
<div class="text-block l-pad-top-2">
|
<div class="text-block l-pad-top-2">
|
||||||
<h2 class="text-headline">Loved by Millions</h2>
|
<div class="text-headline">Loved by Millions</div>
|
||||||
<p class="text-body">From prototype through global deployment, Angular delivers the productivity and scalable infrastructure that supports Google's largest applications.</p>
|
<p class="text-body">From prototype through global deployment, Angular delivers the productivity and scalable infrastructure that supports Google's largest applications.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -83,9 +109,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="cta-bar">
|
<!-- CTA CARDS -->
|
||||||
<a href="/guide/quickstart" md-button="md-button" class="button button-large button-shield mat-raised mat-primary">Get Started</a>
|
<div layout="row" layout-xs="column" class="home-row">
|
||||||
</div>
|
|
||||||
|
<a href="guide/quickstart">
|
||||||
|
<div class="card">
|
||||||
|
<img src="../assets/images/icons/code-icon.svg" height="70px">
|
||||||
|
<div class="card-text-container">
|
||||||
|
<div class="text-headline">Get Started</div>
|
||||||
|
<p>Start building your Angular application.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div> <!-- end of home rows -->
|
||||||
|
|
||||||
</article>
|
</article>
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
<h1 class="no-toc">Press Kit</h1>
|
<header class="marketing-banner">
|
||||||
<div class="presskit-container l-space-neg-top-8">
|
<h1 class="banner-headline no-toc no-anchor">Press kit</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<article class="presskit-container">
|
||||||
<div class="presskit-row">
|
<div class="presskit-row">
|
||||||
<div class="presskit-inner">
|
<div class="presskit-inner">
|
||||||
<div>
|
<div>
|
||||||
@ -189,4 +192,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</article>
|
||||||
|
@ -1,2 +1,7 @@
|
|||||||
<h1 class="no-toc">Explore Angular Resources</h1>
|
<header class="marketing-banner">
|
||||||
<aio-resource-list></aio-resource-list>
|
<h1 class="banner-headline no-toc no-anchor">Explore Angular Resources</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<aio-resource-list></aio-resource-list>
|
||||||
|
</article>
|
||||||
|
@ -15,6 +15,10 @@
|
|||||||
{
|
{
|
||||||
"url": "events",
|
"url": "events",
|
||||||
"title": "Events"
|
"title": "Events"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://blog.angularjs.org/",
|
||||||
|
"title": "Blog"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
@ -33,6 +37,10 @@
|
|||||||
{
|
{
|
||||||
"url": "events",
|
"url": "events",
|
||||||
"title": "Events"
|
"title": "Events"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://blog.angularjs.org/",
|
||||||
|
"title": "Blog"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -47,21 +55,10 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
"url": "guide/quickstart",
|
||||||
"title": "Getting Started",
|
"title": "Getting Started",
|
||||||
"tooltip": "A gentle introduction to Angular.",
|
"tooltip": "A gentle introduction to Angular."
|
||||||
"children": [
|
},
|
||||||
{
|
|
||||||
"url": "guide/quickstart",
|
|
||||||
"title": "Basic Quickstart",
|
|
||||||
"tooltip": "A quick look at an Angular app without tooling."
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"url": "guide/cli-quickstart",
|
|
||||||
"title": "CLI Quickstart",
|
|
||||||
"tooltip": "A quick look at an Angular app built with the Angular CLI."
|
|
||||||
}
|
|
||||||
]},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"title": "Tutorial",
|
"title": "Tutorial",
|
||||||
@ -109,43 +106,14 @@
|
|||||||
"title": "Fundamentals",
|
"title": "Fundamentals",
|
||||||
"tooltip": "The fundamentals of Angular",
|
"tooltip": "The fundamentals of Angular",
|
||||||
"children": [
|
"children": [
|
||||||
|
|
||||||
{
|
|
||||||
"url": "guide/animations",
|
|
||||||
"title": "Animations",
|
|
||||||
"tooltip": "A guide to Angular's animation system."
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"title": "Angular Modules",
|
|
||||||
"tooltip": "Learn how directives modify the layout and behavior of elements.",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"url": "guide/appmodule",
|
|
||||||
"title": "The Root AppModule",
|
|
||||||
"tooltip": "Tell Angular how to construct and bootstrap the app in the root \"AppModule\"."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/ngmodule",
|
|
||||||
"title": "NgModule",
|
|
||||||
"tooltip": "Define application modules with @NgModule."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/ngmodule-faq",
|
|
||||||
"title": "Angular Module FAQs",
|
|
||||||
"tooltip": "Answers to frequently asked questions about @NgModule."
|
|
||||||
}
|
|
||||||
]},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"url": "guide/architecture",
|
"url": "guide/architecture",
|
||||||
"title": "Architecture",
|
"title": "Architecture",
|
||||||
"tooltip": "The basic building blocks of Angular applications."
|
"tooltip": "The basic building blocks of Angular applications."
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"title": "Components",
|
"title": "Template & Data Binding",
|
||||||
"tooltip": "Components present information to users and collect their input.",
|
"tooltip": "Template & Data Binding",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"url": "guide/displaying-data",
|
"url": "guide/displaying-data",
|
||||||
@ -167,23 +135,86 @@
|
|||||||
"title": "Component Interaction",
|
"title": "Component Interaction",
|
||||||
"tooltip": "Share information between different directives and components."
|
"tooltip": "Share information between different directives and components."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"url": "guide/component-styles",
|
|
||||||
"title": "Component Styles",
|
|
||||||
"tooltip": "Learn how to apply CSS styles to components."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"url": "guide/dynamic-component-loader",
|
"url": "guide/dynamic-component-loader",
|
||||||
"title": "Dynamic Components",
|
"title": "Dynamic Components",
|
||||||
"tooltip": "Load components dynamically."
|
"tooltip": "Load components dynamically."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/attribute-directives",
|
||||||
|
"title": "Attribute Directives",
|
||||||
|
"tooltip": "Attribute directives attach behavior to elements."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/structural-directives",
|
||||||
|
"title": "Structural Directives",
|
||||||
|
"tooltip": "Structural directives manipulate the layout of the page."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/pipes",
|
"url": "guide/pipes",
|
||||||
"title": "Pipes",
|
"title": "Pipes",
|
||||||
"tooltip": "Pipes transform displayed values within a template."
|
"tooltip": "Pipes transform displayed values within a template."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/animations",
|
||||||
|
"title": "Animations",
|
||||||
|
"tooltip": "A guide to Angular's animation system."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Forms",
|
||||||
|
"tooltip": "Angular Forms",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"url": "guide/user-input",
|
||||||
|
"title": "User Input",
|
||||||
|
"tooltip": "User input triggers DOM events. We listen to those events with event bindings that funnel updated values back into our components and models."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/forms",
|
||||||
|
"title": "Template-driven Forms",
|
||||||
|
"tooltip": "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."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/form-validation",
|
||||||
|
"title": "Form Validation",
|
||||||
|
"tooltip": "Validate user's form entries."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/reactive-forms",
|
||||||
|
"title": "Reactive Forms",
|
||||||
|
"tooltip": "Create a reactive form using FormBuilder, groups, and arrays."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/dynamic-form",
|
||||||
|
"title": "Dynamic forms",
|
||||||
|
"tooltip": "Render dynamic forms with FormGroup."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"url": "guide/bootstrapping",
|
||||||
|
"title": "Bootstrapping",
|
||||||
|
"tooltip": "Tell Angular how to construct and bootstrap the app in the root \"AppModule\"."
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"title": "NgModules",
|
||||||
|
"tooltip": "Learn how to use NgModules to make your apps efficient.",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"url": "guide/ngmodule",
|
||||||
|
"title": "NgModules",
|
||||||
|
"tooltip": "Define application modules with @NgModule."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/ngmodule-faq",
|
||||||
|
"title": "NgModule FAQs",
|
||||||
|
"tooltip": "Answers to frequently asked questions about @NgModule."
|
||||||
|
}
|
||||||
|
]},
|
||||||
|
|
||||||
{
|
{
|
||||||
"title": "Dependency Injection",
|
"title": "Dependency Injection",
|
||||||
@ -207,73 +238,26 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
"title": "Directives",
|
|
||||||
"tooltip": "Learn how directives modify the layout and behavior of elements.",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"url": "guide/attribute-directives",
|
|
||||||
"title": "Attribute Directives",
|
|
||||||
"tooltip": "Attribute directives attach behavior to elements."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/structural-directives",
|
|
||||||
"title": "Structural Directives",
|
|
||||||
"tooltip": "Structural directives manipulate the layout of the page."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"url": "guide/http",
|
"url": "guide/http",
|
||||||
"title": "HTTP",
|
"title": "Server Communication",
|
||||||
"tooltip": "Use HTTP to talk to a remote server."
|
"tooltip": "Use HTTP to talk to a remote server."
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
"url": "guide/i18n",
|
|
||||||
"title": "Internationalization (i18n)",
|
|
||||||
"tooltip": "Translate the app's template text into multiple languages."
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"url": "guide/router",
|
"url": "guide/router",
|
||||||
"title": "Routing & Navigation",
|
"title": "Routing & Navigation",
|
||||||
"tooltip": "Discover the basics of screen navigation with the Angular Router."
|
"tooltip": "Discover the basics of screen navigation with the Angular Router."
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"title": "User Input",
|
"url": "guide/testing",
|
||||||
"tooltip": "User Input",
|
"title": "Testing",
|
||||||
"children": [
|
"tooltip": "Techniques and practices for testing an Angular app."
|
||||||
{
|
},
|
||||||
"url": "guide/user-input",
|
{
|
||||||
"title": "User Input",
|
"url": "guide/cheatsheet",
|
||||||
"tooltip": "User input triggers DOM events. We listen to those events with event bindings that funnel updated values back into our components and models."
|
"title": "Cheat Sheet",
|
||||||
},
|
"tooltip": "A quick guide to common Angular coding techniques."
|
||||||
|
}
|
||||||
{
|
|
||||||
"url": "guide/forms",
|
|
||||||
"title": "Template-driven Forms",
|
|
||||||
"tooltip": "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."
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"url": "guide/reactive-forms",
|
|
||||||
"title": "Reactive Forms",
|
|
||||||
"tooltip": "Create a reactive form using FormBuilder, groups, and arrays."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/form-validation",
|
|
||||||
"title": "Form Validation",
|
|
||||||
"tooltip": "Validate user's form entries."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "guide/dynamic-form",
|
|
||||||
"title": "Dynamic forms",
|
|
||||||
"tooltip": "Render dynamic forms with FormGroup."
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
]},
|
]},
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -281,18 +265,21 @@
|
|||||||
"tooltip": "Techniques for putting Angular to work in your environment",
|
"tooltip": "Techniques for putting Angular to work in your environment",
|
||||||
"children": [
|
"children": [
|
||||||
|
|
||||||
|
{
|
||||||
|
"url": "guide/i18n",
|
||||||
|
"title": "Internationalization (i18n)",
|
||||||
|
"tooltip": "Translate the app's template text into multiple languages."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/security",
|
"url": "guide/security",
|
||||||
"title": "Security",
|
"title": "Security",
|
||||||
"tooltip": "Developing for content security in Angular applications."
|
"tooltip": "Developing for content security in Angular applications."
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"url": "guide/set-document-title",
|
"url": "guide/ts-to-js",
|
||||||
"title": "Set the document tab title",
|
"title": "TypeScript to JavaScript",
|
||||||
"tooltip": "Set the browser tab title dynamically with the Angular Title service"
|
"tooltip": "Convert Angular TypeScript examples into ES6 and ES5 JavaScript."
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"title": "Setup & Deployment",
|
"title": "Setup & Deployment",
|
||||||
"tooltip": "Setup and Deployment",
|
"tooltip": "Setup and Deployment",
|
||||||
@ -336,31 +323,19 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
"url": "guide/testing",
|
|
||||||
"title": "Testing",
|
|
||||||
"tooltip": "Techniques and practices for testing an Angular app."
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"url": "guide/ts-to-js",
|
|
||||||
"title": "TypeScript to JavaScript",
|
|
||||||
"tooltip": "Convert Angular TypeScript examples into ES6 and ES5 JavaScript."
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"title": "Upgrading",
|
"title": "Upgrading",
|
||||||
"tooltip": "Incrementally upgrade an AngularJS application to Angular.",
|
"tooltip": "Incrementally upgrade an AngularJS application to Angular.",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
|
||||||
"url": "guide/ajs-quick-reference",
|
|
||||||
"title": "AngularJS to Angular",
|
|
||||||
"tooltip": "Learn how AngularJS concepts and techniques map to Angular."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"url": "guide/upgrade",
|
"url": "guide/upgrade",
|
||||||
"title": "Upgrading from AngularJS",
|
"title": "Upgrading from AngularJS",
|
||||||
"tooltip": "Incrementally upgrade an AngularJS application to Angular."
|
"tooltip": "Incrementally upgrade an AngularJS application to Angular."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/ajs-quick-reference",
|
||||||
|
"title": "AngularJS to Angular",
|
||||||
|
"tooltip": "Learn how AngularJS concepts and techniques map to Angular."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -370,11 +345,15 @@
|
|||||||
"title": "Visual Studio 2015 QuickStart",
|
"title": "Visual Studio 2015 QuickStart",
|
||||||
"tooltip": "Use Visual Studio 2015 with the QuickStart files."
|
"tooltip": "Use Visual Studio 2015 with the QuickStart files."
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"url": "guide/webpack",
|
"url": "guide/styleguide",
|
||||||
"title": "Webpack: An Introduction",
|
"title": "Style Guide",
|
||||||
"tooltip": "Create Angular applications with Webpack based tooling."
|
"tooltip": "Write Angular with style."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "guide/glossary",
|
||||||
|
"title": "Glossary",
|
||||||
|
"tooltip": "Brief definitions of the most important words in the Angular vocabulary."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -383,36 +362,6 @@
|
|||||||
"title": "API",
|
"title": "API",
|
||||||
"tooltip": "Details of the Angular classes and values.",
|
"tooltip": "Details of the Angular classes and values.",
|
||||||
"url": "api"
|
"url": "api"
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"title": "References",
|
|
||||||
"tooltip": "References on Angular usage and style.",
|
|
||||||
"children": [
|
|
||||||
{
|
|
||||||
"url": "guide/change-log",
|
|
||||||
"title": "Change Log",
|
|
||||||
"tooltip": "An annotated history of recent documentation improvements."
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"url": "guide/cheatsheet",
|
|
||||||
"title": "Cheat Sheet",
|
|
||||||
"tooltip": "A quick guide to common Angular coding techniques."
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"url": "guide/glossary",
|
|
||||||
"title": "Glossary",
|
|
||||||
"tooltip": "Brief definitions of the most important words in the Angular vocabulary."
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"url": "guide/styleguide",
|
|
||||||
"title": "Style Guide",
|
|
||||||
"tooltip": "Write Angular with style."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
@ -434,6 +383,11 @@
|
|||||||
"url": "presskit",
|
"url": "presskit",
|
||||||
"title": "Press Kit",
|
"title": "Press Kit",
|
||||||
"tooltip": "Press contacts, logos, and branding."
|
"tooltip": "Press contacts, logos, and branding."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://blog.angularjs.org/",
|
||||||
|
"title": "Blog",
|
||||||
|
"tooltip": "Angular Blog"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -1,12 +1,5 @@
|
|||||||
@title
|
|
||||||
Tutorial: Tour of Heroes
|
|
||||||
|
|
||||||
@intro
|
|
||||||
The Tour of Heroes tutorial takes you through the steps of creating an Angular application in TypeScript.
|
|
||||||
|
|
||||||
@description
|
|
||||||
|
|
||||||
|
|
||||||
|
# Tutorial: Tour of Heroes
|
||||||
|
|
||||||
The grand plan for this tutorial is to build an app that helps a staffing agency manage its stable of heroes.
|
The grand plan for this tutorial is to build an app that helps a staffing agency manage its stable of heroes.
|
||||||
|
|
||||||
@ -34,7 +27,7 @@ When you're done with this tutorial, the app will look like this <live-example n
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
## The end game
|
## What you'll build
|
||||||
|
|
||||||
Here's a visual idea of where this tutorial leads, beginning with the "Dashboard"
|
Here's a visual idea of where this tutorial leads, beginning with the "Dashboard"
|
||||||
view and the most heroic heroes:
|
view and the most heroic heroes:
|
||||||
|
@ -1200,7 +1200,7 @@ These correspond to the full set of master styles that you installed earlier dur
|
|||||||
Here's an excerpt:
|
Here's an excerpt:
|
||||||
|
|
||||||
|
|
||||||
<code-example path="toh-pt5/src/styles.css" region="toh" title="src/styles.css (excerpt)">
|
<code-example path="toh-pt5/src/styles.1.css" title="src/styles.css (excerpt)">
|
||||||
|
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
|
@ -22,14 +22,12 @@ describe('site App', function() {
|
|||||||
|
|
||||||
// navigate to a different page
|
// navigate to a different page
|
||||||
page.getLink('features').click();
|
page.getLink('features').click();
|
||||||
expect(page.getDocViewerText()).toMatch(/Features/i);
|
expect(page.getDocViewerText()).toMatch(/Progressive web apps/i);
|
||||||
|
|
||||||
// Show the menu
|
// Show the menu
|
||||||
page.docsMenuLink.click();
|
page.docsMenuLink.click();
|
||||||
|
|
||||||
// Open the tutorial header
|
// Tutorial folder should still be expanded because this test runs in wide mode
|
||||||
page.getNavItem(/tutorial/i).click();
|
|
||||||
|
|
||||||
// Navigate to the tutorial introduction via a link in the sidenav
|
// Navigate to the tutorial introduction via a link in the sidenav
|
||||||
page.getNavItem(/introduction/i).click();
|
page.getNavItem(/introduction/i).click();
|
||||||
expect(page.getDocViewerText()).toMatch(/Tutorial: Tour of Heroes/i);
|
expect(page.getDocViewerText()).toMatch(/Tutorial: Tour of Heroes/i);
|
||||||
@ -41,11 +39,23 @@ describe('site App', function() {
|
|||||||
expect(page.getInnerHtml(codeExample)).toContain('<h1>Tour of Heroes</h1>');
|
expect(page.getInnerHtml(codeExample)).toContain('<h1>Tour of Heroes</h1>');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('api-docs', () => {
|
describe('scrolling to the top', () => {
|
||||||
it('should show a link to github', () => {
|
it('should scroll to the top when navigating to another page', () => {
|
||||||
page.navigateTo('api/common/NgClass');
|
page.navigateTo('guide/docs');
|
||||||
expect(page.ghLink.getAttribute('href'))
|
page.scrollToBottom();
|
||||||
.toMatch(/https:\/\/github.com\/angular\/angular\/tree\/.+\/packages\/common\/src\/directives\/ng_class\.ts/);
|
page.getScrollTop().then(scrollTop => expect(scrollTop).toBeGreaterThan(0));
|
||||||
|
|
||||||
|
page.navigateTo('guide/api');
|
||||||
|
page.getScrollTop().then(scrollTop => expect(scrollTop).toBe(0));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should scroll to the top when navigating to the same page', () => {
|
||||||
|
page.navigateTo('guide/docs');
|
||||||
|
page.scrollToBottom();
|
||||||
|
page.getScrollTop().then(scrollTop => expect(scrollTop).toBeGreaterThan(0));
|
||||||
|
|
||||||
|
page.navigateTo('guide/docs');
|
||||||
|
page.getScrollTop().then(scrollTop => expect(scrollTop).toBe(0));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -42,6 +42,14 @@ export class SitePage {
|
|||||||
return browser.executeScript('return arguments[0].innerHTML;', element);
|
return browser.executeScript('return arguments[0].innerHTML;', element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getScrollTop() {
|
||||||
|
return browser.executeScript('return window.pageYOffset');
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToBottom() {
|
||||||
|
return browser.executeScript('window.scrollTo(0, document.body.scrollHeight)');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace the ambient Google Analytics tracker with homebrew spy
|
* Replace the ambient Google Analytics tracker with homebrew spy
|
||||||
* don't send commands to GA during e2e testing!
|
* don't send commands to GA during e2e testing!
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "yarn check-env && ng",
|
"ng": "yarn check-env && ng",
|
||||||
"start": "yarn check-env && ng serve",
|
"start": "yarn check-env && ng serve",
|
||||||
"build": "yarn check-env && yarn setup && ng build -prod -sm && yarn sw-manifest && yarn sw-copy",
|
"build": "yarn check-env && yarn setup && ng build -prod -sm -vc=false && yarn sw-manifest && yarn sw-copy",
|
||||||
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint",
|
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint",
|
||||||
"test": "yarn check-env && ng test",
|
"test": "yarn check-env && ng test",
|
||||||
"pree2e": "yarn ~~update-webdriver",
|
"pree2e": "yarn ~~update-webdriver",
|
||||||
@ -35,22 +35,22 @@
|
|||||||
"sw-manifest": "ngu-sw-manifest --dist dist --in ngsw-manifest.json --out dist/ngsw-manifest.json",
|
"sw-manifest": "ngu-sw-manifest --dist dist --in ngsw-manifest.json --out dist/ngsw-manifest.json",
|
||||||
"sw-copy": "cp node_modules/@angular/service-worker/bundles/worker-basic.min.js dist/",
|
"sw-copy": "cp node_modules/@angular/service-worker/bundles/worker-basic.min.js dist/",
|
||||||
"postinstall": "node tools/cli-patches/patch.js",
|
"postinstall": "node tools/cli-patches/patch.js",
|
||||||
"build-ie-polyfills": "webpack -p src/ie-polyfills.js src/generated/ie-polyfills.min.js"
|
"build-ie-polyfills": "node node_modules/webpack/bin/webpack.js -p src/ie-polyfills.js src/generated/ie-polyfills.min.js"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "next",
|
"@angular/animations": "4.2.1",
|
||||||
"@angular/common": "next",
|
"@angular/common": "4.2.1",
|
||||||
"@angular/compiler": "next",
|
"@angular/compiler": "4.2.1",
|
||||||
"@angular/core": "next",
|
"@angular/core": "4.2.1",
|
||||||
"@angular/forms": "next",
|
"@angular/forms": "4.2.1",
|
||||||
"@angular/http": "next",
|
"@angular/http": "4.2.1",
|
||||||
"@angular/material": "^2.0.0-beta.3",
|
"@angular/material": "^2.0.0-beta.3",
|
||||||
"@angular/platform-browser": "next",
|
"@angular/platform-browser": "4.2.1",
|
||||||
"@angular/platform-browser-dynamic": "next",
|
"@angular/platform-browser-dynamic": "4.2.1",
|
||||||
"@angular/platform-server": "next",
|
"@angular/platform-server": "4.2.1",
|
||||||
"@angular/router": "next",
|
"@angular/router": "4.2.1",
|
||||||
"@angular/service-worker": "^1.0.0-beta.12",
|
"@angular/service-worker": "^1.0.0-beta.15",
|
||||||
"classlist.js": "^1.1.20150312",
|
"classlist.js": "^1.1.20150312",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
"ng-pwa-tools": "^0.0.10",
|
"ng-pwa-tools": "^0.0.10",
|
||||||
@ -62,8 +62,8 @@
|
|||||||
"zone.js": "^0.8.4"
|
"zone.js": "^0.8.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/cli": "^1.1.0-rc.0",
|
"@angular/cli": "angular/cli-builds#webpack-next",
|
||||||
"@angular/compiler-cli": "next",
|
"@angular/compiler-cli": "4.2.1",
|
||||||
"@types/jasmine": "^2.5.47",
|
"@types/jasmine": "^2.5.47",
|
||||||
"@types/node": "~6.0.60",
|
"@types/node": "~6.0.60",
|
||||||
"archiver": "^1.3.0",
|
"archiver": "^1.3.0",
|
||||||
@ -72,7 +72,7 @@
|
|||||||
"concurrently": "^3.4.0",
|
"concurrently": "^3.4.0",
|
||||||
"cross-spawn": "^5.1.0",
|
"cross-spawn": "^5.1.0",
|
||||||
"dgeni": "^0.4.7",
|
"dgeni": "^0.4.7",
|
||||||
"dgeni-packages": "^0.19.0",
|
"dgeni-packages": "^0.19.1",
|
||||||
"entities": "^1.1.1",
|
"entities": "^1.1.1",
|
||||||
"eslint": "^3.19.0",
|
"eslint": "^3.19.0",
|
||||||
"eslint-plugin-jasmine": "^2.2.0",
|
"eslint-plugin-jasmine": "^2.2.0",
|
||||||
@ -83,15 +83,16 @@
|
|||||||
"hast-util-to-string": "^1.0.0",
|
"hast-util-to-string": "^1.0.0",
|
||||||
"html": "^1.0.0",
|
"html": "^1.0.0",
|
||||||
"http-server": "^0.9.0",
|
"http-server": "^0.9.0",
|
||||||
|
"ignore": "^3.3.3",
|
||||||
"image-size": "^0.5.1",
|
"image-size": "^0.5.1",
|
||||||
"jasmine-core": "~2.5.2",
|
"jasmine-core": "^2.6.3",
|
||||||
"jasmine-spec-reporter": "~3.2.0",
|
"jasmine-spec-reporter": "^4.1.0",
|
||||||
"jsdom": "^9.12.0",
|
"jsdom": "^9.12.0",
|
||||||
"karma": "~1.4.1",
|
"karma": "^1.7.0",
|
||||||
"karma-chrome-launcher": "~2.0.0",
|
"karma-chrome-launcher": "^2.1.1",
|
||||||
"karma-cli": "~1.0.1",
|
"karma-cli": "^1.0.1",
|
||||||
"karma-coverage-istanbul-reporter": "^0.2.0",
|
"karma-coverage-istanbul-reporter": "^1.3.0",
|
||||||
"karma-jasmine": "~1.1.0",
|
"karma-jasmine": "^1.1.0",
|
||||||
"karma-jasmine-html-reporter": "^0.2.2",
|
"karma-jasmine-html-reporter": "^0.2.2",
|
||||||
"lighthouse": "^1.6.3",
|
"lighthouse": "^1.6.3",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.4",
|
||||||
@ -106,12 +107,12 @@
|
|||||||
"tree-kill": "^1.1.0",
|
"tree-kill": "^1.1.0",
|
||||||
"ts-node": "~2.0.0",
|
"ts-node": "~2.0.0",
|
||||||
"tslint": "~4.5.0",
|
"tslint": "~4.5.0",
|
||||||
"typescript": "2.2.0",
|
"typescript": "2.3.2",
|
||||||
|
"unist-util-filter": "^0.2.1",
|
||||||
"unist-util-source": "^1.0.1",
|
"unist-util-source": "^1.0.1",
|
||||||
"unist-util-visit": "^1.1.1",
|
"unist-util-visit": "^1.1.1",
|
||||||
"vrsource-tslint-rules": "^4.0.1",
|
"vrsource-tslint-rules": "^4.0.1",
|
||||||
"watchr": "^3.0.1",
|
"watchr": "^3.0.1",
|
||||||
"webpack": "^2.5.1",
|
|
||||||
"yargs": "^7.0.2"
|
"yargs": "^7.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,41 +1,39 @@
|
|||||||
|
<div id="top-of-page"></div>
|
||||||
|
|
||||||
<div *ngIf="isFetching" class="progress-bar-container">
|
<div *ngIf="isFetching" class="progress-bar-container">
|
||||||
<md-progress-bar mode="indeterminate" color="warn"></md-progress-bar>
|
<md-progress-bar mode="indeterminate" color="warn"></md-progress-bar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<md-toolbar color="primary" class="app-toolbar">
|
<md-toolbar color="primary" class="app-toolbar">
|
||||||
<button class="hamburger" md-button
|
<button class="hamburger" [class.starting]="isStarting" md-button
|
||||||
(click)="sidenav.toggle()" title="Docs menu">
|
(click)="sidenav.toggle()" title="Docs menu">
|
||||||
<md-icon svgIcon="menu"></md-icon>
|
<md-icon [ngClass]="{'sidenav-open': !isSideBySide }" svgIcon="menu"></md-icon>
|
||||||
</button>
|
</button>
|
||||||
<a class="nav-link home" href="/"><img src="{{ homeImageUrl }}" title="Home" alt="Home"></a>
|
<a class="nav-link home" href="/"><img src="{{ homeImageUrl }}" title="Home" alt="Home"></a>
|
||||||
<aio-top-menu *ngIf="isSideBySide" [nodes]="topMenuNodes"></aio-top-menu>
|
<aio-top-menu *ngIf="isSideBySide" [nodes]="topMenuNodes"></aio-top-menu>
|
||||||
<aio-search-box class="search-container" #searchBox (search)="doSearch($event)"></aio-search-box>
|
<aio-search-box class="search-container" #searchBox (search)="doSearch($event)"></aio-search-box>
|
||||||
<span class="fill-remaining-space"></span>
|
|
||||||
</md-toolbar>
|
</md-toolbar>
|
||||||
<aio-search-results #searchResults *ngIf="showSearchResults" (resultSelected)="hideSearchResults()"></aio-search-results>
|
<aio-search-results #searchResults *ngIf="showSearchResults" (resultSelected)="hideSearchResults()"></aio-search-results>
|
||||||
|
|
||||||
<md-sidenav-container class="sidenav-container" [class.starting]="isStarting" role="main">
|
<md-sidenav-container class="sidenav-container" [class.starting]="isStarting" [class.has-floating-toc]="hasFloatingToc" role="main">
|
||||||
|
|
||||||
<md-sidenav [ngClass]="{'collapsed': !isSideBySide }" #sidenav class="sidenav" [opened]="isOpened" [mode]="mode" (open)="updateHostClasses()" (close)="updateHostClasses()">
|
<md-sidenav [ngClass]="{'collapsed': !isSideBySide }" #sidenav class="sidenav" [opened]="isOpened" [mode]="mode" (open)="updateHostClasses()" (close)="updateHostClasses()">
|
||||||
<aio-nav-menu *ngIf="!isSideBySide" [nodes]="topMenuNarrowNodes" [currentNode]="currentNode"></aio-nav-menu>
|
<aio-nav-menu *ngIf="!isSideBySide" [nodes]="topMenuNarrowNodes" [currentNode]="currentNodes?.TopBarNarrow" [isWide]="false"></aio-nav-menu>
|
||||||
<aio-nav-menu [nodes]="sideNavNodes" [currentNode]="currentNode" ></aio-nav-menu>
|
<aio-nav-menu [nodes]="sideNavNodes" [currentNode]="currentNodes?.SideNav" [isWide]="isSideBySide"></aio-nav-menu>
|
||||||
|
|
||||||
<div class="doc-version" title="Angular docs version {{currentDocVersion?.title}}">
|
<div class="doc-version" title="Angular docs version {{currentDocVersion?.title}}">
|
||||||
<select (change)="onDocVersionChange($event.target.selectedIndex)">
|
<aio-select (change)="onDocVersionChange($event.index)" [options]="docVersions" [selected]="docVersions && docVersions[0]"></aio-select>
|
||||||
<option *ngFor="let version of docVersions" [value]="version.title">{{version.title}}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</md-sidenav>
|
</md-sidenav>
|
||||||
|
|
||||||
<section class="sidenav-content" [id]="pageId" role="content">
|
<section class="sidenav-content" [id]="pageId" role="content">
|
||||||
<div id="top-of-page"></div>
|
|
||||||
<aio-doc-viewer [doc]="currentDocument" (docRendered)="onDocRendered()"></aio-doc-viewer>
|
<aio-doc-viewer [doc]="currentDocument" (docRendered)="onDocRendered()"></aio-doc-viewer>
|
||||||
<aio-dt [on]="dtOn" [(doc)]="currentDocument"></aio-dt>
|
<aio-dt [on]="dtOn" [(doc)]="currentDocument"></aio-dt>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</md-sidenav-container>
|
</md-sidenav-container>
|
||||||
|
|
||||||
<div *ngIf="showFloatingToc" class="toc-container" [style.max-height.px]="tocMaxHeight">
|
<div *ngIf="hasFloatingToc" class="toc-container" [style.max-height.px]="tocMaxHeight" (mousewheel)="restrainScrolling($event)">
|
||||||
<aio-toc></aio-toc>
|
<aio-toc></aio-toc>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { async, inject, ComponentFixture, TestBed, fakeAsync, tick } from '@angu
|
|||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { APP_BASE_HREF } from '@angular/common';
|
import { APP_BASE_HREF } from '@angular/common';
|
||||||
import { Http } from '@angular/http';
|
import { Http } from '@angular/http';
|
||||||
import { MdProgressBar } from '@angular/material';
|
import { MdProgressBar, MdSidenav } from '@angular/material';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||||
@ -24,9 +24,13 @@ import { ScrollService } from 'app/shared/scroll.service';
|
|||||||
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
||||||
import { SearchResultsComponent } from 'app/search/search-results/search-results.component';
|
import { SearchResultsComponent } from 'app/search/search-results/search-results.component';
|
||||||
import { SearchService } from 'app/search/search.service';
|
import { SearchService } from 'app/search/search.service';
|
||||||
|
import { SelectComponent, Option } from 'app/shared/select/select.component';
|
||||||
import { SwUpdateNotificationsService } from 'app/sw-updates/sw-update-notifications.service';
|
import { SwUpdateNotificationsService } from 'app/sw-updates/sw-update-notifications.service';
|
||||||
import { TocComponent } from 'app/embedded/toc/toc.component';
|
import { TocComponent } from 'app/embedded/toc/toc.component';
|
||||||
import { MdSidenav } from '@angular/material';
|
import { TocItem, TocService } from 'app/shared/toc.service';
|
||||||
|
|
||||||
|
const sideBySideBreakPoint = 992;
|
||||||
|
const hideToCBreakPoint = 800;
|
||||||
|
|
||||||
describe('AppComponent', () => {
|
describe('AppComponent', () => {
|
||||||
let component: AppComponent;
|
let component: AppComponent;
|
||||||
@ -36,25 +40,31 @@ describe('AppComponent', () => {
|
|||||||
let hamburger: HTMLButtonElement;
|
let hamburger: HTMLButtonElement;
|
||||||
let locationService: MockLocationService;
|
let locationService: MockLocationService;
|
||||||
let sidenav: HTMLElement;
|
let sidenav: HTMLElement;
|
||||||
|
let tocService: TocService;
|
||||||
|
|
||||||
const initializeTest = () => {
|
const initializeTest = () => {
|
||||||
fixture = TestBed.createComponent(AppComponent);
|
fixture = TestBed.createComponent(AppComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
component.onResize(1033); // wide by default
|
component.onResize(sideBySideBreakPoint + 1); // wide by default
|
||||||
|
|
||||||
docViewer = fixture.debugElement.query(By.css('aio-doc-viewer')).nativeElement;
|
const de = fixture.debugElement;
|
||||||
hamburger = fixture.debugElement.query(By.css('.hamburger')).nativeElement;
|
docViewer = de.query(By.css('aio-doc-viewer')).nativeElement;
|
||||||
locationService = fixture.debugElement.injector.get(LocationService) as any;
|
hamburger = de.query(By.css('.hamburger')).nativeElement;
|
||||||
sidenav = fixture.debugElement.query(By.css('md-sidenav')).nativeElement;
|
locationService = de.injector.get(LocationService) as any as MockLocationService;
|
||||||
|
sidenav = de.query(By.css('md-sidenav')).nativeElement;
|
||||||
|
tocService = de.injector.get(TocService);
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('with proper DocViewer', () => {
|
describe('with proper DocViewer', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
// For reasons unknown, not using `done` (even calling it synchronously),
|
||||||
|
// causes Chrome (v58+) to often get disconnected (at least on Windows and Travis).
|
||||||
|
beforeEach(done => {
|
||||||
createTestingModule('a/b');
|
createTestingModule('a/b');
|
||||||
initializeTest();
|
initializeTest();
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
@ -68,19 +78,74 @@ describe('AppComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('onResize', () => {
|
describe('hasFloatingToc', () => {
|
||||||
it('should update `isSideBySide` accordingly', () => {
|
it('should initially be true', () => {
|
||||||
component.onResize(1033);
|
const fixture2 = TestBed.createComponent(AppComponent);
|
||||||
expect(component.isSideBySide).toBe(true);
|
const component2 = fixture2.componentInstance;
|
||||||
component.onResize(500);
|
|
||||||
expect(component.isSideBySide).toBe(false);
|
expect(component2.hasFloatingToc).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update `showFloatingToc` accordingly', () => {
|
it('should be false on narrow screens', () => {
|
||||||
component.onResize(801);
|
component.onResize(hideToCBreakPoint - 1);
|
||||||
expect(component.showFloatingToc).toBe(true);
|
|
||||||
component.onResize(800);
|
tocService.tocList.next([{}, {}, {}] as TocItem[]);
|
||||||
expect(component.showFloatingToc).toBe(false);
|
expect(component.hasFloatingToc).toBe(false);
|
||||||
|
|
||||||
|
tocService.tocList.next([]);
|
||||||
|
expect(component.hasFloatingToc).toBe(false);
|
||||||
|
|
||||||
|
tocService.tocList.next([{}, {}, {}] as TocItem[]);
|
||||||
|
expect(component.hasFloatingToc).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be true on wide screens unless the toc is empty', () => {
|
||||||
|
component.onResize(hideToCBreakPoint + 1);
|
||||||
|
|
||||||
|
tocService.tocList.next([{}, {}, {}] as TocItem[]);
|
||||||
|
expect(component.hasFloatingToc).toBe(true);
|
||||||
|
|
||||||
|
tocService.tocList.next([]);
|
||||||
|
expect(component.hasFloatingToc).toBe(false);
|
||||||
|
|
||||||
|
tocService.tocList.next([{}, {}, {}] as TocItem[]);
|
||||||
|
expect(component.hasFloatingToc).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be false when toc is empty', () => {
|
||||||
|
tocService.tocList.next([]);
|
||||||
|
|
||||||
|
component.onResize(hideToCBreakPoint + 1);
|
||||||
|
expect(component.hasFloatingToc).toBe(false);
|
||||||
|
|
||||||
|
component.onResize(hideToCBreakPoint - 1);
|
||||||
|
expect(component.hasFloatingToc).toBe(false);
|
||||||
|
|
||||||
|
component.onResize(hideToCBreakPoint + 1);
|
||||||
|
expect(component.hasFloatingToc).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be true when toc is not empty unless the screen is narrow', () => {
|
||||||
|
tocService.tocList.next([{}, {}, {}] as TocItem[]);
|
||||||
|
|
||||||
|
component.onResize(hideToCBreakPoint + 1);
|
||||||
|
expect(component.hasFloatingToc).toBe(true);
|
||||||
|
|
||||||
|
component.onResize(hideToCBreakPoint - 1);
|
||||||
|
expect(component.hasFloatingToc).toBe(false);
|
||||||
|
|
||||||
|
component.onResize(hideToCBreakPoint + 1);
|
||||||
|
expect(component.hasFloatingToc).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isSideBySide', () => {
|
||||||
|
it('should be updated on resize', () => {
|
||||||
|
component.onResize(sideBySideBreakPoint - 1);
|
||||||
|
expect(component.isSideBySide).toBe(false);
|
||||||
|
|
||||||
|
component.onResize(sideBySideBreakPoint + 1);
|
||||||
|
expect(component.isSideBySide).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -96,7 +161,7 @@ describe('AppComponent', () => {
|
|||||||
describe('SideNav when side-by-side (wide)', () => {
|
describe('SideNav when side-by-side (wide)', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
component.onResize(1033); // side-by-side
|
component.onResize(sideBySideBreakPoint + 1); // side-by-side
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open when nav to a guide page (guide/pipes)', () => {
|
it('should open when nav to a guide page (guide/pipes)', () => {
|
||||||
@ -155,7 +220,7 @@ describe('AppComponent', () => {
|
|||||||
describe('SideNav when NOT side-by-side (narrow)', () => {
|
describe('SideNav when NOT side-by-side (narrow)', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
component.onResize(1000); // NOT side-by-side
|
component.onResize(sideBySideBreakPoint - 1); // NOT side-by-side
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be closed when nav to a guide page (guide/pipes)', () => {
|
it('should be closed when nav to a guide page (guide/pipes)', () => {
|
||||||
@ -218,26 +283,28 @@ describe('AppComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('SideNav version selector', () => {
|
describe('SideNav version selector', () => {
|
||||||
|
let selectElement: DebugElement;
|
||||||
|
let selectComponent: SelectComponent;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
component.onResize(1033); // side-by-side
|
component.onResize(sideBySideBreakPoint + 1); // side-by-side
|
||||||
|
selectElement = fixture.debugElement.query(By.directive(SelectComponent));
|
||||||
|
selectComponent = selectElement.componentInstance;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pick first (current) version by default', () => {
|
it('should pick first (current) version by default', () => {
|
||||||
const versionSelector = sidenav.querySelector('select');
|
expect(selectComponent.selected.title).toEqual(component.versionInfo.raw);
|
||||||
expect(versionSelector.value).toEqual(component.versionInfo.raw);
|
|
||||||
expect(versionSelector.selectedIndex).toEqual(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Older docs versions have an href
|
// Older docs versions have an href
|
||||||
it('should navigate when change to a version with an href', () => {
|
it('should navigate when change to a version with an href', () => {
|
||||||
component.onDocVersionChange(1);
|
selectElement.triggerEventHandler('change', { option: component.docVersions[1] as Option, index: 1});
|
||||||
expect(locationService.go).toHaveBeenCalledWith(TestHttp.docVersions[0].url);
|
expect(locationService.go).toHaveBeenCalledWith(TestHttp.docVersions[0].url);
|
||||||
});
|
});
|
||||||
|
|
||||||
// The current docs version should not have an href
|
// The current docs version should not have an href
|
||||||
// This may change when we perfect our docs versioning approach
|
// This may change when we perfect our docs versioning approach
|
||||||
it('should not navigate when change to a version without an href', () => {
|
it('should not navigate when change to a version without an href', () => {
|
||||||
component.onDocVersionChange(0);
|
selectElement.triggerEventHandler('change', { option: component.docVersions[0] as Option, index: 0});
|
||||||
expect(locationService.go).not.toHaveBeenCalled();
|
expect(locationService.go).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -466,18 +533,82 @@ describe('AppComponent', () => {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('restrainScrolling()', () => {
|
||||||
|
const preventedScrolling = (currentTarget: object, deltaY: number) => {
|
||||||
|
const evt = {
|
||||||
|
deltaY,
|
||||||
|
currentTarget,
|
||||||
|
defaultPrevented: false,
|
||||||
|
preventDefault() { this.defaultPrevented = true; }
|
||||||
|
} as any as WheelEvent;
|
||||||
|
|
||||||
|
component.restrainScrolling(evt);
|
||||||
|
|
||||||
|
return evt.defaultPrevented;
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should prevent scrolling up if already at the top', () => {
|
||||||
|
const elem = {scrollTop: 0};
|
||||||
|
|
||||||
|
expect(preventedScrolling(elem, -100)).toBe(true);
|
||||||
|
expect(preventedScrolling(elem, +100)).toBe(false);
|
||||||
|
expect(preventedScrolling(elem, -10)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should prevent scrolling down if already at the bottom', () => {
|
||||||
|
const elem = {scrollTop: 100, scrollHeight: 150, clientHeight: 50};
|
||||||
|
|
||||||
|
expect(preventedScrolling(elem, +10)).toBe(true);
|
||||||
|
expect(preventedScrolling(elem, -10)).toBe(false);
|
||||||
|
expect(preventedScrolling(elem, +5)).toBe(true);
|
||||||
|
|
||||||
|
elem.clientHeight -= 10;
|
||||||
|
expect(preventedScrolling(elem, +5)).toBe(false);
|
||||||
|
|
||||||
|
elem.scrollHeight -= 20;
|
||||||
|
expect(preventedScrolling(elem, +5)).toBe(true);
|
||||||
|
|
||||||
|
elem.scrollTop -= 30;
|
||||||
|
expect(preventedScrolling(elem, +5)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not prevent scrolling if neither at the top nor at the bottom', () => {
|
||||||
|
const elem = {scrollTop: 50, scrollHeight: 150, clientHeight: 50};
|
||||||
|
|
||||||
|
expect(preventedScrolling(elem, +100)).toBe(false);
|
||||||
|
expect(preventedScrolling(elem, -100)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('aio-toc', () => {
|
describe('aio-toc', () => {
|
||||||
let tocDebugElement: DebugElement;
|
let tocDebugElement: DebugElement;
|
||||||
let tocContainer: DebugElement;
|
let tocContainer: DebugElement;
|
||||||
|
|
||||||
beforeEach(() => {
|
const setHasFloatingToc = hasFloatingToc => {
|
||||||
|
component.hasFloatingToc = hasFloatingToc;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
tocDebugElement = fixture.debugElement.query(By.directive(TocComponent));
|
tocDebugElement = fixture.debugElement.query(By.directive(TocComponent));
|
||||||
tocContainer = tocDebugElement.parent;
|
tocContainer = tocDebugElement && tocDebugElement.parent;
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => setHasFloatingToc(true));
|
||||||
|
|
||||||
|
|
||||||
|
it('should show/hide `<aio-toc>` based on `hasFloatingToc`', () => {
|
||||||
|
expect(tocDebugElement).toBeTruthy();
|
||||||
|
expect(tocContainer).toBeTruthy();
|
||||||
|
|
||||||
|
setHasFloatingToc(false);
|
||||||
|
expect(tocDebugElement).toBeFalsy();
|
||||||
|
expect(tocContainer).toBeFalsy();
|
||||||
|
|
||||||
|
setHasFloatingToc(true);
|
||||||
|
expect(tocDebugElement).toBeTruthy();
|
||||||
|
expect(tocContainer).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should have a non-embedded `<aio-toc>` element', () => {
|
it('should have a non-embedded `<aio-toc>` element', () => {
|
||||||
expect(tocDebugElement).toBeDefined();
|
|
||||||
expect(tocDebugElement.classes['embedded']).toBeFalsy();
|
expect(tocDebugElement.classes['embedded']).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -489,6 +620,16 @@ describe('AppComponent', () => {
|
|||||||
|
|
||||||
expect(tocContainer.styles['max-height']).toBe('100px');
|
expect(tocContainer.styles['max-height']).toBe('100px');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should restrain scrolling inside the ToC container', () => {
|
||||||
|
const restrainScrolling = spyOn(component, 'restrainScrolling');
|
||||||
|
const evt = {};
|
||||||
|
|
||||||
|
expect(restrainScrolling).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
tocContainer.triggerEventHandler('mousewheel', evt);
|
||||||
|
expect(restrainScrolling).toHaveBeenCalledWith(evt);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('footer', () => {
|
describe('footer', () => {
|
||||||
|
@ -2,7 +2,7 @@ import { Component, ElementRef, HostBinding, HostListener, OnInit,
|
|||||||
QueryList, ViewChild, ViewChildren } from '@angular/core';
|
QueryList, ViewChild, ViewChildren } from '@angular/core';
|
||||||
import { MdSidenav } from '@angular/material';
|
import { MdSidenav } from '@angular/material';
|
||||||
|
|
||||||
import { CurrentNode, NavigationService, NavigationViews, NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
|
import { CurrentNodes, NavigationService, NavigationViews, NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
|
||||||
import { DocumentService, DocumentContents } from 'app/documents/document.service';
|
import { DocumentService, DocumentContents } from 'app/documents/document.service';
|
||||||
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
|
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
|
||||||
import { LocationService } from 'app/shared/location.service';
|
import { LocationService } from 'app/shared/location.service';
|
||||||
@ -12,7 +12,9 @@ import { SearchResultsComponent } from 'app/search/search-results/search-results
|
|||||||
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
||||||
import { SearchService } from 'app/search/search.service';
|
import { SearchService } from 'app/search/search.service';
|
||||||
import { SwUpdateNotificationsService } from 'app/sw-updates/sw-update-notifications.service';
|
import { SwUpdateNotificationsService } from 'app/sw-updates/sw-update-notifications.service';
|
||||||
|
import { TocService } from 'app/shared/toc.service';
|
||||||
|
|
||||||
|
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||||
import { combineLatest } from 'rxjs/observable/combineLatest';
|
import { combineLatest } from 'rxjs/observable/combineLatest';
|
||||||
|
|
||||||
const sideNavView = 'SideNav';
|
const sideNavView = 'SideNav';
|
||||||
@ -25,7 +27,7 @@ export class AppComponent implements OnInit {
|
|||||||
|
|
||||||
currentDocument: DocumentContents;
|
currentDocument: DocumentContents;
|
||||||
currentDocVersion: NavigationNode;
|
currentDocVersion: NavigationNode;
|
||||||
currentNode: CurrentNode;
|
currentNodes: CurrentNodes;
|
||||||
currentPath: string;
|
currentPath: string;
|
||||||
docVersions: NavigationNode[];
|
docVersions: NavigationNode[];
|
||||||
dtOn = false;
|
dtOn = false;
|
||||||
@ -59,15 +61,15 @@ export class AppComponent implements OnInit {
|
|||||||
isSideBySide = false;
|
isSideBySide = false;
|
||||||
private isFetchingTimeout: any;
|
private isFetchingTimeout: any;
|
||||||
private isSideNavDoc = false;
|
private isSideNavDoc = false;
|
||||||
private previousNavView: string;
|
|
||||||
|
|
||||||
private sideBySideWidth = 1032;
|
private sideBySideWidth = 992;
|
||||||
sideNavNodes: NavigationNode[];
|
sideNavNodes: NavigationNode[];
|
||||||
topMenuNodes: NavigationNode[];
|
topMenuNodes: NavigationNode[];
|
||||||
topMenuNarrowNodes: NavigationNode[];
|
topMenuNarrowNodes: NavigationNode[];
|
||||||
|
|
||||||
showFloatingToc = false;
|
hasFloatingToc = true;
|
||||||
showFloatingTocWidth = 800;
|
private showFloatingToc = new BehaviorSubject(false);
|
||||||
|
private showFloatingTocWidth = 800;
|
||||||
tocMaxHeight: string;
|
tocMaxHeight: string;
|
||||||
private tocMaxHeightOffset = 0;
|
private tocMaxHeightOffset = 0;
|
||||||
|
|
||||||
@ -104,12 +106,16 @@ export class AppComponent implements OnInit {
|
|||||||
private navigationService: NavigationService,
|
private navigationService: NavigationService,
|
||||||
private scrollService: ScrollService,
|
private scrollService: ScrollService,
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
private swUpdateNotifications: SwUpdateNotificationsService
|
private swUpdateNotifications: SwUpdateNotificationsService,
|
||||||
) { }
|
private tocService: TocService
|
||||||
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.searchService.initWorker('app/search/search-worker.js');
|
// Do not initialize the search on browsers that lack web worker support
|
||||||
this.searchService.loadIndex();
|
if ('Worker' in window) {
|
||||||
|
this.searchService.initWorker('app/search/search-worker.js');
|
||||||
|
this.searchService.loadIndex();
|
||||||
|
}
|
||||||
|
|
||||||
this.onResize(window.innerWidth);
|
this.onResize(window.innerWidth);
|
||||||
|
|
||||||
@ -136,17 +142,17 @@ export class AppComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.navigationService.currentNode.subscribe(currentNode => {
|
this.navigationService.currentNodes.subscribe(currentNodes => {
|
||||||
this.currentNode = currentNode;
|
this.currentNodes = currentNodes;
|
||||||
|
|
||||||
// Preserve current sidenav open state by default
|
// Preserve current sidenav open state by default
|
||||||
let openSideNav = this.sidenav.opened;
|
let openSideNav = this.sidenav.opened;
|
||||||
|
const isSideNavDoc = !!currentNodes[sideNavView];
|
||||||
|
|
||||||
if (this.previousNavView !== currentNode.view) {
|
if (this.isSideNavDoc !== isSideNavDoc) {
|
||||||
this.previousNavView = currentNode.view;
|
|
||||||
// View type changed. Is it now a sidenav view (e.g, guide or tutorial)?
|
// View type changed. Is it now a sidenav view (e.g, guide or tutorial)?
|
||||||
// Open if changed to a sidenav doc; close if changed to a marketing doc.
|
// Open if changed to a sidenav doc; close if changed to a marketing doc.
|
||||||
openSideNav = this.isSideNavDoc = currentNode.view === sideNavView;
|
openSideNav = this.isSideNavDoc = isSideNavDoc;
|
||||||
}
|
}
|
||||||
// May be open or closed when wide; always closed when narrow
|
// May be open or closed when wide; always closed when narrow
|
||||||
this.sideNavToggle(this.isSideBySide ? openSideNav : false);
|
this.sideNavToggle(this.isSideBySide ? openSideNav : false);
|
||||||
@ -172,6 +178,10 @@ export class AppComponent implements OnInit {
|
|||||||
this.navigationService.versionInfo.subscribe( vi => this.versionInfo = vi );
|
this.navigationService.versionInfo.subscribe( vi => this.versionInfo = vi );
|
||||||
|
|
||||||
this.swUpdateNotifications.enable();
|
this.swUpdateNotifications.enable();
|
||||||
|
|
||||||
|
const hasNonEmptyToc = this.tocService.tocList.map(tocList => tocList.length > 0);
|
||||||
|
combineLatest(hasNonEmptyToc, this.showFloatingToc)
|
||||||
|
.subscribe(([hasToc, showFloatingToc]) => this.hasFloatingToc = hasToc && showFloatingToc);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll to the anchor in the hash fragment or top of doc.
|
// Scroll to the anchor in the hash fragment or top of doc.
|
||||||
@ -183,6 +193,9 @@ export class AppComponent implements OnInit {
|
|||||||
// Stop fetching timeout (which, when render is fast, means progress bar never shown)
|
// Stop fetching timeout (which, when render is fast, means progress bar never shown)
|
||||||
clearTimeout(this.isFetchingTimeout);
|
clearTimeout(this.isFetchingTimeout);
|
||||||
|
|
||||||
|
// Put page in a clean visual state
|
||||||
|
this.scrollService.scrollToTop();
|
||||||
|
|
||||||
// Scroll 500ms after the doc-viewer has finished rendering the new doc
|
// Scroll 500ms after the doc-viewer has finished rendering the new doc
|
||||||
// The delay is to allow time for async layout to complete
|
// The delay is to allow time for async layout to complete
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -202,7 +215,7 @@ export class AppComponent implements OnInit {
|
|||||||
@HostListener('window:resize', ['$event.target.innerWidth'])
|
@HostListener('window:resize', ['$event.target.innerWidth'])
|
||||||
onResize(width) {
|
onResize(width) {
|
||||||
this.isSideBySide = width > this.sideBySideWidth;
|
this.isSideBySide = width > this.sideBySideWidth;
|
||||||
this.showFloatingToc = width > this.showFloatingTocWidth;
|
this.showFloatingToc.next(width > this.showFloatingTocWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('click', ['$event.target', '$event.button', '$event.ctrlKey', '$event.metaKey', '$event.altKey'])
|
@HostListener('click', ['$event.target', '$event.button', '$event.ctrlKey', '$event.metaKey', '$event.altKey'])
|
||||||
@ -250,9 +263,9 @@ export class AppComponent implements OnInit {
|
|||||||
const sideNavOpen = `sidenav-${this.sidenav.opened ? 'open' : 'closed'}`;
|
const sideNavOpen = `sidenav-${this.sidenav.opened ? 'open' : 'closed'}`;
|
||||||
const pageClass = `page-${this.pageId}`;
|
const pageClass = `page-${this.pageId}`;
|
||||||
const folderClass = `folder-${this.folderId}`;
|
const folderClass = `folder-${this.folderId}`;
|
||||||
const viewClass = `view-${this.currentNode && this.currentNode.view}`;
|
const viewClasses = Object.keys(this.currentNodes || {}).map(view => `view-${view}`).join(' ');
|
||||||
|
|
||||||
this.hostClasses = `${sideNavOpen} ${pageClass} ${folderClass} ${viewClass}`;
|
this.hostClasses = `${sideNavOpen} ${pageClass} ${folderClass} ${viewClasses}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamically change height of table of contents container
|
// Dynamically change height of table of contents container
|
||||||
@ -270,6 +283,25 @@ export class AppComponent implements OnInit {
|
|||||||
this.tocMaxHeight = (document.body.scrollHeight - window.pageYOffset - this.tocMaxHeightOffset).toFixed(2);
|
this.tocMaxHeight = (document.body.scrollHeight - window.pageYOffset - this.tocMaxHeightOffset).toFixed(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restrain scrolling inside an element, when the cursor is over it
|
||||||
|
restrainScrolling(evt: WheelEvent) {
|
||||||
|
const elem = evt.currentTarget as Element;
|
||||||
|
const scrollTop = elem.scrollTop;
|
||||||
|
|
||||||
|
if (evt.deltaY < 0) {
|
||||||
|
// Trying to scroll up: Prevent scrolling if already at the top.
|
||||||
|
if (scrollTop < 1) {
|
||||||
|
evt.preventDefault();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Trying to scroll down: Prevent scrolling if already at the bottom.
|
||||||
|
const maxScrollTop = elem.scrollHeight - elem.clientHeight;
|
||||||
|
if (maxScrollTop - scrollTop < 1) {
|
||||||
|
evt.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Search related methods and handlers
|
// Search related methods and handlers
|
||||||
|
|
||||||
|
@ -45,6 +45,8 @@ import { SearchResultsComponent } from './search/search-results/search-results.c
|
|||||||
import { SearchBoxComponent } from './search/search-box/search-box.component';
|
import { SearchBoxComponent } from './search/search-box/search-box.component';
|
||||||
import { TocService } from 'app/shared/toc.service';
|
import { TocService } from 'app/shared/toc.service';
|
||||||
|
|
||||||
|
import { SharedModule } from 'app/shared/shared.module';
|
||||||
|
|
||||||
// These are the hardcoded inline svg sources to be used by the `<md-icon>` component
|
// These are the hardcoded inline svg sources to be used by the `<md-icon>` component
|
||||||
export const svgIconProviders = [
|
export const svgIconProviders = [
|
||||||
{
|
{
|
||||||
@ -80,7 +82,8 @@ export const svgIconProviders = [
|
|||||||
MdSidenavModule,
|
MdSidenavModule,
|
||||||
MdTabsModule,
|
MdTabsModule,
|
||||||
MdToolbarModule,
|
MdToolbarModule,
|
||||||
SwUpdatesModule
|
SwUpdatesModule,
|
||||||
|
SharedModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
|
@ -1,26 +1,17 @@
|
|||||||
<div class="l-flex-wrap info-banner api-filter">
|
<div class="l-flex-wrap info-banner api-filter">
|
||||||
|
|
||||||
<div class="form-select-menu">
|
<aio-select (change)="setType($event.option)"
|
||||||
<button class="form-select-button has-symbol" (click)="toggleTypeMenu()">
|
[options]="types"
|
||||||
<strong>Type:</strong><span class="symbol {{type.name}}"></span>{{type.title}}
|
[selected]="type"
|
||||||
</button>
|
[showSymbol]="true"
|
||||||
<ul class="form-select-dropdown" *ngIf="showTypeMenu">
|
label="Type:">
|
||||||
<li *ngFor="let t of types" (click)="setType(t)" [class.selected]="t === type">
|
</aio-select>
|
||||||
<span class="symbol {{t.name}}"></span>{{t.title}}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-select-menu">
|
<aio-select (change)="setStatus($event.option)"
|
||||||
<button class="form-select-button" (click)="toggleStatusMenu()">
|
[options]="statuses"
|
||||||
<strong>Status:</strong>{{status.title}}
|
[selected]="status"
|
||||||
</button>
|
label="Status:">
|
||||||
<ul class="form-select-dropdown" *ngIf="showStatusMenu">
|
</aio-select>
|
||||||
<li *ngFor="let s of statuses" (click)="setStatus(s)" [class.selected]="s === status">
|
|
||||||
{{s.title}}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-search">
|
<div class="form-search">
|
||||||
<input #filter placeholder="Filter" (input)="setQuery($event.target.value)">
|
<input #filter placeholder="Filter" (input)="setQuery($event.target.value)">
|
||||||
|
@ -4,6 +4,7 @@ import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
|||||||
import { ApiListComponent } from './api-list.component';
|
import { ApiListComponent } from './api-list.component';
|
||||||
import { ApiItem, ApiSection, ApiService } from './api.service';
|
import { ApiItem, ApiSection, ApiService } from './api.service';
|
||||||
import { LocationService } from 'app/shared/location.service';
|
import { LocationService } from 'app/shared/location.service';
|
||||||
|
import { SharedModule } from 'app/shared/shared.module';
|
||||||
|
|
||||||
describe('ApiListComponent', () => {
|
describe('ApiListComponent', () => {
|
||||||
let component: ApiListComponent;
|
let component: ApiListComponent;
|
||||||
@ -12,6 +13,7 @@ describe('ApiListComponent', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
|
imports: [ SharedModule ],
|
||||||
declarations: [ ApiListComponent ],
|
declarations: [ ApiListComponent ],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: ApiService, useClass: TestApiService },
|
{ provide: ApiService, useClass: TestApiService },
|
||||||
@ -75,17 +77,17 @@ describe('ApiListComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('item.show should be true for items with selected status', () => {
|
it('item.show should be true for items with selected status', () => {
|
||||||
component.setStatus({name: 'stable', title: 'Stable'});
|
component.setStatus({value: 'stable', title: 'Stable'});
|
||||||
expectFilteredResult('status: stable', item => item.stability === 'stable');
|
expectFilteredResult('status: stable', item => item.stability === 'stable');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('item.show should be true for items with "security-risk" status when selected', () => {
|
it('item.show should be true for items with "security-risk" status when selected', () => {
|
||||||
component.setStatus({name: 'security-risk', title: 'Security Risk'});
|
component.setStatus({value: 'security-risk', title: 'Security Risk'});
|
||||||
expectFilteredResult('status: security-risk', item => item.securityRisk);
|
expectFilteredResult('status: security-risk', item => item.securityRisk);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('item.show should be true for items of selected type', () => {
|
it('item.show should be true for items of selected type', () => {
|
||||||
component.setType({name: 'class', title: 'Class'});
|
component.setType({value: 'class', title: 'Class'});
|
||||||
expectFilteredResult('type: class', item => item.docType === 'class');
|
expectFilteredResult('type: class', item => item.docType === 'class');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -189,8 +191,8 @@ describe('ApiListComponent', () => {
|
|||||||
|
|
||||||
it('should have query, status, and type', () => {
|
it('should have query, status, and type', () => {
|
||||||
component.setQuery('foo');
|
component.setQuery('foo');
|
||||||
component.setStatus({name: 'stable', title: 'Stable'});
|
component.setStatus({value: 'stable', title: 'Stable'});
|
||||||
component.setType({name: 'class', title: 'Class'});
|
component.setType({value: 'class', title: 'Class'});
|
||||||
|
|
||||||
const search = locationService.setSearch.calls.mostRecent().args[1];
|
const search = locationService.setSearch.calls.mostRecent().args[1];
|
||||||
expect(search.query).toBe('foo');
|
expect(search.query).toBe('foo');
|
||||||
|
@ -15,10 +15,7 @@ import { combineLatest } from 'rxjs/observable/combineLatest';
|
|||||||
import { LocationService } from 'app/shared/location.service';
|
import { LocationService } from 'app/shared/location.service';
|
||||||
import { ApiItem, ApiSection, ApiService } from './api.service';
|
import { ApiItem, ApiSection, ApiService } from './api.service';
|
||||||
|
|
||||||
interface MenuItem {
|
import { Option } from 'app/shared/select/select.component';
|
||||||
name: string;
|
|
||||||
title: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class SearchCriteria {
|
class SearchCriteria {
|
||||||
query? = '';
|
query? = '';
|
||||||
@ -40,29 +37,29 @@ export class ApiListComponent implements OnInit {
|
|||||||
private criteriaSubject = new ReplaySubject<SearchCriteria>(1);
|
private criteriaSubject = new ReplaySubject<SearchCriteria>(1);
|
||||||
private searchCriteria = new SearchCriteria();
|
private searchCriteria = new SearchCriteria();
|
||||||
|
|
||||||
status: MenuItem;
|
status: Option;
|
||||||
type: MenuItem;
|
type: Option;
|
||||||
|
|
||||||
// API types
|
// API types
|
||||||
types: MenuItem[] = [
|
types: Option[] = [
|
||||||
{ name: 'all', title: 'All' },
|
{ value: 'all', title: 'All' },
|
||||||
{ name: 'directive', title: 'Directive' },
|
{ value: 'directive', title: 'Directive' },
|
||||||
{ name: 'pipe', title: 'Pipe'},
|
{ value: 'pipe', title: 'Pipe'},
|
||||||
{ name: 'decorator', title: 'Decorator' },
|
{ value: 'decorator', title: 'Decorator' },
|
||||||
{ name: 'class', title: 'Class' },
|
{ value: 'class', title: 'Class' },
|
||||||
{ name: 'interface', title: 'Interface' },
|
{ value: 'interface', title: 'Interface' },
|
||||||
{ name: 'function', title: 'Function' },
|
{ value: 'function', title: 'Function' },
|
||||||
{ name: 'enum', title: 'Enum' },
|
{ value: 'enum', title: 'Enum' },
|
||||||
{ name: 'type-alias', title: 'Type Alias' },
|
{ value: 'type-alias', title: 'Type Alias' },
|
||||||
{ name: 'const', title: 'Const'}
|
{ value: 'const', title: 'Const'}
|
||||||
];
|
];
|
||||||
|
|
||||||
statuses: MenuItem[] = [
|
statuses: Option[] = [
|
||||||
{ name: 'all', title: 'All' },
|
{ value: 'all', title: 'All' },
|
||||||
{ name: 'stable', title: 'Stable' },
|
{ value: 'stable', title: 'Stable' },
|
||||||
{ name: 'deprecated', title: 'Deprecated' },
|
{ value: 'deprecated', title: 'Deprecated' },
|
||||||
{ name: 'experimental', title: 'Experimental' },
|
{ value: 'experimental', title: 'Experimental' },
|
||||||
{ name: 'security-risk', title: 'Security Risk' }
|
{ value: 'security-risk', title: 'Security Risk' }
|
||||||
];
|
];
|
||||||
|
|
||||||
@ViewChild('filter') queryEl: ElementRef;
|
@ViewChild('filter') queryEl: ElementRef;
|
||||||
@ -90,16 +87,16 @@ export class ApiListComponent implements OnInit {
|
|||||||
this.setSearchCriteria({query: (query || '').toLowerCase().trim() });
|
this.setSearchCriteria({query: (query || '').toLowerCase().trim() });
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatus(status: MenuItem) {
|
setStatus(status: Option) {
|
||||||
this.toggleStatusMenu();
|
this.toggleStatusMenu();
|
||||||
this.status = status;
|
this.status = status;
|
||||||
this.setSearchCriteria({status: status.name});
|
this.setSearchCriteria({status: status.value});
|
||||||
}
|
}
|
||||||
|
|
||||||
setType(type: MenuItem) {
|
setType(type: Option) {
|
||||||
this.toggleTypeMenu();
|
this.toggleTypeMenu();
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.setSearchCriteria({type: type.name});
|
this.setSearchCriteria({type: type.value});
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleStatusMenu() {
|
toggleStatusMenu() {
|
||||||
@ -150,13 +147,13 @@ export class ApiListComponent implements OnInit {
|
|||||||
// Hack: can't bind to query because input cursor always forced to end-of-line.
|
// Hack: can't bind to query because input cursor always forced to end-of-line.
|
||||||
this.queryEl.nativeElement.value = q;
|
this.queryEl.nativeElement.value = q;
|
||||||
|
|
||||||
this.status = this.statuses.find(x => x.name === status) || this.statuses[0];
|
this.status = this.statuses.find(x => x.value === status) || this.statuses[0];
|
||||||
this.type = this.types.find(x => x.name === type) || this.types[0];
|
this.type = this.types.find(x => x.value === type) || this.types[0];
|
||||||
|
|
||||||
this.searchCriteria = {
|
this.searchCriteria = {
|
||||||
query: q,
|
query: q,
|
||||||
status: this.status.name,
|
status: this.status.value,
|
||||||
type: this.type.name
|
type: this.type.value
|
||||||
};
|
};
|
||||||
|
|
||||||
this.criteriaSubject.next(this.searchCriteria);
|
this.criteriaSubject.next(this.searchCriteria);
|
||||||
|
@ -13,16 +13,16 @@ import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
|||||||
|
|
||||||
<div *ngIf="person.picture" class="contributor-image" [style.background-image]="'url('+pictureBase+person.picture+')'">
|
<div *ngIf="person.picture" class="contributor-image" [style.background-image]="'url('+pictureBase+person.picture+')'">
|
||||||
<div class="contributor-info">
|
<div class="contributor-info">
|
||||||
<button>
|
<button *ngIf="person.bio" >
|
||||||
<a *ngIf="person.bio" aria-label="View Bio">View Bio</a>
|
<a aria-label="View Bio">View Bio</a>
|
||||||
</button>
|
</button>
|
||||||
<button class="icon">
|
<button *ngIf="person.twitter" class="icon">
|
||||||
<a *ngIf="person.twitter" href="https://twitter.com/{{person.twitter}}" target="_blank">
|
<a href="https://twitter.com/{{person.twitter}}" target="_blank">
|
||||||
<span class="fa fa-twitter fa-2x"></span>
|
<span class="fa fa-twitter fa-2x"></span>
|
||||||
</a>
|
</a>
|
||||||
</button>
|
</button>
|
||||||
<button class="icon">
|
<button *ngIf="person.website" class="icon">
|
||||||
<a *ngIf="person.website" href="{{person.website}}" target="_blank">
|
<a href="{{person.website}}" target="_blank">
|
||||||
<span class="fa fa-link fa-2x"></span>
|
<span class="fa fa-link fa-2x"></span>
|
||||||
</a>
|
</a>
|
||||||
</button>
|
</button>
|
||||||
@ -31,16 +31,16 @@ import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
|||||||
|
|
||||||
<div *ngIf="!person.picture" class="contributor-image" [style.background-image]="'url('+pictureBase+noPicture+')'">
|
<div *ngIf="!person.picture" class="contributor-image" [style.background-image]="'url('+pictureBase+noPicture+')'">
|
||||||
<div class="contributor-info">
|
<div class="contributor-info">
|
||||||
<button>
|
<button *ngIf="person.bio">
|
||||||
<a *ngIf="person.bio" aria-label="View Bio">View Bio</a>
|
<a aria-label="View Bio">View Bio</a>
|
||||||
</button>
|
</button>
|
||||||
<button class="icon">
|
<button *ngIf="person.twitter" class="icon">
|
||||||
<a *ngIf="person.twitter" href="https://twitter.com/{{person.twitter}}" target="_blank">
|
<a href="https://twitter.com/{{person.twitter}}" target="_blank">
|
||||||
<span class="fa fa-twitter fa-2x"></span>
|
<span class="fa fa-twitter fa-2x"></span>
|
||||||
</a>
|
</a>
|
||||||
</button>
|
</button>
|
||||||
<button class="icon">
|
<button *ngIf="person.website" class="icon">
|
||||||
<a *ngIf="person.website" href="{{person.website}}" target="_blank">
|
<a href="{{person.website}}" target="_blank">
|
||||||
<span class="fa fa-link fa-2x"></span>
|
<span class="fa fa-link fa-2x"></span>
|
||||||
</a>
|
</a>
|
||||||
</button>
|
</button>
|
||||||
|
@ -11,6 +11,7 @@ import { PrettyPrinter } from './code/pretty-printer.service';
|
|||||||
// Reusable components (used inside embedded components)
|
// Reusable components (used inside embedded components)
|
||||||
import { MdIconModule, MdTabsModule } from '@angular/material';
|
import { MdIconModule, MdTabsModule } from '@angular/material';
|
||||||
import { CodeComponent } from './code/code.component';
|
import { CodeComponent } from './code/code.component';
|
||||||
|
import { SharedModule } from 'app/shared/shared.module';
|
||||||
|
|
||||||
// Embedded Components
|
// Embedded Components
|
||||||
import { ApiListComponent } from './api/api-list.component';
|
import { ApiListComponent } from './api/api-list.component';
|
||||||
@ -41,7 +42,8 @@ export class EmbeddedComponents {
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
MdIconModule,
|
MdIconModule,
|
||||||
MdTabsModule
|
MdTabsModule,
|
||||||
|
SharedModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
embeddedComponents,
|
embeddedComponents,
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<md-icon class="rotating-icon" svgIcon="keyboard_arrow_right" [class.collapsed]="isCollapsed"></md-icon>
|
<md-icon class="rotating-icon" svgIcon="keyboard_arrow_right" [class.collapsed]="isCollapsed"></md-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ul class="toc-list">
|
<ul class="toc-list" [class.embedded]="type !== 'Floating'">
|
||||||
<ng-container *ngFor="let toc of tocList; let i = index">
|
<ng-container *ngFor="let toc of tocList; let i = index">
|
||||||
<li #tocItem title="{{toc.title}}" *ngIf="type === 'Floating' || toc.level !== 'h1'"
|
<li #tocItem title="{{toc.title}}" *ngIf="type === 'Floating' || toc.level !== 'h1'"
|
||||||
class="{{toc.level}}" [class.secondary]="type === 'EmbeddedExpandable' && i >= primaryMax" [class.active]="i === activeIndex">
|
class="{{toc.level}}" [class.secondary]="type === 'EmbeddedExpandable' && i >= primaryMax" [class.active]="i === activeIndex">
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="heading-children" [ngClass]="classes">
|
<div class="heading-children" [ngClass]="classes">
|
||||||
<aio-nav-item *ngFor="let node of node.children" [level]="level + 1"
|
<aio-nav-item *ngFor="let node of node.children" [level]="level + 1" [isWide]="isWide"
|
||||||
[node]="node" [selectedNodes]="selectedNodes"></aio-nav-item>
|
[node]="node" [selectedNodes]="selectedNodes"></aio-nav-item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,159 +1,196 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
import { SimpleChange, SimpleChanges } from '@angular/core';
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { SimpleChange, SimpleChanges, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
|
||||||
import { NavItemComponent } from './nav-item.component';
|
import { NavItemComponent } from './nav-item.component';
|
||||||
import { NavigationNode } from 'app/navigation/navigation.model';
|
import { NavigationNode } from 'app/navigation/navigation.model';
|
||||||
|
|
||||||
// Testing the component class behaviors, independent of its template
|
describe('NavItemComponent', () => {
|
||||||
// No dependencies. Just new it and test :)
|
|
||||||
// Let e2e tests verify how it displays.
|
|
||||||
describe('NavItemComponent (class-only)', () => {
|
|
||||||
|
|
||||||
let component: NavItemComponent;
|
// Testing the component class behaviors, independent of its template
|
||||||
|
// No dependencies. Just new it and test :)
|
||||||
|
// Let e2e tests verify how it displays.
|
||||||
|
describe('(class-only)', () => {
|
||||||
|
|
||||||
let selectedNodes: NavigationNode[];
|
let component: NavItemComponent;
|
||||||
let setClassesSpy: jasmine.Spy;
|
|
||||||
|
|
||||||
function initialize(nd: NavigationNode) {
|
let selectedNodes: NavigationNode[];
|
||||||
component.node = nd;
|
let setClassesSpy: jasmine.Spy;
|
||||||
onChanges(); // Angular calls when initializing the component
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enough to triggers component's ngOnChange method
|
function initialize(nd: NavigationNode) {
|
||||||
function onChanges() {
|
component.node = nd;
|
||||||
component.ngOnChanges({node: <SimpleChange><any> 'anything' });
|
onChanges(); // Angular calls when initializing the component
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
// Enough to triggers component's ngOnChange method
|
||||||
|
function onChanges() {
|
||||||
|
component.ngOnChanges({node: <SimpleChange><any> 'anything' });
|
||||||
|
}
|
||||||
|
|
||||||
component = new NavItemComponent();
|
beforeEach(() => {
|
||||||
setClassesSpy = spyOn(component, 'setClasses').and.callThrough();
|
|
||||||
|
|
||||||
// Selected nodes is the selected node and its header ancestors
|
component = new NavItemComponent();
|
||||||
selectedNodes = [
|
setClassesSpy = spyOn(component, 'setClasses').and.callThrough();
|
||||||
{ title: 'a' }, // selected node: an item or a header
|
|
||||||
{ title: 'parent' }, // selected node's header parent
|
|
||||||
{ title: 'grandparent' }, // selected node's header grandparent
|
|
||||||
];
|
|
||||||
component.selectedNodes = selectedNodes;
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('should have expected classes when initialized', () => {
|
// Selected nodes is the selected node and its header ancestors
|
||||||
it('with selected node', () => {
|
selectedNodes = [
|
||||||
initialize(selectedNodes[0]);
|
{ title: 'a' }, // selected node: an item or a header
|
||||||
expect(component.classes).toEqual(
|
{ title: 'parent' }, // selected node's header parent
|
||||||
// selecting the current node has no effect on expanded state,
|
{ title: 'grandparent' }, // selected node's header grandparent
|
||||||
// even if current node is a header.
|
];
|
||||||
{ 'level-1': true, collapsed: true, expanded: false, selected: true}
|
component.selectedNodes = selectedNodes;
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('with selected node ancestor', () => {
|
describe('should have expected classes when initialized', () => {
|
||||||
initialize(selectedNodes[1]);
|
it('with selected node', () => {
|
||||||
expect(component.classes).toEqual(
|
initialize(selectedNodes[0]);
|
||||||
// ancestor is a header and should be expanded
|
expect(component.classes).toEqual(
|
||||||
{ 'level-1': true, collapsed: false, expanded: true, selected: true}
|
// selected node should be expanded even if is a header.
|
||||||
);
|
{ 'level-1': true, collapsed: false, expanded: true, selected: true }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with selected node ancestor', () => {
|
||||||
|
initialize(selectedNodes[1]);
|
||||||
|
expect(component.classes).toEqual(
|
||||||
|
// ancestor is a header and should be expanded
|
||||||
|
{ 'level-1': true, collapsed: false, expanded: true, selected: true }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with other than a selected node or ancestor', () => {
|
||||||
|
initialize({ title: 'x' });
|
||||||
|
expect(component.classes).toEqual(
|
||||||
|
{ 'level-1': true, collapsed: true, expanded: false, selected: false }
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('with other than a selected node or ancestor', () => {
|
describe('when becomes a non-selected node', () => {
|
||||||
initialize({ title: 'x' });
|
|
||||||
expect(component.classes).toEqual(
|
// this node won't be the selected node when ngOnChanges() called
|
||||||
{ 'level-1': true, collapsed: true, expanded: false, selected: false}
|
beforeEach(() => component.node = { title: 'x' });
|
||||||
);
|
|
||||||
|
it('should de-select if previously selected', () => {
|
||||||
|
component.isSelected = true;
|
||||||
|
onChanges();
|
||||||
|
expect(component.isSelected).toBe(false, 'becomes de-selected');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should collapse if previously expanded in narrow mode', () => {
|
||||||
|
component.isWide = false;
|
||||||
|
component.isExpanded = true;
|
||||||
|
onChanges();
|
||||||
|
expect(component.isExpanded).toBe(false, 'becomes collapsed');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remain expanded in wide mode', () => {
|
||||||
|
component.isWide = true;
|
||||||
|
component.isExpanded = true;
|
||||||
|
onChanges();
|
||||||
|
expect(component.isExpanded).toBe(true, 'remains expanded');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when becomes a selected node', () => {
|
||||||
|
|
||||||
|
// this node will be the selected node when ngOnChanges() called
|
||||||
|
beforeEach(() => component.node = selectedNodes[0]);
|
||||||
|
|
||||||
|
it('should select when previously not selected', () => {
|
||||||
|
component.isSelected = false;
|
||||||
|
onChanges();
|
||||||
|
expect(component.isSelected).toBe(true, 'becomes selected');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should expand the current node or keep it expanded', () => {
|
||||||
|
component.isExpanded = false;
|
||||||
|
onChanges();
|
||||||
|
expect(component.isExpanded).toBe(true, 'becomes true');
|
||||||
|
|
||||||
|
component.isExpanded = true;
|
||||||
|
onChanges();
|
||||||
|
expect(component.isExpanded).toBe(true, 'remains true');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when becomes a selected ancestor node', () => {
|
||||||
|
|
||||||
|
// this node will be a selected node ancestor header when ngOnChanges() called
|
||||||
|
beforeEach(() => component.node = selectedNodes[2]);
|
||||||
|
|
||||||
|
it('should select when previously not selected', () => {
|
||||||
|
component.isSelected = false;
|
||||||
|
onChanges();
|
||||||
|
expect(component.isSelected).toBe(true, 'becomes selected');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should always expand this header', () => {
|
||||||
|
component.isExpanded = false;
|
||||||
|
onChanges();
|
||||||
|
expect(component.isExpanded).toBe(true, 'becomes expanded');
|
||||||
|
|
||||||
|
component.isExpanded = false;
|
||||||
|
onChanges();
|
||||||
|
expect(component.isExpanded).toBe(true, 'stays expanded');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when headerClicked()', () => {
|
||||||
|
// current node doesn't matter in these tests.
|
||||||
|
|
||||||
|
it('should expand when headerClicked() and previously collapsed', () => {
|
||||||
|
component.isExpanded = false;
|
||||||
|
component.headerClicked();
|
||||||
|
expect(component.isExpanded).toBe(true, 'should be expanded');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should collapse when headerClicked() and previously expanded', () => {
|
||||||
|
component.isExpanded = true;
|
||||||
|
component.headerClicked();
|
||||||
|
expect(component.isExpanded).toBe(false, 'should be collapsed');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not change isSelected when headerClicked()', () => {
|
||||||
|
component.isSelected = true;
|
||||||
|
component.headerClicked();
|
||||||
|
expect(component.isSelected).toBe(true, 'remains selected');
|
||||||
|
|
||||||
|
component.isSelected = false;
|
||||||
|
component.headerClicked();
|
||||||
|
expect(component.isSelected).toBe(false, 'remains not selected');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set classes', () => {
|
||||||
|
component.headerClicked();
|
||||||
|
expect(setClassesSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when becomes a non-selected node', () => {
|
describe('(via TestBed)', () => {
|
||||||
|
it('should pass the `isWide` property to all child nav-items', () => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [NavItemComponent],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
});
|
||||||
|
const fixture = TestBed.createComponent(NavItemComponent);
|
||||||
|
fixture.componentInstance.node = {
|
||||||
|
title: 'x',
|
||||||
|
children: [{ title: 'a' }, { title: 'b' }]
|
||||||
|
};
|
||||||
|
|
||||||
// this node won't be the selected node when ngOnChanges() called
|
fixture.componentInstance.isWide = true;
|
||||||
beforeEach(() => component.node = { title: 'x' });
|
fixture.detectChanges();
|
||||||
|
let children = fixture.debugElement.queryAll(By.directive(NavItemComponent));
|
||||||
|
expect(children.length).toEqual(2);
|
||||||
|
children.forEach(child => expect(child.componentInstance.isWide).toBe(true));
|
||||||
|
|
||||||
it('should collapse if previously expanded', () => {
|
fixture.componentInstance.isWide = false;
|
||||||
component.isExpanded = true;
|
fixture.detectChanges();
|
||||||
onChanges();
|
children = fixture.debugElement.queryAll(By.directive(NavItemComponent));
|
||||||
expect(component.isExpanded).toBe(false, 'becomes collapsed');
|
expect(children.length).toEqual(2);
|
||||||
});
|
children.forEach(child => expect(child.componentInstance.isWide).toBe(false));
|
||||||
|
|
||||||
it('should de-select if previously selected', () => {
|
|
||||||
component.isSelected = true;
|
|
||||||
onChanges();
|
|
||||||
expect(component.isSelected).toBe(false, 'becomes de-selected');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when becomes a selected node', () => {
|
|
||||||
|
|
||||||
// this node will be the selected node when ngOnChanges() called
|
|
||||||
beforeEach(() => component.node = selectedNodes[0]);
|
|
||||||
|
|
||||||
it('should select when previously not selected', () => {
|
|
||||||
component.isSelected = false;
|
|
||||||
onChanges();
|
|
||||||
expect(component.isSelected).toBe(true, 'becomes selected');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should leave the expanded/collapsed state untouched', () => {
|
|
||||||
component.isExpanded = false;
|
|
||||||
onChanges();
|
|
||||||
expect(component.isExpanded).toBe(false, 'remains false');
|
|
||||||
|
|
||||||
component.isExpanded = true;
|
|
||||||
onChanges();
|
|
||||||
expect(component.isExpanded).toBe(true, 'remains true');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when becomes a selected ancestor node', () => {
|
|
||||||
|
|
||||||
// this node will be a selected node ancestor header when ngOnChanges() called
|
|
||||||
beforeEach(() => component.node = selectedNodes[2]);
|
|
||||||
|
|
||||||
it('should select when previously not selected', () => {
|
|
||||||
component.isSelected = false;
|
|
||||||
onChanges();
|
|
||||||
expect(component.isSelected).toBe(true, 'becomes selected');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should always expand this header', () => {
|
|
||||||
component.isExpanded = false;
|
|
||||||
onChanges();
|
|
||||||
expect(component.isExpanded).toBe(true, 'becomes expanded');
|
|
||||||
|
|
||||||
component.isExpanded = false;
|
|
||||||
onChanges();
|
|
||||||
expect(component.isExpanded).toBe(true, 'stays expanded');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when headerClicked()', () => {
|
|
||||||
// current node doesn't matter in these tests.
|
|
||||||
|
|
||||||
it('should expand when headerClicked() and previously collapsed', () => {
|
|
||||||
component.isExpanded = false;
|
|
||||||
component.headerClicked();
|
|
||||||
expect(component.isExpanded).toBe(true, 'should be expanded');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should collapse when headerClicked() and previously expanded', () => {
|
|
||||||
component.isExpanded = true;
|
|
||||||
component.headerClicked();
|
|
||||||
expect(component.isExpanded).toBe(false, 'should be collapsed');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not change isSelected when headerClicked()', () => {
|
|
||||||
component.isSelected = true;
|
|
||||||
component.headerClicked();
|
|
||||||
expect(component.isSelected).toBe(true, 'remains selected');
|
|
||||||
|
|
||||||
component.isSelected = false;
|
|
||||||
component.headerClicked();
|
|
||||||
expect(component.isSelected).toBe(false, 'remains not selected');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set classes', () => {
|
|
||||||
component.headerClicked();
|
|
||||||
expect(setClassesSpy).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -6,19 +6,26 @@ import { NavigationNode } from 'app/navigation/navigation.model';
|
|||||||
templateUrl: 'nav-item.component.html',
|
templateUrl: 'nav-item.component.html',
|
||||||
})
|
})
|
||||||
export class NavItemComponent implements OnChanges {
|
export class NavItemComponent implements OnChanges {
|
||||||
@Input() selectedNodes: NavigationNode[];
|
@Input() isWide = false;
|
||||||
@Input() node: NavigationNode;
|
|
||||||
@Input() level = 1;
|
@Input() level = 1;
|
||||||
|
@Input() node: NavigationNode;
|
||||||
|
@Input() selectedNodes: NavigationNode[];
|
||||||
|
|
||||||
isExpanded = false;
|
isExpanded = false;
|
||||||
isSelected = false;
|
isSelected = false;
|
||||||
classes: {[index: string]: boolean };
|
classes: {[index: string]: boolean };
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
if (changes['selectedNodes'] || changes['node']) {
|
if (changes['selectedNodes'] || changes['node'] || changes['isWide']) {
|
||||||
const ix = this.selectedNodes.indexOf(this.node);
|
if (this.selectedNodes) {
|
||||||
this.isSelected = ix !== -1;
|
const ix = this.selectedNodes.indexOf(this.node);
|
||||||
if (ix !== 0) { this.isExpanded = this.isSelected; }
|
this.isSelected = ix !== -1; // this node is the selected node or its ancestor
|
||||||
|
this.isExpanded = this.isSelected || // expand if selected or ...
|
||||||
|
// preserve expanded state when display is wide; collapse in mobile.
|
||||||
|
(this.isWide && this.isExpanded);
|
||||||
|
} else {
|
||||||
|
this.isSelected = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.setClasses();
|
this.setClasses();
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,12 @@ import { CurrentNode, NavigationNode } from 'app/navigation/navigation.service';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'aio-nav-menu',
|
selector: 'aio-nav-menu',
|
||||||
template: `
|
template: `
|
||||||
<aio-nav-item *ngFor="let node of filteredNodes" [node]="node" [selectedNodes]="currentNode.nodes">
|
<aio-nav-item *ngFor="let node of filteredNodes" [node]="node" [selectedNodes]="currentNode?.nodes" [isWide]="isWide">
|
||||||
</aio-nav-item>`
|
</aio-nav-item>`
|
||||||
})
|
})
|
||||||
export class NavMenuComponent {
|
export class NavMenuComponent {
|
||||||
@Input() currentNode: CurrentNode;
|
@Input() currentNode: CurrentNode;
|
||||||
@Input() nodes: NavigationNode[] ;
|
@Input() isWide = false;
|
||||||
|
@Input() nodes: NavigationNode[];
|
||||||
get filteredNodes() { return this.nodes ? this.nodes.filter(n => !n.hidden) : []; }
|
get filteredNodes() { return this.nodes ? this.nodes.filter(n => !n.hidden) : []; }
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ export interface NavigationViews {
|
|||||||
/**
|
/**
|
||||||
* Navigation information about a node at specific URL
|
* Navigation information about a node at specific URL
|
||||||
* url: the current URL
|
* url: the current URL
|
||||||
* view: 'SideNav' | 'TopBar' | 'Footer'
|
* view: 'SideNav' | 'TopBar' | 'Footer' | etc
|
||||||
* nodes: the current node and its ancestor nodes within that view
|
* nodes: the current node and its ancestor nodes within that view
|
||||||
*/
|
*/
|
||||||
export interface CurrentNode {
|
export interface CurrentNode {
|
||||||
@ -28,6 +28,15 @@ export interface CurrentNode {
|
|||||||
nodes: NavigationNode[];
|
nodes: NavigationNode[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map of current nodes by view.
|
||||||
|
* This is needed because some urls map to nodes in more than one view.
|
||||||
|
* If a view does not contain a node that matches the current url then the value will be undefined.
|
||||||
|
*/
|
||||||
|
export interface CurrentNodes {
|
||||||
|
[view: string]: CurrentNode;
|
||||||
|
}
|
||||||
|
|
||||||
export interface VersionInfo {
|
export interface VersionInfo {
|
||||||
raw: string;
|
raw: string;
|
||||||
major: number;
|
major: number;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ReflectiveInjector } from '@angular/core';
|
import { ReflectiveInjector } from '@angular/core';
|
||||||
import { Http, ConnectionBackend, RequestOptions, BaseRequestOptions, Response, ResponseOptions } from '@angular/http';
|
import { Http, ConnectionBackend, RequestOptions, BaseRequestOptions, Response, ResponseOptions } from '@angular/http';
|
||||||
import { MockBackend } from '@angular/http/testing';
|
import { MockBackend } from '@angular/http/testing';
|
||||||
import { CurrentNode, NavigationService, NavigationViews, NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
|
import { CurrentNodes, NavigationService, NavigationViews, NavigationNode, VersionInfo } from 'app/navigation/navigation.service';
|
||||||
import { LocationService } from 'app/shared/location.service';
|
import { LocationService } from 'app/shared/location.service';
|
||||||
import { MockLocationService } from 'testing/location.service';
|
import { MockLocationService } from 'testing/location.service';
|
||||||
import { Logger } from 'app/shared/logger.service';
|
import { Logger } from 'app/shared/logger.service';
|
||||||
@ -113,7 +113,7 @@ describe('NavigationService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('currentNode', () => {
|
describe('currentNode', () => {
|
||||||
let currentNode: CurrentNode;
|
let currentNodes: CurrentNodes;
|
||||||
let locationService: MockLocationService;
|
let locationService: MockLocationService;
|
||||||
|
|
||||||
const topBarNodes: NavigationNode[] = [
|
const topBarNodes: NavigationNode[] = [
|
||||||
@ -138,80 +138,92 @@ describe('NavigationService', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
locationService = injector.get(LocationService);
|
locationService = injector.get(LocationService);
|
||||||
navService.currentNode.subscribe(selected => currentNode = selected);
|
navService.currentNodes.subscribe(selected => currentNodes = selected);
|
||||||
backend.connectionsArray[0].mockRespond(createResponse(navJson));
|
backend.connectionsArray[0].mockRespond(createResponse(navJson));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should list the side navigation node that matches the current location, and all its ancestors', () => {
|
it('should list the side navigation node that matches the current location, and all its ancestors', () => {
|
||||||
locationService.go('b');
|
locationService.go('b');
|
||||||
expect(currentNode).toEqual({
|
expect(currentNodes).toEqual({
|
||||||
url: 'b',
|
SideNav: {
|
||||||
view: 'SideNav',
|
url: 'b',
|
||||||
nodes: [
|
view: 'SideNav',
|
||||||
sideNavNodes[0].children[0],
|
nodes: [
|
||||||
sideNavNodes[0]
|
sideNavNodes[0].children[0],
|
||||||
]
|
sideNavNodes[0]
|
||||||
|
]
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
locationService.go('d');
|
locationService.go('d');
|
||||||
expect(currentNode).toEqual({
|
expect(currentNodes).toEqual({
|
||||||
url: 'd',
|
SideNav: {
|
||||||
view: 'SideNav',
|
url: 'd',
|
||||||
nodes: [
|
view: 'SideNav',
|
||||||
sideNavNodes[0].children[0].children[1],
|
nodes: [
|
||||||
sideNavNodes[0].children[0],
|
sideNavNodes[0].children[0].children[1],
|
||||||
sideNavNodes[0]
|
sideNavNodes[0].children[0],
|
||||||
]
|
sideNavNodes[0]
|
||||||
|
]
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
locationService.go('f');
|
locationService.go('f');
|
||||||
expect(currentNode).toEqual({
|
expect(currentNodes).toEqual({
|
||||||
url: 'f',
|
SideNav: {
|
||||||
view: 'SideNav',
|
url: 'f',
|
||||||
nodes: [ sideNavNodes[1] ]
|
view: 'SideNav',
|
||||||
|
nodes: [ sideNavNodes[1] ]
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be a TopBar selected node if the current location is a top menu node', () => {
|
it('should be a TopBar selected node if the current location is a top menu node', () => {
|
||||||
locationService.go('features');
|
locationService.go('features');
|
||||||
expect(currentNode).toEqual({
|
expect(currentNodes).toEqual({
|
||||||
url: 'features',
|
TopBar: {
|
||||||
view: 'TopBar',
|
url: 'features',
|
||||||
nodes: [ topBarNodes[0] ]
|
view: 'TopBar',
|
||||||
|
nodes: [ topBarNodes[0] ]
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be a plain object if no side navigation node matches the current location', () => {
|
it('should be a plain object if no navigation node matches the current location', () => {
|
||||||
locationService.go('g?search=moo#anchor-1');
|
locationService.go('g?search=moo#anchor-1');
|
||||||
expect(currentNode).toEqual({
|
expect(currentNodes).toEqual({
|
||||||
url: 'g',
|
'': {
|
||||||
view: '',
|
url: 'g',
|
||||||
nodes: []
|
view: '',
|
||||||
|
nodes: []
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should ignore trailing slashes, hashes, and search params on URLs in the navmap', () => {
|
it('should ignore trailing slashes, hashes, and search params on URLs in the navmap', () => {
|
||||||
const cnode: CurrentNode = {
|
const cnode: CurrentNodes = {
|
||||||
url: 'c',
|
SideNav: {
|
||||||
view: 'SideNav',
|
url: 'c',
|
||||||
nodes: [
|
view: 'SideNav',
|
||||||
sideNavNodes[0].children[0].children[0],
|
nodes: [
|
||||||
sideNavNodes[0].children[0],
|
sideNavNodes[0].children[0].children[0],
|
||||||
sideNavNodes[0]
|
sideNavNodes[0].children[0],
|
||||||
]
|
sideNavNodes[0]
|
||||||
|
]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
locationService.go('c');
|
locationService.go('c');
|
||||||
expect(currentNode).toEqual(cnode, 'location: c');
|
expect(currentNodes).toEqual(cnode, 'location: c');
|
||||||
|
|
||||||
locationService.go('c#foo');
|
locationService.go('c#foo');
|
||||||
expect(currentNode).toEqual(cnode, 'location: c#foo');
|
expect(currentNodes).toEqual(cnode, 'location: c#foo');
|
||||||
|
|
||||||
locationService.go('c?foo=1');
|
locationService.go('c?foo=1');
|
||||||
expect(currentNode).toEqual(cnode, 'location: c?foo=1');
|
expect(currentNodes).toEqual(cnode, 'location: c?foo=1');
|
||||||
|
|
||||||
locationService.go('c#foo?bar=1&baz=2');
|
locationService.go('c#foo?bar=1&baz=2');
|
||||||
expect(currentNode).toEqual(cnode, 'location: c#foo?bar=1&baz=2');
|
expect(currentNodes).toEqual(cnode, 'location: c#foo?bar=1&baz=2');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -13,8 +13,8 @@ import { LocationService } from 'app/shared/location.service';
|
|||||||
import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
||||||
|
|
||||||
// Import and re-export the Navigation model types
|
// Import and re-export the Navigation model types
|
||||||
import { CurrentNode, NavigationNode, NavigationResponse, NavigationViews, VersionInfo } from './navigation.model';
|
import { CurrentNodes, NavigationNode, NavigationResponse, NavigationViews, VersionInfo } from './navigation.model';
|
||||||
export { CurrentNode, NavigationNode, NavigationResponse, NavigationViews, VersionInfo } from './navigation.model';
|
export { CurrentNodes, CurrentNode, NavigationNode, NavigationResponse, NavigationViews, VersionInfo } from './navigation.model';
|
||||||
|
|
||||||
const navigationPath = CONTENT_URL_PREFIX + 'navigation.json';
|
const navigationPath = CONTENT_URL_PREFIX + 'navigation.json';
|
||||||
|
|
||||||
@ -35,13 +35,13 @@ export class NavigationService {
|
|||||||
* node (if any) that matches the current URL location
|
* node (if any) that matches the current URL location
|
||||||
* including its navigation view and its ancestor nodes in that view
|
* including its navigation view and its ancestor nodes in that view
|
||||||
*/
|
*/
|
||||||
currentNode: Observable<CurrentNode>;
|
currentNodes: Observable<CurrentNodes>;
|
||||||
|
|
||||||
constructor(private http: Http, private location: LocationService, private logger: Logger) {
|
constructor(private http: Http, private location: LocationService, private logger: Logger) {
|
||||||
const navigationInfo = this.fetchNavigationInfo();
|
const navigationInfo = this.fetchNavigationInfo();
|
||||||
this.navigationViews = this.getNavigationViews(navigationInfo);
|
this.navigationViews = this.getNavigationViews(navigationInfo);
|
||||||
|
|
||||||
this.currentNode = this.getCurrentNode(this.navigationViews);
|
this.currentNodes = this.getCurrentNodes(this.navigationViews);
|
||||||
// The version information is packaged inside the navigation response to save us an extra request.
|
// The version information is packaged inside the navigation response to save us an extra request.
|
||||||
this.versionInfo = this.getVersionInfo(navigationInfo);
|
this.versionInfo = this.getVersionInfo(navigationInfo);
|
||||||
}
|
}
|
||||||
@ -88,23 +88,23 @@ export class NavigationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an observable of the current node (the one that matches the current URL)
|
* Get an observable of the current nodes (the ones that match the current URL)
|
||||||
* We use `publishReplay(1)` because otherwise subscribers will have to wait until the next
|
* We use `publishReplay(1)` because otherwise subscribers will have to wait until the next
|
||||||
* URL change before they receive an emission.
|
* URL change before they receive an emission.
|
||||||
* See above for discussion of using `connect`.
|
* See above for discussion of using `connect`.
|
||||||
*/
|
*/
|
||||||
private getCurrentNode(navigationViews: Observable<NavigationViews>): Observable<CurrentNode> {
|
private getCurrentNodes(navigationViews: Observable<NavigationViews>): Observable<CurrentNodes> {
|
||||||
const currentNode = combineLatest(
|
const currentNodes = combineLatest(
|
||||||
navigationViews.map(views => this.computeUrlToNavNodesMap(views)),
|
navigationViews.map(views => this.computeUrlToNavNodesMap(views)),
|
||||||
this.location.currentPath,
|
this.location.currentPath,
|
||||||
|
|
||||||
(navMap, url) => {
|
(navMap, url) => {
|
||||||
const urlKey = url.startsWith('api/') ? 'api' : url;
|
const urlKey = url.startsWith('api/') ? 'api' : url;
|
||||||
return navMap[urlKey] || { view: '', url: urlKey, nodes: [] };
|
return navMap[urlKey] || { '' : { view: '', url: urlKey, nodes: [] }};
|
||||||
})
|
})
|
||||||
.publishReplay(1);
|
.publishReplay(1);
|
||||||
currentNode.connect();
|
currentNodes.connect();
|
||||||
return currentNode;
|
return currentNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -114,7 +114,7 @@ export class NavigationService {
|
|||||||
* @param navigation - A collection of navigation nodes that are to be mapped
|
* @param navigation - A collection of navigation nodes that are to be mapped
|
||||||
*/
|
*/
|
||||||
private computeUrlToNavNodesMap(navigation: NavigationViews) {
|
private computeUrlToNavNodesMap(navigation: NavigationViews) {
|
||||||
const navMap = new Map<string, CurrentNode>();
|
const navMap = new Map<string, CurrentNodes>();
|
||||||
Object.keys(navigation)
|
Object.keys(navigation)
|
||||||
.forEach(view => navigation[view]
|
.forEach(view => navigation[view]
|
||||||
.forEach(node => this.walkNodes(view, navMap, node)));
|
.forEach(node => this.walkNodes(view, navMap, node)));
|
||||||
@ -138,7 +138,7 @@ export class NavigationService {
|
|||||||
* patching them and computing their ancestor nodes
|
* patching them and computing their ancestor nodes
|
||||||
*/
|
*/
|
||||||
private walkNodes(
|
private walkNodes(
|
||||||
view: string, navMap: Map<string, CurrentNode>,
|
view: string, navMap: Map<string, CurrentNodes>,
|
||||||
node: NavigationNode, ancestors: NavigationNode[] = []) {
|
node: NavigationNode, ancestors: NavigationNode[] = []) {
|
||||||
const nodes = [node, ...ancestors];
|
const nodes = [node, ...ancestors];
|
||||||
const url = node.url;
|
const url = node.url;
|
||||||
@ -147,7 +147,9 @@ export class NavigationService {
|
|||||||
// only map to this node if it has a url
|
// only map to this node if it has a url
|
||||||
if (url) {
|
if (url) {
|
||||||
// Strip off trailing slashes from nodes in the navMap - they are not relevant to matching
|
// Strip off trailing slashes from nodes in the navMap - they are not relevant to matching
|
||||||
navMap[url.replace(/\/$/, '')] = { url, view, nodes };
|
const cleanedUrl = url.replace(/\/$/, '');
|
||||||
|
const navMapItem = navMap[cleanedUrl] = navMap[cleanedUrl] || {};
|
||||||
|
navMapItem[view] = { url, view, nodes };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.children) {
|
if (node.children) {
|
||||||
|
@ -44,6 +44,14 @@ describe('SearchBoxComponent', () => {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('on input', () => {
|
||||||
|
it('should trigger the search event', () => {
|
||||||
|
const input = fixture.debugElement.query(By.css('input'));
|
||||||
|
input.triggerEventHandler('input', { target: { value: 'some query' } });
|
||||||
|
expect(host.doSearch).toHaveBeenCalledWith('some query');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('on keyup', () => {
|
describe('on keyup', () => {
|
||||||
it('should trigger the search event', () => {
|
it('should trigger the search event', () => {
|
||||||
const input = fixture.debugElement.query(By.css('input'));
|
const input = fixture.debugElement.query(By.css('input'));
|
||||||
|
@ -14,8 +14,10 @@ import { LocationService } from 'app/shared/location.service';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'aio-search-box',
|
selector: 'aio-search-box',
|
||||||
template: `<input #searchBox
|
template: `<input #searchBox
|
||||||
|
type="search"
|
||||||
aria-label="search"
|
aria-label="search"
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
|
(input)="onSearch($event.target.value)"
|
||||||
(keyup)="onSearch($event.target.value)"
|
(keyup)="onSearch($event.target.value)"
|
||||||
(focus)="onSearch($event.target.value)"
|
(focus)="onSearch($event.target.value)"
|
||||||
(click)="onSearch($event.target.value)">`
|
(click)="onSearch($event.target.value)">`
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<ng-template #searchResults>
|
<ng-template #searchResults>
|
||||||
<h2 class="visually-hidden">Search Results</h2>
|
<h2 class="visually-hidden">Search Results</h2>
|
||||||
<div class="search-area" *ngFor="let area of searchAreas">
|
<div class="search-area" *ngFor="let area of searchAreas">
|
||||||
<h3>{{area.name}} ({{area.pages.length}})</h3>
|
<h3>{{area.name}} ({{area.pages.length + area.priorityPages.length}})</h3>
|
||||||
<ul class="priority-pages" >
|
<ul class="priority-pages" >
|
||||||
<li class="search-page" *ngFor="let page of area.priorityPages">
|
<li class="search-page" *ngFor="let page of area.priorityPages">
|
||||||
<a class="search-result-item" href="{{ page.path }}" (click)="onResultSelected(page)">
|
<a class="search-result-item" href="{{ page.path }}" (click)="onResultSelected(page)">
|
||||||
@ -13,7 +13,6 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="more-items material-icons" *ngIf="area.priorityPages.length > 0">more_horiz</div>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li class="search-page" *ngFor="let page of area.pages">
|
<li class="search-page" *ngFor="let page of area.pages">
|
||||||
<a class="search-result-item" href="{{ page.path }}" (click)="onResultSelected(page)">
|
<a class="search-result-item" href="{{ page.path }}" (click)="onResultSelected(page)">
|
||||||
|
@ -33,6 +33,11 @@ describe('SearchResultsComponent', () => {
|
|||||||
return take === undefined ? results : results.slice(0, take);
|
return take === undefined ? results : results.slice(0, take);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function compareTitle(l: {title: string}, r: {title: string}) {
|
||||||
|
return l.title.toUpperCase() > r.title.toUpperCase() ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ SearchResultsComponent ],
|
declarations: [ SearchResultsComponent ],
|
||||||
@ -54,13 +59,13 @@ describe('SearchResultsComponent', () => {
|
|||||||
|
|
||||||
searchResults.next({ query: '', results: results});
|
searchResults.next({ query: '', results: results});
|
||||||
expect(component.searchAreas).toEqual([
|
expect(component.searchAreas).toEqual([
|
||||||
{ name: 'api', pages: [
|
{ name: 'api', priorityPages: [
|
||||||
{ path: 'api/d', title: 'API D', type: '', keywords: '', titleWords: '' }
|
{ path: 'api/d', title: 'API D', type: '', keywords: '', titleWords: '' }
|
||||||
], priorityPages: [] },
|
], pages: [] },
|
||||||
{ name: 'guide', pages: [
|
{ name: 'guide', priorityPages: [
|
||||||
{ path: 'guide/a', title: 'Guide A', type: '', keywords: '', titleWords: '' },
|
{ path: 'guide/a', title: 'Guide A', type: '', keywords: '', titleWords: '' },
|
||||||
{ path: 'guide/b', title: 'Guide B', type: '', keywords: '', titleWords: '' },
|
{ path: 'guide/b', title: 'Guide B', type: '', keywords: '', titleWords: '' },
|
||||||
], priorityPages: [] }
|
], pages: [] }
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -70,48 +75,35 @@ describe('SearchResultsComponent', () => {
|
|||||||
{ path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '' },
|
{ path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '' },
|
||||||
]});
|
]});
|
||||||
expect(component.searchAreas).toEqual([
|
expect(component.searchAreas).toEqual([
|
||||||
{ name: 'tutorial', pages: [
|
{ name: 'tutorial', priorityPages: [
|
||||||
{ path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '' },
|
|
||||||
{ path: 'tutorial', title: 'Tutorial index', type: '', keywords: '', titleWords: '' },
|
{ path: 'tutorial', title: 'Tutorial index', type: '', keywords: '', titleWords: '' },
|
||||||
], priorityPages: [] }
|
{ path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '' },
|
||||||
|
], pages: [] }
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sort by title within sorted area', () => {
|
it('should put first 5 results for each area into priorityPages', () => {
|
||||||
const results = getTestResults(5);
|
|
||||||
searchResults.next({ query: '', results: results });
|
|
||||||
|
|
||||||
expect(component.searchAreas).toEqual([
|
|
||||||
{ name: 'api', pages: [
|
|
||||||
{ path: 'api/c', title: 'API C', type: '', keywords: '', titleWords: '' },
|
|
||||||
{ path: 'api/d', title: 'API D', type: '', keywords: '', titleWords: '' },
|
|
||||||
], priorityPages: [] },
|
|
||||||
{ name: 'guide', pages: [
|
|
||||||
{ path: 'guide/a', title: 'Guide A', type: '', keywords: '', titleWords: '' },
|
|
||||||
{ path: 'guide/a/c', title: 'Guide A - C', type: '', keywords: '', titleWords: '' },
|
|
||||||
{ path: 'guide/b', title: 'Guide B', type: '', keywords: '', titleWords: '' },
|
|
||||||
], priorityPages: [] }
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should put first 5 area results into priorityPages when more than 10 pages', () => {
|
|
||||||
const results = getTestResults();
|
const results = getTestResults();
|
||||||
const sorted = results.slice().sort((l, r) => l.title > r.title ? 1 : -1);
|
|
||||||
const expected = [
|
|
||||||
{
|
|
||||||
name: 'api',
|
|
||||||
pages: sorted.filter(p => p.path.startsWith('api')),
|
|
||||||
priorityPages: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'guide',
|
|
||||||
pages: sorted.filter(p => p.path.startsWith('guide')),
|
|
||||||
priorityPages: results.filter(p => p.path.startsWith('guide')).slice(0, 5)
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
searchResults.next({ query: '', results: results });
|
searchResults.next({ query: '', results: results });
|
||||||
expect(component.searchAreas).toEqual(expected);
|
expect(component.searchAreas[0].priorityPages).toEqual(results.filter(p => p.path.startsWith('api')).slice(0, 5));
|
||||||
|
expect(component.searchAreas[1].priorityPages).toEqual(results.filter(p => p.path.startsWith('guide')).slice(0, 5));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should put the nonPriorityPages into the pages array, sorted by title', () => {
|
||||||
|
const results = getTestResults();
|
||||||
|
searchResults.next({ query: '', results: results });
|
||||||
|
expect(component.searchAreas[0].pages).toEqual([]);
|
||||||
|
expect(component.searchAreas[1].pages).toEqual(results.filter(p => p.path.startsWith('guide')).slice(5).sort(compareTitle));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should put a total count in the header of each area of search results', () => {
|
||||||
|
const results = getTestResults();
|
||||||
|
searchResults.next({ query: '', results: results });
|
||||||
|
fixture.detectChanges();
|
||||||
|
const headers = fixture.debugElement.queryAll(By.css('h3'));
|
||||||
|
expect(headers.length).toEqual(2);
|
||||||
|
expect(headers[0].nativeElement.textContent).toContain('(2)');
|
||||||
|
expect(headers[1].nativeElement.textContent).toContain('(13)');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should put search results with no containing folder into the default area (other)', () => {
|
it('should put search results with no containing folder into the default area (other)', () => {
|
||||||
@ -121,9 +113,9 @@ describe('SearchResultsComponent', () => {
|
|||||||
|
|
||||||
searchResults.next({ query: '', results: results });
|
searchResults.next({ query: '', results: results });
|
||||||
expect(component.searchAreas).toEqual([
|
expect(component.searchAreas).toEqual([
|
||||||
{ name: 'other', pages: [
|
{ name: 'other', priorityPages: [
|
||||||
{ path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' }
|
{ path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' }
|
||||||
], priorityPages: [] }
|
], pages: [] }
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -60,8 +60,10 @@ export class SearchResultsComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
const keys = Object.keys(searchAreaMap).sort((l, r) => l > r ? 1 : -1);
|
const keys = Object.keys(searchAreaMap).sort((l, r) => l > r ? 1 : -1);
|
||||||
return keys.map(name => {
|
return keys.map(name => {
|
||||||
let pages = searchAreaMap[name];
|
let pages: SearchResult[] = searchAreaMap[name];
|
||||||
const priorityPages = pages.length > 10 ? searchAreaMap[name].slice(0, 5) : [];
|
|
||||||
|
// Extract the top 5 most relevant results as priorityPages
|
||||||
|
const priorityPages = pages.splice(0, 5);
|
||||||
pages = pages.sort(compareResults);
|
pages = pages.sort(compareResults);
|
||||||
return { name, pages, priorityPages };
|
return { name, pages, priorityPages };
|
||||||
});
|
});
|
||||||
|
@ -300,6 +300,7 @@ describe('ScrollSpyService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('window resize events', () => {
|
describe('window resize events', () => {
|
||||||
|
const RESIZE_EVENT_DELAY = 300;
|
||||||
let onResizeSpy: jasmine.Spy;
|
let onResizeSpy: jasmine.Spy;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -317,7 +318,7 @@ describe('ScrollSpyService', () => {
|
|||||||
window.dispatchEvent(new Event('resize'));
|
window.dispatchEvent(new Event('resize'));
|
||||||
expect(onResizeSpy).not.toHaveBeenCalled();
|
expect(onResizeSpy).not.toHaveBeenCalled();
|
||||||
|
|
||||||
tick(300);
|
tick(RESIZE_EVENT_DELAY);
|
||||||
expect(onResizeSpy).toHaveBeenCalled();
|
expect(onResizeSpy).toHaveBeenCalled();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -327,58 +328,59 @@ describe('ScrollSpyService', () => {
|
|||||||
onResizeSpy.calls.reset();
|
onResizeSpy.calls.reset();
|
||||||
|
|
||||||
window.dispatchEvent(new Event('resize'));
|
window.dispatchEvent(new Event('resize'));
|
||||||
tick(300);
|
tick(RESIZE_EVENT_DELAY);
|
||||||
expect(onResizeSpy).toHaveBeenCalled();
|
expect(onResizeSpy).toHaveBeenCalled();
|
||||||
|
|
||||||
info1.unspy();
|
info1.unspy();
|
||||||
onResizeSpy.calls.reset();
|
onResizeSpy.calls.reset();
|
||||||
|
|
||||||
window.dispatchEvent(new Event('resize'));
|
window.dispatchEvent(new Event('resize'));
|
||||||
tick(300);
|
tick(RESIZE_EVENT_DELAY);
|
||||||
expect(onResizeSpy).toHaveBeenCalled();
|
expect(onResizeSpy).toHaveBeenCalled();
|
||||||
|
|
||||||
info2.unspy();
|
info2.unspy();
|
||||||
onResizeSpy.calls.reset();
|
onResizeSpy.calls.reset();
|
||||||
|
|
||||||
window.dispatchEvent(new Event('resize'));
|
window.dispatchEvent(new Event('resize'));
|
||||||
tick(300);
|
tick(RESIZE_EVENT_DELAY);
|
||||||
expect(onResizeSpy).not.toHaveBeenCalled();
|
expect(onResizeSpy).not.toHaveBeenCalled();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should only fire every 300ms', fakeAsync(() => {
|
it(`should only fire every ${RESIZE_EVENT_DELAY}ms`, fakeAsync(() => {
|
||||||
scrollSpyService.spyOn([]);
|
scrollSpyService.spyOn([]);
|
||||||
onResizeSpy.calls.reset();
|
onResizeSpy.calls.reset();
|
||||||
|
|
||||||
window.dispatchEvent(new Event('resize'));
|
window.dispatchEvent(new Event('resize'));
|
||||||
tick(100);
|
tick(RESIZE_EVENT_DELAY - 2);
|
||||||
expect(onResizeSpy).not.toHaveBeenCalled();
|
expect(onResizeSpy).not.toHaveBeenCalled();
|
||||||
|
|
||||||
window.dispatchEvent(new Event('resize'));
|
window.dispatchEvent(new Event('resize'));
|
||||||
tick(100);
|
tick(1);
|
||||||
expect(onResizeSpy).not.toHaveBeenCalled();
|
expect(onResizeSpy).not.toHaveBeenCalled();
|
||||||
|
|
||||||
window.dispatchEvent(new Event('resize'));
|
window.dispatchEvent(new Event('resize'));
|
||||||
tick(100);
|
tick(1);
|
||||||
expect(onResizeSpy).toHaveBeenCalledTimes(1);
|
expect(onResizeSpy).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
onResizeSpy.calls.reset();
|
onResizeSpy.calls.reset();
|
||||||
tick(150);
|
tick(RESIZE_EVENT_DELAY / 2);
|
||||||
|
|
||||||
window.dispatchEvent(new Event('resize'));
|
window.dispatchEvent(new Event('resize'));
|
||||||
tick(100);
|
tick(RESIZE_EVENT_DELAY - 2);
|
||||||
expect(onResizeSpy).not.toHaveBeenCalled();
|
expect(onResizeSpy).not.toHaveBeenCalled();
|
||||||
|
|
||||||
window.dispatchEvent(new Event('resize'));
|
window.dispatchEvent(new Event('resize'));
|
||||||
tick(100);
|
tick(1);
|
||||||
expect(onResizeSpy).not.toHaveBeenCalled();
|
expect(onResizeSpy).not.toHaveBeenCalled();
|
||||||
|
|
||||||
window.dispatchEvent(new Event('resize'));
|
window.dispatchEvent(new Event('resize'));
|
||||||
tick(100);
|
tick(1);
|
||||||
expect(onResizeSpy).toHaveBeenCalledTimes(1);
|
expect(onResizeSpy).toHaveBeenCalledTimes(1);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('window scroll events', () => {
|
describe('window scroll events', () => {
|
||||||
|
const SCROLL_EVENT_DELAY = 10;
|
||||||
let onScrollSpy: jasmine.Spy;
|
let onScrollSpy: jasmine.Spy;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -395,7 +397,7 @@ describe('ScrollSpyService', () => {
|
|||||||
window.dispatchEvent(new Event('scroll'));
|
window.dispatchEvent(new Event('scroll'));
|
||||||
expect(onScrollSpy).not.toHaveBeenCalled();
|
expect(onScrollSpy).not.toHaveBeenCalled();
|
||||||
|
|
||||||
tick(300);
|
tick(SCROLL_EVENT_DELAY);
|
||||||
expect(onScrollSpy).toHaveBeenCalled();
|
expect(onScrollSpy).toHaveBeenCalled();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -404,52 +406,52 @@ describe('ScrollSpyService', () => {
|
|||||||
const info2 = scrollSpyService.spyOn([]);
|
const info2 = scrollSpyService.spyOn([]);
|
||||||
|
|
||||||
window.dispatchEvent(new Event('scroll'));
|
window.dispatchEvent(new Event('scroll'));
|
||||||
tick(300);
|
tick(SCROLL_EVENT_DELAY);
|
||||||
expect(onScrollSpy).toHaveBeenCalled();
|
expect(onScrollSpy).toHaveBeenCalled();
|
||||||
|
|
||||||
info1.unspy();
|
info1.unspy();
|
||||||
onScrollSpy.calls.reset();
|
onScrollSpy.calls.reset();
|
||||||
|
|
||||||
window.dispatchEvent(new Event('scroll'));
|
window.dispatchEvent(new Event('scroll'));
|
||||||
tick(300);
|
tick(SCROLL_EVENT_DELAY);
|
||||||
expect(onScrollSpy).toHaveBeenCalled();
|
expect(onScrollSpy).toHaveBeenCalled();
|
||||||
|
|
||||||
info2.unspy();
|
info2.unspy();
|
||||||
onScrollSpy.calls.reset();
|
onScrollSpy.calls.reset();
|
||||||
|
|
||||||
window.dispatchEvent(new Event('scroll'));
|
window.dispatchEvent(new Event('scroll'));
|
||||||
tick(300);
|
tick(SCROLL_EVENT_DELAY);
|
||||||
expect(onScrollSpy).not.toHaveBeenCalled();
|
expect(onScrollSpy).not.toHaveBeenCalled();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should only fire every 300ms', fakeAsync(() => {
|
it(`should only fire every ${SCROLL_EVENT_DELAY}ms`, fakeAsync(() => {
|
||||||
scrollSpyService.spyOn([]);
|
scrollSpyService.spyOn([]);
|
||||||
|
|
||||||
window.dispatchEvent(new Event('scroll'));
|
window.dispatchEvent(new Event('scroll'));
|
||||||
tick(100);
|
tick(SCROLL_EVENT_DELAY - 2);
|
||||||
expect(onScrollSpy).not.toHaveBeenCalled();
|
expect(onScrollSpy).not.toHaveBeenCalled();
|
||||||
|
|
||||||
window.dispatchEvent(new Event('scroll'));
|
window.dispatchEvent(new Event('scroll'));
|
||||||
tick(100);
|
tick(1);
|
||||||
expect(onScrollSpy).not.toHaveBeenCalled();
|
expect(onScrollSpy).not.toHaveBeenCalled();
|
||||||
|
|
||||||
window.dispatchEvent(new Event('scroll'));
|
window.dispatchEvent(new Event('scroll'));
|
||||||
tick(100);
|
tick(1);
|
||||||
expect(onScrollSpy).toHaveBeenCalledTimes(1);
|
expect(onScrollSpy).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
onScrollSpy.calls.reset();
|
onScrollSpy.calls.reset();
|
||||||
tick(150);
|
tick(SCROLL_EVENT_DELAY / 2);
|
||||||
|
|
||||||
window.dispatchEvent(new Event('scroll'));
|
window.dispatchEvent(new Event('scroll'));
|
||||||
tick(100);
|
tick(SCROLL_EVENT_DELAY - 2);
|
||||||
expect(onScrollSpy).not.toHaveBeenCalled();
|
expect(onScrollSpy).not.toHaveBeenCalled();
|
||||||
|
|
||||||
window.dispatchEvent(new Event('scroll'));
|
window.dispatchEvent(new Event('scroll'));
|
||||||
tick(100);
|
tick(1);
|
||||||
expect(onScrollSpy).not.toHaveBeenCalled();
|
expect(onScrollSpy).not.toHaveBeenCalled();
|
||||||
|
|
||||||
window.dispatchEvent(new Event('scroll'));
|
window.dispatchEvent(new Event('scroll'));
|
||||||
tick(100);
|
tick(1);
|
||||||
expect(onScrollSpy).toHaveBeenCalledTimes(1);
|
expect(onScrollSpy).toHaveBeenCalledTimes(1);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
@ -124,7 +124,7 @@ export class ScrollSpyService {
|
|||||||
private spiedElementGroups: ScrollSpiedElementGroup[] = [];
|
private spiedElementGroups: ScrollSpiedElementGroup[] = [];
|
||||||
private onStopListening = new Subject();
|
private onStopListening = new Subject();
|
||||||
private resizeEvents = Observable.fromEvent(window, 'resize').auditTime(300).takeUntil(this.onStopListening);
|
private resizeEvents = Observable.fromEvent(window, 'resize').auditTime(300).takeUntil(this.onStopListening);
|
||||||
private scrollEvents = Observable.fromEvent(window, 'scroll').auditTime(300).takeUntil(this.onStopListening);
|
private scrollEvents = Observable.fromEvent(window, 'scroll').auditTime(10).takeUntil(this.onStopListening);
|
||||||
private lastContentHeight: number;
|
private lastContentHeight: number;
|
||||||
private lastMaxScrollTop: number;
|
private lastMaxScrollTop: number;
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import { DOCUMENT } from '@angular/platform-browser';
|
|||||||
import { ScrollService, topMargin } from './scroll.service';
|
import { ScrollService, topMargin } from './scroll.service';
|
||||||
|
|
||||||
describe('ScrollService', () => {
|
describe('ScrollService', () => {
|
||||||
|
const topOfPageElem = {} as Element;
|
||||||
let injector: ReflectiveInjector;
|
let injector: ReflectiveInjector;
|
||||||
let document: MockDocument;
|
let document: MockDocument;
|
||||||
let location: MockPlatformLocation;
|
let location: MockPlatformLocation;
|
||||||
@ -16,7 +17,7 @@ describe('ScrollService', () => {
|
|||||||
|
|
||||||
class MockDocument {
|
class MockDocument {
|
||||||
body = new MockElement();
|
body = new MockElement();
|
||||||
getElementById = jasmine.createSpy('Document getElementById');
|
getElementById = jasmine.createSpy('Document getElementById').and.returnValue(topOfPageElem);
|
||||||
querySelector = jasmine.createSpy('Document querySelector');
|
querySelector = jasmine.createSpy('Document querySelector');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,6 +40,69 @@ describe('ScrollService', () => {
|
|||||||
scrollService = injector.get(ScrollService);
|
scrollService = injector.get(ScrollService);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#topOffset', () => {
|
||||||
|
it('should query for the top-bar by CSS selector', () => {
|
||||||
|
expect(document.querySelector).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(scrollService.topOffset).toBe(topMargin);
|
||||||
|
expect(document.querySelector).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be calculated based on the top-bar\'s height + margin', () => {
|
||||||
|
(document.querySelector as jasmine.Spy).and.returnValue({clientHeight: 50});
|
||||||
|
expect(scrollService.topOffset).toBe(50 + topMargin);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only query for the top-bar once', () => {
|
||||||
|
expect(scrollService.topOffset).toBe(topMargin);
|
||||||
|
(document.querySelector as jasmine.Spy).calls.reset();
|
||||||
|
|
||||||
|
expect(scrollService.topOffset).toBe(topMargin);
|
||||||
|
expect(document.querySelector).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve the top-bar\'s height again after resize', () => {
|
||||||
|
let clientHeight = 50;
|
||||||
|
(document.querySelector as jasmine.Spy).and.callFake(() => ({clientHeight}));
|
||||||
|
|
||||||
|
expect(scrollService.topOffset).toBe(50 + topMargin);
|
||||||
|
expect(document.querySelector).toHaveBeenCalled();
|
||||||
|
|
||||||
|
(document.querySelector as jasmine.Spy).calls.reset();
|
||||||
|
clientHeight = 100;
|
||||||
|
|
||||||
|
expect(scrollService.topOffset).toBe(50 + topMargin);
|
||||||
|
expect(document.querySelector).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
window.dispatchEvent(new Event('resize'));
|
||||||
|
|
||||||
|
expect(scrollService.topOffset).toBe(100 + topMargin);
|
||||||
|
expect(document.querySelector).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#topOfPageElement', () => {
|
||||||
|
it('should query for the top-of-page element by ID', () => {
|
||||||
|
expect(document.getElementById).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(scrollService.topOfPageElement).toBe(topOfPageElem);
|
||||||
|
expect(document.getElementById).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only query for the top-of-page element once', () => {
|
||||||
|
expect(scrollService.topOfPageElement).toBe(topOfPageElem);
|
||||||
|
(document.getElementById as jasmine.Spy).calls.reset();
|
||||||
|
|
||||||
|
expect(scrollService.topOfPageElement).toBe(topOfPageElem);
|
||||||
|
expect(document.getElementById).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return `<body>` if unable to find the top-of-page element', () => {
|
||||||
|
(document.getElementById as jasmine.Spy).and.returnValue(null);
|
||||||
|
expect(scrollService.topOfPageElement).toBe(document.body as any);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('#scroll', () => {
|
describe('#scroll', () => {
|
||||||
it('should scroll to the top if there is no hash', () => {
|
it('should scroll to the top if there is no hash', () => {
|
||||||
location.hash = '';
|
location.hash = '';
|
||||||
@ -74,10 +138,28 @@ describe('ScrollService', () => {
|
|||||||
|
|
||||||
describe('#scrollToElement', () => {
|
describe('#scrollToElement', () => {
|
||||||
it('should scroll to element', () => {
|
it('should scroll to element', () => {
|
||||||
const element = <Element><any> new MockElement();
|
const element: Element = new MockElement() as any;
|
||||||
scrollService.scrollToElement(element);
|
scrollService.scrollToElement(element);
|
||||||
expect(element.scrollIntoView).toHaveBeenCalled();
|
expect(element.scrollIntoView).toHaveBeenCalled();
|
||||||
expect(window.scrollBy).toHaveBeenCalledWith(0, -topMargin);
|
expect(window.scrollBy).toHaveBeenCalledWith(0, -scrollService.topOffset);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should scroll all the way to the top if close enough', () => {
|
||||||
|
const element: Element = new MockElement() as any;
|
||||||
|
|
||||||
|
(window as any).pageYOffset = 25;
|
||||||
|
scrollService.scrollToElement(element);
|
||||||
|
|
||||||
|
expect(element.scrollIntoView).toHaveBeenCalled();
|
||||||
|
expect(window.scrollBy).toHaveBeenCalledWith(0, -scrollService.topOffset);
|
||||||
|
(window.scrollBy as jasmine.Spy).calls.reset();
|
||||||
|
|
||||||
|
(window as any).pageYOffset = 15;
|
||||||
|
scrollService.scrollToElement(element);
|
||||||
|
|
||||||
|
expect(element.scrollIntoView).toHaveBeenCalled();
|
||||||
|
expect(window.scrollBy).toHaveBeenCalledWith(0, -scrollService.topOffset);
|
||||||
|
expect(window.scrollBy).toHaveBeenCalledWith(0, -15);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should do nothing if no element', () => {
|
it('should do nothing if no element', () => {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Injectable, Inject } from '@angular/core';
|
import { Injectable, Inject } from '@angular/core';
|
||||||
import { PlatformLocation } from '@angular/common';
|
import { PlatformLocation } from '@angular/common';
|
||||||
import { DOCUMENT } from '@angular/platform-browser';
|
import { DOCUMENT } from '@angular/platform-browser';
|
||||||
|
import {fromEvent} from 'rxjs/observable/fromEvent';
|
||||||
|
|
||||||
export const topMargin = 16;
|
export const topMargin = 16;
|
||||||
/**
|
/**
|
||||||
@ -9,19 +10,20 @@ export const topMargin = 16;
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class ScrollService {
|
export class ScrollService {
|
||||||
|
|
||||||
private _topOffset: number;
|
private _topOffset: number | null;
|
||||||
private _topOfPageElement: Element;
|
private _topOfPageElement: Element;
|
||||||
|
|
||||||
/** Offset from the top of the document to bottom of toolbar + some margin */
|
// Offset from the top of the document to bottom of any static elements
|
||||||
|
// at the top (e.g. toolbar) + some margin
|
||||||
get topOffset() {
|
get topOffset() {
|
||||||
if (!this._topOffset) {
|
if (!this._topOffset) {
|
||||||
const toolbar = document.querySelector('md-toolbar.app-toolbar');
|
const toolbar = this.document.querySelector('md-toolbar.app-toolbar');
|
||||||
this._topOffset = (toolbar ? toolbar.clientHeight : 0) + topMargin;
|
this._topOffset = (toolbar && toolbar.clientHeight || 0) + topMargin;
|
||||||
}
|
}
|
||||||
return this._topOffset;
|
return this._topOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get topOfPageElement() {
|
get topOfPageElement() {
|
||||||
if (!this._topOfPageElement) {
|
if (!this._topOfPageElement) {
|
||||||
this._topOfPageElement = this.document.getElementById('top-of-page') || this.document.body;
|
this._topOfPageElement = this.document.getElementById('top-of-page') || this.document.body;
|
||||||
}
|
}
|
||||||
@ -30,7 +32,10 @@ export class ScrollService {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DOCUMENT) private document: any,
|
@Inject(DOCUMENT) private document: any,
|
||||||
private location: PlatformLocation) { }
|
private location: PlatformLocation) {
|
||||||
|
// On resize, the toolbar might change height, so "invalidate" the top offset.
|
||||||
|
fromEvent(window, 'resize').subscribe(() => this._topOffset = null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scroll to the element with id extracted from the current location hash fragment.
|
* Scroll to the element with id extracted from the current location hash fragment.
|
||||||
@ -52,7 +57,14 @@ export class ScrollService {
|
|||||||
scrollToElement(element: Element) {
|
scrollToElement(element: Element) {
|
||||||
if (element) {
|
if (element) {
|
||||||
element.scrollIntoView();
|
element.scrollIntoView();
|
||||||
if (window && window.scrollBy) { window.scrollBy(0, -this.topOffset); }
|
if (window && window.scrollBy) {
|
||||||
|
window.scrollBy(0, -this.topOffset);
|
||||||
|
if (window.pageYOffset < 20) {
|
||||||
|
// If we are very close to the top (<20px), then scroll all the way up.
|
||||||
|
// (This can happen if `element` is at the top of the page, but has a small top-margin.)
|
||||||
|
window.scrollBy(0, -window.pageYOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
aio/src/app/shared/select/select.component.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<div class="form-select-menu">
|
||||||
|
<button class="form-select-button" (click)="toggleOptions()">
|
||||||
|
<strong>{{label}}</strong><span *ngIf="showSymbol" class="symbol {{selected?.value}}"></span>{{selected?.title}}
|
||||||
|
</button>
|
||||||
|
<ul class="form-select-dropdown" *ngIf="showOptions">
|
||||||
|
<li *ngFor="let option of options; index as i"
|
||||||
|
[class.selected]="option === selected"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
(click)="select(option, i)"
|
||||||
|
(keydown.enter)="select(option, i)"
|
||||||
|
(keydown.space)="select(option, i); $event.preventDefault()">
|
||||||
|
<span *ngIf="showSymbol" class="symbol {{option.value}}"></span>{{option.title}}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
165
aio/src/app/shared/select/select.component.spec.ts
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
import { Component, DebugElement } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { SelectComponent, Option } from './select.component';
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{ title: 'Option A', value: 'option-a' },
|
||||||
|
{ title: 'Option B', value: 'option-b' }
|
||||||
|
];
|
||||||
|
|
||||||
|
let component: SelectComponent;
|
||||||
|
let host: HostComponent;
|
||||||
|
let fixture: ComponentFixture<HostComponent>;
|
||||||
|
let element: DebugElement;
|
||||||
|
|
||||||
|
describe('SelectComponent', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ SelectComponent, HostComponent ],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(HostComponent);
|
||||||
|
host = fixture.componentInstance;
|
||||||
|
element = fixture.debugElement.query(By.directive(SelectComponent));
|
||||||
|
component = element.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('(initially)', () => {
|
||||||
|
it('should show the button and no options', () => {
|
||||||
|
expect(getButton()).toBeDefined();
|
||||||
|
expect(getOptionContainer()).toEqual(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('button', () => {
|
||||||
|
it('should display the label if provided', () => {
|
||||||
|
expect(getButton().textContent.trim()).toEqual('');
|
||||||
|
host.label = 'Label:';
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getButton().textContent.trim()).toEqual('Label:');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a symbol `<span>` if hasSymbol is true', () => {
|
||||||
|
expect(getButton().querySelector('span')).toEqual(null);
|
||||||
|
host.showSymbol = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
const span = getButton().querySelector('span');
|
||||||
|
expect(span).not.toEqual(null);
|
||||||
|
expect(span.className).toContain('symbol');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the selected option, if there is one', () => {
|
||||||
|
host.showSymbol = true;
|
||||||
|
host.selected = options[0];
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getButton().textContent).toContain(options[0].title);
|
||||||
|
expect(getButton().querySelector('span').className).toContain(options[0].value);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should toggle the visibility of the options list when clicked', () => {
|
||||||
|
host.options = options;
|
||||||
|
getButton().click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getOptionContainer()).not.toEqual(null);
|
||||||
|
getButton().click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getOptionContainer()).toEqual(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('options list', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
host.options = options;
|
||||||
|
host.showSymbol = true;
|
||||||
|
getButton().click(); // ensure the the options are visible
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show the corresponding title of each option', () => {
|
||||||
|
getOptions().forEach((li, index) => {
|
||||||
|
expect(li.textContent).toContain(options[index].title);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should select the option that is clicked', () => {
|
||||||
|
getOptions()[0].click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(host.onChange).toHaveBeenCalledWith({ option: options[0], index: 0 });
|
||||||
|
expect(getButton().textContent).toContain(options[0].title);
|
||||||
|
expect(getButton().querySelector('span').className).toContain(options[0].value);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should select the current option when enter is pressed', () => {
|
||||||
|
const e = new KeyboardEvent('keydown', {bubbles: true, cancelable: true, key: 'Enter'});
|
||||||
|
getOptions()[0].dispatchEvent(e);
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(host.onChange).toHaveBeenCalledWith({ option: options[0], index: 0 });
|
||||||
|
expect(getButton().textContent).toContain(options[0].title);
|
||||||
|
expect(getButton().querySelector('span').className).toContain(options[0].value);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should select the current option when space is pressed', () => {
|
||||||
|
const e = new KeyboardEvent('keydown', {bubbles: true, cancelable: true, key: ' '});
|
||||||
|
getOptions()[0].dispatchEvent(e);
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(host.onChange).toHaveBeenCalledWith({ option: options[0], index: 0 });
|
||||||
|
expect(getButton().textContent).toContain(options[0].title);
|
||||||
|
expect(getButton().querySelector('span').className).toContain(options[0].value);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should hide when an option is clicked', () => {
|
||||||
|
getOptions()[0].click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getOptionContainer()).toEqual(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should hide when there is a click that is not on the option list', () => {
|
||||||
|
fixture.nativeElement.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getOptionContainer()).toEqual(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should hide if the escape button is pressed', () => {
|
||||||
|
const e = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, key: 'Escape' });
|
||||||
|
document.dispatchEvent(e);
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(getOptionContainer()).toEqual(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<aio-select (change)="onChange($event)"
|
||||||
|
[options]="options"
|
||||||
|
[selected]="selected"
|
||||||
|
[label]="label"
|
||||||
|
[showSymbol]="showSymbol">
|
||||||
|
</aio-select>`
|
||||||
|
})
|
||||||
|
class HostComponent {
|
||||||
|
onChange = jasmine.createSpy('onChange');
|
||||||
|
options: Option[];
|
||||||
|
selected: Option;
|
||||||
|
label: string;
|
||||||
|
showSymbol: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getButton(): HTMLButtonElement {
|
||||||
|
return element.query(By.css('button')).nativeElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOptionContainer(): HTMLUListElement {
|
||||||
|
const de = element.query(By.css('ul'));
|
||||||
|
return de && de.nativeElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOptions(): HTMLLIElement[] {
|
||||||
|
return element.queryAll(By.css('li')).map(de => de.nativeElement);
|
||||||
|
}
|
62
aio/src/app/shared/select/select.component.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { Component, ElementRef, EventEmitter, HostListener, Input, Output, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
export interface Option {
|
||||||
|
title: string;
|
||||||
|
value?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'aio-select',
|
||||||
|
templateUrl: 'select.component.html'
|
||||||
|
})
|
||||||
|
export class SelectComponent implements OnInit {
|
||||||
|
@Input()
|
||||||
|
selected: Option;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
options: Option[];
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
change = new EventEmitter<{option: Option, index: number}>();
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
showSymbol = false;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
label: string;
|
||||||
|
|
||||||
|
showOptions = false;
|
||||||
|
|
||||||
|
constructor(private hostElement: ElementRef) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.label = this.label || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleOptions() {
|
||||||
|
this.showOptions = !this.showOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideOptions() {
|
||||||
|
this.showOptions = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
select(option: Option, index: number) {
|
||||||
|
this.selected = option;
|
||||||
|
this.change.emit({option, index});
|
||||||
|
this.hideOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('document:click', ['$event.target'])
|
||||||
|
onClick(eventTarget: HTMLElement) {
|
||||||
|
// Hide the options if we clicked outside the component
|
||||||
|
if (!this.hostElement.nativeElement.contains(eventTarget)) {
|
||||||
|
this.hideOptions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@HostListener('document:keydown.escape')
|
||||||
|
onKeyDown() {
|
||||||
|
this.hideOptions();
|
||||||
|
}
|
||||||
|
}
|
16
aio/src/app/shared/shared.module.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { SelectComponent } from './select/select.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
SelectComponent
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
SelectComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class SharedModule {}
|
@ -243,6 +243,11 @@ describe('TocService', () => {
|
|||||||
expect(tocItem.href).toEqual(`${docId}#heading-one-special-id`);
|
expect(tocItem.href).toEqual(`${docId}#heading-one-special-id`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should have level "h1" for an <h1>', () => {
|
||||||
|
const tocItem = lastTocList.find(item => item.title === 'Fun with TOC');
|
||||||
|
expect(tocItem.level).toEqual('h1');
|
||||||
|
});
|
||||||
|
|
||||||
it('should have level "h2" for an <h2>', () => {
|
it('should have level "h2" for an <h2>', () => {
|
||||||
const tocItem = lastTocList.find(item => item.title === 'Heading one');
|
const tocItem = lastTocList.find(item => item.title === 'Heading one');
|
||||||
expect(tocItem.level).toEqual('h2');
|
expect(tocItem.level).toEqual('h2');
|
||||||
|
1
aio/src/assets/images/icons/code-icon.svg
Normal file
After Width: | Height: | Size: 15 KiB |
1
aio/src/assets/images/icons/feature-icon.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 150"><style>.st0{fill:#C3002F;} .st1{fill:#DD0031;} .st2{fill:#FFFFFF;}</style><path class="st0" d="M140 75c0-35.9-29.1-65-65-65-11.2 0-21.8 2.9-31 7.9l88.2 88.2c4.9-9.3 7.8-19.9 7.8-31.1z"/><path class="st1" d="M44 17.9C23.7 28.9 10 50.3 10 75c0 35.9 29.1 65 65 65 24.7 0 46.1-13.7 57.1-34L44 17.9z"/><path class="st2" d="M77.2 90.2H34c-5 0-9.1-4.1-9.1-9.1V59.2c0-5 4.1-9.1 9.1-9.1h43.2c5 0 9.1 4.1 9.1 9.1v21.9c0 5-4.1 9.1-9.1 9.1zM34 55.2c-2.2 0-4.1 1.8-4.1 4.1v21.9c0 2.2 1.8 4.1 4.1 4.1h43.2c2.2 0 4.1-1.8 4.1-4.1v-22c0-2.2-1.8-4.1-4.1-4.1H34z"/><path class="st2" d="M55.6 99.9c-1.4 0-2.5-1.1-2.5-2.5v-9.7c0-1.4 1.1-2.5 2.5-2.5s2.5 1.1 2.5 2.5v9.7c0 1.3-1.1 2.5-2.5 2.5z"/><path class="st2" d="M69.9 99.9H41.3c-1.4 0-2.5-1.1-2.5-2.5s1.1-2.5 2.5-2.5h28.5c1.4 0 2.5 1.1 2.5 2.5.1 1.3-1 2.5-2.4 2.5zM53.5 76.2l-3-5.1c-.4-.6-.2-1.4.5-1.7.6-.4 1.4-.2 1.7.5l1.2 2 4.9-5.8c.4-.5 1.2-.6 1.8-.2.5.4.6 1.2.2 1.8l-7.3 8.5zm66.1 18.9h-15.5c-3 0-5.5-2.5-5.5-5.5V60.5c0-3 2.5-5.5 5.5-5.5h15.5c3 0 5.5 2.5 5.5 5.5v29.1c0 3-2.5 5.5-5.5 5.5zm-15.5-35.9c-.7 0-1.2.6-1.2 1.2v29.1c0 .7.6 1.2 1.2 1.2h15.5c.7 0 1.2-.6 1.2-1.2v-29c0-.7-.6-1.2-1.2-1.2h-15.5z"/><path class="st2" d="M109.7 80.7l-3-5.1c-.4-.6-.2-1.4.5-1.7.6-.4 1.4-.2 1.7.5l1.2 2 4.9-5.8c.4-.5 1.2-.6 1.8-.2.5.4.6 1.2.2 1.8l-7.3 8.5zm13.3 7.8h-21.2c-.4 0-.8-.3-.8-.8s.3-.8.8-.8H123c.4 0 .8.3.8.8s-.4.8-.8.8z"/></svg>
|
After Width: | Height: | Size: 1.4 KiB |
@ -37,35 +37,28 @@
|
|||||||
<!-- End Google Analytics -->
|
<!-- End Google Analytics -->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
window.onerror = function (message, url, lineNo, colNo, error) {
|
|
||||||
let container = document.createElement('div');
|
|
||||||
|
|
||||||
container.style.color = 'red';
|
|
||||||
container.style.position = 'fixed';
|
|
||||||
container.style.background = '#eee';
|
|
||||||
container.style.padding = '2em';
|
|
||||||
container.style.top = '3em';
|
|
||||||
container.style.left = '1em';
|
|
||||||
|
|
||||||
let msg = document.createElement('pre');
|
|
||||||
msg.innerText = [
|
|
||||||
'UA: ' + window.navigator.userAgent,
|
|
||||||
'Message: ' + message,
|
|
||||||
'URL: ' + url,
|
|
||||||
'Line: ' + lineNo,
|
|
||||||
'Column: ' + colNo,
|
|
||||||
'Stack: ' + (error && error.stack)
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
container.appendChild(msg);
|
|
||||||
|
|
||||||
setTimeout(function() {document.body.appendChild(container)}, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (window.document.documentMode) {
|
if (window.document.documentMode) {
|
||||||
var script = document.createElement('script');
|
// polyfill IE11 in a blocking way
|
||||||
script.src = 'generated/ie-polyfills.min.js';
|
var s = document.createElement('script');
|
||||||
document.head.appendChild(script);
|
s.src = 'generated/ie-polyfills.min.js';
|
||||||
|
document.head.appendChild(s);
|
||||||
|
} else if (!Object.assign) {
|
||||||
|
// polyfill other non-evergreen browsers in a blocking way
|
||||||
|
var polyfillUrl = "https://cdn.polyfill.io/v2/polyfill.min.js?features=default,Array.prototype.find&flags=gated&unknown=polyfill";
|
||||||
|
|
||||||
|
// send a blocking XHR to fetch the polyfill
|
||||||
|
// then append it to the document so that its eval-ed synchronously
|
||||||
|
// this is required because the method used for IE is not reliable with other non-evergreen browsers
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.addEventListener("load", function() {
|
||||||
|
var s = document.createElement('script');
|
||||||
|
s.type = 'text/javascript';
|
||||||
|
var code = this.responseText;
|
||||||
|
s.appendChild(document.createTextNode(code));
|
||||||
|
document.head.appendChild(s);
|
||||||
|
});
|
||||||
|
xhr.open("GET", polyfillUrl, false);
|
||||||
|
xhr.send();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
@ -1,168 +1,161 @@
|
|||||||
// @mixin docs-site-typography() {
|
body {
|
||||||
// .docs-component-viewer-tabbed-content .docs-component-view-text-content,
|
font-family: $main-font;
|
||||||
// .docs-guide-content {
|
margin: 0;
|
||||||
|
color: $darkgray;
|
||||||
|
font-size: 14px;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
h1 {
|
||||||
font-family: $main-font;
|
display: inline-block;
|
||||||
margin: 0;
|
font-size: 24px;
|
||||||
color: $darkgray;
|
font-weight: 500;
|
||||||
font-size: 14px;
|
margin: 8px 0px;
|
||||||
-webkit-font-smoothing: antialiased;
|
}
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
h1:after {
|
||||||
display:inline-block;
|
content: "";
|
||||||
font-size: 24px;
|
display: block;
|
||||||
font-weight: 500;
|
height: 1px;
|
||||||
margin: 8px 0px;
|
width: 40%;
|
||||||
}
|
margin: 24px 0px 10px;
|
||||||
|
background: $lightgray;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
h1:after {
|
h2 {
|
||||||
content: "";
|
font-size: 22px;
|
||||||
display: block;
|
font-weight: 500;
|
||||||
height: 1px;
|
margin: 32px 0px 24px;
|
||||||
width: 40%;
|
clear: both;
|
||||||
margin: 24px 0px 10px;
|
}
|
||||||
background: $lightgray;
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
h3 {
|
||||||
font-size: 22px;
|
font-size: 20px;
|
||||||
font-weight: 500;
|
font-weight: 400;
|
||||||
margin: 32px 0px 24px;
|
margin: 24px 0px;
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h4 {
|
||||||
font-size: 20px;
|
font-size: 18px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
margin: 24px 0px;
|
margin: 8px 0px;
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h5 {
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
font-weight: 400;
|
font-weight: 500;
|
||||||
margin: 8px 0px;
|
margin: 8px 0px;
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
h5 {
|
h6 {
|
||||||
font-size: 16px;
|
color: $mediumgray;
|
||||||
font-weight: 500;
|
font-size: 16px;
|
||||||
margin: 8px 0px;
|
font-weight: 500;
|
||||||
clear: both;
|
margin: 8px 0px;
|
||||||
}
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
h6 {
|
h1 {
|
||||||
color: $mediumgray;
|
@media screen and (max-width: 600px) {
|
||||||
font-size: 16px;
|
margin: 0;
|
||||||
font-weight: 500;
|
}
|
||||||
margin: 8px 0px;
|
}
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
h1:after, h2, h3, h4, h5, h6 {
|
||||||
@media screen and (max-width: 600px) {
|
@media screen and (max-width: 600px) {
|
||||||
margin: 0;
|
margin: 8px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h1:after, h2, h3, h4, h5, h6 {
|
.mat-tab-body-wrapper h2 {
|
||||||
@media screen and (max-width: 600px) {
|
margin-top: 0;
|
||||||
margin: 8px 0;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mat-tab-body-wrapper h2 {
|
p, ol, ul, ol, li, input, a {
|
||||||
margin-top: 0;
|
font-size: 14px;
|
||||||
}
|
line-height: 24px;
|
||||||
|
letter-spacing: 0.30px;
|
||||||
|
font-weight: 400;
|
||||||
|
& > em {
|
||||||
|
letter-spacing: 0.30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
p, ol, ul, ol, li, input, a {
|
ol li {
|
||||||
font-size: 14px;
|
margin: 14px 0px;
|
||||||
line-height: 24px;
|
}
|
||||||
letter-spacing: 0.30px;
|
|
||||||
font-weight: 400;
|
|
||||||
& > em {
|
|
||||||
letter-spacing: 0.30px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ol li {
|
a {
|
||||||
margin: 14px 0px;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
.mat-toolbar-row a {
|
||||||
text-decoration: none;
|
font-size: 16px;
|
||||||
}
|
font-weight: 400;
|
||||||
|
color: white;
|
||||||
|
font-family: $main-font;
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 21px 0;
|
||||||
|
}
|
||||||
|
|
||||||
.mat-toolbar-row a {
|
strong {
|
||||||
font-size: 16px;
|
font-weight: 500;
|
||||||
font-weight: 300;
|
}
|
||||||
color: white;
|
|
||||||
font-family: $main-font;
|
|
||||||
text-transform: uppercase;
|
|
||||||
padding: 21px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
strong {
|
table {
|
||||||
font-weight: 500;
|
border-collapse: collapse;
|
||||||
}
|
border-radius: 2px;
|
||||||
|
border-spacing: 0;
|
||||||
|
margin: 0 0 32px 0;
|
||||||
|
}
|
||||||
|
|
||||||
table {
|
table tbody th {
|
||||||
border-collapse: collapse;
|
max-width: 100px;
|
||||||
border-radius: 2px;
|
padding: 13px 32px;
|
||||||
border-spacing: 0;
|
text-align: left;
|
||||||
margin: 0 0 32px 0;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
table tbody th {
|
td {
|
||||||
max-width: 100px;
|
font-weight: 400;
|
||||||
padding: 13px 32px;
|
padding: 8px 30px;
|
||||||
text-align: left;
|
letter-spacing: 0.30px;
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
p {
|
||||||
font-weight: 400;
|
margin: 0;
|
||||||
padding: 8px 30px;
|
}
|
||||||
letter-spacing: 0.30px;
|
}
|
||||||
|
|
||||||
p {
|
th {
|
||||||
margin: 0;
|
font-size: 16px;
|
||||||
}
|
font-weight: 500;
|
||||||
}
|
padding: 13px 32px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
th {
|
p > code, li > code, table code {
|
||||||
font-size: 16px;
|
font-family: $code-font;
|
||||||
font-weight: 500;
|
font-size: 85%;
|
||||||
padding: 13px 32px;
|
color: $darkgray;
|
||||||
text-align: left;
|
letter-spacing: 0;
|
||||||
}
|
line-height: 1;
|
||||||
|
padding: 2px 6px;
|
||||||
|
background-color: $backgroundgray;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
p > code, li > code, table code {
|
code {
|
||||||
font-family: $code-font;
|
font-family: $code-font;
|
||||||
font-size: 85%;
|
font-size: 90%;
|
||||||
color: $darkgray;
|
}
|
||||||
letter-spacing: 0;
|
|
||||||
line-height: 1;
|
|
||||||
padding: 2px 6px;
|
|
||||||
background-color: $backgroundgray;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
.sidenav-content a {
|
||||||
font-family: $code-font;
|
color: $blue;
|
||||||
font-size: 90%;
|
&:hover {
|
||||||
}
|
color: $mediumgray;
|
||||||
|
}
|
||||||
.sidenav-content a {
|
}
|
||||||
color: $blue;
|
|
||||||
&:hover {
|
|
||||||
color: $mediumgray;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
@ -1,20 +1,13 @@
|
|||||||
.progress-bar-container {
|
aio-shell.page-docs {
|
||||||
height: 2px;
|
.sidenav-content {
|
||||||
overflow: hidden;
|
// padding: 6rem 3rem 3rem 3rem; // THIS CAUSES THE TOP NAV TOOLBAR TO JUMP BETWEEN DOCS AND OTHER PAGES
|
||||||
position: fixed;
|
margin: auto;
|
||||||
top: 64px;
|
}
|
||||||
width: 100vw;
|
|
||||||
z-index: 5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidenav-content {
|
.sidenav-content {
|
||||||
padding: 1rem 3rem 3rem;
|
|
||||||
margin: auto;
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
padding: 80px 3rem 1rem;
|
||||||
|
|
||||||
.fill-remaining-space {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
@ -22,14 +15,9 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-bar-container {
|
|
||||||
top: 56px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidenav-content {
|
.sidenav-content {
|
||||||
min-height: 450px;
|
min-height: 450px;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidenav-container {
|
.sidenav-container {
|
||||||
|
@ -49,3 +49,7 @@ l-relative {
|
|||||||
top: -9999px !important;
|
top: -9999px !important;
|
||||||
left: -9999px !important;
|
left: -9999px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-uppercase {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
@ -2,12 +2,12 @@
|
|||||||
LAYOUT STYLES
|
LAYOUT STYLES
|
||||||
============================== */
|
============================== */
|
||||||
|
|
||||||
@import 'sidenav';
|
@import 'api-page';
|
||||||
@import 'content-layout';
|
@import 'content-layout';
|
||||||
@import 'top-menu';
|
|
||||||
@import 'marketing-layout';
|
|
||||||
@import 'footer';
|
@import 'footer';
|
||||||
@import 'layout-global';
|
@import 'layout-global';
|
||||||
|
@import 'marketing-layout';
|
||||||
@import 'not-found';
|
@import 'not-found';
|
||||||
@import 'api-page';
|
@import 'sidenav';
|
||||||
@import 'table-of-contents';
|
@import 'table-of-contents';
|
||||||
|
@import 'top-menu';
|
||||||
|
@ -1,13 +1,339 @@
|
|||||||
#home {
|
.hero {
|
||||||
padding: 0rem 0rem 3rem;
|
width: 100%;
|
||||||
|
min-height: 480px;
|
||||||
|
height: 80vh;
|
||||||
|
max-height: 560px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 48px 48px 32px 48px;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
transform: skewY(8deg);
|
||||||
|
transform-origin: 100%;
|
||||||
|
|
||||||
@media (max-width: 800px) {
|
@media (max-width: 480px) {
|
||||||
article {
|
padding-top: 40px;
|
||||||
padding: 0 30px;
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1:after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 48px;
|
||||||
|
margin: 0 $unit 0 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
&.is-standard-case {
|
||||||
|
text-transform: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
section#intro {
|
||||||
|
display: flex;
|
||||||
|
width: 900px;
|
||||||
|
height: 480px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 48px 0 0;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
@media (max-width: 780px) {
|
||||||
|
flex-direction: column;
|
||||||
|
max-width: 100vw;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 64px 0 32px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.homepage-container {
|
||||||
|
margin-top: -7%;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
max-width: 1040px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
@media (max-width: 780px) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-headline {
|
||||||
|
font-size: 40px;
|
||||||
|
line-height: 64px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 32px 0;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 780px) {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-logo {
|
||||||
|
width: 400px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
@media (max-width: 780px) {
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
filter: drop-shadow(0 2px 2px rgba($black, 0.24));
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding: 0;
|
||||||
|
width: 400px;
|
||||||
|
height: 400px;
|
||||||
|
|
||||||
|
@media (max-width: 780px) {
|
||||||
|
width: 250px;
|
||||||
|
height: 250px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.announcement-bar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-around;
|
||||||
|
padding: 16px;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 50vw;
|
||||||
|
background-color: $white;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 2px rgba($black, 0.24), 0 0 2px rgba($black, 0.12);
|
||||||
|
box-sizing: border-box;
|
||||||
|
align-items: center;
|
||||||
|
transition: all 0.3s ease-in;
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
min-width: 100%;
|
||||||
|
padding: 32px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
min-width: 160px;
|
||||||
|
background-color: $blue;
|
||||||
|
border-radius: 48px;
|
||||||
|
box-shadow: 0 2px 5px 0 rgba(0,0,0,.26);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 40px;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
a {
|
||||||
|
color: $white;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $white;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.material-icons {
|
||||||
|
right: 0;
|
||||||
|
position: static;
|
||||||
|
display: none;
|
||||||
|
transition: all 0.3s ease-in;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ANGULAR LINE
|
||||||
|
.background-sky {
|
||||||
|
background-color: $blue;
|
||||||
|
background: $bluegradient;
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-row .card {
|
||||||
|
@include card(70%, auto);
|
||||||
|
min-width: 350px;
|
||||||
|
width: 70%;
|
||||||
|
height: auto;
|
||||||
|
padding: 24px;
|
||||||
|
margin: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
box-shadow: 0 6px 6px rgba(10, 16, 20, 0.15), 0 0 52px rgba(10, 16, 20, 0.12);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
margin: 16px auto 0;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: none;
|
||||||
|
height: 70px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1300px) {
|
||||||
|
img {
|
||||||
|
max-width: none;
|
||||||
|
height: 70px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
margin: 16px ;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-text-container {
|
||||||
|
margin: 0 16px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px 0;
|
||||||
|
color: $darkgray;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
h2 {
|
||||||
|
color: $blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button.hero-cta {
|
||||||
|
border-radius: 48px;
|
||||||
|
display: flex;
|
||||||
|
height: 40px;
|
||||||
|
box-shadow: 0 2px 5px 0 rgba(0,0,0,.26);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aio-shell {
|
||||||
|
&.page-resources, &.page-events, &.page-features, &.page-presskit, &.page-contribute {
|
||||||
|
section {
|
||||||
|
padding: 0rem 0rem 3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.page-home {
|
||||||
|
section {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.page-home, &.page-resources, &.page-events, &.page-contribute {
|
||||||
|
article {
|
||||||
|
padding: 32px;
|
||||||
|
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.page-home, &.page-resources, &.page-events, &.page-features {
|
||||||
|
|
||||||
|
.content img {
|
||||||
|
@media (max-width: 1300px) {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-section img {
|
||||||
|
max-width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
md-sidenav-container.sidenav-container {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-bar .hero-cta {
|
||||||
|
color: $blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta-bar.announcement-bar {
|
||||||
|
background: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-headline {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-top: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: $blue;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-section {
|
||||||
|
.feature-header {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.text-headline {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 8px 0px;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
aio-shell:not(.view-SideNav) {
|
aio-shell:not(.view-SideNav) {
|
||||||
md-sidenav-container.sidenav-container {
|
md-sidenav-container.sidenav-container {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
@ -16,6 +342,7 @@ aio-shell:not(.view-SideNav) {
|
|||||||
|
|
||||||
div[layout=row]{
|
div[layout=row]{
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
@ -29,81 +356,50 @@ div[layout=row]{
|
|||||||
}
|
}
|
||||||
|
|
||||||
.home-rows {
|
.home-rows {
|
||||||
margin-top: 112px;
|
overflow: hidden;
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-row .promo-image-container, .home-row .text-container {
|
.home-row .promo-img-container, .home-row .text-container {
|
||||||
max-width: 50%;
|
max-width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.background-superhero-paper {
|
.background-superhero-paper {
|
||||||
background: url('assets/images/backgrounds/super-hero-large.png') no-repeat bottom center , $bluegradient;
|
|
||||||
background-blend-mode: multiply;
|
background-blend-mode: multiply;
|
||||||
background-size: 100%;
|
background-size: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.background-sky {
|
|
||||||
background-color: $blue;
|
|
||||||
background: $bluegradient;
|
|
||||||
color: $white;
|
|
||||||
}
|
|
||||||
|
|
||||||
header.bckground-sky.l-relative {
|
header.bckground-sky.l-relative {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-rows {
|
|
||||||
// NOTE (ericjim): if a banner is placed on the homescreen, add this margin.
|
|
||||||
margin-top: 112px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.home-row {
|
.home-row {
|
||||||
max-width: 920px;
|
max-width: 920px;
|
||||||
margin: 0 0 60px 0;
|
margin: 32px;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
|
||||||
|
|
||||||
h3.text-headline {
|
.text-headline, .promo-img-container {
|
||||||
font-size: 28px;
|
text-align: center;
|
||||||
margin-top: 10px;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE (ericjim): remove if graphic changes.
|
|
||||||
.promo-3 {
|
|
||||||
margin-top: 15px;
|
|
||||||
|
|
||||||
@media(max-width: 599px) {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- Utility margins for promos
|
|
||||||
.promo-1-desc, .promo-3-desc {
|
|
||||||
padding-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- Reset margins for the utility margins
|
|
||||||
@media(max-width: 599px) {
|
|
||||||
.promo-1, .promo-2, .promo-3, .promo-4 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-block {
|
.text-block {
|
||||||
padding-right: 15%;
|
padding-right: 15%;
|
||||||
|
|
||||||
@media(max-width: 599px) {
|
@media(max-width: 600px) {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(max-width: 599px) {
|
@media(max-width: 600px) {
|
||||||
.promo-image-container, .text-container {
|
.promo-img-container, .text-container {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,6 +430,35 @@ header.bckground-sky.l-relative {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-uppercase {
|
.marketing-banner {
|
||||||
text-transform: uppercase;
|
background-color: lighten($blue, 10);
|
||||||
|
margin-top: 64px;
|
||||||
|
padding: 32px;
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
margin-top: 56px;
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-headline {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 300;
|
||||||
|
color: white;
|
||||||
|
margin: 0;
|
||||||
|
-webkit-margin-before: 0;
|
||||||
|
-webkit-margin-after: 0;
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.page-features .marketing-banner {
|
||||||
|
margin-bottom: 20px;
|
||||||
}
|
}
|
@ -8,33 +8,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
aio-nav-menu {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-size: 13px;
|
||||||
|
ul, a {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
aio-nav-item div a {
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
md-sidenav.mat-sidenav.sidenav {
|
md-sidenav.mat-sidenav.sidenav {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
top: 64px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
padding: 80px 0px 0px;
|
padding: 0;
|
||||||
min-width: 260px;
|
min-width: 260px;
|
||||||
background-color: $offwhite;
|
background-color: $offwhite;
|
||||||
box-shadow: 6px 0 6px rgba(0,0,0,0.10);
|
box-shadow: 6px 0 6px rgba(0,0,0,0.10);
|
||||||
|
height: calc(100vh - 64px);
|
||||||
|
|
||||||
&.collapsed {
|
&.collapsed {
|
||||||
padding-top: 64px;
|
top: 56px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
md-sidenav-container.sidenav-container {
|
md-sidenav-container.sidenav-container {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
max-width: 82%;
|
max-width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
transform: none;
|
transform: none;
|
||||||
padding-top: 64px;
|
|
||||||
|
|
||||||
@media (max-width: 800px) {
|
&.has-floating-toc {
|
||||||
max-width: 100%;
|
max-width: 82%;
|
||||||
}
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
padding-top: 56px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,13 +76,11 @@ md-sidenav-container div.mat-sidenav-content {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $lightgray;
|
text-shadow: 0 0 5px #ffffff;
|
||||||
}
|
color: $blue;
|
||||||
|
|
||||||
&:focus {
|
|
||||||
color: $darkgray;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//icons _within_ nav
|
//icons _within_ nav
|
||||||
@ -80,10 +94,6 @@ md-sidenav-container div.mat-sidenav-content {
|
|||||||
|
|
||||||
.vertical-menu-item.selected {
|
.vertical-menu-item.selected {
|
||||||
color: $blue;
|
color: $blue;
|
||||||
|
|
||||||
&:focus {
|
|
||||||
color: $blue-900;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button.vertical-menu-item {
|
button.vertical-menu-item {
|
||||||
@ -97,7 +107,7 @@ button.vertical-menu-item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.heading {
|
.heading {
|
||||||
color: $blue-grey-700;
|
color: $darkgray;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@ -122,18 +132,13 @@ button.vertical-menu-item {
|
|||||||
transition-timing-function: ease-out;
|
transition-timing-function: ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.selected.level-1,
|
|
||||||
.heading.selected.level-1,
|
|
||||||
.heading-children.selected.level-1 {
|
|
||||||
border-left: 3px $blue solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.level-1 {
|
.level-1 {
|
||||||
font-family: $main-font;
|
font-family: $main-font;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.level-2 {
|
.level-2 {
|
||||||
@ -149,6 +154,7 @@ a.selected.level-1,
|
|||||||
font-family: $main-font;
|
font-family: $main-font;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: $mediumgray;
|
color: $mediumgray;
|
||||||
|
padding-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.level-1.expanded .mat-icon, .level-2.expanded .mat-icon {
|
.level-1.expanded .mat-icon, .level-2.expanded .mat-icon {
|
||||||
@ -168,19 +174,20 @@ a.selected.level-1,
|
|||||||
}
|
}
|
||||||
|
|
||||||
aio-nav-menu.top-menu {
|
aio-nav-menu.top-menu {
|
||||||
aio-nav-item:first-child .vertical-menu-item {
|
padding: 24px 0 0;
|
||||||
padding-top: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
aio-nav-item:last-child div {
|
aio-nav-item:last-child div {
|
||||||
border-bottom: 2px solid rgba($mediumgray, 0.5);
|
border-bottom: 1px solid rgba($mediumgray, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
aio-nav-item:first-child div {
|
||||||
|
border-top: 1px solid rgba($mediumgray, 0.5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Angular Version Selector
|
// Angular Version Selector
|
||||||
md-sidenav .doc-version {
|
md-sidenav .doc-version {
|
||||||
padding: 10px;
|
padding: 8px;
|
||||||
border-top: 1px solid $lightgray;
|
border-top: 1px solid $lightgray;
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
@ -1,18 +1,3 @@
|
|||||||
|
|
||||||
.fill-remaining-space {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
aio-top-menu a.nav-link {
|
|
||||||
margin: 0;
|
|
||||||
padding: 24px 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link.home img {
|
.nav-link.home img {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: -21px;
|
margin-top: -21px;
|
||||||
@ -21,22 +6,6 @@ aio-top-menu a.nav-link {
|
|||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 700px) {
|
|
||||||
.nav-link {
|
|
||||||
font-size: 80%;
|
|
||||||
margin-right: 8px;
|
|
||||||
margin-left: 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.nav-link {
|
|
||||||
font-size: 80%;
|
|
||||||
margin-right: 8px;
|
|
||||||
margin-left: 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
aio-top-menu {
|
aio-top-menu {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@ -52,13 +21,12 @@ aio-top-menu {
|
|||||||
margin: 0px;
|
margin: 0px;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
|
padding-bottom: 2px;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
line-height: 64px;
|
|
||||||
height: 64px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $accentblue;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
@ -67,38 +35,102 @@ aio-top-menu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.nav-link {
|
||||||
|
margin: 0;
|
||||||
|
padding: 24px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
// Temporarily remove the focus styling until we update to an @angular/material version that
|
||||||
|
// includes https://github.com/angular/material2/commit/3bc82f6dc.
|
||||||
|
// background: rgba($white, 0.15);
|
||||||
|
// border-radius: 4px;
|
||||||
|
// padding: 8px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HOME PAGE OVERRIDE: TOPNAV TOOLBAR HAMBURGER MENU
|
||||||
|
aio-shell.page-home md-toolbar.app-toolbar.mat-toolbar {
|
||||||
|
background-color: transparent;
|
||||||
|
transition: background-color .2s linear .3s;
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
background-color: $blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DOCS PAGE / STANDARD: TOPNAV TOOLBAR FIXED
|
||||||
md-toolbar.mat-toolbar {
|
md-toolbar.mat-toolbar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.30);
|
|
||||||
padding: 0 16px 0 0;
|
padding: 0 16px 0 0;
|
||||||
|
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.30);
|
||||||
|
|
||||||
|
md-icon {
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-container {
|
// MARKETING PAGES OVERRIDE: TOPNAV TOOLBAR AND HAMBURGER
|
||||||
|
aio-shell.page-home md-toolbar.mat-toolbar,
|
||||||
|
aio-shell.page-features md-toolbar.mat-toolbar,
|
||||||
|
aio-shell.page-events md-toolbar.mat-toolbar,
|
||||||
|
aio-shell.page-resources md-toolbar.mat-toolbar {
|
||||||
|
// FIXED TOPNAV TOOLBAR FOR SMALL MOBILE
|
||||||
|
@media (min-width: 481px) {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
button.hamburger {
|
||||||
|
margin: 0 24px 0 -88px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// REMOVE BOX SHADOW ON CERTAIN MARKETING PAGES
|
||||||
|
aio-shell.page-home md-toolbar.mat-toolbar,
|
||||||
|
aio-shell.page-events md-toolbar.mat-toolbar,
|
||||||
|
aio-shell.page-resources md-toolbar.mat-toolbar {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// SEARCH BOX
|
||||||
|
aio-search-box.search-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
|
||||||
|
|
||||||
aio-search-box input {
|
input {
|
||||||
color: $darkgray;
|
color: $darkgray;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: 100px;
|
||||||
background-color: $offwhite;
|
background-color: $offwhite;
|
||||||
padding: 5px 10px;
|
padding: 5px 16px;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
width: 200px;
|
|
||||||
height: 40%;
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
width: 180px;
|
width: 180px;
|
||||||
|
max-width: 240px;
|
||||||
|
height: 50%;
|
||||||
|
|
||||||
|
@include bp(big) {
|
||||||
|
transition: width 0.4s ease-in-out;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|