Compare commits

..

105 Commits
6.0.1 ... 6.0.4

Author SHA1 Message Date
41698bf5fd release: cut the v6.0.4 release 2018-06-06 11:49:25 -07:00
23c50e27fc build(docs-infra): log warning rather than error if content errors are not fatal (#24320)
PR Close #24320
2018-06-06 10:25:05 -07:00
0ae8ea254a build(aio): ensure the correct decorator properties are merged (#24289)
Previously only the `description` and `usageNotes` were being copied over
from the call-member of the decorator interface. Important properties such
as `shortDescription` were missed.

These are now added and the code has been refactored to make it simpler and
clearer to update which properties get copied as the requirements change.

PR Close #24289
2018-06-06 10:23:47 -07:00
19deca159b fix(animations): retain trigger-state for nodes that are moved around (#24238)
This patch ensures that if a list of nodes (that contain
animation triggers) are moved around then they will retain their
trigger-value state when animated again at a later point.

PR Close #24238
2018-06-05 18:29:47 -07:00
dc3e8aa0fb fix(forms): properly handle special properties in FormGroup.get (#22249)
closes #17195

PR Close #22249
2018-06-05 18:28:13 -07:00
a9222c0ade docs(aio): Add null type to form validation example (#23949)
Closes #20282

PR Close #23949
2018-06-05 17:32:36 -07:00
ea3127e3f9 build(bazel): ran format (#24279)
PR Close #24279
2018-06-05 13:36:27 -07:00
8e30f7b1aa build(bazel): ran buildifier (#24279)
PR Close #24279
2018-06-05 13:36:27 -07:00
46d81b48ac build(bazel): fix //packages/platform-browser/test:test_web (#24279)
PR Close #24279
2018-06-05 13:36:27 -07:00
3e51a2cb26 build(bazel): enable manual ts_web_test_suite tests that require static_files (#24279)
PR Close #24279
2018-06-05 13:36:27 -07:00
faf60d9310 docs: rename the "aio" component to "docs-infra" (#24295)
The legacy "aio" is still active for currently pending PRs,
The GH label has been renamed as well

PR Close #24295
2018-06-04 17:25:13 -07:00
0639cb9de1 fix(aio): remove unnecessary scrollbar in code-tabs (#24207)
PR Close #24207
2018-06-04 12:07:25 -07:00
810d025488 fix(aio): add right-margin to .home link (#24207)
PR Close #24207
2018-06-04 12:07:25 -07:00
efe49c141a docs(aio): clean up frequent ng-modules (#24025)
Closes #24017

PR Close #24025
2018-06-04 10:13:19 -07:00
a3d9878c9d docs(aio): remove an extraneous apostrophe (#24293)
PR Close #24293
2018-06-04 10:11:28 -07:00
10213d0ca0 docs(common): improve deprecation notices to be parsed by tslint
Closes: #24237
2018-06-04 09:37:05 -07:00
0e1919c2db build(bazel): update bazel integration test to test secondary angular imports such as @angular/common/http (#24170)
PR Close #24170
2018-06-01 13:40:48 -07:00
d579b8ce05 build(bazel): fix bazel built es5 ngfactory with secondary entry-point angular imports (#24170)
PR Close #24170
2018-06-01 13:40:48 -07:00
d69ba735ee feat(compiler-cli): update tsickle to 0.29.x (#24241)
PR Close #24241
2018-06-01 08:37:46 -07:00
2991b1b217 fix(platform-server): avoid dependency cycle when using http interceptor (#24229)
Fixes #23023.

When a HTTP Interceptor injects HttpClient it causes a DI cycle. This fix is to use Injector to lazily inject HTTP_INTERCEPTORS while setting up the HttpHandler on the server so as to break the cycle.

PR Close #24229
2018-06-01 08:33:46 -07:00
b18cf21e99 build(bazel): re-enable packages/upgrade/test:test_web test with static_files in ts_web_test_suite (#24214)
PR Close #24214
2018-05-31 16:13:06 -07:00
c17098dae6 fix(platform-server): don't reflect innerHTML property to attibute (#24213)
Fixes #19278.

innerHTML is conservatively marked as an attribute for security purpose so that it's sanitized when set. However this same mapping is used by the server renderer to decide whether the `innerHTML` property needs to be reflected to the `innerhtml` attribute. The fix is to just skip the property to attribute reflection for `innerHTML`.

PR Close #24213
2018-05-31 10:08:29 -07:00
43e3073687 build: update to rules_nodejs 0.9.1 and rules_typescript 0.15.0 (#24212)
PR Close #24212
2018-05-31 10:08:07 -07:00
f28878f92f build: sync g3 exclude list from copybara to ngbot (#24224)
PR Close #24224
2018-05-31 10:07:45 -07:00
a10bdefa8b docs(aio): Add GDE Kim Maida to contributors 2018-05-30 17:34:15 -07:00
2f377dbcdd style(compiler-cli): fix typo error (#23897)
PR Close #23897
2018-05-30 17:29:04 -07:00
e2e7b4943e docs: fix typo (#24210)
closes #24191

PR Close #24210
2018-05-30 17:06:13 -07:00
b8a1363bb2 docs(forms): fix API doc (#24210)
closes #24090

PR Close #24210
2018-05-30 17:06:13 -07:00
e9e6a58dd0 docs: fix typo (#24210)
closes #23891

PR Close #24210
2018-05-30 17:06:12 -07:00
5932a79713 refactor(animations): fix typo (#24210)
closes #22459

PR Close #24210
2018-05-30 17:06:12 -07:00
7c6a082afd docs: fix typo if FAQ section (#24210)
closes #22360

PR Close #24210
2018-05-30 17:06:12 -07:00
8a2fe5b7c9 docs: fix WebStorm name (#24210)
closes #21900

PR Close #24210
2018-05-30 17:06:12 -07:00
c9eb4910eb fix(animations): Fix browser detection logic (#24188)
Element type is being polyfilled on the server now and cannot be used to detect browser environment.

PR Close #24188
2018-05-30 16:39:09 -07:00
a634a5abbc Revert "docs: update docs to use HttpClientModule instead of HttpModule (#22727)"
This reverts commit b5533e0ee5.
2018-05-30 16:13:59 -07:00
41225026e4 docs: remove unfinished observables file (#23801)
PR Close #23801
2018-05-30 14:44:29 -07:00
e9f2203347 fix(platform-server): avoid clash between server and client style encapsulation attributes (#24158)
Previously the style encapsulation attributes(_nghost-* and _ngcontent-*) created on the server could overlap with the attributes and styles created by the client side app when it botstraps. In case the client is bootstrapping a lazy route, the client side styles are added before the server-side styles are removed. If the components on the client are bootstrapped in a different order than on the server, the styles generated by the client will cause the elements on the server to have the wrong styles.

The fix puts the styles and attributes generated on the server in a completely differemt space so that they are not affected by the client generated styles. The client generated styles will only affect elements bootstrapped on the client.

PR Close #24158
2018-05-30 14:39:10 -07:00
906b3ec8e7 fix(platform-server): provide Domino DOM types globally (#24116)
Fixes #23280, #23133.

This fix lets code access DOM types like Node, HTMLElement in the code. These are invariant across requests and the corresponding classes from Domino can be safely provided during platform initialization.

This is needed for the current sanitizer to work properly on platform-server. Also allows HTML types in injection - Ex. `@inject(DOCUMENT) doc: Document`.

PR Close #24116
2018-05-30 14:38:57 -07:00
1cb0c4644a feat(platform-server): use EventManagerPlugin on the server (#24132)
Previously event handlers on the server were setup directly. This change makes it so that the event registration on the server go through EventManagerPlugin just like on client. This allows us to add custom event registration handlers on the server which allows us to hook up preboot event handlers cleanly.

PR Close #24132
2018-05-30 14:38:39 -07:00
9d28a27215 test(animations): fix Node.js detection in animation tests (#24139)
PR Close #24139
2018-05-30 14:36:35 -07:00
f04aef48f2 build: replace hard-coded master branch with the variable (#24199)
PR Close #24199
2018-05-30 11:31:39 -07:00
d249852f4a build: update rules_webtesting (#24198)
this includes a fix for spammy browser installs that makes our CI logs hard to read

PR Close #24198
2018-05-30 11:31:03 -07:00
707dd59767 build: update brotli version in WORKSPACE (#24194)
The updated version includes the fix for google/brotli#671.

PR Close #24194
2018-05-30 11:30:40 -07:00
4d2570576a docs(aio): fix typo for @NgModuledecorator to @NgModule decorator (#24201)
closes #23974

PR Close #24201
2018-05-30 11:24:12 -07:00
76e58e6cca docs: fix typo (#24201)
closes #23853

PR Close #24201
2018-05-30 11:24:12 -07:00
a27066b9d2 style(compiler): fix up grammar in error message (#24201)
closes #22746

PR Close #24201
2018-05-30 11:24:12 -07:00
29dfc8f3ac docs(bazel): improve error message for ng_package with no metadata (#22964)
PR Close #22964
2018-05-30 10:04:35 -07:00
b5533e0ee5 docs: update docs to use HttpClientModule instead of HttpModule (#22727)
Updated most examples to use HttpClientModule instead of deprecated HttpModule

fix #19280

PR Close #22727
2018-05-30 10:03:14 -07:00
b275d378df docs(aio): add blox material library to resources (#20539)
PR Close #20539
2018-05-30 10:02:32 -07:00
96655f7aac style(compiler): fix typo and formatting (#23729)
PR Close #23729
2018-05-29 18:48:55 -04:00
253f509493 refactor(core): use Partial<T> for MetadataOverride (#24103)
Allows to write:

const fixture = TestBed
      .overridePipe(DisplayNamePipe, { set: { pure: false } })
      .createComponent(MenuComponent);

when you only want to set the `pure` metadata,
instead of currently:

const fixture = TestBed
      .overridePipe(DisplayNamePipe, { set: { name: 'displayName', pure: false } })
      .createComponent(MenuComponent);

which forces you to redefine the name of the pipe even if it is useless.

Fixes #24102

PR Close #24103
2018-05-29 18:40:05 -04:00
5a02ae2f84 docs: update lts and labs practices (#23922)
PR Close #23922
2018-05-29 18:01:31 -04:00
f860752902 refactor(aio): rename method (loadContainingCustomElements --> loadContainedCustomElements) (#23944)
PR Close #23944
2018-05-29 18:00:34 -04:00
efa126f157 fix(aio): avoid loading the ToC component until it is necessary (#23944)
PR Close #23944
2018-05-29 18:00:34 -04:00
715135b117 fix(aio): show embedded ToC (#23944)
On narrow screens (where there is not enough room on the right to show
the floating ToC), an embedded ToC is shown (via an `<aio-toc embedded>`
element in the document). Since ToC was not a custom element, the
component was not instantiated for the embedded element.

This commit fixes it by making `aio-toc` a custom element and loading it
manually for the floating ToC (if necessary).

PR Close #23944
2018-05-29 18:00:34 -04:00
c30fc52d99 feat(aio): add component for lazy-loading custom element (#23944)
PR Close #23944
2018-05-29 18:00:34 -04:00
434eb971e4 refactor(aio): order custom elements by selector (#23944)
PR Close #23944
2018-05-29 18:00:34 -04:00
6e31e22d41 fix(aio): do not load custom elements again while already loading (#23944)
PR Close #23944
2018-05-29 18:00:34 -04:00
8fef926cd2 build(aio): update payload size limits (#23944)
PR Close #23944
2018-05-29 18:00:34 -04:00
7698afedb1 docs(http): correct spelling error (#23675)
Correct a spelling error. I changed HttpParms to HttpParams
PR Close #23675
2018-05-29 16:55:07 -04:00
a583d12660 feat(aio): display code-tabs in a Material Design "card" (#24027)
This helps to connect the "content" of the tab to its label.

Closes #23985

PR Close #24027
2018-05-29 16:52:06 -04:00
a867d71ece feat(aio): render hover status on code-tabs (#24027)
The Material Design spec states that there should be a change
of background when hovering over a tab label.

See https://material.io/design/components/tabs.html#states

Related to #23985

PR Close #24027
2018-05-29 16:52:06 -04:00
84b43daf65 build: pick up rules_typescript karma fix (#24184)
Error was Cannot find module 'karma/bin/karma'

PR Close #24184
2018-05-29 16:50:11 -04:00
9b3423b50d docs(aio): fix link to correct bio image (#24150)
PR Close #24150
2018-05-29 16:27:49 -04:00
d6041d83ec docs(aio): Added Mashhood as GDE in contributors (#24157)
PR Close #24157
2018-05-29 16:20:05 -04:00
a638f504eb docs(aio): add material community components (#24042)
PR Close #24042
2018-05-25 13:45:40 -04:00
a29c756251 refactor(aio): improve logging output in update-preview-server.sh (#24071)
PR Close #24071
2018-05-25 13:44:44 -04:00
f206eb94bb docs(aio): add Juri Strumpflohner to GDE resources (#24086)
PR Close #24086
2018-05-25 13:43:50 -04:00
3d6e82eccd docs(aio): remove outdated rangle link (#24108)
PR Close #24108
2018-05-25 13:41:59 -04:00
154289305e release: cut the v6.0.3 release 2018-05-22 16:16:26 -07:00
469b1e4a9a docs: add doc to event-management api (#23656)
PR Close #23656
2018-05-22 17:33:49 -04:00
7fee1fd442 docs(aio): fix typo (#23925)
PR Close #23925
2018-05-22 16:35:18 -04:00
0ee5b7efa5 fix(service-worker): check platformBrowser before accessing navigator.serviceWorker (#21231)
PR Close #21231
2018-05-22 15:09:32 -04:00
afff84c03f docs(aio): Remove outdated README.md from cli-quickstart zip (#23947)
Closes #23936

PR Close #23947
2018-05-22 13:37:15 -04:00
23f2a7069f docs: fix typo (#23998)
"Made" doesn't make sense (redoing and closing #23940)
PR Close #23998
2018-05-22 13:35:13 -04:00
42260702f7 docs(aio): Add missing dependencies and files to testing zip file download (#23948)
Closes #23060

PR Close #23948
2018-05-21 16:12:40 -04:00
eec231d21e docs(aio): add ant design of angular in resources (#23953)
PR Close #23953
2018-05-21 16:11:13 -04:00
95344d6039 fix(aio): avoid unnecessary re-calculations in live-examples (#23960)
With `plnkrs`, we used to choose a different plnkr mode (normal vs
embedded) based on the size of the screen. This affected the layout of
the plnkr page ("embedded" plnkr mode was usable on small screens, while
"normal" mode wasn't). This is not to be confused with the live-example
mode we use today to determine whether the live-example should be a link
(that open StackBlitz on a new page) or embedded into the document
(using an iframe).

Since we no longer need to change the live-example URL based on the
screen size, there is no need to listen for rezise events on Window. The
necessary properties can be computed once and certain variables are
obsolete.

PR Close #23960
2018-05-21 16:10:12 -04:00
1c9839e91d fix(aio): allow setting live-example title from content (#23960)
Previously, it was possible to set the live-example title as content in
the `<live-example>` element. This relied on our custom loader
functionality that extracted the content from the DOM element before
passing it to the Angular compiler and stored it on a property for later
retrieval.
Since we switched to custom elements (and got rid of the custom loader),
the property is no longer populated with the contents. As a result, many
live examples show the default title ("live example") instead of the one
specified as content.

This commit fixes it by projecting the content into an invisible node
for later retrieval  (similar to what we do in other components, such as
the `CodeExampleComponent`).

PR Close #23960
2018-05-21 16:10:12 -04:00
bc1a66e907 refactor(aio): clean up attribute-utils (#23960)
PR Close #23960
2018-05-21 16:10:12 -04:00
2a83dbb0d8 style(aio): remove background from lazy-loading concept icon (#23950)
Fixes #23938

PR Close #23950
2018-05-21 16:08:08 -04:00
4007d00403 docs(aio): add mhartington to gde (#23777)
PR Close #23777
2018-05-21 16:05:18 -04:00
61e32ac3c4 docs(aio): fix typo (#23990)
are are -> are
PR Close #23990
2018-05-21 16:04:09 -04:00
43ee10fbbd docs(aio): changed 'onVoted' output property to 'voted' to be in line with the styleguide (#23832)
PR Close #23832
2018-05-16 17:23:05 -04:00
eb90039ea1 docs(aio): Expose server and CLI configuration for universal in guide (#23842)
Closes #23795

PR Close #23842
2018-05-16 17:21:44 -04:00
5d318ff234 ci: ensure github-robot listens on circleci builds (#23863)
PR Close #23863
2018-05-16 17:20:44 -04:00
f3361abdd7 test: don't run unit tests on Firefox (#23942)
PR Close #23942
2018-05-16 17:19:45 -04:00
52cbe894e9 docs(aio): add Angular Conf Australia to events (#22929)
Angular Conf Australia 2018 will be held at June 22 in Melbourne, Australia! 

https://www.angularconf.com.au/
PR Close #22929
2018-05-16 17:18:47 -04:00
41c2030534 docs(aio): fix path to observables guide (#23858)
PR Close #23858
2018-05-16 17:16:48 -04:00
daee41a40a docs(aio): update HTTP error test example (#22844)
Update the example to match the description preceding it, which refers to the
use of the error method and ErrorEvent rather than the flush method with a
non-2xx status as shown previously.

PR Close #22844
2018-05-16 17:15:20 -04:00
68bd45ba87 docs(bazel): add a link to the Bazel doc (#22940)
The developer doc mentions but doesn't link to BAZEL.md. Add link and fix capitalization.
PR Close #22940
2018-05-16 17:14:04 -04:00
0f6407750b docs(aio): Remove Intertech with no courses scheduled (#22867)
PR Close #22867
2018-05-16 17:09:44 -04:00
95fca24fd8 feat(aio): add brand and concept icons, img style class more flexible (#23589)
PR Close #23589
2018-05-15 15:36:07 -07:00
a8f6542115 release: cut the v6.0.2 release 2018-05-15 12:34:04 -07:00
c6b618d020 fix(service-worker): deprecate versionedFiles in asset-group resources (#23584)
Since `versionedFiles` behaves in the exact same way as `files`, there
is no reaason to have both. Users should use `files` instead.

This commit deprecates the property and prints a warning when coming
across an asset-group that uses it. It should be completely removed in
a future version.

Note, it has also been removed from the default `ngsw-config.json`
template in angular/devkit#754.

PR Close #23584
2018-05-15 12:19:08 -07:00
ad6052e397 test: switch to ts_web_test_suite (#23859)
Unit tests now run on Firefox too

PR Close #23859
2018-05-15 11:41:58 -07:00
e9d1709156 build: only match version tags for BUILD_SCM_VERSION (#23903)
PR Close #23903
2018-05-14 12:44:26 -07:00
b5d3de50cc docs(aio): use heroesUrl (#23884)
PR Close #23884
2018-05-14 10:38:15 -07:00
734d37b231 build(aio): improve enum API rendering (#23872)
* The member details section is now called "Members", rather
than "Properties".
* The property table now displays appropriate table headings:
"Member", "Value", "Description".
* The "Value" column is not shown if none of the members have
a value.

Closes #22678

PR Close #23872
2018-05-14 10:37:42 -07:00
bc2063807c build(aio): ensure usageNotes are copied into decorator API docs (#23901)
PR Close #23901
2018-05-14 10:35:33 -07:00
2a528fcb15 build(aio): display types of API const docs correctly (#23850)
Previously these docs always displayed `any` as the type
of the const export. Now the type is computed correctly from
the declared type or initializer of the constant.

PR Close #23850
2018-05-11 16:44:51 -04:00
752b83ac81 fix(animations): do not throw errors when a destroyed component is animated (#23836)
PR Close #23836
2018-05-11 16:08:15 -04:00
56be3375ec fix(aio): make background transparent in 144x144 PWA icon (#23851)
Fixes #23827

PR Close #23851
2018-05-11 12:38:21 -04:00
1b83b3fb15 docs(aio): add Cory Rylan to GDE resources (#23840)
PR Close #23840
2018-05-11 12:32:38 -04:00
14d4625ede docs: update version to 6 in language-service (#20795)
PR Close #20795
2018-05-11 12:22:51 -04:00
fd880a8de4 build: replace the old publish script with a new bazel-based one 2018-05-10 23:01:41 -07:00
250 changed files with 3382 additions and 1155 deletions

View File

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

View File

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

View File

@ -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",
],
)

View File

@ -1,3 +1,39 @@
<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)
### Bug Fixes
* **animations:** do not throw errors when a destroyed component is animated ([#23836](https://github.com/angular/angular/issues/23836)) ([752b83a](https://github.com/angular/angular/commit/752b83a))
* **service-worker:** deprecate `versionedFiles` in asset-group resources ([#23584](https://github.com/angular/angular/issues/23584)) ([c6b618d](https://github.com/angular/angular/commit/c6b618d))
<a name="6.0.1"></a>
# [6.0.1](https://github.com/angular/angular/compare/6.0.0...6.0.1) (2018-05-11)
@ -21,7 +57,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).
@ -172,10 +208,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
{
# ...
@ -185,12 +221,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.
@ -203,35 +239,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.

View File

@ -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/ca7b8062d9cf4ef2fde9193c7d37a0764c4262d7.zip",
strip_prefix = "rules_webtesting-ca7b8062d9cf4ef2fde9193c7d37a0764c4262d7",
sha256 = "28c73cf9d310fa6dba30e66bdb98071341c99c3feb8662f2d3883a632de97d72",
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(
@ -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",
)
#

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,12 +7,9 @@
"resources": {
"files": [
"/favicon.ico",
"/index.html"
],
"versionedFiles": [
"/*.bundle.css",
"/*.bundle.js",
"/*.chunk.js"
"/index.html",
"/*.css",
"/*.js"
]
}
}, {
@ -25,4 +22,4 @@
]
}
}]
}
}

View File

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

View File

@ -84,7 +84,7 @@ export class HeroService {
// if not search term, return empty hero array.
return of([]);
}
return this.http.get<Hero[]>(`api/heroes/?name=${term}`).pipe(
return this.http.get<Hero[]>(`${this.heroesUrl}/?name=${term}`).pipe(
tap(_ => this.log(`found heroes matching "${term}"`)),
catchError(this.handleError<Hero[]>('searchHeroes', []))
);

View File

@ -64,7 +64,7 @@ export class HeroService {
// if not search term, return empty hero array.
return of([]);
}
return this.http.get<Hero[]>(`api/heroes/?name=${term}`).pipe(
return this.http.get<Hero[]>(`${this.heroesUrl}/?name=${term}`).pipe(
tap(_ => this.log(`found heroes matching "${term}"`)),
catchError(this.handleError<Hero[]>('searchHeroes', []))
);

View File

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

View File

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

View File

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

View File

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

View File

@ -1,33 +1,33 @@
# Angular Language Service
The Angular Language Service is a way to get completions, errors,
hints, and navigation inside your Angular templates whether they
are external in an HTML file or embedded in annotations/decorators
in a string. The Angular Language Service autodetects that you are
opening an Angular file, reads your `tsconfig.json` file, finds all the
templates you have in your application, and then provides language
The Angular Language Service is a way to get completions, errors,
hints, and navigation inside your Angular templates whether they
are external in an HTML file or embedded in annotations/decorators
in a string. The Angular Language Service autodetects that you are
opening an Angular file, reads your `tsconfig.json` file, finds all the
templates you have in your application, and then provides language
services for any templates that you open.
## Autocompletion
Autocompletion can speed up your development time by providing you with
contextual possibilities and hints as you type. This example shows
autocomplete in an interpolation. As you type it out,
Autocompletion can speed up your development time by providing you with
contextual possibilities and hints as you type. This example shows
autocomplete in an interpolation. As you type it out,
you can hit tab to complete.
<figure>
<img src="generated/images/guide/language-service/language-completion.gif" alt="autocompletion">
</figure>
There are also completions within
elements. Any elements you have as a component selector will
There are also completions within
elements. Any elements you have as a component selector will
show up in the completion list.
## Error checking
The Angular Language Service can also forewarn you of mistakes in your code.
In this example, Angular doesn't know what `orders` is or where it comes from.
The Angular Language Service can also forewarn you of mistakes in your code.
In this example, Angular doesn't know what `orders` is or where it comes from.
<figure>
<img src="generated/images/guide/language-service/language-error.gif" alt="error checking">
@ -35,8 +35,8 @@ In this example, Angular doesn't know what `orders` is or where it comes from.
## Navigation
Navigation allows you to hover to
see where a component, directive, module, etc. is from and then
Navigation allows you to hover to
see where a component, directive, module, etc. is from and then
click and press F12 to go directly to its definition.
<figure>
@ -46,53 +46,52 @@ click and press F12 to go directly to its definition.
## Angular Language Service in your editor
Angular Language Service is currently available for [Visual Studio Code](https://code.visualstudio.com/) and
[WebStorm](https://www.jetbrains.com/webstorm).
Angular Language Service is currently available for [Visual Studio Code](https://code.visualstudio.com/) and
[WebStorm](https://www.jetbrains.com/webstorm).
### Visual Studio Code
In Visual Studio Code, install Angular Language Service from the store,
which is accessible from the bottom icon on the left menu pane.
You can also use the VS Quick Open (⌘+P) to search for the extension. When you've opened it,
enter the following command:
In Visual Studio Code, install Angular Language Service from the store,
which is accessible from the bottom icon on the left menu pane.
You can also use the VS Quick Open (⌘+P) to search for the extension. When you've opened it,
enter the following command:
```sh
ext install Angular.ng-template
```
Then click the install button to install the Angular Language Service.
Then click the install button to install the Angular Language Service.
### WebStorm
In webstorm, you have to install the language service as a dev dependency.
When Angular sees this dev dependency, it provides the
language service inside of WebStorm. Webstorm then gives you
In webstorm, you have to install the language service as a dev dependency.
When Angular sees this dev dependency, it provides the
language service inside of WebStorm. Webstorm then gives you
colorization inside the template and autocomplete in addition to the Angular Language Service.
Here's the dev dependency
Here's the dev dependency
you need to have in `package.json`:
```json
devDependencies {
"@angular/language-service": "^4.0.0"
"@angular/language-service": "^6.0.0"
}
```
Then in the terminal window at the root of your project,
install the `devDependencies` with `npm` or `yarn`:
Then in the terminal window at the root of your project,
install the `devDependencies` with `npm` or `yarn`:
```sh
npm install
npm install
```
*OR*
*OR*
```sh
yarn
```
*OR*
*OR*
```sh
yarn install
@ -101,7 +100,7 @@ yarn install
### Sublime Text
In [Sublime Text](https://www.sublimetext.com/), you first need an extension to allow Typescript.
In [Sublime Text](https://www.sublimetext.com/), you first need an extension to allow Typescript.
Install the latest version of typescript in a local `node_modules` directory:
```sh
@ -113,7 +112,7 @@ Then install the Angular Language Service in the same location:
npm install --save-dev @angular/language-service
```
Starting with TypeScript 2.3, TypeScript has a language service plugin model that the language service can use.
Starting with TypeScript 2.3, TypeScript has a language service plugin model that the language service can use.
Next, in your user preferences (`Cmd+,` or `Ctrl+,`), add:
@ -124,13 +123,13 @@ Next, in your user preferences (`Cmd+,` or `Ctrl+,`), add:
## Installing in your project
You can also install Angular Language Service in your project with the
You can also install Angular Language Service in your project with the
following `npm` command:
```sh
npm install --save-dev @angular/language-service
```
Additionally, add the following to the `"compilerOptions"` section of
Additionally, add the following to the `"compilerOptions"` section of
your project's `tsconfig.json`.
```json
@ -138,25 +137,25 @@ your project's `tsconfig.json`.
{"name": "@angular/language-service"}
]
```
Note that this only provides diagnostics and completions in `.ts`
files. You need a custom sublime plugin (or modifications to the current plugin)
Note that this only provides diagnostics and completions in `.ts`
files. You need a custom sublime plugin (or modifications to the current plugin)
for completions in HTML files.
## How the Language Service works
When you use an editor with a language service, there's an
editor process which starts a separate language process/service
to which it speaks through an [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call).
Any time you type inside of the editor, it sends information to the other process to
track the state of your project. When you trigger a completion list within a template, the editor process first parses the template into an HTML AST, or [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree). Then the Angular compiler interprets
what module the template is part of, the scope you're in, and the component selector. Then it figures out where in the template AST your cursor is. When it determines the
When you use an editor with a language service, there's an
editor process which starts a separate language process/service
to which it speaks through an [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call).
Any time you type inside of the editor, it sends information to the other process to
track the state of your project. When you trigger a completion list within a template, the editor process first parses the template into an HTML AST, or [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree). Then the Angular compiler interprets
what module the template is part of, the scope you're in, and the component selector. Then it figures out where in the template AST your cursor is. When it determines the
context, it can then determine what the children can be.
It's a little more involved if you are in an interpolation. If you have an interpolation of `{{data.---}}` inside a `div` and need the completion list after `data.---`, the compiler can't use the HTML AST to find the answer. The HTML AST can only tell the compiler that there is some text with the characters "`{{data.---}}`". That's when the template parser produces an expression AST, which resides within the template AST. The Angular Language Services then looks at `data.---` within its context and asks the TypeScript Language Service what the members of data are. TypeScript then returns the list of possibilities.
For more in-depth information, see the
For more in-depth information, see the
[Angular Language Service API](https://github.com/angular/angular/blob/master/packages/language-service/src/types.ts)
@ -170,7 +169,7 @@ For more in-depth information, see the
## More on Information
For more information, see [Chuck Jazdzewski's presentation](https://www.youtube.com/watch?v=ez3R0Gi4z5A&t=368s) on the Angular Language
For more information, see [Chuck Jazdzewski's presentation](https://www.youtube.com/watch?v=ez3R0Gi4z5A&t=368s) on the Angular Language
Service from [ng-conf](https://www.ng-conf.org/) 2017.

View File

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

View File

@ -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&mdash;`AppModule`,
`HeroesModule`, `AdminModule` and `CrisisCenterModule`&mdash;you
have natural candidates for lazy loading.

View File

@ -70,6 +70,7 @@ interface AssetGroup {
updateMode?: 'prefetch' | 'lazy';
resources: {
files?: string[];
/** @deprecated As of v6 `versionedFiles` and `files` options have the same behavior. Use `files` instead. */
versionedFiles?: string[];
urls?: string[];
};
@ -102,7 +103,7 @@ This section describes the resources to cache, broken up into three groups.
* `files` lists patterns that match files in the distribution directory. These can be single files or glob-like patterns that match a number of files.
* `versionedFiles` is like `files` but should be used for build artifacts that already include a hash in the filename, which is used for cache busting. The Angular service worker can optimize some aspects of its operation if it can assume file contents are immutable.
* `versionedFiles` has been deprecated. As of v6 `versionedFiles` and `files` options have the same behavior. Use `files` instead.
* `urls` includes both URLs and URL patterns that will be matched at runtime. These resources are not fetched directly and do not have content hashes, but they will be cached according to their HTTP headers. This is most useful for CDNs such as the Google Fonts service.<br>
_(Negative glob patterns are not supported.)_

View File

@ -1,3 +0,0 @@
# Testing
TBD. Original content [here](https://docs.google.com/document/d/1gGP5sqWNCHAWWV_GLdZQ1XyMO4K-CHksUxux0BFtVxk/edit#heading=h.ohqykkhzdhb2).

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -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",
@ -600,5 +600,50 @@
"website": "http://www.angulartraining.com",
"bio": "Alain Chautard is a Google Developer Expert in Web Technologies / Angular. He started working with Angular JS in 2011. Since then he has worked with all Angular versions on a daily basis, both as a developer and as a technical trainer. He is the organizer of the Sacramento Angular Meetup group, co-organizer of the Google Developer Group chapter in Sacramento, California, and published author of the Packt video course 'Getting Started with Angular'",
"group": "GDE"
},
"coryrylan": {
"name": "Cory Rylan",
"picture": "cory-rylan.jpg",
"twitter": "coryrylan",
"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"
}
}

View File

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

View File

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

View File

@ -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": {
@ -345,6 +345,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 +377,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 +542,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 +610,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,

View File

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

View File

@ -112,7 +112,7 @@
"cross-spawn": "^5.1.0",
"css-selector-parser": "^1.3.0",
"dgeni": "^0.4.7",
"dgeni-packages": "^0.26.0",
"dgeni-packages": "^0.26.1",
"entities": "^1.1.1",
"eslint": "^3.19.0",
"eslint-plugin-jasmine": "^2.2.0",

View File

@ -2,11 +2,11 @@
"aio": {
"master": {
"uncompressed": {
"runtime": 2689,
"main": 478529,
"runtime": 2768,
"main": 475855,
"polyfills": 38453,
"prettify": 14913
}
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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'
},
];
/**

View File

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

View File

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

View File

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

View 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);
}
}

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View 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{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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View 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{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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 186 200"><defs><style>.cls-1{fill:#9c27b0;}.cls-2{fill:#7b1fa2;}.cls-3,.cls-4{fill:#fff;}.cls-4{opacity:0.8;}</style></defs><title>pwa</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="M123.2,115.4l3.7-9.3h10.7L132.5,92l6.4-16L157,124H143.6l-3.1-8.6Z"/><path class="cls-4" d="M111.8,124l19.4-48H118.4l-13.3,31L95.6,76H85.7L75.6,107,68.4,92.9l-6.5,19.9L68.5,124H81.2l9.1-27.8L99.1,124Z"/><path class="cls-3" d="M41.2,107.5h8a21.2,21.2,0,0,0,6.4-.8l2.1-6.3,5.7-17.6a9,9,0,0,0-1.5-1.9C58.9,77.6,54.6,76,49,76H29v48H41.2ZM51.7,87a6.3,6.3,0,0,1,1.8,4.7A6.7,6.7,0,0,1,52,96.3c-1.2,1.3-3.2,1.9-6.2,1.9H41.2V85.3h4.7C48.6,85.3,50.6,85.9,51.7,87Z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1014 B

Some files were not shown because too many files have changed in this diff Show More