Compare commits
117 Commits
Author | SHA1 | Date | |
---|---|---|---|
b91b9efc91 | |||
03718c95ce | |||
0834710c18 | |||
9a9a7de9a4 | |||
ff51691221 | |||
2ad90ab0d3 | |||
c5872e6782 | |||
d20877bf3f | |||
b3089b4963 | |||
880b03101e | |||
451d996414 | |||
ea3669e334 | |||
ea2987c7af | |||
5b823dedcc | |||
80f7c83bdd | |||
53072b10da | |||
30c2f560b1 | |||
18c4976f3b | |||
0139173c7c | |||
f3c9954a68 | |||
e8765352e8 | |||
9e61ad897e | |||
0d81151cbc | |||
70319722a1 | |||
cfa5b6567d | |||
41698bf5fd | |||
23c50e27fc | |||
0ae8ea254a | |||
19deca159b | |||
dc3e8aa0fb | |||
a9222c0ade | |||
ea3127e3f9 | |||
8e30f7b1aa | |||
46d81b48ac | |||
3e51a2cb26 | |||
faf60d9310 | |||
0639cb9de1 | |||
810d025488 | |||
efe49c141a | |||
a3d9878c9d | |||
10213d0ca0 | |||
0e1919c2db | |||
d579b8ce05 | |||
d69ba735ee | |||
2991b1b217 | |||
b18cf21e99 | |||
c17098dae6 | |||
43e3073687 | |||
f28878f92f | |||
a10bdefa8b | |||
2f377dbcdd | |||
e2e7b4943e | |||
b8a1363bb2 | |||
e9e6a58dd0 | |||
5932a79713 | |||
7c6a082afd | |||
8a2fe5b7c9 | |||
c9eb4910eb | |||
a634a5abbc | |||
41225026e4 | |||
e9f2203347 | |||
906b3ec8e7 | |||
1cb0c4644a | |||
9d28a27215 | |||
f04aef48f2 | |||
d249852f4a | |||
707dd59767 | |||
4d2570576a | |||
76e58e6cca | |||
a27066b9d2 | |||
29dfc8f3ac | |||
b5533e0ee5 | |||
b275d378df | |||
96655f7aac | |||
253f509493 | |||
5a02ae2f84 | |||
f860752902 | |||
efa126f157 | |||
715135b117 | |||
c30fc52d99 | |||
434eb971e4 | |||
6e31e22d41 | |||
8fef926cd2 | |||
7698afedb1 | |||
a583d12660 | |||
a867d71ece | |||
84b43daf65 | |||
9b3423b50d | |||
d6041d83ec | |||
a638f504eb | |||
a29c756251 | |||
f206eb94bb | |||
3d6e82eccd | |||
154289305e | |||
469b1e4a9a | |||
7fee1fd442 | |||
0ee5b7efa5 | |||
afff84c03f | |||
23f2a7069f | |||
42260702f7 | |||
eec231d21e | |||
95344d6039 | |||
1c9839e91d | |||
bc1a66e907 | |||
2a83dbb0d8 | |||
4007d00403 | |||
61e32ac3c4 | |||
43ee10fbbd | |||
eb90039ea1 | |||
5d318ff234 | |||
f3361abdd7 | |||
52cbe894e9 | |||
41c2030534 | |||
daee41a40a | |||
68bd45ba87 | |||
0f6407750b | |||
95fca24fd8 |
@ -12,8 +12,8 @@
|
||||
## IMPORTANT
|
||||
# If you change the `docker_image` version, also change the `cache_key` suffix and the version of
|
||||
# `com_github_bazelbuild_buildtools` in the `/WORKSPACE` file.
|
||||
var_1: &docker_image angular/ngcontainer:0.3.0
|
||||
var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.3.0
|
||||
var_1: &docker_image angular/ngcontainer:0.3.1
|
||||
var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.3.1
|
||||
|
||||
# Define common ENV vars
|
||||
var_3: &define_env_vars
|
||||
@ -80,7 +80,7 @@ jobs:
|
||||
|
||||
- run: ls /home/circleci/bazel_repository_cache || true
|
||||
- run: bazel info release
|
||||
- run: bazel run @yarn//:yarn
|
||||
- run: bazel run @nodejs//:yarn
|
||||
# Use bazel query so that we explicitly ask for all buildable targets to be built as well
|
||||
# This avoids waiting for the slowest build target to finish before running the first test
|
||||
# See https://github.com/bazelbuild/bazel/issues/4257
|
||||
@ -131,7 +131,7 @@ jobs:
|
||||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||
- *setup-bazel-remote-cache
|
||||
|
||||
- run: bazel run @yarn//:yarn
|
||||
- run: bazel run @nodejs//:yarn
|
||||
- run: scripts/build-packages-dist.sh
|
||||
|
||||
# Save the npm packages from //packages/... for other workflow jobs to read
|
||||
|
3
.github/angular-robot.yml
vendored
@ -4,6 +4,7 @@
|
||||
size:
|
||||
disabled: false
|
||||
maxSizeIncrease: 1000
|
||||
circleCiStatusName: "ci/circleci: build-packages-dist"
|
||||
status:
|
||||
disabled: false
|
||||
context: "ci/angular: size"
|
||||
@ -44,10 +45,12 @@ merge:
|
||||
- "packages/language-service/**"
|
||||
- "**/.gitignore"
|
||||
- "**/.gitkeep"
|
||||
- "**/package.json"
|
||||
- "**/tsconfig-build.json"
|
||||
- "**/tsconfig.json"
|
||||
- "**/rollup.config.js"
|
||||
- "**/BUILD.bazel"
|
||||
- "packages/**/integrationtest/**"
|
||||
- "packages/**/test/**"
|
||||
|
||||
# comment that will be added to a PR when there is a conflict, leave empty or set to false to disable
|
||||
|
10
BUILD.bazel
@ -11,7 +11,7 @@ exports_files([
|
||||
# This ensures that package.json in subdirectories get installed as well.
|
||||
alias(
|
||||
name = "install",
|
||||
actual = "@yarn//:yarn",
|
||||
actual = "@nodejs//:yarn",
|
||||
)
|
||||
|
||||
node_modules_filegroup(
|
||||
@ -44,14 +44,16 @@ filegroup(
|
||||
"//:node_modules/zone.js/dist/zone.js",
|
||||
"//:node_modules/zone.js/dist/zone-testing.js",
|
||||
"//:node_modules/zone.js/dist/task-tracking.js",
|
||||
"//:test-events.js",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "angularjs",
|
||||
# do not sort
|
||||
name = "angularjs_scripts",
|
||||
srcs = [
|
||||
"//:node_modules/angular/angular.js",
|
||||
"//:node_modules/angular-1.5/angular.js",
|
||||
"//:node_modules/angular-mocks-1.5/angular-mocks.js",
|
||||
"//:node_modules/angular-mocks/angular-mocks.js",
|
||||
"//:node_modules/angular/angular.js",
|
||||
],
|
||||
)
|
||||
|
67
CHANGELOG.md
@ -1,3 +1,42 @@
|
||||
<a name="6.0.5"></a>
|
||||
## [6.0.5](https://github.com/angular/angular/compare/6.0.4...6.0.5) (2018-06-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** always render end-state styles for orphaned DOM nodes ([#24236](https://github.com/angular/angular/issues/24236)) ([0139173](https://github.com/angular/angular/commit/0139173))
|
||||
* **bazel:** Allow ng_module to depend on targets w no deps ([#24446](https://github.com/angular/angular/issues/24446)) ([ea3669e](https://github.com/angular/angular/commit/ea3669e))
|
||||
* **docs-infra:** use script nomodule to load IE polyfills, skip other polyfills ([#24317](https://github.com/angular/angular/issues/24317)) ([e876535](https://github.com/angular/angular/commit/e876535)), closes [#23647](https://github.com/angular/angular/issues/23647)
|
||||
* **router:** fix lazy loading of aux routes ([#23459](https://github.com/angular/angular/issues/23459)) ([d20877b](https://github.com/angular/angular/commit/d20877b)), closes [#10981](https://github.com/angular/angular/issues/10981)
|
||||
* **service-worker:** fix `SwPush.unsubscribe()` ([#24162](https://github.com/angular/angular/issues/24162)) ([ea2987c](https://github.com/angular/angular/commit/ea2987c)), closes [#24095](https://github.com/angular/angular/issues/24095)
|
||||
|
||||
|
||||
|
||||
<a name="6.0.4"></a>
|
||||
## [6.0.4](https://github.com/angular/angular/compare/6.0.3...6.0.4) (2018-06-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** Fix browser detection logic ([#24188](https://github.com/angular/angular/issues/24188)) ([c9eb491](https://github.com/angular/angular/commit/c9eb491))
|
||||
* **animations:** retain trigger-state for nodes that are moved around ([#24238](https://github.com/angular/angular/issues/24238)) ([19deca1](https://github.com/angular/angular/commit/19deca1))
|
||||
* **forms:** properly handle special properties in FormGroup.get ([#22249](https://github.com/angular/angular/issues/22249)) ([dc3e8aa](https://github.com/angular/angular/commit/dc3e8aa)), closes [#17195](https://github.com/angular/angular/issues/17195)
|
||||
* **platform-server:** avoid clash between server and client style encapsulation attributes ([#24158](https://github.com/angular/angular/issues/24158)) ([e9f2203](https://github.com/angular/angular/commit/e9f2203))
|
||||
* **platform-server:** avoid dependency cycle when using http interceptor ([#24229](https://github.com/angular/angular/issues/24229)) ([2991b1b](https://github.com/angular/angular/commit/2991b1b)), closes [#23023](https://github.com/angular/angular/issues/23023)
|
||||
* **platform-server:** don't reflect innerHTML property to attibute ([#24213](https://github.com/angular/angular/issues/24213)) ([c17098d](https://github.com/angular/angular/commit/c17098d)), closes [#19278](https://github.com/angular/angular/issues/19278)
|
||||
* **platform-server:** provide Domino DOM types globally ([#24116](https://github.com/angular/angular/issues/24116)) ([906b3ec](https://github.com/angular/angular/commit/906b3ec)), closes [#23280](https://github.com/angular/angular/issues/23280) [#23133](https://github.com/angular/angular/issues/23133)
|
||||
|
||||
|
||||
<a name="6.0.3"></a>
|
||||
## [6.0.3](https://github.com/angular/angular/compare/6.0.2...6.0.3) (2018-05-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **service-worker:** check platformBrowser before accessing navigator.serviceWorker ([#21231](https://github.com/angular/angular/issues/21231)) ([0ee5b7e](https://github.com/angular/angular/commit/0ee5b7e))
|
||||
|
||||
|
||||
|
||||
<a name="6.0.2"></a>
|
||||
## [6.0.2](https://github.com/angular/angular/compare/6.0.1...6.0.2) (2018-05-15)
|
||||
|
||||
@ -32,7 +71,7 @@
|
||||
|
||||
Angular v6 is the first release of Angular that unifies the Framework, Material and CLI.
|
||||
|
||||
To learn about the release highlights and our new CLI-powered update workflow for your projects please check out the [v6 release announcement](https://blog.angular.io/version-6-0-0-of-angular-now-available-cc56b0efa7a4).
|
||||
To learn about the release highlights and our new CLI-powered update workflow for your projects please check out the [v6 release announcement](https://blog.angular.io/version-6-0-0-of-angular-now-available-cc56b0efa7a4).
|
||||
|
||||
|
||||
|
||||
@ -183,10 +222,10 @@ To learn about the release highlights and our new CLI-powered update workflow fo
|
||||
This change removes support for `<template>`. `<ng-template>` should be used instead.
|
||||
|
||||
BEFORE:
|
||||
|
||||
|
||||
<!-- html template -->
|
||||
<template>some template content</template>
|
||||
|
||||
|
||||
# tsconfig.json
|
||||
{
|
||||
# ...
|
||||
@ -196,12 +235,12 @@ To learn about the release highlights and our new CLI-powered update workflow fo
|
||||
"enableLegacyTemplate": [true|false]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AFTER:
|
||||
|
||||
|
||||
<!-- html template -->
|
||||
<ng-template>some template content</ng-template>
|
||||
|
||||
|
||||
* **core:** it is no longer possible to import animation-related functions from @angular/core. All animation symbols must now be imported from @angular/animations.
|
||||
|
||||
|
||||
@ -214,35 +253,35 @@ To learn about the release highlights and our new CLI-powered update workflow fo
|
||||
|
||||
Previously, ngModelChange was emitted before its underlying control was updated.
|
||||
This was fine if you passed through the value directly through the $event keyword, e.g.
|
||||
|
||||
|
||||
```
|
||||
<input [(ngModel)]="name" (ngModelChange)="onChange($event)">
|
||||
|
||||
|
||||
onChange(value) {
|
||||
console.log(value); // would log updated value
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
However, if you had a handler for the ngModelChange event that checked the value through the control,
|
||||
you would get the old value rather than the updated value. e.g:
|
||||
|
||||
|
||||
```
|
||||
<input #modelDir="ngModel" [(ngModel)]="name" (ngModelChange)="onChange(modelDir)">
|
||||
|
||||
|
||||
onChange(ngModel: NgModel) {
|
||||
console.log(ngModel.value); // would log old value, not updated value
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Now the value and validity will be updated before the ngModelChange event is emitted,
|
||||
so the same setup will log the updated value.
|
||||
|
||||
|
||||
```
|
||||
onChange(ngModel: NgModel) {
|
||||
console.log(ngModel.value); // will log updated value
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
We think this order will be less confusing when the control is checked directly.
|
||||
You will only need to update your app if it has relied on this bug to keep track of the old control value.
|
||||
If that is the case, you should be able to track the old value directly by saving it on your component.
|
||||
|
@ -227,10 +227,15 @@ The following is the list of supported scopes:
|
||||
|
||||
There are currently a few exceptions to the "use package name" rule:
|
||||
|
||||
* **packaging**: used for changes that change the npm package layout in all of our packages, e.g. public path changes, package.json changes done to all packages, d.ts file/format changes, changes to bundles, etc.
|
||||
* **packaging**: used for changes that change the npm package layout in all of our packages, e.g.
|
||||
public path changes, package.json changes done to all packages, d.ts file/format changes, changes
|
||||
to bundles, etc.
|
||||
* **changelog**: used for updating the release notes in CHANGELOG.md
|
||||
* **aio**: used for docs-app (angular.io) related changes within the /aio directory of the repo
|
||||
* none/empty string: useful for `style`, `test` and `refactor` changes that are done across all packages (e.g. `style: add missing semicolons`)
|
||||
* **docs-infra**: used for docs-app (angular.io) related changes within the /aio directory of the
|
||||
repo
|
||||
* none/empty string: useful for `style`, `test` and `refactor` changes that are done across all
|
||||
packages (e.g. `style: add missing semicolons`) and for docs changes that are not related to a
|
||||
specific package (e.g. `docs: fix typo in tutorial`).
|
||||
|
||||
### Subject
|
||||
The subject contains a succinct description of the change:
|
||||
@ -269,7 +274,7 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise
|
||||
* https://help.github.com/articles/about-commit-email-addresses/
|
||||
* https://help.github.com/articles/blocking-command-line-pushes-that-expose-your-personal-email-address/
|
||||
|
||||
Note that if you have more than one Git identity, it is important to verify that you are logged in with the same ID with which you signed the CLA, before you commit changes. If not, your PR will fail the CLA check.
|
||||
Note that if you have more than one Git identity, it is important to verify that you are logged in with the same ID with which you signed the CLA, before you commit changes. If not, your PR will fail the CLA check.
|
||||
|
||||
<hr>
|
||||
|
||||
|
30
WORKSPACE
@ -6,23 +6,23 @@ workspace(name = "angular")
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_nodejs",
|
||||
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.8.0.zip",
|
||||
strip_prefix = "rules_nodejs-0.8.0",
|
||||
sha256 = "4e40dd49ae7668d245c3107645f2a138660fcfd975b9310b91eda13f0c973953",
|
||||
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.9.1.zip",
|
||||
strip_prefix = "rules_nodejs-0.9.1",
|
||||
sha256 = "6139762b62b37c1fd171d7f22aa39566cb7dc2916f0f801d505a9aaf118c117f",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "io_bazel_rules_webtesting",
|
||||
url = "https://github.com/bazelbuild/rules_webtesting/archive/cfcaaf98553fee8e7063b5f5c11fd1b77e43d683.zip",
|
||||
strip_prefix = "rules_webtesting-cfcaaf98553fee8e7063b5f5c11fd1b77e43d683",
|
||||
sha256 = "636c7a9ac2ca13a04d982c2f9c874876ecc90a7b9ccfe4188156122b26ada7b3",
|
||||
url = "https://github.com/bazelbuild/rules_webtesting/archive/v0.2.0.zip",
|
||||
strip_prefix = "rules_webtesting-0.2.0",
|
||||
sha256 = "cecc12f07e95740750a40d38e8b14b76fefa1551bef9332cb432d564d693723c",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_typescript",
|
||||
url = "https://github.com/bazelbuild/rules_typescript/archive/v0.13.0.zip",
|
||||
strip_prefix = "rules_typescript-0.13.0",
|
||||
sha256 = "8f2767ff56ad68c80c62e9a1cdc2ba2c2ba0b19d350f713365e5333045df02e3",
|
||||
url = "https://github.com/bazelbuild/rules_typescript/archive/0.15.0.zip",
|
||||
strip_prefix = "rules_typescript-0.15.0",
|
||||
sha256 = "1aa75917330b820cb239b3c10a936a10f0a46fe215063d4492dd76dc6e1616f4",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
@ -34,13 +34,13 @@ http_archive(
|
||||
# This commit matches the version of buildifier in angular/ngcontainer
|
||||
# If you change this, also check if it matches the version in the angular/ngcontainer
|
||||
# version in /.circleci/config.yml
|
||||
BAZEL_BUILDTOOLS_VERSION = "fd9878fd5de921e0bbab3dcdcb932c2627812ee1"
|
||||
BAZEL_BUILDTOOLS_VERSION = "82b21607e00913b16fe1c51bec80232d9d6de31c"
|
||||
|
||||
http_archive(
|
||||
name = "com_github_bazelbuild_buildtools",
|
||||
url = "https://github.com/bazelbuild/buildtools/archive/%s.zip" % BAZEL_BUILDTOOLS_VERSION,
|
||||
strip_prefix = "buildtools-%s" % BAZEL_BUILDTOOLS_VERSION,
|
||||
sha256 = "27bb461ade23fd44ba98723ad98f84ee9c83cd3540b773b186a1bc5037f3d862",
|
||||
sha256 = "edb24c2f9c55b10a820ec74db0564415c0cf553fa55e9fc709a6332fb6685eff",
|
||||
)
|
||||
|
||||
# Fetching the Bazel source code allows us to compile the Skylark linter
|
||||
@ -66,9 +66,9 @@ http_archive(
|
||||
|
||||
http_archive(
|
||||
name = "org_brotli",
|
||||
url = "https://github.com/google/brotli/archive/c6333e1e79fb62ea088443f192293f964409b04e.zip",
|
||||
strip_prefix = "brotli-c6333e1e79fb62ea088443f192293f964409b04e",
|
||||
sha256 = "3f781988dee7dd3bcce2bf238294663cfaaf3b6433505bdb762e24d0a284d1dc",
|
||||
url = "https://github.com/google/brotli/archive/f9b8c02673c576a3e807edbf3a9328e9e7af6d7c.zip",
|
||||
strip_prefix = "brotli-f9b8c02673c576a3e807edbf3a9328e9e7af6d7c",
|
||||
sha256 = "8a517806d2b7c8505ba5c53934e7d7c70d341b68ffd268e9044d35b564a48828",
|
||||
)
|
||||
|
||||
#
|
||||
@ -77,7 +77,7 @@ http_archive(
|
||||
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install")
|
||||
|
||||
check_bazel_version("0.13.0")
|
||||
check_bazel_version("0.14.0")
|
||||
node_repositories(package_json = ["//:package.json"])
|
||||
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
|
||||
|
@ -52,8 +52,7 @@ export class BuildCleaner {
|
||||
protected removeDir(dir: string) {
|
||||
try {
|
||||
if (shell.test('-d', dir)) {
|
||||
// Undocumented signature (see https://github.com/shelljs/shelljs/pull/663).
|
||||
(shell as any).chmod('-R', 'a+w', dir);
|
||||
shell.chmod('-R', 'a+w', dir);
|
||||
shell.rm('-rf', dir);
|
||||
}
|
||||
} catch (err) {
|
||||
|
@ -106,8 +106,7 @@ export class BuildCreator extends EventEmitter {
|
||||
}
|
||||
|
||||
try {
|
||||
// Undocumented signature (see https://github.com/shelljs/shelljs/pull/663).
|
||||
(shell as any).chmod('-R', 'a-w', outputDir);
|
||||
shell.chmod('-R', 'a-w', outputDir);
|
||||
shell.rm('-f', inputFile);
|
||||
resolve();
|
||||
} catch (err) {
|
||||
|
@ -98,8 +98,7 @@ class Helper {
|
||||
const prDir = this.getPrDir(pr, isPublic);
|
||||
|
||||
if (fs.existsSync(prDir)) {
|
||||
// Undocumented signature (see https://github.com/shelljs/shelljs/pull/663).
|
||||
(shell as any).chmod('-R', 'a+w', prDir);
|
||||
shell.chmod('-R', 'a+w', prDir);
|
||||
shell.rm('-rf', prDir);
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
"scripts": {
|
||||
"prebuild": "yarn clean-dist",
|
||||
"build": "tsc",
|
||||
"build-watch": "yarn tsc --watch",
|
||||
"build-watch": "yarn build --watch",
|
||||
"clean-dist": "node --eval \"require('shelljs').rm('-rf', 'dist')\"",
|
||||
"dev": "concurrently --kill-others --raw --success first \"yarn build-watch\" \"yarn test-watch\"",
|
||||
"lint": "tslint --project tsconfig.json",
|
||||
@ -33,7 +33,7 @@
|
||||
"@types/jasmine": "^2.6.0",
|
||||
"@types/jsonwebtoken": "^7.2.3",
|
||||
"@types/node": "^8.0.30",
|
||||
"@types/shelljs": "^0.7.4",
|
||||
"@types/shelljs": "^0.8.0",
|
||||
"@types/supertest": "^2.0.3",
|
||||
"concurrently": "^3.5.0",
|
||||
"nodemon": "^1.12.1",
|
||||
|
@ -69,9 +69,9 @@
|
||||
"@types/express-serve-static-core" "*"
|
||||
"@types/mime" "*"
|
||||
|
||||
"@types/shelljs@^0.7.4":
|
||||
version "0.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.7.4.tgz#137b5f31306eaff4de120ffe5b9d74b297809cfc"
|
||||
"@types/shelljs@^0.8.0":
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.0.tgz#0caa56b68baae4f68f44e0dd666ab30b098e3632"
|
||||
dependencies:
|
||||
"@types/glob" "*"
|
||||
"@types/node" "*"
|
||||
|
@ -3,7 +3,7 @@
|
||||
set -eux -o pipefail
|
||||
exec 3>&1
|
||||
|
||||
echo "\n\n[`date`] - Updating the preview server..."
|
||||
echo -e "\n\n[`date`] - Updating the preview server..."
|
||||
|
||||
# Input
|
||||
readonly HOST_REPO_DIR=$1
|
||||
|
@ -1,27 +0,0 @@
|
||||
# MasterProject
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.0.0-rc.0.
|
||||
|
||||
## Development server
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
|
||||
Before running the tests make sure you are serving the app via `ng serve`.
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
@ -5,18 +5,18 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
selector: 'app-voter',
|
||||
template: `
|
||||
<h4>{{name}}</h4>
|
||||
<button (click)="vote(true)" [disabled]="voted">Agree</button>
|
||||
<button (click)="vote(false)" [disabled]="voted">Disagree</button>
|
||||
<button (click)="vote(true)" [disabled]="didVote">Agree</button>
|
||||
<button (click)="vote(false)" [disabled]="didVote">Disagree</button>
|
||||
`
|
||||
})
|
||||
export class VoterComponent {
|
||||
@Input() name: string;
|
||||
@Output() onVoted = new EventEmitter<boolean>();
|
||||
voted = false;
|
||||
@Output() voted = new EventEmitter<boolean>();
|
||||
didVote = false;
|
||||
|
||||
vote(agreed: boolean) {
|
||||
this.onVoted.emit(agreed);
|
||||
this.voted = true;
|
||||
this.voted.emit(agreed);
|
||||
this.didVote = true;
|
||||
}
|
||||
}
|
||||
// #enddocregion
|
||||
|
@ -8,7 +8,7 @@ import { Component } from '@angular/core';
|
||||
<h3>Agree: {{agreed}}, Disagree: {{disagreed}}</h3>
|
||||
<app-voter *ngFor="let voter of voters"
|
||||
[name]="voter"
|
||||
(onVoted)="onVoted($event)">
|
||||
(voted)="onVoted($event)">
|
||||
</app-voter>
|
||||
`
|
||||
})
|
||||
|
@ -5,7 +5,7 @@ import { AbstractControl, NG_VALIDATORS, Validator, ValidatorFn, Validators } fr
|
||||
// #docregion custom-validator
|
||||
/** A hero's name can't match the given regular expression */
|
||||
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
|
||||
return (control: AbstractControl): {[key: string]: any} => {
|
||||
return (control: AbstractControl): {[key: string]: any} | null => {
|
||||
const forbidden = nameRe.test(control.value);
|
||||
return forbidden ? {'forbiddenName': {value: control.value}} : null;
|
||||
};
|
||||
@ -22,7 +22,7 @@ export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
|
||||
export class ForbiddenValidatorDirective implements Validator {
|
||||
@Input('appForbiddenName') forbiddenName: string;
|
||||
|
||||
validate(control: AbstractControl): {[key: string]: any} {
|
||||
validate(control: AbstractControl): {[key: string]: any} | null {
|
||||
return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control)
|
||||
: null;
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ export class ComposeMessageComponent {
|
||||
@HostBinding('style.position') position = 'absolute';
|
||||
|
||||
details: string;
|
||||
message: string;
|
||||
sending = false;
|
||||
|
||||
constructor(private router: Router) {}
|
||||
|
@ -1,26 +1,26 @@
|
||||
// Import spec files individually for Stackblitz
|
||||
import 'app/about/about.component.spec.ts';
|
||||
import 'app/app-initial.component.spec.ts';
|
||||
import 'app/app.component.router.spec.ts';
|
||||
import 'app/app.component.spec.ts';
|
||||
import 'app/banner/banner-initial.component.spec.ts';
|
||||
import 'app/banner/banner.component.spec.ts';
|
||||
import 'app/banner/banner.component.detect-changes.spec.ts';
|
||||
import 'app/banner/banner-external.component.spec.ts';
|
||||
import 'app/dashboard/dashboard-hero.component.spec.ts';
|
||||
import 'app/dashboard/dashboard.component.no-testbed.spec.ts';
|
||||
import 'app/dashboard/dashboard.component.spec.ts';
|
||||
import 'app/demo/async-helper.spec.ts';
|
||||
import 'app/demo/demo.spec.ts';
|
||||
import 'app/demo/demo.testbed.spec.ts';
|
||||
import 'app/hero/hero-detail.component.no-testbed.spec.ts';
|
||||
import 'app/hero/hero-detail.component.spec.ts';
|
||||
import 'app/hero/hero-list.component.spec.ts';
|
||||
import 'app/model/hero.service.spec.ts';
|
||||
import 'app/model/http-hero.service.spec.ts';
|
||||
import 'app/model/testing/http-client.spec.ts';
|
||||
import 'app/shared/highlight.directive.spec.ts';
|
||||
import 'app/shared/title-case.pipe.spec.ts';
|
||||
import 'app/twain/twain.component.spec.ts';
|
||||
import 'app/twain/twain.component.marbles.spec.ts';
|
||||
import 'app/welcome/welcome.component.spec.ts';
|
||||
import './app/about/about.component.spec.ts';
|
||||
import './app/app-initial.component.spec.ts';
|
||||
import './app/app.component.router.spec.ts';
|
||||
import './app/app.component.spec.ts';
|
||||
import './app/banner/banner-initial.component.spec.ts';
|
||||
import './app/banner/banner.component.spec.ts';
|
||||
import './app/banner/banner.component.detect-changes.spec.ts';
|
||||
import './app/banner/banner-external.component.spec.ts';
|
||||
import './app/dashboard/dashboard-hero.component.spec.ts';
|
||||
import './app/dashboard/dashboard.component.no-testbed.spec.ts';
|
||||
import './app/dashboard/dashboard.component.spec.ts';
|
||||
import './app/demo/async-helper.spec.ts';
|
||||
import './app/demo/demo.spec.ts';
|
||||
import './app/demo/demo.testbed.spec.ts';
|
||||
import './app/hero/hero-detail.component.no-testbed.spec.ts';
|
||||
import './app/hero/hero-detail.component.spec.ts';
|
||||
import './app/hero/hero-list.component.spec.ts';
|
||||
import './app/model/hero.service.spec.ts';
|
||||
import './app/model/http-hero.service.spec.ts';
|
||||
import './app/model/testing/http-client.spec.ts';
|
||||
import './app/shared/highlight.directive.spec.ts';
|
||||
import './app/shared/title-case.pipe.spec.ts';
|
||||
import './app/twain/twain.component.spec.ts';
|
||||
import './app/twain/twain.component.marbles.spec.ts';
|
||||
import './app/welcome/welcome.component.spec.ts';
|
||||
|
@ -18,11 +18,11 @@ nav a {
|
||||
border-radius: 4px;
|
||||
}
|
||||
nav a:visited, a:link {
|
||||
color: #607D8B;
|
||||
color: #607d8b;
|
||||
}
|
||||
nav a:hover {
|
||||
color: #039be5;
|
||||
background-color: #CFD8DC;
|
||||
background-color: #cfd8dc;
|
||||
}
|
||||
nav a.active {
|
||||
color: #039be5;
|
||||
|
@ -33,11 +33,11 @@ h4 {
|
||||
color: #eee;
|
||||
max-height: 120px;
|
||||
min-width: 120px;
|
||||
background-color: #607D8B;
|
||||
background-color: #607d8b;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.module:hover {
|
||||
background-color: #EEE;
|
||||
background-color: #eee;
|
||||
cursor: pointer;
|
||||
color: #607d8b;
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ from the [The Tour of Heroes](tutorial/).
|
||||
</code-tabs>
|
||||
|
||||
The `HeroesComponent` is the top-level heroes component.
|
||||
It's only purpose is to display the `HeroListComponent`
|
||||
Its only purpose is to display the `HeroListComponent`
|
||||
which displays a list of hero names.
|
||||
|
||||
This version of the `HeroListComponent` gets its `heroes` from the `HEROES` array, an in-memory collection
|
||||
|
@ -44,25 +44,25 @@ of some of the things they contain:
|
||||
<tr>
|
||||
<td><code>FormsModule</code></td>
|
||||
<td><code>@angular/forms</code></td>
|
||||
<td>When you build template driven forms (includes <code>NgModel</code>)</td>
|
||||
<td>When you want to build template driven forms (includes <code>NgModel</code>)</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>ReactiveFormsModule</code></td>
|
||||
<td><code>@angular/forms</code></td>
|
||||
<td>When building reactive forms</td>
|
||||
<td>When you want to build reactive forms</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>RouterModule</code></td>
|
||||
<td><code>@angular/router</code></td>
|
||||
<td>For Routing and when you want to use <code>RouterLink</code>,<code>.forRoot()</code>, and <code>.forChild()</code></td>
|
||||
<td>When you want to use <code>RouterLink</code>, <code>.forRoot()</code>, and <code>.forChild()</code></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><code>HttpClientModule</code></td>
|
||||
<td><code>@angular/common/http</code></td>
|
||||
<td>When you to talk to a server</td>
|
||||
<td>When you want to talk to a server</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
@ -439,7 +439,7 @@ Observables can deliver single or multiple values of any type to subscribers, ei
|
||||
|
||||
Angular uses a third-party library called [Reactive Extensions (RxJS)](http://reactivex.io/rxjs/).
|
||||
|
||||
To learn more, see the [Observables](guide/glossary#observable) guide.
|
||||
To learn more, see the [Observables](guide/observables) guide.
|
||||
|
||||
|
||||
{@a observer}
|
||||
|
@ -450,7 +450,7 @@ Here is a `searchHeroes` method that queries for heroes whose names contain the
|
||||
|
||||
If there is a search term, the code constructs an options object with an HTML URL-encoded search parameter. If the term were "foo", the GET request URL would be `api/heroes/?name=foo`.
|
||||
|
||||
The `HttpParms` are immutable so you'll have to use the `set()` method to update the options.
|
||||
The `HttpParams` are immutable so you'll have to use the `set()` method to update the options.
|
||||
|
||||
### Debouncing requests
|
||||
|
||||
@ -1034,7 +1034,7 @@ Call `request.flush()` with an error message, as seen in the following example.
|
||||
|
||||
<code-example
|
||||
path="http/src/testing/http-client.spec.ts"
|
||||
region="404"
|
||||
region="network-error"
|
||||
linenums="false">
|
||||
</code-example>
|
||||
|
||||
@ -1044,4 +1044,4 @@ Alternatively, you can call `request.error()` with an `ErrorEvent`.
|
||||
path="http/src/testing/http-client.spec.ts"
|
||||
region="network-error"
|
||||
linenums="false">
|
||||
</code-example>
|
||||
</code-example>
|
||||
|
@ -74,22 +74,58 @@ The following table contains our current target release dates for the next two m
|
||||
|
||||
|
||||
{@a lts}
|
||||
## Long-term support
|
||||
{@a support}
|
||||
## Support policy
|
||||
|
||||
All of our releases are supported actively for about 6 months (until the next major release), and then they are supported through long-term support (LTS) for another 12 months.
|
||||
All of our major releases are supported for 18 months.
|
||||
|
||||
During the LTS period, only critical fixes and security patches will be merged and released.
|
||||
* 6 months of active support, during which regularly-scheduled updates and patches are released, as described above in [Release frequency](#frequency "Release frequency").
|
||||
|
||||
The LTS state of one major version starts on the day of the next major release. LTS status ends approximately one year later, when we release another major version.
|
||||
* 12 months of long-term support (LTS). During the LTS period, only critical fixes and security patches will be released.
|
||||
|
||||
The following table provides the support status and key dates for Angular version 4.0.0 and higher.
|
||||
|
||||
<style>
|
||||
|
||||
Version | LTS Start Date | LTS End Date
|
||||
----------- | -------------- | ------------
|
||||
^4.0.0 | October 2017 | October 2018
|
||||
^5.0.0 | April 2018 | April 2019
|
||||
^6.0.0 | October 2018 | October 2019
|
||||
td, th {vertical-align: top}
|
||||
|
||||
</style>
|
||||
|
||||
<table>
|
||||
|
||||
<tr>
|
||||
<th>Version</th>
|
||||
<th>Status</th>
|
||||
<th>Release Date</th>
|
||||
<th>LTS Start Date</th>
|
||||
<th>LTS End Date</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>^4.0.0</td>
|
||||
<td>LTS</td>
|
||||
<td>March 23, 2017</td>
|
||||
<td>September 23, 2017</td>
|
||||
<td>September 23, 2018</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>^5.0.0</td>
|
||||
<td>LTS</td>
|
||||
<td>November 1, 2017</td>
|
||||
<td>May 1, 2018</td>
|
||||
<td>May 1, 2019</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>^6.0.0</td>
|
||||
<td>Active</td>
|
||||
<td>May 3, 2018</td>
|
||||
<td>November 3, 2018</td>
|
||||
<td>November 3, 2019</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
@ -106,7 +142,7 @@ To make these transitions as easy as possible, we make two commitments to you:
|
||||
|
||||
To help ensure that you have sufficient time and a clear path to update, this is our deprecation policy:
|
||||
|
||||
* When announce deprecated features in the [change log](https://github.com/angular/angular/blob/master/CHANGELOG.md "Angular change log").
|
||||
* We announce deprecated features in the [change log](https://github.com/angular/angular/blob/master/CHANGELOG.md "Angular change log").
|
||||
|
||||
* When we announce a deprecation, we also announce a recommended update path.
|
||||
|
||||
@ -127,8 +163,6 @@ Any changes to the public API surface will be done using the versioning, support
|
||||
{@a labs}
|
||||
## Angular Labs
|
||||
|
||||
Angular Labs is an initiative to cultivate new features and iterate on them quickly. Angular Labs provides a safe place for exploration and experimentation by the Angular team.
|
||||
Angular Labs is an initiative to cultivate new features and iterate on them quickly. Angular Labs provides a safe place for exploration and experimentation by the Angular team.
|
||||
|
||||
Angular Labs projects are are not ready for production use, and no commitment is made to bring them to production. The policies and practices that are described in this document do not apply to Angular Labs projects.
|
||||
|
||||
Angular Labs projects typically are in separate branches in the Angular repo, clearly separated from the main Angular codebase.
|
||||
Angular Labs projects are not ready for production use, and no commitment is made to bring them to production. The policies and practices that are described in this document do not apply to Angular Labs projects.
|
||||
|
@ -3656,7 +3656,7 @@ Lazy loading has multiple benefits.
|
||||
* You can speed up load time for users that only visit certain areas of the application.
|
||||
* You can continue expanding lazy loaded feature areas without increasing the size of the initial load bundle.
|
||||
|
||||
You're already made part way there.
|
||||
You're already part of the way there.
|
||||
By organizing the application into modules—`AppModule`,
|
||||
`HeroesModule`, `AdminModule` and `CrisisCenterModule`—you
|
||||
have natural candidates for lazy loading.
|
||||
|
@ -933,29 +933,18 @@ As always, strive for consistency.
|
||||
|
||||
<a href="#toc">Back to top</a>
|
||||
|
||||
{@a 02-06}
|
||||
{@a 05-02}
|
||||
|
||||
### Directive selectors
|
||||
### Component selectors
|
||||
|
||||
#### Style 02-06
|
||||
#### Style 05-02
|
||||
|
||||
|
||||
<div class="s-rule do">
|
||||
|
||||
|
||||
|
||||
**Do** Use lower camel case for naming the selectors of directives.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-why">
|
||||
|
||||
|
||||
|
||||
**Why?** Keeps the names of the properties defined in the directives that are bound to the view consistent with the attribute names.
|
||||
**Do** use _dashed-case_ or _kebab-case_ for naming the element selectors of components.
|
||||
|
||||
|
||||
</div>
|
||||
@ -966,16 +955,40 @@ As always, strive for consistency.
|
||||
|
||||
|
||||
|
||||
**Why?** The Angular HTML parser is case sensitive and recognizes lower camel case.
|
||||
**Why?** Keeps the element names consistent with the specification for [Custom Elements](https://www.w3.org/TR/custom-elements/).
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<code-example path="styleguide/src/05-02/app/heroes/shared/hero-button/hero-button.component.avoid.ts" region="example" title="app/heroes/shared/hero-button/hero-button.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="app/heroes/shared/hero-button/hero-button.component.ts" path="styleguide/src/05-02/app/heroes/shared/hero-button/hero-button.component.ts" region="example">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="app/app.component.html" path="styleguide/src/05-02/app/app.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
<a href="#toc">Back to top</a>
|
||||
|
||||
{@a 02-07}
|
||||
|
||||
### Custom prefix for components
|
||||
### Component custom prefix
|
||||
|
||||
#### Style 02-07
|
||||
|
||||
@ -1078,11 +1091,51 @@ For example, the prefix `toh` represents from **T**our **o**f **H**eroes and the
|
||||
|
||||
|
||||
|
||||
<a href="#toc">Back to top</a>
|
||||
|
||||
{@a 02-06}
|
||||
|
||||
### Directive selectors
|
||||
|
||||
#### Style 02-06
|
||||
|
||||
|
||||
<div class="s-rule do">
|
||||
|
||||
|
||||
|
||||
**Do** Use lower camel case for naming the selectors of directives.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-why">
|
||||
|
||||
|
||||
|
||||
**Why?** Keeps the names of the properties defined in the directives that are bound to the view consistent with the attribute names.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-why-last">
|
||||
|
||||
|
||||
|
||||
**Why?** The Angular HTML parser is case sensitive and recognizes lower camel case.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<a href="#toc">Back to top</a>
|
||||
|
||||
{@a 02-08}
|
||||
|
||||
### Custom prefix for directives
|
||||
### Directive custom prefix
|
||||
|
||||
#### Style 02-08
|
||||
|
||||
@ -3056,9 +3109,9 @@ module are referenced across the entire application.
|
||||
|
||||
|
||||
|
||||
**Avoid** providing services in shared modules. Services are usually
|
||||
**Consider** _not_ providing services in shared modules. Services are usually
|
||||
singletons that are provided once for the entire application or
|
||||
in a particular feature module.
|
||||
in a particular feature module. There are exceptions, however. For example, in the sample code that follows, notice that the `SharedModule` provides `FilterTextService`. This is acceptable here because the service is stateless;that is, the consumers of the service aren't impacted by new instances.
|
||||
|
||||
|
||||
</div>
|
||||
@ -3710,59 +3763,6 @@ A typical *lazy loaded folder* contains a *routing component*, its child compone
|
||||
|
||||
## Components
|
||||
|
||||
{@a 05-02}
|
||||
|
||||
### Component selector names
|
||||
|
||||
#### Style 05-02
|
||||
|
||||
|
||||
<div class="s-rule do">
|
||||
|
||||
|
||||
|
||||
**Do** use _dashed-case_ or _kebab-case_ for naming the element selectors of components.
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="s-why-last">
|
||||
|
||||
|
||||
|
||||
**Why?** Keeps the element names consistent with the specification for [Custom Elements](https://www.w3.org/TR/custom-elements/).
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<code-example path="styleguide/src/05-02/app/heroes/shared/hero-button/hero-button.component.avoid.ts" region="example" title="app/heroes/shared/hero-button/hero-button.component.ts">
|
||||
|
||||
</code-example>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<code-tabs>
|
||||
|
||||
<code-pane title="app/heroes/shared/hero-button/hero-button.component.ts" path="styleguide/src/05-02/app/heroes/shared/hero-button/hero-button.component.ts" region="example">
|
||||
|
||||
</code-pane>
|
||||
|
||||
<code-pane title="app/app.component.html" path="styleguide/src/05-02/app/app.component.html">
|
||||
|
||||
</code-pane>
|
||||
|
||||
</code-tabs>
|
||||
|
||||
|
||||
|
||||
<a href="#toc">Back to top</a>
|
||||
|
||||
{@a 05-03}
|
||||
|
||||
### Components as elements
|
||||
|
@ -1,3 +0,0 @@
|
||||
# Testing
|
||||
|
||||
TBD. Original content [here](https://docs.google.com/document/d/1gGP5sqWNCHAWWV_GLdZQ1XyMO4K-CHksUxux0BFtVxk/edit#heading=h.ohqykkhzdhb2).
|
@ -204,8 +204,7 @@ The test consumes that spy in the same way it did earlier.
|
||||
Most test suites in this guide call `beforeEach()` to set the preconditions for each `it()` test
|
||||
and rely on the `TestBed` to create classes and inject services.
|
||||
|
||||
There's another school of testing that never calls `beforeEach()` and
|
||||
and prefers to create classes explicitly rather than use the `TestBed`.
|
||||
There's another school of testing that never calls `beforeEach()` and prefers to create classes explicitly rather than use the `TestBed`.
|
||||
|
||||
Here's how you might rewrite one of the `MasterService` tests in that style.
|
||||
|
||||
@ -347,7 +346,7 @@ It appears within the template of a parent component,
|
||||
which binds a _hero_ to the `@Input` property and
|
||||
listens for an event raised through the _selected_ `@Output` property.
|
||||
|
||||
You can test that the class code works without creating the the `DashboardHeroComponent`
|
||||
You can test that the class code works without creating the `DashboardHeroComponent`
|
||||
or its parent component.
|
||||
|
||||
<code-example
|
||||
@ -399,7 +398,7 @@ But a component is more than just its class.
|
||||
A component interacts with the DOM and with other components.
|
||||
The _class-only_ tests can tell you about class behavior.
|
||||
They cannot tell you if the component is going to render properly,
|
||||
respond to user input and gestures, or integrate with its parent and and child components.
|
||||
respond to user input and gestures, or integrate with its parent and child components.
|
||||
|
||||
None of the _class-only_ tests above can answer key questions about how the
|
||||
components actually behave on screen.
|
||||
@ -2367,9 +2366,9 @@ The [override metadata object](#metadata-override-object) is a generic defined a
|
||||
|
||||
<code-example format="." language="javascript">
|
||||
type MetadataOverride<T> = {
|
||||
add?: T;
|
||||
remove?: T;
|
||||
set?: T;
|
||||
add?: Partial<T>;
|
||||
remove?: Partial<T>;
|
||||
set?: Partial<T>;
|
||||
};
|
||||
</code-example>
|
||||
|
||||
@ -2725,9 +2724,9 @@ appropriate to the method, that is, the parameter of an `@NgModule`,
|
||||
|
||||
<code-example format="." language="javascript">
|
||||
type MetadataOverride<T> = {
|
||||
add?: T;
|
||||
remove?: T;
|
||||
set?: T;
|
||||
add?: Partial<T>;
|
||||
remove?: Partial<T>;
|
||||
set?: Partial<T>;
|
||||
};
|
||||
</code-example>
|
||||
|
||||
@ -3379,11 +3378,11 @@ next to their corresponding helper files.
|
||||
{@a q-e2e}
|
||||
#### Why not rely on E2E tests of DOM integration?
|
||||
|
||||
The component DOM tests describe in this guide often require extensive setup and
|
||||
advanced techniques where as the [class-only test](#component-class-testing)
|
||||
were comparatively simple.
|
||||
The component DOM tests described in this guide often require extensive setup and
|
||||
advanced techniques whereas the [unit tests](#component-class-testing)
|
||||
are comparatively simple.
|
||||
|
||||
Why not defer DOM integration tests to end-to-end (E2E) testing?
|
||||
#### Why not defer DOM integration tests to end-to-end (E2E) testing?
|
||||
|
||||
E2E tests are great for high-level validation of the entire system.
|
||||
But they can't give you the comprehensive test coverage that you'd expect from unit tests.
|
||||
@ -3400,4 +3399,4 @@ accidental corruption of remote resources.
|
||||
It can even be hard to navigate to the component you want to test.
|
||||
|
||||
Because of these many obstacles, you should test DOM interaction
|
||||
with unit testing techniques as much as possible.
|
||||
with unit testing techniques as much as possible.
|
||||
|
@ -180,7 +180,7 @@ npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-lo
|
||||
|
||||
{@a transition}
|
||||
|
||||
### Modify the client app
|
||||
## Modify the client app
|
||||
|
||||
A Universal app can act as a dynamic, content-rich "splash screen" that engages the user.
|
||||
It gives the appearance of a near-instant application.
|
||||
@ -190,7 +190,9 @@ Once loaded, Angular transitions from the static server-rendered page to the dyn
|
||||
|
||||
You must make a few changes to your application code to support both server-side rendering and the transition to the client app.
|
||||
|
||||
#### The root `AppModule`
|
||||
{@a root-app-module}
|
||||
|
||||
### The root `AppModule`
|
||||
|
||||
Open file `src/app/app.module.ts` and find the `BrowserModule` import in the `NgModule` metadata.
|
||||
Replace that import with this one:
|
||||
@ -206,9 +208,29 @@ You can get runtime information about the current platform and the `appId` by in
|
||||
<code-example path="universal/src/app/app.module.ts" region="platform-detection" title="src/app/app.module.ts (platform detection)">
|
||||
</code-example>
|
||||
|
||||
{@a cli-output}
|
||||
|
||||
### Build Destination
|
||||
|
||||
A Universal app is distributed in two parts: the server-side code that serves up the initial application, and the client-side code that's loaded in dynamically.
|
||||
|
||||
The Angular CLI outputs the client-side code in the `dist` directory by default, so you modify the `outputPath` for the __build__ target in the `angular.json` to keep the client-side build outputs separate from the server-side code. The client-side build output will be served by the Express server.
|
||||
|
||||
```
|
||||
...
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/browser",
|
||||
...
|
||||
}
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
{@a http-urls}
|
||||
|
||||
#### Absolute HTTP URLs
|
||||
### Absolute HTTP URLs
|
||||
|
||||
The tutorial's `HeroService` and `HeroSearchService` delegate to the Angular `HttpClient` module to fetch application data.
|
||||
These services send requests to _relative_ URLs such as `api/heroes`.
|
||||
@ -262,6 +284,19 @@ The `ModuleMapLoaderModule` is a server-side module that allows lazy-loading of
|
||||
|
||||
This is also the place to register providers that are specific to running your app under Universal.
|
||||
|
||||
{@a app-server-entry-point}
|
||||
|
||||
### App server entry point
|
||||
|
||||
The `Angular CLI` uses the `AppServerModule` to build the server-side bundle.
|
||||
|
||||
Create a `main.server.ts` file in the `src/` directory that exports the `AppServerModule`:
|
||||
|
||||
<code-example path="universal/src/main.server.ts" title="src/main.server.ts">
|
||||
</code-example>
|
||||
|
||||
The `main.server.ts` will be referenced later to add a `server` target to the `Angular CLI` configuration.
|
||||
|
||||
{@a web-server}
|
||||
|
||||
### Universal web server
|
||||
@ -421,6 +456,8 @@ This config extends from the root's `tsconfig.json` file. Certain settings are n
|
||||
* The `angularCompilerOptions` section guides the AOT compiler:
|
||||
* `entryModule` - the root module of the server application, expressed as `path/to/file#ClassName`.
|
||||
|
||||
{@a universal-webpack-configuration}
|
||||
|
||||
### Universal Webpack configuration
|
||||
|
||||
Universal applications doesn't need any extra Webpack configuration, the CLI takes care of that for you,
|
||||
@ -433,13 +470,38 @@ Create a `webpack.server.config.js` file in the project root directory with the
|
||||
|
||||
**Webpack configuration** is a rich topic beyond the scope of this guide.
|
||||
|
||||
{@a universal-cli-configuration}
|
||||
|
||||
### Angular CLI configuration
|
||||
|
||||
The CLI provides builders for different types of __targets__. Commonly known targets such as `build` and `serve` already exist in the `angular.json` configuration. To target a server-side build, add a `server` target to the `architect` configuration object.
|
||||
|
||||
* The `outputPath` tells where the resulting build will be created.
|
||||
* The `main` provides the main entry point to the previously created `main.server.ts`
|
||||
* The `tsConfig` uses the `tsconfig.server.json` as configuration for the TypeScript and AOT compilation.
|
||||
|
||||
```
|
||||
"architect": {
|
||||
...
|
||||
"server": {
|
||||
"builder": "@angular-devkit/build-angular:server",
|
||||
"options": {
|
||||
"outputPath": "dist/server",
|
||||
"main": "src/main.server.ts",
|
||||
"tsConfig": "src/tsconfig.server.json"
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Build and run with universal
|
||||
|
||||
Now that you've created the TypeScript and Webpack config files, you can build and run the Universal application.
|
||||
Now that you've created the TypeScript and Webpack config files and configured the Angular CLI, you can build and run the Universal application.
|
||||
|
||||
First add the _build_ and _serve_ commands to the `scripts` section of the `package.json`:
|
||||
|
||||
<code-example format="." language="ts">
|
||||
```
|
||||
"scripts": {
|
||||
...
|
||||
"build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server",
|
||||
@ -448,7 +510,7 @@ First add the _build_ and _serve_ commands to the `scripts` section of the `pack
|
||||
"webpack:server": "webpack --config webpack.server.config.js --progress --colors"
|
||||
...
|
||||
}
|
||||
</code-example>
|
||||
```
|
||||
|
||||
{@a build}
|
||||
|
||||
|
BIN
aio/content/images/bios/juristr.jpg
Normal file
After Width: | Height: | Size: 207 KiB |
BIN
aio/content/images/bios/kimmaida.jpg
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
aio/content/images/bios/mashhood.jpg
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
aio/content/images/bios/mhartington.png
Normal file
After Width: | Height: | Size: 28 KiB |
@ -12,5 +12,19 @@
|
||||
"message": "Watch ng-conf live stream <br/>Apr 18th-20th, 2018",
|
||||
"imageUrl": "generated/images/marketing/home/ng-conf.png",
|
||||
"linkUrl": "https://www.ng-conf.org/livestream/"
|
||||
},
|
||||
{
|
||||
"startDate": "2018-06-01",
|
||||
"endDate": "2018-08-15",
|
||||
"message": "Join us for Angular Mix<br/>October 10th-12th, 2018",
|
||||
"imageUrl": "generated/images/marketing/home/angular-mix.png",
|
||||
"linkUrl": "https://angularmix.com/"
|
||||
},
|
||||
{
|
||||
"startDate": "2018-08-15",
|
||||
"endDate": "2018-11-06",
|
||||
"message": "Join us for Angular Connect<br/>November 6th-7th, 2018",
|
||||
"imageUrl": "generated/images/marketing/home/angular-connect.png",
|
||||
"linkUrl": "https://angularconnect.com/"
|
||||
}
|
||||
]
|
||||
|
@ -592,7 +592,7 @@
|
||||
"bio": "Nir is a Principal Frontend Consultant & Head of the Angular department at 500Tech, Google Developer Expert and community leader. He organizes the largest Angular meetup group in Israel (Angular-IL), talks and teaches about front-end technologies around the world. He is also the author of two books about Angular and the founder of the 'Frontend Band'.",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
|
||||
"achautard": {
|
||||
"name": "Alain Chautard",
|
||||
"picture": "alainchautard.png",
|
||||
@ -609,5 +609,41 @@
|
||||
"website": "https://coryrylan.com",
|
||||
"bio": "Cory is a full time front end web developer. He works full time building responsive web applications and progressive web apps. When not building web apps he is busy teaching Angular and other web technologies in workshops and conferences. He loves the web and is optimistic of the places it can take us.",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
"mhartington": {
|
||||
"name": "Mike Hartington",
|
||||
"picture": "mhartington.png",
|
||||
"twitter": "mhartington",
|
||||
"website": "https://mhartington.io",
|
||||
"bio": "Mike is a Developer Advocate for the Ionic Framework and a GDE in Angular. He spends most of his time making fast PWAs and exploring emerging web standards. When not behind a keyboard, you'll probably find him with a guitar and beer.",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
"juristr": {
|
||||
"name": "Juri Strumpflohner",
|
||||
"picture": "juristr.jpg",
|
||||
"twitter": "juristr",
|
||||
"website": "https://juristr.com",
|
||||
"bio": "Juri is a software engineer and freelance trainer and consultant currently mostly focusing on the frontend side using JavaScript, TypeScript and Angular. He has a passion for teaching and sharing his knowledge and experiences with others. This mostly happens by writing tech articles for his personal blog, by creating video courses for Egghead.io, during on-site workshops at companies or by speaking at conferences. In his free time he enjoys practicing Yoseikan Budo, a martial art where he currently owns the 3rd DAN black belt.",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
"mashhoodr": {
|
||||
"name": "Mashhood Rastgar",
|
||||
"picture": "mashhood.jpg",
|
||||
"twitter": "mashhoodr",
|
||||
"website": "http://imars.info/",
|
||||
"bio": "Mashhood is the principal technical consultant at Recurship and a Google Developer Expert. He works with different startups in US and EU to helps them crawl through the technical maze and quickly build amazing products focused around the problems they are trying to solve. He specializes in using the latest web technologies available to execute the best possible solutions.",
|
||||
"group": "GDE"
|
||||
},
|
||||
|
||||
"kimmaida": {
|
||||
"name": "Kim Maida",
|
||||
"picture": "kimmaida.jpg",
|
||||
"twitter": "KimMaida",
|
||||
"website": "https://kmaida.io/",
|
||||
"bio": "Kim is an an Angular consultant, developer, speaker, writer, and Google Developer Expert. She is passionate about learning from and sharing knowledge with other developers through blogging, speaking, workshops, and open source.",
|
||||
"group": "GDE"
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,12 @@
|
||||
<td>Tokyo, Japan</td>
|
||||
<td>Jun 16, 2018</td>
|
||||
</tr>
|
||||
<!-- Angular Conf Australia-->
|
||||
<tr>
|
||||
<th><a href="https://www.angularconf.com.au/" title="Angular Conf Australia">Angular Conf Australia</a></th>
|
||||
<td>Melbourne, Australia</td>
|
||||
<td>Jun 22, 2018</td>
|
||||
</tr>
|
||||
<!-- AngularConnect-->
|
||||
<tr>
|
||||
<th><a href="http://angularconnect.com" title="AngularConnect">AngularConnect</a></th>
|
||||
|
@ -78,6 +78,440 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div>
|
||||
<h2>BRAND ICONS</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/animations.png" alt="Animations Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">ANIMATIONS</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Animations Icon (png) - <a href="assets/images/logos/concept-icons/animations.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Animations Icon (svg) - <a href="assets/images/logos/concept-icons/animations.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/augury.png" alt="Augury Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">AUGURY</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Augury Icon (png) - <a href="assets/images/logos/concept-icons/augury.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Augury Icon (svg) - <a href="assets/images/logos/concept-icons/augury.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/cdk.png" alt="CDK Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">COMPONENT DEV KIT (CDK)</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>CDK Icon (png) - <a href="assets/images/logos/concept-icons/cdk.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>CDK Icon (svg) - <a href="assets/images/logos/concept-icons/cdk.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/cli.png" alt="CLI Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">CLI</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>CLI Icon (png) - <a href="assets/images/logos/concept-icons/cli.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>CLI Icon (svg) - <a href="assets/images/logos/concept-icons/cli.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/compiler.png" alt="Compiler Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">COMPILER</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Compiler Icon (png) - <a href="assets/images/logos/concept-icons/compiler.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Compiler Icon (svg) - <a href="assets/images/logos/concept-icons/compiler.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/components.png" alt="Components Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">WEB COMPONENTS</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Web Components Icon (png) - <a href="assets/images/logos/concept-icons/components.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Web Components Icon (svg) - <a href="assets/images/logos/concept-icons/components.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/forms.png" alt="Forms Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">FORMS</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Forms Icon (png) - <a href="assets/images/logos/concept-icons/forms.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Forms Icon (svg) - <a href="assets/images/logos/concept-icons/forms.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/http.png" alt="HTTP Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">HTTP</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>HTTP Icon (png) - <a href="assets/images/logos/concept-icons/http.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>HTTP Icon (svg) - <a href="assets/images/logos/concept-icons/http.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/i18n.png" alt="i18n Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">i18n</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>HTTP Icon (png) - <a href="assets/images/logos/concept-icons/i18n.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>HTTP Icon (svg) - <a href="assets/images/logos/concept-icons/i18n.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/karma.png" alt="Karma Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">KARMA</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Karma Icon (png) - <a href="assets/images/logos/concept-icons/karma.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Karma Icon (svg) - <a href="assets/images/logos/concept-icons/karma.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/labs.png" alt="Labs Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">LABS</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Labs Icon (png) - <a href="assets/images/logos/concept-icons/labs.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Labs Icon (svg) - <a href="assets/images/logos/concept-icons/labs.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/language-services.png" alt="Language Services Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">LANGUAGE SERVICES</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Language Services Icon (png) - <a href="assets/images/logos/concept-icons/language-services.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Language Services Icon (svg) - <a href="assets/images/logos/concept-icons/language-services.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/material.png" alt="Material Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">MATERIAL</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Material Icon (png) - <a href="assets/images/logos/concept-icons/material.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Material Icon (svg) - <a href="assets/images/logos/concept-icons/material.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/protractor.png" alt="Protractor Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">PROTRACTOR</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Protractor Icon (png) - <a href="assets/images/logos/concept-icons/protractor.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Protractor Icon (svg) - <a href="assets/images/logos/concept-icons/protractor.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/pwa.png" alt="PWA Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">PWA</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>PWA Icon (png) - <a href="assets/images/logos/concept-icons/pwa.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>PWA Icon (svg) - <a href="assets/images/logos/concept-icons/pwa.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/router.png" alt="Router Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">ROUTER</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Router Icon (png) - <a href="assets/images/logos/concept-icons/router.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Router Icon (svg) - <a href="assets/images/logos/concept-icons/router.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/universal.png" alt="Universal Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">UNIVERSAL</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Universal Icon (png) - <a href="assets/images/logos/concept-icons/universal.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Universal Icon (svg) - <a href="assets/images/logos/concept-icons/universal.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div>
|
||||
<h2>CONCEPT & FEATURE ICONS</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/dependency-injection.png" alt="Dependency Injection Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">DEPENDENCY INJECTION</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Dependency Injection Icon (png) - <a href="assets/images/logos/concept-icons/dependency-injection.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Dependency Injection Icon (svg) - <a href="assets/images/logos/concept-icons/dependency-injection.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/lazy-loading.png" alt="Lazy Loading Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">LAZY LOADING</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Lazy Loading Icon (png) - <a href="assets/images/logos/concept-icons/lazy-loading.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Lazy Loading Icon (svg) - <a href="assets/images/logos/concept-icons/lazy-loading.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/libraries.png" alt="Libraries Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">LIBRARIES</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Libraries Icon (png) - <a href="assets/images/logos/concept-icons/libraries.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Libraries Icon (svg) - <a href="assets/images/logos/concept-icons/libraries.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/performance.png" alt="Performance Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">PERFORMANCE</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Performance Icon (png) - <a href="assets/images/logos/concept-icons/performance.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Performance Icon (svg) - <a href="assets/images/logos/concept-icons/performance.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div class="presskit-image-container">
|
||||
<img src="assets/images/logos/concept-icons/templates.png" alt="Templates Icon">
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="l-space-left-3">TEMPLATES</h3>
|
||||
<ul class="l-space-left-3">
|
||||
<li>
|
||||
<span>Templates Icon (png) - <a href="assets/images/logos/concept-icons/templates.png" download>Download</a></span>
|
||||
</li>
|
||||
<li>
|
||||
<span>Templates Icon (svg) - <a href="assets/images/logos/concept-icons/templates.svg" download>Download</a></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="presskit-row">
|
||||
<div class="presskit-inner">
|
||||
<div>
|
||||
|
@ -160,7 +160,7 @@
|
||||
"desc": "Lightweight yet powerful IDE, perfectly equipped for complex client-side development and server-side development with Node.js",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Webstorm",
|
||||
"title": "WebStorm",
|
||||
"url": "https://www.jetbrains.com/webstorm/"
|
||||
},
|
||||
"ab3": {
|
||||
@ -175,7 +175,13 @@
|
||||
"rev": true,
|
||||
"title": "Angular IDE by Webclipse",
|
||||
"url": "https://www.genuitec.com/products/angular-ide"
|
||||
}
|
||||
},
|
||||
"amexio-canvas": {
|
||||
"desc": "Amexio Canvas is Drag and Drop Environment to create Fully Responsive Web and Smart Device HTML5/Angular Apps. Code will be auto generated and hot deployed by the Canvas for live testing. Out of the box 50+ Material Design Theme support. Commit your code to GitHub public or private repository.",
|
||||
"rev": true,
|
||||
"title": "Amexio Canvas Web Based Drag and Drop IDE by MetaMagic",
|
||||
"url": "https://amexio.tech/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Tooling": {
|
||||
@ -345,6 +351,19 @@
|
||||
"title": "Angular Material",
|
||||
"url": "https://github.com/angular/material2"
|
||||
},
|
||||
"mcc": {
|
||||
"desc": "Material components made by the community",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Material Community Components",
|
||||
"url": "https://github.com/tiaguinho/material-community-components"
|
||||
},
|
||||
"ngzorro": {
|
||||
"desc": "A set of enterprise-class UI components based on Ant Design and Angular",
|
||||
"rev": true,
|
||||
"title": "Ant Design of Angular (ng-zorro-antd)",
|
||||
"url": "https://ng.ant.design/docs/introduce/en"
|
||||
},
|
||||
"aggrid": {
|
||||
"desc": "A datagrid for Angular with enterprise style features such as sorting, filtering, custom rendering, editing, grouping, aggregation and pivoting.",
|
||||
"rev": true,
|
||||
@ -364,6 +383,13 @@
|
||||
"url": "http://www.amexio.tech/",
|
||||
"logo": "http://www.amexio.org/amexio-logo.png"
|
||||
},
|
||||
"bm": {
|
||||
"desc": "A lightweight Material Design library for Angular, based upon Google's Material Components for the Web",
|
||||
"logo": "https://blox.src.zone/assets/bloxmaterial.03ecfe4fa0147a781487749dc1cc4580.svg",
|
||||
"rev": true,
|
||||
"title": "Blox Material",
|
||||
"url": "https://github.com/src-zone/material"
|
||||
},
|
||||
"essentialjs2": {
|
||||
"desc": "Essential JS 2 for Angular is a collection modern TypeScript based true Angular Components. It has support for Ahead Of Time (AOT) compilation and Tree-Shaking. All the components are developed from the ground up to be lightweight, responsive, modular and touch friendly.",
|
||||
"rev": true,
|
||||
@ -522,13 +548,6 @@
|
||||
"title": "Pluralsight",
|
||||
"url": "https://www.pluralsight.com/search?q=angular+2&categories=all"
|
||||
},
|
||||
"ab": {
|
||||
"desc": "Take this introduction to Angular course, to learn the fundamentals in just two days, free of charge.",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Rangle.io",
|
||||
"url": "https://rangle.io/services/javascript-training/training-angular1-angular2-with-ngupgrade/"
|
||||
},
|
||||
"ab3": {
|
||||
"desc": "Angular courses hosted by Udemy",
|
||||
"logo": "",
|
||||
@ -597,12 +616,6 @@
|
||||
"Workshops & Onsite Training": {
|
||||
"order": 2,
|
||||
"resources": {
|
||||
"-KLIBo3ANF3-1B9wxsoB": {
|
||||
"desc": "Angular Classes from Intertech in Minnesota",
|
||||
"rev": true,
|
||||
"title": "Intertech",
|
||||
"url": "http://www.intertech.com/Training/Web-Dev/AngularJS/AngularJS/Angular-2-Training"
|
||||
},
|
||||
"-KLIBoFWStce29UCwkvY": {
|
||||
"desc": "Private Angular Training and Mentoring",
|
||||
"rev": true,
|
||||
|
@ -175,7 +175,7 @@ This information is called _metadata_
|
||||
Some of the metadata is in the `@Component` decorators that you added to your component classes.
|
||||
Other critical metadata is in [`@NgModule`](guide/ngmodules) decorators.
|
||||
|
||||
The most important `@NgModule`decorator annotates the top-level **AppModule** class.
|
||||
The most important `@NgModule` decorator annotates the top-level **AppModule** class.
|
||||
|
||||
The Angular CLI generated an `AppModule` class in `src/app/app.module.ts` when it created the project.
|
||||
This is where you _opt-in_ to the `FormsModule`.
|
||||
|
@ -2,11 +2,11 @@
|
||||
"aio": {
|
||||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime": 2689,
|
||||
"main": 478529,
|
||||
"runtime": 2768,
|
||||
"main": 475855,
|
||||
"polyfills": 38453,
|
||||
"prettify": 14913
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@
|
||||
</mat-sidenav-container>
|
||||
|
||||
<div *ngIf="hasFloatingToc" class="toc-container no-print" [style.max-height.px]="tocMaxHeight" (mousewheel)="restrainScrolling($event)">
|
||||
<aio-toc></aio-toc>
|
||||
<aio-lazy-ce selector="aio-toc"></aio-lazy-ce>
|
||||
</div>
|
||||
|
||||
<footer class="no-print">
|
||||
|
@ -6,7 +6,7 @@ import { HttpClient } from '@angular/common/http';
|
||||
import { MatProgressBar, MatSidenav } from '@angular/material';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { timer } from 'rxjs';
|
||||
import { of, timer } from 'rxjs';
|
||||
import { first, mapTo } from 'rxjs/operators';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
@ -14,6 +14,7 @@ import { AppModule } from './app.module';
|
||||
import { DocumentService } from 'app/documents/document.service';
|
||||
import { DocViewerComponent } from 'app/layout/doc-viewer/doc-viewer.component';
|
||||
import { Deployment } from 'app/shared/deployment.service';
|
||||
import { ElementsLoader } from 'app/custom-elements/elements-loader';
|
||||
import { GaService } from 'app/shared/ga.service';
|
||||
import { LocationService } from 'app/shared/location.service';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
@ -26,7 +27,6 @@ import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
||||
import { SearchResultsComponent } from 'app/shared/search-results/search-results.component';
|
||||
import { SearchService } from 'app/search/search.service';
|
||||
import { SelectComponent } from 'app/shared/select/select.component';
|
||||
import { TocComponent } from 'app/layout/toc/toc.component';
|
||||
import { TocItem, TocService } from 'app/shared/toc.service';
|
||||
|
||||
const sideBySideBreakPoint = 992;
|
||||
@ -92,11 +92,11 @@ describe('AppComponent', () => {
|
||||
});
|
||||
|
||||
describe('hasFloatingToc', () => {
|
||||
it('should initially be true', () => {
|
||||
it('should initially be false', () => {
|
||||
const fixture2 = TestBed.createComponent(AppComponent);
|
||||
const component2 = fixture2.componentInstance;
|
||||
|
||||
expect(component2.hasFloatingToc).toBe(true);
|
||||
expect(component2.hasFloatingToc).toBe(false);
|
||||
});
|
||||
|
||||
it('should be false on narrow screens', () => {
|
||||
@ -621,55 +621,65 @@ describe('AppComponent', () => {
|
||||
});
|
||||
|
||||
describe('aio-toc', () => {
|
||||
let tocDebugElement: DebugElement;
|
||||
let tocContainer: DebugElement|null;
|
||||
let tocContainer: HTMLElement|null;
|
||||
let toc: HTMLElement|null;
|
||||
|
||||
const setHasFloatingToc = (hasFloatingToc: boolean) => {
|
||||
component.hasFloatingToc = hasFloatingToc;
|
||||
fixture.detectChanges();
|
||||
|
||||
tocDebugElement = fixture.debugElement.query(By.directive(TocComponent));
|
||||
tocContainer = tocDebugElement && tocDebugElement.parent;
|
||||
tocContainer = fixture.debugElement.nativeElement.querySelector('.toc-container');
|
||||
toc = tocContainer && tocContainer.querySelector('aio-toc');
|
||||
};
|
||||
|
||||
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();
|
||||
expect(toc).toBeFalsy();
|
||||
|
||||
setHasFloatingToc(true);
|
||||
expect(tocDebugElement).toBeTruthy();
|
||||
expect(tocContainer).toBeTruthy();
|
||||
expect(toc).toBeTruthy();
|
||||
|
||||
setHasFloatingToc(false);
|
||||
expect(tocContainer).toBeFalsy();
|
||||
expect(toc).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should have a non-embedded `<aio-toc>` element', () => {
|
||||
expect(tocDebugElement.classes['embedded']).toBeFalsy();
|
||||
setHasFloatingToc(true);
|
||||
expect(toc!.classList.contains('embedded')).toBe(false);
|
||||
});
|
||||
|
||||
it('should update the TOC container\'s `maxHeight` based on `tocMaxHeight`', () => {
|
||||
expect(tocContainer!.styles['max-height']).toBeNull();
|
||||
setHasFloatingToc(true);
|
||||
|
||||
expect(tocContainer!.style['max-height']).toBe('');
|
||||
|
||||
component.tocMaxHeight = '100';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(tocContainer!.styles['max-height']).toBe('100px');
|
||||
expect(tocContainer!.style['max-height']).toBe('100px');
|
||||
});
|
||||
|
||||
it('should restrain scrolling inside the ToC container', () => {
|
||||
const restrainScrolling = spyOn(component, 'restrainScrolling');
|
||||
const evt = {};
|
||||
const evt = new MouseEvent('mousewheel');
|
||||
|
||||
setHasFloatingToc(true);
|
||||
expect(restrainScrolling).not.toHaveBeenCalled();
|
||||
|
||||
tocContainer!.triggerEventHandler('mousewheel', evt);
|
||||
tocContainer!.dispatchEvent(evt);
|
||||
expect(restrainScrolling).toHaveBeenCalledWith(evt);
|
||||
});
|
||||
|
||||
it('should not be loaded/registered until necessary', () => {
|
||||
const loader: TestElementsLoader = fixture.debugElement.injector.get(ElementsLoader);
|
||||
expect(loader.loadCustomElement).not.toHaveBeenCalled();
|
||||
|
||||
setHasFloatingToc(true);
|
||||
expect(loader.loadCustomElement).toHaveBeenCalledWith('aio-toc');
|
||||
});
|
||||
});
|
||||
|
||||
describe('footer', () => {
|
||||
@ -1280,6 +1290,7 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') {
|
||||
imports: [ AppModule ],
|
||||
providers: [
|
||||
{ provide: APP_BASE_HREF, useValue: '/' },
|
||||
{ provide: ElementsLoader, useClass: TestElementsLoader },
|
||||
{ provide: GaService, useClass: TestGaService },
|
||||
{ provide: HttpClient, useClass: TestHttpClient },
|
||||
{ provide: LocationService, useFactory: () => mockLocationService },
|
||||
@ -1294,6 +1305,14 @@ function createTestingModule(initialUrl: string, mode: string = 'stable') {
|
||||
});
|
||||
}
|
||||
|
||||
class TestElementsLoader {
|
||||
loadContainedCustomElements = jasmine.createSpy('loadContainedCustomElements')
|
||||
.and.returnValue(of(undefined));
|
||||
|
||||
loadCustomElement = jasmine.createSpy('loadCustomElement')
|
||||
.and.returnValue(Promise.resolve());
|
||||
}
|
||||
|
||||
class TestGaService {
|
||||
locationChanged = jasmine.createSpy('locationChanged');
|
||||
}
|
||||
@ -1368,7 +1387,7 @@ class TestHttpClient {
|
||||
const id = match[1]!;
|
||||
// Make up a title for test purposes
|
||||
const title = id.split('/').pop()!.replace(/^([a-z])/, (_, letter) => letter.toUpperCase());
|
||||
const h1 = (id === 'no-title') ? '' : `<h1>${title}</h1>`;
|
||||
const h1 = (id === 'no-title') ? '' : `<h1 class="no-toc">${title}</h1>`;
|
||||
const contents = `${h1}<h2 id="#somewhere">Some heading</h2>`;
|
||||
data = { id, contents };
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ export class AppComponent implements OnInit {
|
||||
topMenuNodes: NavigationNode[];
|
||||
topMenuNarrowNodes: NavigationNode[];
|
||||
|
||||
hasFloatingToc = true;
|
||||
hasFloatingToc = false;
|
||||
private showFloatingToc = new BehaviorSubject(false);
|
||||
private showFloatingTocWidth = 800;
|
||||
tocMaxHeight: string;
|
||||
|
@ -32,7 +32,6 @@ import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { ScrollSpyService } from 'app/shared/scroll-spy.service';
|
||||
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
||||
import { NotificationComponent } from 'app/layout/notification/notification.component';
|
||||
import { TocComponent } from 'app/layout/toc/toc.component';
|
||||
import { TocService } from 'app/shared/toc.service';
|
||||
import { CurrentDateToken, currentDateProvider } from 'app/shared/current-date';
|
||||
import { WindowToken, windowProvider } from 'app/shared/window';
|
||||
@ -111,7 +110,6 @@ export const svgIconProviders = [
|
||||
NavItemComponent,
|
||||
SearchBoxComponent,
|
||||
NotificationComponent,
|
||||
TocComponent,
|
||||
TopMenuComponent,
|
||||
],
|
||||
providers: [
|
||||
@ -133,7 +131,6 @@ export const svgIconProviders = [
|
||||
{ provide: CurrentDateToken, useFactory: currentDateProvider },
|
||||
{ provide: WindowToken, useFactory: windowProvider },
|
||||
],
|
||||
entryComponents: [ TocComponent ],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
@ -25,20 +25,22 @@ export interface TabInfo {
|
||||
<!-- Use content projection so that the provided HTML's code-panes can be split into tabs -->
|
||||
<div #content style="display: none"><ng-content></ng-content></div>
|
||||
|
||||
<mat-tab-group class="code-tab-group" disableRipple>
|
||||
<mat-tab style="overflow-y: hidden;" *ngFor="let tab of tabs">
|
||||
<ng-template mat-tab-label>
|
||||
<span class="{{ tab.class }}">{{ tab.title }}</span>
|
||||
</ng-template>
|
||||
<aio-code class="{{ tab.class }}"
|
||||
[language]="tab.language"
|
||||
[linenums]="tab.linenums"
|
||||
[path]="tab.path"
|
||||
[region]="tab.region"
|
||||
[title]="tab.title">
|
||||
</aio-code>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
<mat-card>
|
||||
<mat-tab-group class="code-tab-group" disableRipple>
|
||||
<mat-tab style="overflow-y: hidden;" *ngFor="let tab of tabs">
|
||||
<ng-template mat-tab-label>
|
||||
<span class="{{ tab.class }}">{{ tab.title }}</span>
|
||||
</ng-template>
|
||||
<aio-code class="{{ tab.class }}"
|
||||
[language]="tab.language"
|
||||
[linenums]="tab.linenums"
|
||||
[path]="tab.path"
|
||||
[region]="tab.region"
|
||||
[title]="tab.title">
|
||||
</aio-code>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</mat-card>
|
||||
`,
|
||||
})
|
||||
export class CodeTabsComponent implements OnInit, AfterViewInit {
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { NgModule, Type } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CodeTabsComponent } from './code-tabs.component';
|
||||
import { MatTabsModule } from '@angular/material';
|
||||
import { MatCardModule, MatTabsModule } from '@angular/material';
|
||||
import { CodeModule } from './code.module';
|
||||
import { WithCustomElementComponent } from '../element-registry';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule, MatTabsModule, CodeModule ],
|
||||
imports: [ CommonModule, MatCardModule, MatTabsModule, CodeModule ],
|
||||
declarations: [ CodeTabsComponent ],
|
||||
exports: [ CodeTabsComponent ],
|
||||
entryComponents: [ CodeTabsComponent ]
|
||||
|
@ -6,8 +6,11 @@ import {
|
||||
ELEMENT_MODULE_PATHS_AS_ROUTES,
|
||||
ELEMENT_MODULE_PATHS_TOKEN
|
||||
} from './element-registry';
|
||||
import { LazyCustomElementComponent } from './lazy-custom-element.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [ LazyCustomElementComponent ],
|
||||
exports: [ LazyCustomElementComponent ],
|
||||
providers: [
|
||||
ElementsLoader,
|
||||
{ provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader },
|
||||
|
@ -13,8 +13,8 @@ export const ELEMENT_MODULE_PATHS_AS_ROUTES = [
|
||||
loadChildren: './api/api-list.module#ApiListModule'
|
||||
},
|
||||
{
|
||||
selector: 'live-example',
|
||||
loadChildren: './live-example/live-example.module#LiveExampleModule'
|
||||
selector: 'aio-contributor-list',
|
||||
loadChildren: './contributor/contributor-list.module#ContributorListModule'
|
||||
},
|
||||
{
|
||||
selector: 'aio-file-not-found-search',
|
||||
@ -25,25 +25,29 @@ export const ELEMENT_MODULE_PATHS_AS_ROUTES = [
|
||||
loadChildren: './resource/resource-list.module#ResourceListModule'
|
||||
},
|
||||
{
|
||||
selector: 'current-location',
|
||||
loadChildren: './current-location/current-location.module#CurrentLocationModule'
|
||||
},
|
||||
{
|
||||
selector: 'aio-contributor-list',
|
||||
loadChildren: './contributor/contributor-list.module#ContributorListModule'
|
||||
},
|
||||
{
|
||||
selector: 'code-tabs',
|
||||
loadChildren: './code/code-tabs.module#CodeTabsModule'
|
||||
selector: 'aio-toc',
|
||||
loadChildren: './toc/toc.module#TocModule'
|
||||
},
|
||||
{
|
||||
selector: 'code-example',
|
||||
loadChildren: './code/code-example.module#CodeExampleModule'
|
||||
},
|
||||
{
|
||||
selector: 'code-tabs',
|
||||
loadChildren: './code/code-tabs.module#CodeTabsModule'
|
||||
},
|
||||
{
|
||||
selector: 'current-location',
|
||||
loadChildren: './current-location/current-location.module#CurrentLocationModule'
|
||||
},
|
||||
{
|
||||
selector: 'expandable-section',
|
||||
loadChildren: './expandable-section/expandable-section.module#ExpandableSectionModule'
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'live-example',
|
||||
loadChildren: './live-example/live-example.module#LiveExampleModule'
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -4,49 +4,19 @@ import {
|
||||
NgModuleRef,
|
||||
Type
|
||||
} from '@angular/core';
|
||||
import {TestBed, fakeAsync, tick} from '@angular/core/testing';
|
||||
import { TestBed, fakeAsync, flushMicrotasks } from '@angular/core/testing';
|
||||
|
||||
import { ElementsLoader } from './elements-loader';
|
||||
import { ELEMENT_MODULE_PATHS_TOKEN, WithCustomElementComponent } from './element-registry';
|
||||
|
||||
class FakeComponentFactory extends ComponentFactory<any> {
|
||||
selector: string;
|
||||
componentType: Type<any>;
|
||||
ngContentSelectors: string[];
|
||||
inputs = [{propName: this.identifyingInput, templateName: this.identifyingInput}];
|
||||
outputs = [];
|
||||
|
||||
constructor(private identifyingInput: string) { super(); }
|
||||
|
||||
create(injector: Injector,
|
||||
projectableNodes?: any[][],
|
||||
rootSelectorOrNode?: string | any,
|
||||
ngModule?: NgModuleRef<any>): ComponentRef<any> {
|
||||
return (jasmine.createSpy('ComponentRef') as any) as ComponentRef<any>;
|
||||
};
|
||||
interface Deferred {
|
||||
resolve(): void;
|
||||
reject(err: any): void;
|
||||
}
|
||||
|
||||
const FAKE_COMPONENT_FACTORIES = new Map([
|
||||
['element-a-module-path', new FakeComponentFactory('element-a-input')],
|
||||
['element-b-module-path', new FakeComponentFactory('element-b-input')],
|
||||
]);
|
||||
|
||||
describe('ElementsLoader', () => {
|
||||
let elementsLoader: ElementsLoader;
|
||||
let actualCustomElementsDefine;
|
||||
let fakeCustomElementsDefine;
|
||||
|
||||
// ElementsLoader uses the window's customElements API. Provide a fake for this test.
|
||||
beforeEach(() => {
|
||||
actualCustomElementsDefine = window.customElements.define;
|
||||
|
||||
fakeCustomElementsDefine = jasmine.createSpy('define');
|
||||
|
||||
window.customElements.define = fakeCustomElementsDefine;
|
||||
});
|
||||
afterEach(() => {
|
||||
window.customElements.define = actualCustomElementsDefine;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
const injector = TestBed.configureTestingModule({
|
||||
@ -63,63 +33,196 @@ describe('ElementsLoader', () => {
|
||||
elementsLoader = injector.get(ElementsLoader);
|
||||
});
|
||||
|
||||
it('should be able to register an element', fakeAsync(() => {
|
||||
// Verify that the elements loader considered `element-a-selector` to be unregistered.
|
||||
expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeTruthy();
|
||||
describe('loadContainedCustomElements()', () => {
|
||||
let loadCustomElementSpy: jasmine.Spy;
|
||||
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `<element-a-selector></element-a-selector>`;
|
||||
beforeEach(() => loadCustomElementSpy = spyOn(elementsLoader, 'loadCustomElement'));
|
||||
|
||||
elementsLoader.loadContainingCustomElements(hostEl);
|
||||
tick();
|
||||
it('should attempt to load and register all contained elements', fakeAsync(() => {
|
||||
expect(loadCustomElementSpy).not.toHaveBeenCalled();
|
||||
|
||||
const defineArgs = fakeCustomElementsDefine.calls.argsFor(0);
|
||||
expect(defineArgs[0]).toBe('element-a-selector');
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-a-selector></element-a-selector>
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
|
||||
// Verify the right component was loaded/created
|
||||
expect(defineArgs[1].observedAttributes[0]).toBe('element-a-input');
|
||||
elementsLoader.loadContainedCustomElements(hostEl);
|
||||
flushMicrotasks();
|
||||
|
||||
expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeFalsy();
|
||||
}));
|
||||
expect(loadCustomElementSpy).toHaveBeenCalledTimes(2);
|
||||
expect(loadCustomElementSpy).toHaveBeenCalledWith('element-a-selector');
|
||||
expect(loadCustomElementSpy).toHaveBeenCalledWith('element-b-selector');
|
||||
}));
|
||||
|
||||
it('should be able to register multiple elements', fakeAsync(() => {
|
||||
// Verify that the elements loader considered `element-a-selector` to be unregistered.
|
||||
expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeTruthy();
|
||||
it('should attempt to load and register only contained elements', fakeAsync(() => {
|
||||
expect(loadCustomElementSpy).not.toHaveBeenCalled();
|
||||
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-a-selector></element-a-selector>
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
|
||||
elementsLoader.loadContainingCustomElements(hostEl);
|
||||
tick();
|
||||
elementsLoader.loadContainedCustomElements(hostEl);
|
||||
flushMicrotasks();
|
||||
|
||||
const defineElementA = fakeCustomElementsDefine.calls.argsFor(0);
|
||||
expect(defineElementA[0]).toBe('element-a-selector');
|
||||
expect(defineElementA[1].observedAttributes[0]).toBe('element-a-input');
|
||||
expect(elementsLoader.elementsToLoad.has('element-a-selector')).toBeFalsy();
|
||||
expect(loadCustomElementSpy).toHaveBeenCalledTimes(1);
|
||||
expect(loadCustomElementSpy).toHaveBeenCalledWith('element-b-selector');
|
||||
}));
|
||||
|
||||
const defineElementB = fakeCustomElementsDefine.calls.argsFor(1);
|
||||
expect(defineElementB[0]).toBe('element-b-selector');
|
||||
expect(defineElementB[1].observedAttributes[0]).toBe('element-b-input');
|
||||
expect(elementsLoader.elementsToLoad.has('element-b-selector')).toBeFalsy();
|
||||
}));
|
||||
it('should wait for all contained elements to load and register', fakeAsync(() => {
|
||||
const deferreds = returnPromisesFromSpy(loadCustomElementSpy);
|
||||
|
||||
it('should only register an element one time', fakeAsync(() => {
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `<element-a-selector></element-a-selector>`;
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-a-selector></element-a-selector>
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
|
||||
elementsLoader.loadContainingCustomElements(hostEl);
|
||||
tick(); // Tick for the module factory loader's async `load` function
|
||||
const log: any[] = [];
|
||||
elementsLoader.loadContainedCustomElements(hostEl).subscribe(
|
||||
v => log.push(`emitted: ${v}`),
|
||||
e => log.push(`errored: ${e}`),
|
||||
() => log.push('completed'),
|
||||
);
|
||||
|
||||
// Call again to to check how many times customElements.define was called.
|
||||
elementsLoader.loadContainingCustomElements(hostEl);
|
||||
tick(); // Tick for the module factory loader's async `load` function
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual([]);
|
||||
|
||||
// Should have only been called once, since the second load would not query for element-a
|
||||
expect(window.customElements.define).toHaveBeenCalledTimes(1);
|
||||
}));
|
||||
deferreds[0].resolve();
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual([]);
|
||||
|
||||
deferreds[1].resolve();
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual(['emitted: undefined', 'completed']);
|
||||
}));
|
||||
|
||||
it('should fail if any of the contained elements fails to load and register', fakeAsync(() => {
|
||||
const deferreds = returnPromisesFromSpy(loadCustomElementSpy);
|
||||
|
||||
const hostEl = document.createElement('div');
|
||||
hostEl.innerHTML = `
|
||||
<element-a-selector></element-a-selector>
|
||||
<element-b-selector></element-b-selector>
|
||||
`;
|
||||
|
||||
const log: any[] = [];
|
||||
elementsLoader.loadContainedCustomElements(hostEl).subscribe(
|
||||
v => log.push(`emitted: ${v}`),
|
||||
e => log.push(`errored: ${e}`),
|
||||
() => log.push('completed'),
|
||||
);
|
||||
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual([]);
|
||||
|
||||
deferreds[0].resolve();
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual([]);
|
||||
|
||||
deferreds[1].reject('foo');
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual(['errored: foo']);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('loadCustomElement()', () => {
|
||||
let definedSpy: jasmine.Spy;
|
||||
let whenDefinedSpy: jasmine.Spy;
|
||||
let whenDefinedDeferreds: Deferred[];
|
||||
|
||||
beforeEach(() => {
|
||||
// `loadCustomElement()` uses the `window.customElements` API. Provide mocks for these tests.
|
||||
definedSpy = spyOn(window.customElements, 'define');
|
||||
whenDefinedSpy = spyOn(window.customElements, 'whenDefined');
|
||||
whenDefinedDeferreds = returnPromisesFromSpy(whenDefinedSpy);
|
||||
});
|
||||
|
||||
it('should be able to load and register an element', fakeAsync(() => {
|
||||
elementsLoader.loadCustomElement('element-a-selector');
|
||||
flushMicrotasks();
|
||||
|
||||
expect(definedSpy).toHaveBeenCalledTimes(1);
|
||||
expect(definedSpy).toHaveBeenCalledWith('element-a-selector', jasmine.any(Function));
|
||||
|
||||
// Verify the right component was loaded/registered.
|
||||
const Ctor = definedSpy.calls.argsFor(0)[1];
|
||||
expect(Ctor.observedAttributes).toEqual(['element-a-module-path']);
|
||||
}));
|
||||
|
||||
it('should wait until the element is defined', fakeAsync(() => {
|
||||
let state = 'pending';
|
||||
elementsLoader.loadCustomElement('element-b-selector').then(() => state = 'resolved');
|
||||
flushMicrotasks();
|
||||
|
||||
expect(state).toBe('pending');
|
||||
expect(whenDefinedSpy).toHaveBeenCalledTimes(1);
|
||||
expect(whenDefinedSpy).toHaveBeenCalledWith('element-b-selector');
|
||||
|
||||
whenDefinedDeferreds[0].resolve();
|
||||
flushMicrotasks();
|
||||
expect(state).toBe('resolved');
|
||||
}));
|
||||
|
||||
it('should not load and register the same element more than once', fakeAsync(() => {
|
||||
elementsLoader.loadCustomElement('element-a-selector');
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
definedSpy.calls.reset();
|
||||
|
||||
// While loading/registering is still in progress:
|
||||
elementsLoader.loadCustomElement('element-a-selector');
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).not.toHaveBeenCalled();
|
||||
|
||||
definedSpy.calls.reset();
|
||||
whenDefinedDeferreds[0].resolve();
|
||||
|
||||
// Once loading/registering is already completed:
|
||||
let state = 'pending';
|
||||
elementsLoader.loadCustomElement('element-a-selector').then(() => state = 'resolved');
|
||||
flushMicrotasks();
|
||||
expect(state).toBe('resolved');
|
||||
expect(definedSpy).not.toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should fail if defining the the custom element fails', fakeAsync(() => {
|
||||
let state = 'pending';
|
||||
elementsLoader.loadCustomElement('element-b-selector').catch(e => state = `rejected: ${e}`);
|
||||
flushMicrotasks();
|
||||
expect(state).toBe('pending');
|
||||
|
||||
whenDefinedDeferreds[0].reject('foo');
|
||||
flushMicrotasks();
|
||||
expect(state).toBe('rejected: foo');
|
||||
}));
|
||||
|
||||
it('should be able to load and register an element again if previous attempt failed',
|
||||
fakeAsync(() => {
|
||||
elementsLoader.loadCustomElement('element-a-selector');
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
definedSpy.calls.reset();
|
||||
|
||||
// While loading/registering is still in progress:
|
||||
elementsLoader.loadCustomElement('element-a-selector').catch(() => undefined);
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).not.toHaveBeenCalled();
|
||||
|
||||
whenDefinedDeferreds[0].reject('foo');
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).not.toHaveBeenCalled();
|
||||
|
||||
// Once loading/registering has already failed:
|
||||
elementsLoader.loadCustomElement('element-a-selector');
|
||||
flushMicrotasks();
|
||||
expect(definedSpy).toHaveBeenCalledTimes(1);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// TEST CLASSES/HELPERS
|
||||
@ -128,11 +231,28 @@ class FakeCustomElementModule implements WithCustomElementComponent {
|
||||
customElementComponent: Type<any>;
|
||||
}
|
||||
|
||||
class FakeComponentFactory extends ComponentFactory<any> {
|
||||
selector: string;
|
||||
componentType: Type<any>;
|
||||
ngContentSelectors: string[];
|
||||
inputs = [{propName: this.identifyingInput, templateName: this.identifyingInput}];
|
||||
outputs = [];
|
||||
|
||||
constructor(private identifyingInput: string) { super(); }
|
||||
|
||||
create(injector: Injector,
|
||||
projectableNodes?: any[][],
|
||||
rootSelectorOrNode?: string | any,
|
||||
ngModule?: NgModuleRef<any>): ComponentRef<any> {
|
||||
return jasmine.createSpy('ComponentRef') as any;
|
||||
};
|
||||
}
|
||||
|
||||
class FakeComponentFactoryResolver extends ComponentFactoryResolver {
|
||||
constructor(private modulePath) { super(); }
|
||||
|
||||
resolveComponentFactory(component: Type<any>): ComponentFactory<any> {
|
||||
return FAKE_COMPONENT_FACTORIES.get(this.modulePath)!;
|
||||
return new FakeComponentFactory(this.modulePath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,3 +288,9 @@ class FakeModuleFactoryLoader extends NgModuleFactoryLoader {
|
||||
return Promise.resolve(fakeModuleFactory);
|
||||
}
|
||||
}
|
||||
|
||||
function returnPromisesFromSpy(spy: jasmine.Spy): Deferred[] {
|
||||
const deferreds: Deferred[] = [];
|
||||
spy.and.callFake(() => new Promise((resolve, reject) => deferreds.push({resolve, reject})));
|
||||
return deferreds;
|
||||
}
|
||||
|
@ -11,11 +11,13 @@ import { createCustomElement } from '@angular/elements';
|
||||
@Injectable()
|
||||
export class ElementsLoader {
|
||||
/** Map of unregistered custom elements and their respective module paths to load. */
|
||||
elementsToLoad: Map<string, string>;
|
||||
private elementsToLoad: Map<string, string>;
|
||||
/** Map of custom elements that are in the process of being loaded and registered. */
|
||||
private elementsLoading = new Map<string, Promise<void>>();
|
||||
|
||||
constructor(private moduleFactoryLoader: NgModuleFactoryLoader,
|
||||
private moduleRef: NgModuleRef<any>,
|
||||
@Inject(ELEMENT_MODULE_PATHS_TOKEN) elementModulePaths) {
|
||||
@Inject(ELEMENT_MODULE_PATHS_TOKEN) elementModulePaths: Map<string, string>) {
|
||||
this.elementsToLoad = new Map(elementModulePaths);
|
||||
}
|
||||
|
||||
@ -24,31 +26,57 @@ export class ElementsLoader {
|
||||
* the browser. Custom elements that are registered will be removed from the list of unregistered
|
||||
* elements so that they will not be queried in subsequent calls.
|
||||
*/
|
||||
loadContainingCustomElements(element: HTMLElement): Observable<void> {
|
||||
const selectors: any[] = Array.from(this.elementsToLoad.keys())
|
||||
loadContainedCustomElements(element: HTMLElement): Observable<void> {
|
||||
const unregisteredSelectors = Array.from(this.elementsToLoad.keys())
|
||||
.filter(s => element.querySelector(s));
|
||||
|
||||
if (!selectors.length) { return of(undefined); }
|
||||
if (!unregisteredSelectors.length) { return of(undefined); }
|
||||
|
||||
// Returns observable that completes when all discovered elements have been registered.
|
||||
return fromPromise(Promise.all(selectors.map(s => this.register(s))).then(result => undefined));
|
||||
const allRegistered = Promise.all(unregisteredSelectors.map(s => this.loadCustomElement(s)));
|
||||
return fromPromise(allRegistered.then(() => undefined));
|
||||
}
|
||||
|
||||
/** Registers the custom element defined on the WithCustomElement module factory. */
|
||||
private register(selector: string) {
|
||||
const modulePath = this.elementsToLoad.get(selector)!;
|
||||
return this.moduleFactoryLoader.load(modulePath).then(elementModuleFactory => {
|
||||
if (!this.elementsToLoad.has(selector)) { return; }
|
||||
/** Loads and registers the custom element defined on the `WithCustomElement` module factory. */
|
||||
loadCustomElement(selector: string): Promise<void> {
|
||||
if (this.elementsLoading.has(selector)) {
|
||||
// The custom element is in the process of being loaded and registered.
|
||||
return this.elementsLoading.get(selector)!;
|
||||
}
|
||||
|
||||
const elementModuleRef = elementModuleFactory.create(this.moduleRef.injector);
|
||||
const CustomElementComponent = elementModuleRef.instance.customElementComponent;
|
||||
const CustomElement =
|
||||
createCustomElement(CustomElementComponent, {injector: elementModuleRef.injector});
|
||||
if (this.elementsToLoad.has(selector)) {
|
||||
// Load and register the custom element (for the first time).
|
||||
const modulePath = this.elementsToLoad.get(selector)!;
|
||||
const loadedAndRegistered = this.moduleFactoryLoader
|
||||
.load(modulePath)
|
||||
.then(elementModuleFactory => {
|
||||
const elementModuleRef = elementModuleFactory.create(this.moduleRef.injector);
|
||||
const injector = elementModuleRef.injector;
|
||||
const CustomElementComponent = elementModuleRef.instance.customElementComponent;
|
||||
const CustomElement = createCustomElement(CustomElementComponent, {injector});
|
||||
|
||||
customElements!.define(selector, CustomElement);
|
||||
this.elementsToLoad.delete(selector);
|
||||
customElements!.define(selector, CustomElement);
|
||||
return customElements.whenDefined(selector);
|
||||
})
|
||||
.then(() => {
|
||||
// The custom element has been successfully loaded and registered.
|
||||
// Remove from `elementsLoading` and `elementsToLoad`.
|
||||
this.elementsLoading.delete(selector);
|
||||
this.elementsToLoad.delete(selector);
|
||||
})
|
||||
.catch(err => {
|
||||
// The custom element has failed to load and register.
|
||||
// Remove from `elementsLoading`.
|
||||
// (Do not remove from `elementsToLoad` in case it was a temporary error.)
|
||||
this.elementsLoading.delete(selector);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
|
||||
return customElements.whenDefined(selector);
|
||||
});
|
||||
this.elementsLoading.set(selector, loadedAndRegistered);
|
||||
return loadedAndRegistered;
|
||||
}
|
||||
|
||||
// The custom element has already been loaded and registered.
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,67 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { MockLogger } from 'testing/logger.service';
|
||||
import { LazyCustomElementComponent } from './lazy-custom-element.component';
|
||||
import { ElementsLoader } from './elements-loader';
|
||||
|
||||
describe('LazyCustomElementComponent', () => {
|
||||
let mockElementsLoader: jasmine.SpyObj<ElementsLoader>;
|
||||
let mockLogger: MockLogger;
|
||||
let fixture: ComponentFixture<LazyCustomElementComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockElementsLoader = jasmine.createSpyObj<ElementsLoader>('ElementsLoader', [
|
||||
'loadContainedCustomElements',
|
||||
'loadCustomElement',
|
||||
]);
|
||||
|
||||
const injector = TestBed.configureTestingModule({
|
||||
declarations: [ LazyCustomElementComponent ],
|
||||
providers: [
|
||||
{ provide: ElementsLoader, useValue: mockElementsLoader },
|
||||
{ provide: Logger, useClass: MockLogger },
|
||||
],
|
||||
});
|
||||
|
||||
mockLogger = injector.get(Logger);
|
||||
fixture = TestBed.createComponent(LazyCustomElementComponent);
|
||||
});
|
||||
|
||||
it('should set the HTML content based on the selector', () => {
|
||||
const elem = fixture.nativeElement;
|
||||
|
||||
expect(elem.innerHTML).toBe('');
|
||||
|
||||
fixture.componentInstance.selector = 'foo-bar';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(elem.innerHTML).toBe('<foo-bar></foo-bar>');
|
||||
});
|
||||
|
||||
it('should load the specified custom element', () => {
|
||||
expect(mockElementsLoader.loadCustomElement).not.toHaveBeenCalled();
|
||||
|
||||
fixture.componentInstance.selector = 'foo-bar';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(mockElementsLoader.loadCustomElement).toHaveBeenCalledWith('foo-bar');
|
||||
});
|
||||
|
||||
it('should log an error (and abort) if the selector is empty', () => {
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(mockElementsLoader.loadCustomElement).not.toHaveBeenCalled();
|
||||
expect(mockLogger.output.error).toEqual([[jasmine.any(Error)]]);
|
||||
expect(mockLogger.output.error[0][0].message).toBe('Invalid selector for \'aio-lazy-ce\': ');
|
||||
});
|
||||
|
||||
it('should log an error (and abort) if the selector is invalid', () => {
|
||||
fixture.componentInstance.selector = 'foo-bar><script></script><foo-bar';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(mockElementsLoader.loadCustomElement).not.toHaveBeenCalled();
|
||||
expect(mockLogger.output.error).toEqual([[jasmine.any(Error)]]);
|
||||
expect(mockLogger.output.error[0][0].message).toBe(
|
||||
'Invalid selector for \'aio-lazy-ce\': foo-bar><script></script><foo-bar');
|
||||
});
|
||||
});
|
27
aio/src/app/custom-elements/lazy-custom-element.component.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { Component, ElementRef, Input, OnInit } from '@angular/core';
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { ElementsLoader } from './elements-loader';
|
||||
|
||||
@Component({
|
||||
selector: 'aio-lazy-ce',
|
||||
template: '',
|
||||
})
|
||||
export class LazyCustomElementComponent implements OnInit {
|
||||
@Input() selector = '';
|
||||
|
||||
constructor(
|
||||
private elementRef: ElementRef,
|
||||
private elementsLoader: ElementsLoader,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.selector || /[^\w-]/.test(this.selector)) {
|
||||
this.logger.error(new Error(`Invalid selector for 'aio-lazy-ce': ${this.selector}`));
|
||||
return;
|
||||
}
|
||||
|
||||
this.elementRef.nativeElement.innerHTML = `<${this.selector}></${this.selector}>`;
|
||||
this.elementsLoader.loadCustomElement(this.selector);
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
<!-- Content projection is used to get the content HTML provided to the component. -->
|
||||
<span #content style="display: none"><ng-content></ng-content></span>
|
||||
|
||||
<span [ngSwitch]="mode">
|
||||
<span *ngSwitchCase="'disabled'">{{title}} <em>(not available on this device)</em></span>
|
||||
<span *ngSwitchCase="'embedded'">
|
||||
<div title="{{title}}">
|
||||
<aio-embedded-stackblitz [src]="stackblitz"></aio-embedded-stackblitz>
|
||||
@ -14,7 +16,7 @@
|
||||
<span *ngSwitchDefault>
|
||||
<a [href]="stackblitz" target="_blank" title="{{title}}">{{title}}</a>
|
||||
<span *ngIf="enableDownload">
|
||||
/ <a [href]="zip" download title="Download example">download example</a>
|
||||
/ <a [href]="zip" download title="Download example">download example</a>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -12,7 +12,6 @@ describe('LiveExampleComponent', () => {
|
||||
let liveExampleComponent: LiveExampleComponent;
|
||||
let fixture: ComponentFixture<HostComponent>;
|
||||
let testPath: string;
|
||||
let liveExampleContent: string|null;
|
||||
|
||||
//////// test helpers ////////
|
||||
|
||||
@ -41,12 +40,7 @@ describe('LiveExampleComponent', () => {
|
||||
liveExampleDe = fixture.debugElement.children[0];
|
||||
liveExampleComponent = liveExampleDe.componentInstance;
|
||||
|
||||
// Copy the LiveExample's innerHTML (content)
|
||||
// into the `liveExampleContent` property as the DocViewer does
|
||||
liveExampleDe.nativeElement.liveExampleContent = liveExampleContent;
|
||||
|
||||
fixture.detectChanges();
|
||||
liveExampleComponent.onResize(1033); // wide by default
|
||||
// Trigger `ngAfterContentInit()`.
|
||||
fixture.detectChanges();
|
||||
|
||||
testFn();
|
||||
@ -64,7 +58,6 @@ describe('LiveExampleComponent', () => {
|
||||
.overrideComponent(EmbeddedStackblitzComponent, {set: {template: 'NO IFRAME'}});
|
||||
|
||||
testPath = defaultTestPath;
|
||||
liveExampleContent = null;
|
||||
});
|
||||
|
||||
describe('when not embedded', () => {
|
||||
@ -103,15 +96,6 @@ describe('LiveExampleComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have expected flat-style stackblitz when has `flat-style`', () => {
|
||||
testPath = '/tutorial/toh-pt1';
|
||||
setHostTemplate('<live-example flat-style></live-example>');
|
||||
testComponent(() => {
|
||||
// The file should be "stackblitz.html", not "stackblitz.html"
|
||||
expect(getLiveExampleAnchor().href).toContain('/stackblitz.html');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have expected stackblitz & download hrefs when has example directory (name)', () => {
|
||||
testPath = '/guide/somewhere';
|
||||
setHostTemplate('<live-example name="toh-pt1"></live-example>');
|
||||
@ -150,15 +134,7 @@ describe('LiveExampleComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should be flat style when flat-style requested', () => {
|
||||
setHostTemplate('<live-example flat-style></live-example>');
|
||||
testComponent(() => {
|
||||
const hrefs = getHrefs();
|
||||
expect(hrefs[0]).toContain(defaultTestPath + '/stackblitz.html');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not have a download link when `noDownload` atty present', () => {
|
||||
it('should not have a download link when `noDownload` attr present', () => {
|
||||
setHostTemplate('<live-example noDownload></live-example>');
|
||||
testComponent(() => {
|
||||
const hrefs = getHrefs();
|
||||
@ -167,7 +143,7 @@ describe('LiveExampleComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should only have a download link when `downloadOnly` atty present', () => {
|
||||
it('should only have a download link when `downloadOnly` attr present', () => {
|
||||
setHostTemplate('<live-example downloadOnly>download this</live-example>');
|
||||
testComponent(() => {
|
||||
const hrefs = getHrefs();
|
||||
@ -196,12 +172,12 @@ describe('LiveExampleComponent', () => {
|
||||
});
|
||||
|
||||
it('should add title from <live-example> body', () => {
|
||||
liveExampleContent = 'The Greatest Example';
|
||||
setHostTemplate('<live-example title="ignore this title"></live-example>');
|
||||
const expectedTitle = 'The Greatest Example';
|
||||
setHostTemplate(`<live-example title="ignore this title">${expectedTitle}</live-example>`);
|
||||
testComponent(() => {
|
||||
const anchor = getLiveExampleAnchor();
|
||||
expect(anchor.textContent).toBe(liveExampleContent, 'anchor content');
|
||||
expect(anchor.getAttribute('title')).toBe(liveExampleContent, 'title');
|
||||
expect(anchor.textContent).toBe(expectedTitle, 'anchor content');
|
||||
expect(anchor.getAttribute('title')).toBe(expectedTitle, 'title');
|
||||
});
|
||||
});
|
||||
|
||||
@ -251,27 +227,4 @@ describe('LiveExampleComponent', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when narrow display (mobile)', () => {
|
||||
|
||||
it('should be embedded style when no style defined', () => {
|
||||
setHostTemplate('<live-example></live-example>');
|
||||
testComponent(() => {
|
||||
liveExampleComponent.onResize(600); // narrow
|
||||
fixture.detectChanges();
|
||||
const hrefs = getHrefs();
|
||||
expect(hrefs[0]).toContain(defaultTestPath + '/stackblitz.html');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be embedded style even when flat-style requested', () => {
|
||||
setHostTemplate('<live-example flat-style></live-example>');
|
||||
testComponent(() => {
|
||||
liveExampleComponent.onResize(600); // narrow
|
||||
fixture.detectChanges();
|
||||
const hrefs = getHrefs();
|
||||
expect(hrefs[0]).toContain(defaultTestPath + '/stackblitz.html');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,130 +1,131 @@
|
||||
/* tslint:disable component-selector */
|
||||
import { Component, ElementRef, HostListener, Input, OnInit, AfterViewInit, ViewChild } from '@angular/core';
|
||||
import { AfterContentInit, AfterViewInit, Component, ElementRef, Input, ViewChild } from '@angular/core';
|
||||
import { Location } from '@angular/common';
|
||||
import { CONTENT_URL_PREFIX } from 'app/documents/document.service';
|
||||
import { AttrMap, boolFromValue, getAttrs, getAttrValue } from 'app/shared/attribute-utils';
|
||||
|
||||
import { boolFromValue, getAttrs, getAttrValue } from 'app/shared/attribute-utils';
|
||||
|
||||
const liveExampleBase = CONTENT_URL_PREFIX + 'live-examples/';
|
||||
const zipBase = CONTENT_URL_PREFIX + 'zips/';
|
||||
const LIVE_EXAMPLE_BASE = CONTENT_URL_PREFIX + 'live-examples/';
|
||||
const ZIP_BASE = CONTENT_URL_PREFIX + 'zips/';
|
||||
|
||||
/**
|
||||
* Angular.io Live Example Embedded Component
|
||||
*
|
||||
* Renders a link to a live/host example of the doc page.
|
||||
*
|
||||
* All attributes and the text content are optional
|
||||
*
|
||||
* Usage:
|
||||
* <live-example
|
||||
* [name="..."] // name of the example directory
|
||||
* [stackblitz="...""] // name of the stackblitz file (becomes part of zip file name as well)
|
||||
* [embedded] // embed the stackblitz in the doc page, else display in new browser tab (default)
|
||||
* [noDownload] // no downloadable zip option
|
||||
* [downloadOnly] // just the zip
|
||||
* [title="..."]> // text for live example link and tooltip
|
||||
* text // higher precedence way to specify text for live example link and tooltip
|
||||
* </live-example>
|
||||
* Example:
|
||||
* <p>Run <live-example>Try the live example</live-example></p>.
|
||||
* // ~/resources/live-examples/{page}/stackblitz.json
|
||||
*
|
||||
* <p>Run <live-example name="toh-pt1">this example</live-example></p>.
|
||||
* // ~/resources/live-examples/toh-pt1/stackblitz.json
|
||||
*
|
||||
* // Link to the default stackblitz in the toh-pt1 sample
|
||||
* // The title overrides default ("live example") with "Tour of Heroes - Part 1"
|
||||
* <p>Run <live-example name="toh-pt1" title="Tour of Heroes - Part 1"></live-example></p>.
|
||||
* // ~/resources/live-examples/toh-pt1/stackblitz.json
|
||||
*
|
||||
* <p>Run <live-example stackblitz="minimal"></live-example></p>.
|
||||
* // ~/resources/live-examples/{page}/minimal.stackblitz.json
|
||||
*
|
||||
* // Embed the current page's default stackblitz
|
||||
* // Text within tag is "live example"
|
||||
* // No title (no tooltip)
|
||||
* <live-example embedded title=""></live-example>
|
||||
* // ~/resources/live-examples/{page}/stackblitz.json
|
||||
*
|
||||
* // Displays within the document page as an embedded style stackblitz editor
|
||||
* <live-example name="toh-pt1" embedded stackblitz="minimal">Tour of Heroes - Part 1</live-example>
|
||||
* // ~/resources/live-examples/toh-pt1/minimal.stackblitz.json
|
||||
*/
|
||||
* Angular.io Live Example Embedded Component
|
||||
*
|
||||
* Renders a link to a live/host example of the doc page.
|
||||
*
|
||||
* All attributes and the text content are optional
|
||||
*
|
||||
* Usage:
|
||||
* <live-example
|
||||
* [name="..."] // name of the example directory
|
||||
* [stackblitz="...""] // name of the stackblitz file (becomes part of zip file name as well)
|
||||
* [embedded] // embed the stackblitz in the doc page, else display in new browser tab (default)
|
||||
* [noDownload] // no downloadable zip option
|
||||
* [downloadOnly] // just the zip
|
||||
* [title="..."]> // text for live example link and tooltip
|
||||
* text // higher precedence way to specify text for live example link and tooltip
|
||||
* </live-example>
|
||||
* Example:
|
||||
* <p>Run <live-example>Try the live example</live-example></p>.
|
||||
* // ~/resources/live-examples/{page}/stackblitz.json
|
||||
*
|
||||
* <p>Run <live-example name="toh-pt1">this example</live-example></p>.
|
||||
* // ~/resources/live-examples/toh-pt1/stackblitz.json
|
||||
*
|
||||
* // Link to the default stackblitz in the toh-pt1 sample
|
||||
* // The title overrides default ("live example") with "Tour of Heroes - Part 1"
|
||||
* <p>Run <live-example name="toh-pt1" title="Tour of Heroes - Part 1"></live-example></p>.
|
||||
* // ~/resources/live-examples/toh-pt1/stackblitz.json
|
||||
*
|
||||
* <p>Run <live-example stackblitz="minimal"></live-example></p>.
|
||||
* // ~/resources/live-examples/{page}/minimal.stackblitz.json
|
||||
*
|
||||
* // Embed the current page's default stackblitz
|
||||
* // Text within tag is "live example"
|
||||
* // No title (no tooltip)
|
||||
* <live-example embedded title=""></live-example>
|
||||
* // ~/resources/live-examples/{page}/stackblitz.json
|
||||
*
|
||||
* // Displays within the document page as an embedded style stackblitz editor
|
||||
* <live-example name="toh-pt1" embedded stackblitz="minimal">Tour of Heroes - Part 1</live-example>
|
||||
* // ~/resources/live-examples/toh-pt1/minimal.stackblitz.json
|
||||
*/
|
||||
@Component({
|
||||
selector: 'live-example',
|
||||
templateUrl: 'live-example.component.html'
|
||||
})
|
||||
export class LiveExampleComponent implements OnInit {
|
||||
export class LiveExampleComponent implements AfterContentInit {
|
||||
|
||||
// Will force to embedded-style when viewport width is narrow
|
||||
// "narrow" value was picked based on phone dimensions from http://screensiz.es/phone
|
||||
readonly narrowWidth = 1000;
|
||||
|
||||
attrs: any;
|
||||
enableDownload = true;
|
||||
exampleDir: string;
|
||||
isEmbedded = false;
|
||||
mode = 'disabled';
|
||||
stackblitz: string;
|
||||
stackblitzName: string;
|
||||
readonly mode: 'default' | 'embedded' | 'downloadOnly';
|
||||
readonly enableDownload: boolean;
|
||||
readonly stackblitz: string;
|
||||
readonly zip: string;
|
||||
title: string;
|
||||
zip: string;
|
||||
zipName: string;
|
||||
|
||||
constructor(
|
||||
private elementRef: ElementRef,
|
||||
location: Location ) {
|
||||
@ViewChild('content')
|
||||
private content: ElementRef;
|
||||
|
||||
const attrs = this.attrs = getAttrs(this.elementRef);
|
||||
let exampleDir = attrs.name;
|
||||
constructor(elementRef: ElementRef, location: Location) {
|
||||
const attrs = getAttrs(elementRef);
|
||||
const exampleDir = this.getExampleDir(attrs, location.path(false));
|
||||
const stackblitzName = this.getStackblitzName(attrs);
|
||||
|
||||
this.mode = this.getMode(attrs);
|
||||
this.enableDownload = this.getEnableDownload(attrs);
|
||||
this.stackblitz = this.getStackblitz(exampleDir, stackblitzName, this.mode === 'embedded');
|
||||
this.zip = this.getZip(exampleDir, stackblitzName);
|
||||
this.title = this.getTitle(attrs);
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
// Angular will sanitize this title when displayed, so it should be plain text.
|
||||
const textContent = this.content.nativeElement.textContent.trim();
|
||||
if (textContent) {
|
||||
this.title = textContent;
|
||||
}
|
||||
}
|
||||
|
||||
private getEnableDownload(attrs: AttrMap) {
|
||||
const downloadDisabled = boolFromValue(getAttrValue(attrs, 'noDownload'));
|
||||
return !downloadDisabled;
|
||||
}
|
||||
|
||||
private getExampleDir(attrs: AttrMap, path: string) {
|
||||
let exampleDir = getAttrValue(attrs, 'name');
|
||||
if (!exampleDir) {
|
||||
// take last segment, excluding hash fragment and query params
|
||||
exampleDir = (location.path(false).match(/[^\/?\#]+(?=\/?(?:$|\#|\?))/) || [])[0];
|
||||
}
|
||||
this.exampleDir = exampleDir.trim();
|
||||
this.zipName = exampleDir.indexOf('/') === -1 ? this.exampleDir : exampleDir.split('/')[0];
|
||||
this.stackblitzName = attrs.stackblitz ? attrs.stackblitz.trim() + '.' : '';
|
||||
this.zip = `${zipBase}${exampleDir}/${this.stackblitzName}${this.zipName}.zip`;
|
||||
|
||||
this.enableDownload = !boolFromValue(getAttrValue(attrs, 'nodownload'));
|
||||
|
||||
if (boolFromValue(getAttrValue(attrs, 'downloadonly'))) {
|
||||
this.mode = 'downloadOnly';
|
||||
// Take the last path segment, excluding query params and hash fragment.
|
||||
const match = path.match(/[^/?#]+(?=\/?(?:\?|#|$))/);
|
||||
exampleDir = match ? match[0] : 'index';
|
||||
}
|
||||
return exampleDir.trim();
|
||||
}
|
||||
|
||||
calcStackblitzLink(width: number) {
|
||||
private getMode(this: LiveExampleComponent, attrs: AttrMap): typeof this.mode {
|
||||
const downloadOnly = boolFromValue(getAttrValue(attrs, 'downloadOnly'));
|
||||
const isEmbedded = boolFromValue(getAttrValue(attrs, 'embedded'));
|
||||
|
||||
const attrs = this.attrs;
|
||||
const exampleDir = this.exampleDir;
|
||||
let urlQuery = '';
|
||||
|
||||
this.mode = 'default'; // display in another browser tab by default
|
||||
|
||||
this.isEmbedded = boolFromValue(attrs.embedded);
|
||||
|
||||
if (this.isEmbedded) {
|
||||
this.mode = 'embedded'; // display embedded in the doc
|
||||
urlQuery = '?ctl=1';
|
||||
}
|
||||
|
||||
this.stackblitz = `${liveExampleBase}${exampleDir}/${this.stackblitzName}stackblitz.html${urlQuery}`;
|
||||
return downloadOnly ? 'downloadOnly'
|
||||
: isEmbedded ? 'embedded' :
|
||||
'default';
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// The `liveExampleContent` property is set by the DocViewer when it builds this component.
|
||||
// It is the original innerHTML of the host element.
|
||||
// Angular will sanitize this title when displayed so should be plain text.
|
||||
const title = this.elementRef.nativeElement.liveExampleContent;
|
||||
this.title = (title || this.attrs.title || 'live example').trim();
|
||||
this.onResize(window.innerWidth);
|
||||
private getStackblitz(exampleDir: string, stackblitzName: string, isEmbedded: boolean) {
|
||||
const urlQuery = isEmbedded ? '?ctl=1' : '';
|
||||
return `${LIVE_EXAMPLE_BASE}${exampleDir}/${stackblitzName}stackblitz.html${urlQuery}`;
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event.target.innerWidth'])
|
||||
onResize(width: number) {
|
||||
if (this.mode !== 'downloadOnly') {
|
||||
this.calcStackblitzLink(width);
|
||||
}
|
||||
private getStackblitzName(attrs: AttrMap) {
|
||||
const attrValue = (getAttrValue(attrs, 'stackblitz') || '').trim();
|
||||
return attrValue && `${attrValue}.`;
|
||||
}
|
||||
|
||||
private getTitle(attrs: AttrMap) {
|
||||
return (getAttrValue(attrs, 'title') || 'live example').trim();
|
||||
}
|
||||
|
||||
private getZip(exampleDir: string, stackblitzName: string) {
|
||||
const zipName = exampleDir.split('/')[0];
|
||||
return `${ZIP_BASE}${exampleDir}/${stackblitzName}${zipName}.zip`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,7 +137,7 @@ export class LiveExampleComponent implements OnInit {
|
||||
@Component({
|
||||
selector: 'aio-embedded-stackblitz',
|
||||
template: `<iframe #iframe frameborder="0" width="100%" height="100%"></iframe>`,
|
||||
styles: [ 'iframe { min-height: 400px; }']
|
||||
styles: [ 'iframe { min-height: 400px; }' ]
|
||||
})
|
||||
export class EmbeddedStackblitzComponent implements AfterViewInit {
|
||||
@Input() src: string;
|
||||
|
@ -4,8 +4,8 @@ import { By } from '@angular/platform-browser';
|
||||
import { asapScheduler as asap, BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { TocComponent } from './toc.component';
|
||||
import { TocItem, TocService } from 'app/shared/toc.service';
|
||||
import { TocComponent } from './toc.component';
|
||||
|
||||
describe('TocComponent', () => {
|
||||
let tocComponentDe: DebugElement;
|
14
aio/src/app/custom-elements/toc/toc.module.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { NgModule, Type } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { WithCustomElementComponent } from '../element-registry';
|
||||
import { TocComponent } from './toc.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [ CommonModule, MatIconModule ],
|
||||
declarations: [ TocComponent ],
|
||||
entryComponents: [ TocComponent ],
|
||||
})
|
||||
export class TocModule implements WithCustomElementComponent {
|
||||
customElementComponent: Type<any> = TocComponent;
|
||||
}
|
@ -300,7 +300,7 @@ describe('DocViewerComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
const elementsLoader = TestBed.get(ElementsLoader) as MockElementsLoader;
|
||||
loadElementsSpy = elementsLoader.loadContainingCustomElements.and.returnValue(of(undefined));
|
||||
loadElementsSpy = elementsLoader.loadContainedCustomElements.and.returnValue(of(undefined));
|
||||
prepareTitleAndTocSpy = spyOn(docViewer, 'prepareTitleAndToc');
|
||||
swapViewsSpy = spyOn(docViewer, 'swapViews').and.returnValue(of(undefined));
|
||||
});
|
||||
|
@ -136,7 +136,7 @@ export class DocViewerComponent implements OnDestroy {
|
||||
// and is considered to be safe.
|
||||
tap(() => this.nextViewContainer.innerHTML = doc.contents || ''),
|
||||
tap(() => addTitleAndToc = this.prepareTitleAndToc(this.nextViewContainer, doc.id)),
|
||||
switchMap(() => this.elementsLoader.loadContainingCustomElements(this.nextViewContainer)),
|
||||
switchMap(() => this.elementsLoader.loadContainedCustomElements(this.nextViewContainer)),
|
||||
tap(() => this.docReady.emit()),
|
||||
switchMap(() => this.swapViews(addTitleAndToc)),
|
||||
tap(() => this.docRendered.emit()),
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ElementRef } from '@angular/core';
|
||||
|
||||
import { getAttrs, getAttrValue, getBoolFromAttribute, boolFromValue } from './attribute-utils';
|
||||
import { AttrMap, getAttrs, getAttrValue, getBoolFromAttribute, boolFromValue } from './attribute-utils';
|
||||
|
||||
describe('Attribute Utilities', () => {
|
||||
let testEl: HTMLElement;
|
||||
@ -32,17 +32,17 @@ describe('Attribute Utilities', () => {
|
||||
});
|
||||
|
||||
describe('getAttrValue', () => {
|
||||
let attrMap: { [index: string]: string };
|
||||
let attrMap: AttrMap;
|
||||
|
||||
beforeEach(() => {
|
||||
attrMap = getAttrs(testEl);
|
||||
});
|
||||
|
||||
it('should return empty string value for attribute "a"', () => {
|
||||
it('should return empty string for attribute "a"', () => {
|
||||
expect(getAttrValue(attrMap, 'a')).toBe('');
|
||||
});
|
||||
|
||||
it('should return empty string value for attribute "A"', () => {
|
||||
it('should return empty string for attribute "A"', () => {
|
||||
expect(getAttrValue(attrMap, 'A')).toBe('');
|
||||
});
|
||||
|
||||
@ -50,7 +50,7 @@ describe('Attribute Utilities', () => {
|
||||
expect(getAttrValue(attrMap, 'b')).toBe('true');
|
||||
});
|
||||
|
||||
it('should return empty string value for attribute "d-E"', () => {
|
||||
it('should return empty string for attribute "d-E"', () => {
|
||||
expect(getAttrValue(attrMap, 'd-E')).toBe('');
|
||||
});
|
||||
|
||||
@ -68,12 +68,10 @@ describe('Attribute Utilities', () => {
|
||||
expect(getAttrValue(attrMap, ['d-e', 'd'])).toBe('');
|
||||
});
|
||||
|
||||
it('should return undefined value for non-existent attribute "x"', () => {
|
||||
it('should return undefined for non-existent attributes', () => {
|
||||
expect(getAttrValue(attrMap, 'x')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined if no argument', () => {
|
||||
expect(getAttrValue(attrMap)).toBeUndefined();
|
||||
expect(getAttrValue(attrMap, '')).toBeUndefined();
|
||||
expect(getAttrValue(attrMap, ['', 'x'])).toBeUndefined();
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -1,17 +1,19 @@
|
||||
// Utilities for processing HTML element attributes
|
||||
import { ElementRef } from '@angular/core';
|
||||
|
||||
interface StringMap { [index: string]: string; }
|
||||
export interface AttrMap {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attribute map from element or ElementRef `attributes`.
|
||||
* Attribute map keys are forced lowercase for case-insensitive lookup.
|
||||
* @param el The source of the attributes
|
||||
* @param el The source of the attributes.
|
||||
*/
|
||||
export function getAttrs(el: HTMLElement | ElementRef): StringMap {
|
||||
export function getAttrs(el: HTMLElement | ElementRef): AttrMap {
|
||||
const attrs: NamedNodeMap = el instanceof ElementRef ? el.nativeElement.attributes : el.attributes;
|
||||
const attrMap: StringMap = {};
|
||||
for (const attr of attrs as any /* cast due to https://github.com/Microsoft/TypeScript/issues/2695 */) {
|
||||
const attrMap: AttrMap = {};
|
||||
for (const attr of attrs as any as Attr[] /* cast due to https://github.com/Microsoft/TypeScript/issues/2695 */) {
|
||||
attrMap[attr.name.toLowerCase()] = attr.value;
|
||||
}
|
||||
return attrMap;
|
||||
@ -19,29 +21,29 @@ export function getAttrs(el: HTMLElement | ElementRef): StringMap {
|
||||
|
||||
/**
|
||||
* Return the attribute that matches `attr`.
|
||||
* @param attr Name of the attribute or a string of candidate attribute names
|
||||
* @param attr Name of the attribute or a string of candidate attribute names.
|
||||
*/
|
||||
export function getAttrValue(attrs: StringMap, attr: string | string[] = ''): string {
|
||||
return attrs[typeof attr === 'string' ?
|
||||
attr.toLowerCase() :
|
||||
attr.find(a => attrs[a.toLowerCase()] !== undefined) || ''
|
||||
];
|
||||
export function getAttrValue(attrs: AttrMap, attr: string | string[]): string | undefined {
|
||||
const key = (typeof attr === 'string')
|
||||
? attr
|
||||
: attr.find(a => attrs.hasOwnProperty(a.toLowerCase()));
|
||||
|
||||
return (key === undefined) ? undefined : attrs[key.toLowerCase()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the boolean state of an attribute value (if supplied)
|
||||
* @param attrValue The string value of some attribute (or undefined if attribute not present)
|
||||
* @param attrValue The string value of some attribute (or undefined if attribute not present).
|
||||
* @param def Default boolean value when attribute is undefined.
|
||||
*/
|
||||
export function boolFromValue(attrValue: string|undefined, def: boolean = false) {
|
||||
// tslint:disable-next-line:triple-equals
|
||||
return attrValue == undefined ? def : attrValue.trim() !== 'false';
|
||||
export function boolFromValue(attrValue: string | undefined, def: boolean = false) {
|
||||
return attrValue === undefined ? def : attrValue.trim() !== 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the boolean state of attribute from an element
|
||||
* @param el The source of the attributes
|
||||
* @param atty Name of the attribute or a string of candidate attribute names
|
||||
* @param el The source of the attributes.
|
||||
* @param atty Name of the attribute or a string of candidate attribute names.
|
||||
* @param def Default boolean value when attribute is undefined.
|
||||
*/
|
||||
export function getBoolFromAttribute(
|
||||
|
BIN
aio/src/assets/images/logos/concept-icons/animations.png
Executable file
After Width: | Height: | Size: 4.6 KiB |
1
aio/src/assets/images/logos/concept-icons/animations.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 186 200"><defs><style>.cls-1{fill:#ff7043;}.cls-2{fill:#f4511e;}.cls-3{fill:#ffb8a1;}.cls-4{fill:#ffc6b4;}.cls-5{fill:#ffe2d9;}.cls-6{fill:#fff;}</style></defs><title>animations</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.8 0 92.8 0 92.8 0 0 33.3 14 156.1 92.8 200 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><polygon class="cls-2" points="92.8 0 92.8 22.2 92.8 22.2 92.8 123.2 92.8 123.2 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><rect class="cls-3" x="43" y="71.2" width="68.4" height="68.84" rx="4" ry="4"/><rect class="cls-4" x="57.4" y="62.3" width="68.8" height="68.84" rx="4" ry="4" transform="translate(38.6 -25.6) rotate(20)"/><rect class="cls-5" x="71.3" y="59.2" width="68.8" height="68.84" rx="4" ry="4" transform="translate(66.9 -42.1) rotate(32.5)"/><rect class="cls-6" x="81.6" y="58.3" width="68.8" height="68.84" rx="4" ry="4" transform="translate(99.6 -54.9) rotate(45)"/></g></g></svg>
|
After Width: | Height: | Size: 1.0 KiB |
BIN
aio/src/assets/images/logos/concept-icons/augury.png
Executable file
After Width: | Height: | Size: 5.2 KiB |
1
aio/src/assets/images/logos/concept-icons/augury.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 185.6 200"><defs><style>.cls-1{fill:#4a3493;}.cls-2{fill:#311b92;}.cls-3{opacity:0.5;}.cls-4{fill:#fff;}</style></defs><title>augury</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.4 0 92.4 0 92.4 0 0 33.3 14 156.1 92.4 200 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-2" points="92.4 0 92.4 22.2 92.4 22.2 92.4 123.2 92.4 123.2 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><g id="Group-2"><g id="Group-2-2" class="cls-3"><path id="Combined-Shape" class="cls-4" d="M142.4,90.2A44.1,44.1,0,1,1,92.7,52.8a40.3,40.3,0,0,1,6-.4,44.8,44.8,0,0,1,15.9,2.9,25.8,25.8,0,1,0,27.8,34.9Z"/></g><path id="Combined-Shape-2" class="cls-4" d="M140.6,87.8A44.1,44.1,0,1,1,90.9,50.4a40.3,40.3,0,0,1,6-.4,45.6,45.6,0,0,1,15.9,2.9,25.8,25.8,0,1,0,27.8,34.9Z"/></g></g></g></svg>
|
After Width: | Height: | Size: 908 B |
BIN
aio/src/assets/images/logos/concept-icons/cdk.png
Executable file
After Width: | Height: | Size: 2.9 KiB |
1
aio/src/assets/images/logos/concept-icons/cdk.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 185.6 200"><defs><style>.cls-1{fill:#03a9f4;}.cls-2{fill:#039be5;}.cls-3{fill:#fff;}</style></defs><title>cdk</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.4 0 92.4 0 92.4 0 0 33.3 14 156.1 92.4 200 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-2" points="92.4 0 92.4 22.2 92.4 22.2 92.4 123.2 92.4 123.2 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><rect class="cls-3" x="117.8" y="45" width="30" height="30" rx="1" ry="1"/><rect class="cls-3" x="117.8" y="85" width="30" height="30" rx="1" ry="1"/><rect class="cls-3" x="117.8" y="125" width="30" height="30" rx="1" ry="1"/><rect class="cls-3" x="77.8" y="125" width="30" height="30" rx="1" ry="1"/><rect class="cls-3" x="37.8" y="125" width="30" height="30" rx="1" ry="1"/><rect class="cls-3" x="37.8" y="85" width="30" height="30" rx="1" ry="1"/><rect class="cls-3" x="77.8" y="85" width="30" height="30" rx="1" ry="1"/></g></g></svg>
|
After Width: | Height: | Size: 1.0 KiB |
BIN
aio/src/assets/images/logos/concept-icons/cli.png
Executable file
After Width: | Height: | Size: 3.2 KiB |
1
aio/src/assets/images/logos/concept-icons/cli.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 185.6 200"><defs><style>.cls-1{fill:#37474f;}.cls-2{fill:#263238;}.cls-3{fill:#fff;}</style></defs><title>cli</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.4 0 92.4 0 92.4 0 0 33.3 14 156.1 92.4 200 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-2" points="92.4 0 92.4 22.2 92.4 22.2 92.4 123.2 92.4 123.2 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><path class="cls-3" d="M42.8,55v90h100V55ZM138,139.9H47.5V60.1h90.6v79.8Z"/><polygon class="cls-3" points="60.1 111.4 63.5 114.9 82.8 95.5 63.2 75.9 59.8 79.3 76 95.5 60.1 111.4"/><rect class="cls-3" x="82.8" y="117.2" width="30" height="4"/></g></g></svg>
|
After Width: | Height: | Size: 757 B |
BIN
aio/src/assets/images/logos/concept-icons/compiler.png
Executable file
After Width: | Height: | Size: 6.1 KiB |
1
aio/src/assets/images/logos/concept-icons/compiler.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 185.6 200"><defs><style>.cls-1{fill:#8bc34a;}.cls-2{fill:#7cb342;}.cls-3{fill:#fff;stroke:#fff;stroke-miterlimit:10;stroke-width:0.08px;}.cls-4{fill:#bed9a1;}</style></defs><title>compiler</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.4 0 92.4 0 92.4 0 0 33.3 14 156.1 92.4 200 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-2" points="92.4 0 92.4 22.2 92.4 22.2 92.4 123.2 92.4 123.2 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><path class="cls-3" d="M113.9,144.1l-1.5-5.5a7,7,0,0,1,2.7-7.4,26.2,26.2,0,0,0,4.8-4,36,36,0,0,0,4.1-4.8,6.7,6.7,0,0,1,7.4-2.7l5.4,1.4a1.7,1.7,0,0,0,1.4-.1c.5-.3.9-.7.9-1.1l3.7-13.8a1.9,1.9,0,0,0-1.4-2.2l-5.4-1.5a7.2,7.2,0,0,1-5.2-6,38.7,38.7,0,0,0-3.2-12.1,7.1,7.1,0,0,1,1.3-7.9l4-3.9a1.9,1.9,0,0,0,0-2.6l-10-10a1.8,1.8,0,0,0-2.5,0l-4,4a7,7,0,0,1-7.8,1.3A37.4,37.4,0,0,0,96.5,62a7.3,7.3,0,0,1-6.1-5.2L89,51.4a1.9,1.9,0,0,0-2.3-1.3L73,53.7a2.3,2.3,0,0,0-1.1.9,3.4,3.4,0,0,0-.2,1.4l1.5,5.4a6.9,6.9,0,0,1-2.7,7.4,38.3,38.3,0,0,0-4.8,4.1,47.2,47.2,0,0,0-4.1,4.8,6.8,6.8,0,0,1-7.4,2.7L48.8,79a1.7,1.7,0,0,0-1.4.1,1.9,1.9,0,0,0-.9,1.1L42.9,93.9a1.9,1.9,0,0,0,1.3,2.3l5.4,1.4a6.2,6.2,0,0,1,3,1.8,6.8,6.8,0,0,1,2.1,4.2,38.7,38.7,0,0,0,3.2,12.1,6.8,6.8,0,0,1-1.3,7.9l-4,3.9a1.9,1.9,0,0,0,0,2.6l10,10a1.9,1.9,0,0,0,2.6,0l3.9-4a6.8,6.8,0,0,1,7.9-1.3A36.8,36.8,0,0,0,89.1,138a7.2,7.2,0,0,1,6,5.2l1.5,5.4a1.7,1.7,0,0,0,2.2,1.3l13.8-3.5a2.3,2.3,0,0,0,1.1-.9A1.8,1.8,0,0,0,113.9,144.1ZM76.5,116.3a23,23,0,1,1,32.5,0A23.2,23.2,0,0,1,76.5,116.3Z"/><ellipse class="cls-4" cx="92.8" cy="100" rx="13.3" ry="13.3"/></g></g></svg>
|
After Width: | Height: | Size: 1.6 KiB |
BIN
aio/src/assets/images/logos/concept-icons/components.png
Executable file
After Width: | Height: | Size: 4.7 KiB |
1
aio/src/assets/images/logos/concept-icons/components.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 185.6 200"><defs><style>.cls-1{fill:#448aff;}.cls-2{fill:#2979ff;}.cls-3{fill:#fff;}.cls-4{fill:#a2c5ff;}</style></defs><title>components</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.4 0 92.4 0 92.4 0 0 33.3 14 156.1 92.4 200 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-2" points="92.4 0 92.4 22.2 92.4 22.2 92.4 123.2 92.4 123.2 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-3" points="73.6 131.4 54.3 98 84.3 46.1 62.8 46.1 32.8 98 62.8 150 84.4 150 73.6 131.4 73.6 131.4"/><polygon class="cls-4" points="152.8 98 122.8 46.1 84.3 46.1 54.3 98 73.6 64.7 112.1 64.7 131.3 98 112.1 131.4 73.6 131.4 84.4 150 122.8 150 152.8 98"/></g></g></svg>
|
After Width: | Height: | Size: 815 B |
BIN
aio/src/assets/images/logos/concept-icons/dependency-injection.png
Executable file
After Width: | Height: | Size: 4.3 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"><defs><style>.cls-1{fill:#546e7a;}.cls-2{fill:#455a64;}.cls-3,.cls-4{fill:#fff;}.cls-3{opacity:0.5;}</style></defs><title>dependency-injection</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M0,100A100,100,0,0,0,100,200V0A100,100,0,0,0,0,100Z"/><path class="cls-2" d="M100,0V200A100,100,0,0,0,100,0Z"/><polygon class="cls-3" points="131.2 73.2 151 47.7 125.6 67.5 108.6 61.8 59.1 111.3 87.4 139.6 136.9 90.1 131.2 73.2"/><rect class="cls-4" x="74.5" y="74.3" width="40" height="60" transform="translate(101.4 -36.3) rotate(45)"/><rect class="cls-4" x="66.8" y="107" width="20" height="30" transform="translate(108.7 -18.6) rotate(45)"/><rect class="cls-4" x="42.6" y="131.1" width="40" height="10" transform="translate(114.6 -4.4) rotate(45)"/></g></g></svg>
|
After Width: | Height: | Size: 876 B |
BIN
aio/src/assets/images/logos/concept-icons/forms.png
Executable file
After Width: | Height: | Size: 3.4 KiB |
1
aio/src/assets/images/logos/concept-icons/forms.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 186 200"><defs><style>.cls-1{fill:#e57373;}.cls-2{fill:#ef5350;}.cls-3{fill:#fff;}</style></defs><title>forms</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.8 0 92.8 0 92.8 0 0 33.3 14 156.1 92.8 200 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><polygon class="cls-2" points="92.8 0 92.8 22.2 92.8 22.2 92.8 123.2 92.8 123.2 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><path class="cls-3" d="M138.7,90H47c-2.4,0-4-2.3-4-5.6V55.6c0-3.3,1.6-5.6,4-5.6h92.1c2.3,0,3.9,2.3,3.9,5.6V84.4C142.6,87.7,141,90,138.7,90Z"/><path class="cls-3" d="M139.8,120H96.3c-2,0-3.3-1.4-3.3-3.6V103.6c0-2.2,1.3-3.6,3.3-3.6h43.5c1.9,0,3.2,1.4,3.2,3.6v12.8A3.4,3.4,0,0,1,139.8,120Z"/><path class="cls-3" d="M139.8,150H96.3c-2,0-3.3-1.5-3.3-3.8V133.8c0-2.3,1.3-3.8,3.3-3.8h43.5c1.9,0,3.2,1.5,3.2,3.8v12.4C143,148.1,141.7,150,139.8,150Z"/><rect class="cls-3" x="43" y="100" width="40" height="50" rx="4" ry="4"/></g></g></svg>
|
After Width: | Height: | Size: 1.0 KiB |
BIN
aio/src/assets/images/logos/concept-icons/http.png
Executable file
After Width: | Height: | Size: 4.7 KiB |
1
aio/src/assets/images/logos/concept-icons/http.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 185.6 200"><defs><style>.cls-1{fill:#2e7d32;}.cls-2{fill:#1b5e20;}.cls-3,.cls-4,.cls-5{fill:#fff;stroke:#fff;stroke-miterlimit:10;}.cls-3{stroke-width:0.24px;}.cls-4{stroke-width:0.27px;}.cls-5{stroke-width:0.27px;}</style></defs><title>http</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.4 0 92.4 0 92.4 0 0 33.3 14 156.1 92.4 200 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-2" points="92.4 0 92.4 22.2 92.4 22.2 92.4 123.2 92.4 123.2 92.4 200 92.4 200 171.3 156.1 185.6 33.3 92.4 0"/><polygon class="cls-3" points="107.5 150 100.3 148.2 125.7 50 132.9 51.8 107.5 150"/><polygon class="cls-3" points="81.5 150 74.3 148.2 99.7 50 106.8 51.8 81.5 150"/><circle class="cls-4" cx="65.5" cy="82.8" r="7.1"/><circle class="cls-5" cx="65.5" cy="113.1" r="7.1"/></g></g></svg>
|
After Width: | Height: | Size: 916 B |
BIN
aio/src/assets/images/logos/concept-icons/i18n.png
Executable file
After Width: | Height: | Size: 7.1 KiB |
1
aio/src/assets/images/logos/concept-icons/i18n.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 186 200"><defs><style>.cls-1{fill:#4caf50;}.cls-2{fill:#388e3c;}.cls-3{fill:#fff;}</style></defs><title>i18n</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.8 0 92.8 0 92.8 0 0 33.3 14 156.1 92.8 200 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><polygon class="cls-2" points="92.8 0 92.8 22.2 92.8 22.2 92.8 123.2 92.8 123.2 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><path class="cls-3" d="M93,150a50,50,0,0,1-50-50A49.3,49.3,0,0,1,71.4,55.1,43.5,43.5,0,0,1,82,51.3,41.1,41.1,0,0,1,92.6,50c27.4,0,50,22.4,50.4,50a49.5,49.5,0,0,1-17.3,37.7,47.7,47.7,0,0,1-21.7,11A42.5,42.5,0,0,1,93,150Zm-.4-95.9a39,39,0,0,0-9.7,1.1,47.2,47.2,0,0,0-9.7,3.5A45.4,45.4,0,0,0,47.1,100,46,46,0,0,0,93,145.9a45.3,45.3,0,0,0,10.1-1.1A43.1,43.1,0,0,0,123,134.6,45.5,45.5,0,0,0,139,100C138.6,74.7,117.8,54.1,92.6,54.1Z"/><rect class="cls-3" x="90.9" y="54.1" width="3.9" height="91.78"/><path class="cls-3" d="M92.8,147.8c-16.3,0-29.7-21.5-29.7-48s13.4-48,29.7-48,29.7,21.5,29.7,48S109.2,147.8,92.8,147.8Zm0-92.1C78.6,55.7,67,75.5,67,99.8s11.6,44.1,25.8,44.1,25.8-19.8,25.8-44.1S107,55.7,92.8,55.7Z"/><rect class="cls-3" x="47.1" y="97.9" width="91.8" height="3.91"/><rect class="cls-3" x="53" y="76" width="80.5" height="3.91"/><rect class="cls-3" x="53" y="117.8" width="80.5" height="3.91"/></g></g></svg>
|
After Width: | Height: | Size: 1.4 KiB |
BIN
aio/src/assets/images/logos/concept-icons/karma.png
Executable file
After Width: | Height: | Size: 5.0 KiB |
1
aio/src/assets/images/logos/concept-icons/karma.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 186 200"><defs><style>.cls-1{fill:#3dbeae;}.cls-2{fill:#349b8b;}.cls-3,.cls-4{fill:#fff;}.cls-4{opacity:0.5;isolation:isolate;}</style></defs><title>karma</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.8 0 92.8 0 92.8 0 0 33.3 14 156.1 92.8 200 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><polygon class="cls-2" points="92.8 0 92.8 22.2 92.8 22.2 92.8 123.2 92.8 123.2 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><polygon class="cls-3" points="76.6 59.9 99.7 59.9 99.7 82.2 115.5 59.9 140.1 59.9 99.9 119.2 99.9 139.9 92.4 139.9 76.6 84.2 76.6 59.9"/><polygon class="cls-4" points="143 139.9 114.4 97.8 101.8 116.4 118.2 139.9 143 139.9"/><polygon class="cls-3" points="39 84.2 51.3 128.4 69.6 95.6 57.7 106.3 39 84.2"/><polygon class="cls-4" points="80.7 139.1 69.6 95.6 51.3 128.4 62.8 117.3 80.7 139.1"/></g></g></svg>
|
After Width: | Height: | Size: 950 B |
BIN
aio/src/assets/images/logos/concept-icons/labs.png
Executable file
After Width: | Height: | Size: 5.6 KiB |
1
aio/src/assets/images/logos/concept-icons/labs.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 179.2 192.7"><defs><style>.cls-1{fill:#1de9b6;}.cls-2{fill:#00bfa5;}.cls-3{fill:#bbf8e9;}.cls-4{fill:#fff;}</style></defs><title>labs</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="89.4 0 89.4 0 89.4 0 0 32 13.5 150.4 89.4 192.7 89.4 192.7 89.4 192.7 165.4 150.4 179.2 32 89.4 0"/><polygon class="cls-2" points="89.4 0 89.4 21.4 89.4 21.4 89.4 118.7 89.4 118.7 89.4 192.7 89.4 192.7 165.4 150.4 179.2 32 89.4 0"/><path class="cls-3" d="M124.1,75.8l-37-11.5a2.8,2.8,0,0,0-3.5,1.9l-.2.5a2.9,2.9,0,0,0,1.9,3.5l7.1,2.2L85.2,95.5a30.4,30.4,0,0,0,1.7,60.7A30.7,30.7,0,0,0,98,153.8h0l1.5-.7a30.5,30.5,0,0,0,4.8-51.5l7.2-23.3,10.7,3.4a3,3,0,0,0,3.6-1.9v-.5A2.7,2.7,0,0,0,124.1,75.8Z"/><ellipse class="cls-4" cx="90.4" cy="44.1" rx="7.8" ry="7.9"/><ellipse class="cls-4" cx="126.8" cy="60.3" rx="3.8" ry="3.8"/><ellipse class="cls-4" cx="51.5" cy="73.9" rx="4.9" ry="4.9"/><path class="cls-4" d="M111.3,134.2a26.3,26.3,0,1,1-50.2-15.6"/></g></g></svg>
|
After Width: | Height: | Size: 1.0 KiB |
BIN
aio/src/assets/images/logos/concept-icons/language-services.png
Executable file
After Width: | Height: | Size: 5.2 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 186 200"><defs><style>.cls-1{fill:#5c6bc0;}.cls-2{fill:#3949ab;}.cls-3{fill:#fff;}.cls-4{fill:#aeb5e0;}</style></defs><title>language-services</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.8 0 92.8 0 92.8 0 0 33.3 14 156.1 92.8 200 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><polygon class="cls-2" points="92.8 0 92.8 22.2 92.8 22.2 92.8 123.2 92.8 123.2 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><path class="cls-3" d="M93,50a50,50,0,1,0,50,50A50,50,0,0,0,93,50Zm-5.3,80.3L68.9,111.5l6.7-6.7L86,115.2l24-38.8,8,5Z"/><polygon class="cls-4" points="86 115.2 75.6 104.8 68.9 111.5 87.7 130.3 118 81.4 110 76.4 86 115.2"/></g></g></svg>
|
After Width: | Height: | Size: 766 B |
BIN
aio/src/assets/images/logos/concept-icons/lazy-loading.png
Executable file
After Width: | Height: | Size: 3.7 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"><defs><style>.cls-1{fill:#546e7a;}.cls-2{fill:#455a64;}.cls-3{fill:#fff;}</style></defs><title>lazy-loading</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M0,100A100,100,0,0,0,100,200V0A100,100,0,0,0,0,100Z"/><path class="cls-2" d="M100,0V200A100,100,0,0,0,100,0Z"/><path class="cls-3" d="M120,93.7a24.4,24.4,0,0,0,10.1,2.1,24.9,24.9,0,1,0-20.3-10.6l-10,10.1-9.8-9.8a24.5,24.5,0,0,0,4.7-14.6,24.7,24.7,0,1,0-15,22.9l10.8,10.8L47.7,147.5l3.7,3.8a7.9,7.9,0,0,0,11.2,0L99.8,114l37.6,37.7a7.9,7.9,0,0,0,11.2,0l3.7-3.8-43.2-43.3ZM109.8,70.9a20.4,20.4,0,1,1,20.3,20.4,20.8,20.8,0,0,1-6.6-1.1,20.5,20.5,0,0,1-13.7-19.3ZM76.2,90.3a18.9,18.9,0,0,1-6.3,1,20.4,20.4,0,1,1,20.3-20.4,20.6,20.6,0,0,1-3.4,11.3A20.3,20.3,0,0,1,76.2,90.3Z"/></g></g></svg>
|
After Width: | Height: | Size: 858 B |
BIN
aio/src/assets/images/logos/concept-icons/libraries.png
Executable file
After Width: | Height: | Size: 4.0 KiB |
1
aio/src/assets/images/logos/concept-icons/libraries.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"><defs><style>.cls-1{fill:#546e7a;}.cls-2{fill:#455a64;}.cls-3,.cls-4{fill:#fff;}.cls-4{opacity:0.5;}</style></defs><title>libraries</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M0,100A100,100,0,0,0,100,200V0A100,100,0,0,0,0,100Z"/><path class="cls-2" d="M100,0V200A100,100,0,0,0,100,0Z"/><path class="cls-3" d="M25,63.3v75H58.3v-75Zm25,60H33.3V120H50ZM50,90H33.3V86.6H50Zm0-8.4H33.3V78.3H50Z"/><rect class="cls-4" x="33.3" y="77.9" width="16.7" height="4"/><rect class="cls-4" x="33.3" y="86.6" width="16.7" height="3.34"/><rect class="cls-4" x="33.3" y="120" width="16.7" height="3.34"/><path class="cls-3" d="M65,63.3v75H98.3v-75Zm25,60H73.3V120H90ZM90,90H73.3V86.6H90Zm0-8.4H73.3V78.3H90Z"/><rect class="cls-4" x="73.3" y="78.3" width="16.7" height="3.34"/><rect class="cls-4" x="73.3" y="86.6" width="16.7" height="3.34"/><rect class="cls-4" x="73.3" y="120" width="16.7" height="3.34"/><path class="cls-3" d="M98.3,85.3l53.1,53L175,114.7l-53.1-53ZM158.5,110l-11.8,11.8-2.3-2.3,11.8-11.8ZM134.9,86.4,123.1,98.2l-2.3-2.3,11.8-11.8ZM129,80.5,117.2,92.3,114.9,90l11.8-11.8Z"/><rect class="cls-4" x="113.6" y="83.6" width="16.7" height="3.34" transform="translate(-24.6 111.2) rotate(-45)"/><rect class="cls-4" x="119.5" y="89.5" width="16.7" height="3.34" transform="translate(-27 117.1) rotate(-45)"/><rect class="cls-4" x="143.1" y="113.1" width="16.7" height="3.34" transform="translate(-36.8 140.7) rotate(-45)"/></g></g></svg>
|
After Width: | Height: | Size: 1.5 KiB |
BIN
aio/src/assets/images/logos/concept-icons/material.png
Executable file
After Width: | Height: | Size: 5.2 KiB |
1
aio/src/assets/images/logos/concept-icons/material.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 179.2 192.7"><defs><style>.cls-1{fill:#ffa726;}.cls-2{fill:#fb8c00;}.cls-3{fill:#ffe0b2;}.cls-4{fill:#fff3e0;}.cls-5{fill:#fff;}</style></defs><title>material</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="89.4 0 89.4 0 89.4 0 0 32 13.5 150.4 89.4 192.7 89.4 192.7 89.4 192.7 165.4 150.4 179.2 32 89.4 0"/><polygon class="cls-2" points="89.4 0 89.4 21.4 89.4 21.4 89.4 118.7 89.4 118.7 89.4 192.7 89.4 192.7 165.4 150.4 179.2 32 89.4 0"/><polygon class="cls-3" points="102.9 146.3 39.6 115.8 75.9 93.4 139.6 124 102.9 146.3"/><polygon class="cls-4" points="102.9 122.8 39.6 92.2 75.9 69.9 139.6 100.5 102.9 122.8"/><polygon class="cls-5" points="102.9 99.3 39.6 68.7 75.9 46.3 139.6 76.9 102.9 99.3"/></g></g></svg>
|
After Width: | Height: | Size: 833 B |
BIN
aio/src/assets/images/logos/concept-icons/performance.png
Executable file
After Width: | Height: | Size: 5.4 KiB |
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"><defs><style>.cls-1{fill:#546e7a;}.cls-2{fill:#455a64;}.cls-3{fill:#bbc5ca;}.cls-4{fill:#fff;}.cls-5{fill:none;}</style></defs><title>performance</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M0,100A100,100,0,0,0,100,200V0A100,100,0,0,0,0,100Z"/><path class="cls-2" d="M100,0V200A100,100,0,0,0,100,0Z"/><circle class="cls-3" cx="100" cy="100" r="50"/><circle class="cls-4" cx="100" cy="100" r="8.33"/><rect class="cls-4" x="94.82" y="85.42" width="35.36" height="4.17" transform="translate(-28.92 105.18) rotate(-45)"/><path class="cls-5" d="M100,59.09A41,41,0,0,0,59.09,100h81.82A41,41,0,0,0,100,59.09Z"/><path class="cls-5" d="M100,140.91A41,41,0,0,0,140.91,100H59.09A41,41,0,0,0,100,140.91Z"/><path class="cls-4" d="M100,50a50,50,0,0,0-50,50h9.09a40.91,40.91,0,0,1,81.82,0H150A50,50,0,0,0,100,50Z"/></g></g></svg>
|
After Width: | Height: | Size: 936 B |
BIN
aio/src/assets/images/logos/concept-icons/protractor.png
Executable file
After Width: | Height: | Size: 4.8 KiB |
1
aio/src/assets/images/logos/concept-icons/protractor.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 186 200"><defs><style>.cls-1{fill:#e13439;}.cls-2{fill:#b52f32;}.cls-3{fill:#fff;}</style></defs><title>protractor</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="92.8 0 92.8 0 92.8 0 0 33.3 14 156.1 92.8 200 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><polygon class="cls-2" points="92.8 0 92.8 22.2 92.8 22.2 92.8 123.2 92.8 123.2 92.8 200 92.8 200 171.7 156.1 186 33.3 92.8 0"/><path class="cls-3" d="M145.3,117.1v-3.6H158a64.9,64.9,0,0,0-18-43.1L130.4,80l-2.5-2.5,9.5-9.6A64.3,64.3,0,0,0,95.3,50.4V63H91.7V50.3A65,65,0,0,0,48.6,67.9l9.6,9.6L55.6,80,46,70.4a64.8,64.8,0,0,0-17.9,43.1H40.7v3.6H28v14.8H158V117.1Zm-93.5-2.2a41.3,41.3,0,1,1,82.5,0Z"/></g></g></svg>
|
After Width: | Height: | Size: 786 B |