Compare commits

...

93 Commits
6.0.3 ... 6.0.5

Author SHA1 Message Date
b91b9efc91 release: cut the v6.0.5 release 2018-06-13 15:00:28 -07:00
03718c95ce docs: change capitalization for css hex color values (#23511)
PR Close #23511
2018-06-13 13:31:30 -07:00
0834710c18 docs(aio): remove links to outdated live examples from the API documenation (#23966)
Closes #21525

PR Close #23966
2018-06-13 13:29:13 -07:00
9a9a7de9a4 docs: add message property to compose-message component (#24310)
PR Close #24310
2018-06-13 13:28:47 -07:00
ff51691221 docs: fix typo (#24470)
PR Close #24470
2018-06-13 11:54:26 -07:00
2ad90ab0d3 docs: fix wording in 4-10 (#24385)
PR Close #24385
2018-06-13 11:53:20 -07:00
c5872e6782 docs(aio): Reorganize style guide sections on prefixing components/directives (#22571)
Closes https://github.com/angular/angular/issues/22081

PR Close #22571
2018-06-13 11:20:43 -07:00
d20877bf3f fix(router): fix lazy loading of aux routes (#23459)
Fixes #10981

PR Close #23459
2018-06-13 11:20:20 -07:00
b3089b4963 build: update buildifier to latest (#24296) (#24453)
this matches the version in ngcontainer:0.3.1

PR Close #24296

PR Close #24453
2018-06-13 11:19:59 -07:00
880b03101e build: update to Bazel 0.14.0 (#24296) (#24453)
Also update usage of the ctx.actions.args to a newer preferred API

PR Close #24296

PR Close #24453
2018-06-13 11:19:59 -07:00
451d996414 docs: change aio scope to docs-infra (#24410)
Related to #24295

PR Close #24410
2018-06-12 11:36:14 -07:00
ea3669e334 fix(bazel): Allow ng_module to depend on targets w no deps (#24446)
PR Close #24446
2018-06-12 11:35:53 -07:00
ea2987c7af fix(service-worker): fix SwPush.unsubscribe() (#24162)
Fixes #24095

PR Close #24162
2018-06-11 14:04:31 -04:00
5b823dedcc test(service-worker): allow SwPush tests to run on Node.js (#24162)
PR Close #24162
2018-06-11 14:04:30 -04:00
80f7c83bdd test(service-worker): add tests for SwPush (#24162)
PR Close #24162
2018-06-11 14:04:30 -04:00
53072b10da refactor(service-worker): minor mocks refactoring (#24162)
PR Close #24162
2018-06-11 14:04:30 -04:00
30c2f560b1 build(docs-infra): ensure dist/ directory is cleaned before running tsc --watch (#24372)
PR Close #24372
2018-06-11 09:18:46 -07:00
18c4976f3b build(docs-infra): upgrade preview server to latest @types/shelljs (#24372)
PR Close #24372
2018-06-11 09:18:46 -07:00
0139173c7c fix(animations): always render end-state styles for orphaned DOM nodes (#24236)
This patch ensures that any destination animation styling (state values)
are always applied even if the DOM node is not apart of the DOM.

PR Close #24236
2018-06-08 16:35:27 -07:00
f3c9954a68 docs: adds information about the VSCode clang-format extension (#24351)
PR Close #24351
2018-06-08 16:35:06 -07:00
e8765352e8 fix(docs-infra): use script nomodule to load IE polyfills, skip other polyfills (#24317)
This commit includes two changes:
1. It changes the unreliable dynamic way of loading IE polyfills to use
   `<script nomodule>` instead - for IE it's equivalent to a regular script tag
   while modern browsers will ignore it.
2. It removes other polyfills for browsers not supporting `Object.assign` as
   this API is supported by Chrome 45+, Firefox 34+ and Safari 9+ i.e. it's been
   supported for some time.

Note that as of June 2018 Googlebot uses Chrome 41 to render sites to be
indexed. Chrome 41 doesn't support `Object.assign` but it also doesn't support
ES6 modules so it'll load polyfills meant for IE - which it should do anyway
as it doesn't support most of ES6.

Fixes #23647

PR Close #24317
2018-06-08 13:34:06 -07:00
9e61ad897e build(docs-infra): ensure stability is computed before the API list (#24356)
Previously the API list was being generated before the stability had
been computed. This meant that the API list page showed no API docs
when filtering by `stable` stability status.

Closes #24329

PR Close #24356
2018-06-08 13:33:32 -07:00
0d81151cbc docs(aio): add mix and connect to front page campaigns (#24357)
PR Close #24357
2018-06-08 13:31:28 -07:00
70319722a1 docs(aio): Added resource link to Amexio Canvas Web Based IDE (#24336)
PR Close #24336
2018-06-07 18:46:32 -04:00
cfa5b6567d build(bazel): fix ng_package rollup root dir for fesm2015 output (#24298)
PR Close #24298
2018-06-07 17:56:10 -04:00
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
190 changed files with 2921 additions and 1068 deletions

View File

@ -12,8 +12,8 @@
## IMPORTANT
# If you change the `docker_image` version, also change the `cache_key` suffix and the version of
# `com_github_bazelbuild_buildtools` in the `/WORKSPACE` file.
var_1: &docker_image angular/ngcontainer:0.3.0
var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.3.0
var_1: &docker_image angular/ngcontainer:0.3.1
var_2: &cache_key v2-angular-{{ .Branch }}-{{ checksum "yarn.lock" }}-0.3.1
# Define common ENV vars
var_3: &define_env_vars
@ -80,7 +80,7 @@ jobs:
- run: ls /home/circleci/bazel_repository_cache || true
- run: bazel info release
- run: bazel run @yarn//:yarn
- run: bazel run @nodejs//:yarn
# Use bazel query so that we explicitly ask for all buildable targets to be built as well
# This avoids waiting for the slowest build target to finish before running the first test
# See https://github.com/bazelbuild/bazel/issues/4257
@ -131,7 +131,7 @@ jobs:
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
- *setup-bazel-remote-cache
- run: bazel run @yarn//:yarn
- run: bazel run @nodejs//:yarn
- run: scripts/build-packages-dist.sh
# Save the npm packages from //packages/... for other workflow jobs to read

View File

@ -45,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,32 @@
<a name="6.0.5"></a>
## [6.0.5](https://github.com/angular/angular/compare/6.0.4...6.0.5) (2018-06-13)
### Bug Fixes
* **animations:** always render end-state styles for orphaned DOM nodes ([#24236](https://github.com/angular/angular/issues/24236)) ([0139173](https://github.com/angular/angular/commit/0139173))
* **bazel:** Allow ng_module to depend on targets w no deps ([#24446](https://github.com/angular/angular/issues/24446)) ([ea3669e](https://github.com/angular/angular/commit/ea3669e))
* **docs-infra:** use script nomodule to load IE polyfills, skip other polyfills ([#24317](https://github.com/angular/angular/issues/24317)) ([e876535](https://github.com/angular/angular/commit/e876535)), closes [#23647](https://github.com/angular/angular/issues/23647)
* **router:** fix lazy loading of aux routes ([#23459](https://github.com/angular/angular/issues/23459)) ([d20877b](https://github.com/angular/angular/commit/d20877b)), closes [#10981](https://github.com/angular/angular/issues/10981)
* **service-worker:** fix `SwPush.unsubscribe()` ([#24162](https://github.com/angular/angular/issues/24162)) ([ea2987c](https://github.com/angular/angular/commit/ea2987c)), closes [#24095](https://github.com/angular/angular/issues/24095)
<a name="6.0.4"></a>
## [6.0.4](https://github.com/angular/angular/compare/6.0.3...6.0.4) (2018-06-06)
### Bug Fixes
* **animations:** Fix browser detection logic ([#24188](https://github.com/angular/angular/issues/24188)) ([c9eb491](https://github.com/angular/angular/commit/c9eb491))
* **animations:** retain trigger-state for nodes that are moved around ([#24238](https://github.com/angular/angular/issues/24238)) ([19deca1](https://github.com/angular/angular/commit/19deca1))
* **forms:** properly handle special properties in FormGroup.get ([#22249](https://github.com/angular/angular/issues/22249)) ([dc3e8aa](https://github.com/angular/angular/commit/dc3e8aa)), closes [#17195](https://github.com/angular/angular/issues/17195)
* **platform-server:** avoid clash between server and client style encapsulation attributes ([#24158](https://github.com/angular/angular/issues/24158)) ([e9f2203](https://github.com/angular/angular/commit/e9f2203))
* **platform-server:** avoid dependency cycle when using http interceptor ([#24229](https://github.com/angular/angular/issues/24229)) ([2991b1b](https://github.com/angular/angular/commit/2991b1b)), closes [#23023](https://github.com/angular/angular/issues/23023)
* **platform-server:** don't reflect innerHTML property to attibute ([#24213](https://github.com/angular/angular/issues/24213)) ([c17098d](https://github.com/angular/angular/commit/c17098d)), closes [#19278](https://github.com/angular/angular/issues/19278)
* **platform-server:** provide Domino DOM types globally ([#24116](https://github.com/angular/angular/issues/24116)) ([906b3ec](https://github.com/angular/angular/commit/906b3ec)), closes [#23280](https://github.com/angular/angular/issues/23280) [#23133](https://github.com/angular/angular/issues/23133)
<a name="6.0.3"></a>
## [6.0.3](https://github.com/angular/angular/compare/6.0.2...6.0.3) (2018-05-22)
@ -42,7 +71,7 @@
Angular v6 is the first release of Angular that unifies the Framework, Material and CLI.
To learn about the release highlights and our new CLI-powered update workflow for your projects please check out the [v6 release announcement](https://blog.angular.io/version-6-0-0-of-angular-now-available-cc56b0efa7a4).
To learn about the release highlights and our new CLI-powered update workflow for your projects please check out the [v6 release announcement](https://blog.angular.io/version-6-0-0-of-angular-now-available-cc56b0efa7a4).
@ -193,10 +222,10 @@ To learn about the release highlights and our new CLI-powered update workflow fo
This change removes support for `<template>`. `<ng-template>` should be used instead.
BEFORE:
<!-- html template -->
<template>some template content</template>
# tsconfig.json
{
# ...
@ -206,12 +235,12 @@ To learn about the release highlights and our new CLI-powered update workflow fo
"enableLegacyTemplate": [true|false]
}
}
AFTER:
<!-- html template -->
<ng-template>some template content</ng-template>
* **core:** it is no longer possible to import animation-related functions from @angular/core. All animation symbols must now be imported from @angular/animations.
@ -224,35 +253,35 @@ To learn about the release highlights and our new CLI-powered update workflow fo
Previously, ngModelChange was emitted before its underlying control was updated.
This was fine if you passed through the value directly through the $event keyword, e.g.
```
<input [(ngModel)]="name" (ngModelChange)="onChange($event)">
onChange(value) {
console.log(value); // would log updated value
}
```
However, if you had a handler for the ngModelChange event that checked the value through the control,
you would get the old value rather than the updated value. e.g:
```
<input #modelDir="ngModel" [(ngModel)]="name" (ngModelChange)="onChange(modelDir)">
onChange(ngModel: NgModel) {
console.log(ngModel.value); // would log old value, not updated value
}
```
Now the value and validity will be updated before the ngModelChange event is emitted,
so the same setup will log the updated value.
```
onChange(ngModel: NgModel) {
console.log(ngModel.value); // will log updated value
}
```
We think this order will be less confusing when the control is checked directly.
You will only need to update your app if it has relied on this bug to keep track of the old control value.
If that is the case, you should be able to track the old value directly by saving it on your component.

View File

@ -227,10 +227,15 @@ The following is the list of supported scopes:
There are currently a few exceptions to the "use package name" rule:
* **packaging**: used for changes that change the npm package layout in all of our packages, e.g. public path changes, package.json changes done to all packages, d.ts file/format changes, changes to bundles, etc.
* **packaging**: used for changes that change the npm package layout in all of our packages, e.g.
public path changes, package.json changes done to all packages, d.ts file/format changes, changes
to bundles, etc.
* **changelog**: used for updating the release notes in CHANGELOG.md
* **aio**: used for docs-app (angular.io) related changes within the /aio directory of the repo
* none/empty string: useful for `style`, `test` and `refactor` changes that are done across all packages (e.g. `style: add missing semicolons`)
* **docs-infra**: used for docs-app (angular.io) related changes within the /aio directory of the
repo
* none/empty string: useful for `style`, `test` and `refactor` changes that are done across all
packages (e.g. `style: add missing semicolons`) and for docs changes that are not related to a
specific package (e.g. `docs: fix typo in tutorial`).
### Subject
The subject contains a succinct description of the change:
@ -269,7 +274,7 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise
* https://help.github.com/articles/about-commit-email-addresses/
* https://help.github.com/articles/blocking-command-line-pushes-that-expose-your-personal-email-address/
Note that if you have more than one Git identity, it is important to verify that you are logged in with the same ID with which you signed the CLA, before you commit changes. If not, your PR will fail the CLA check.
Note that if you have more than one Git identity, it is important to verify that you are logged in with the same ID with which you signed the CLA, before you commit changes. If not, your PR will fail the CLA check.
<hr>

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/cfcaaf98553fee8e7063b5f5c11fd1b77e43d683.zip",
strip_prefix = "rules_webtesting-cfcaaf98553fee8e7063b5f5c11fd1b77e43d683",
sha256 = "636c7a9ac2ca13a04d982c2f9c874876ecc90a7b9ccfe4188156122b26ada7b3",
url = "https://github.com/bazelbuild/rules_webtesting/archive/v0.2.0.zip",
strip_prefix = "rules_webtesting-0.2.0",
sha256 = "cecc12f07e95740750a40d38e8b14b76fefa1551bef9332cb432d564d693723c",
)
http_archive(
name = "build_bazel_rules_typescript",
url = "https://github.com/bazelbuild/rules_typescript/archive/v0.13.0.zip",
strip_prefix = "rules_typescript-0.13.0",
sha256 = "8f2767ff56ad68c80c62e9a1cdc2ba2c2ba0b19d350f713365e5333045df02e3",
url = "https://github.com/bazelbuild/rules_typescript/archive/0.15.0.zip",
strip_prefix = "rules_typescript-0.15.0",
sha256 = "1aa75917330b820cb239b3c10a936a10f0a46fe215063d4492dd76dc6e1616f4",
)
http_archive(
@ -34,13 +34,13 @@ http_archive(
# This commit matches the version of buildifier in angular/ngcontainer
# If you change this, also check if it matches the version in the angular/ngcontainer
# version in /.circleci/config.yml
BAZEL_BUILDTOOLS_VERSION = "fd9878fd5de921e0bbab3dcdcb932c2627812ee1"
BAZEL_BUILDTOOLS_VERSION = "82b21607e00913b16fe1c51bec80232d9d6de31c"
http_archive(
name = "com_github_bazelbuild_buildtools",
url = "https://github.com/bazelbuild/buildtools/archive/%s.zip" % BAZEL_BUILDTOOLS_VERSION,
strip_prefix = "buildtools-%s" % BAZEL_BUILDTOOLS_VERSION,
sha256 = "27bb461ade23fd44ba98723ad98f84ee9c83cd3540b773b186a1bc5037f3d862",
sha256 = "edb24c2f9c55b10a820ec74db0564415c0cf553fa55e9fc709a6332fb6685eff",
)
# Fetching the Bazel source code allows us to compile the Skylark linter
@ -66,9 +66,9 @@ http_archive(
http_archive(
name = "org_brotli",
url = "https://github.com/google/brotli/archive/c6333e1e79fb62ea088443f192293f964409b04e.zip",
strip_prefix = "brotli-c6333e1e79fb62ea088443f192293f964409b04e",
sha256 = "3f781988dee7dd3bcce2bf238294663cfaaf3b6433505bdb762e24d0a284d1dc",
url = "https://github.com/google/brotli/archive/f9b8c02673c576a3e807edbf3a9328e9e7af6d7c.zip",
strip_prefix = "brotli-f9b8c02673c576a3e807edbf3a9328e9e7af6d7c",
sha256 = "8a517806d2b7c8505ba5c53934e7d7c70d341b68ffd268e9044d35b564a48828",
)
#
@ -77,7 +77,7 @@ http_archive(
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories", "yarn_install")
check_bazel_version("0.13.0")
check_bazel_version("0.14.0")
node_repositories(package_json = ["//:package.json"])
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")

View File

@ -52,8 +52,7 @@ export class BuildCleaner {
protected removeDir(dir: string) {
try {
if (shell.test('-d', dir)) {
// Undocumented signature (see https://github.com/shelljs/shelljs/pull/663).
(shell as any).chmod('-R', 'a+w', dir);
shell.chmod('-R', 'a+w', dir);
shell.rm('-rf', dir);
}
} catch (err) {

View File

@ -106,8 +106,7 @@ export class BuildCreator extends EventEmitter {
}
try {
// Undocumented signature (see https://github.com/shelljs/shelljs/pull/663).
(shell as any).chmod('-R', 'a-w', outputDir);
shell.chmod('-R', 'a-w', outputDir);
shell.rm('-f', inputFile);
resolve();
} catch (err) {

View File

@ -98,8 +98,7 @@ class Helper {
const prDir = this.getPrDir(pr, isPublic);
if (fs.existsSync(prDir)) {
// Undocumented signature (see https://github.com/shelljs/shelljs/pull/663).
(shell as any).chmod('-R', 'a+w', prDir);
shell.chmod('-R', 'a+w', prDir);
shell.rm('-rf', prDir);
}
}

View File

@ -8,7 +8,7 @@
"scripts": {
"prebuild": "yarn clean-dist",
"build": "tsc",
"build-watch": "yarn tsc --watch",
"build-watch": "yarn build --watch",
"clean-dist": "node --eval \"require('shelljs').rm('-rf', 'dist')\"",
"dev": "concurrently --kill-others --raw --success first \"yarn build-watch\" \"yarn test-watch\"",
"lint": "tslint --project tsconfig.json",
@ -33,7 +33,7 @@
"@types/jasmine": "^2.6.0",
"@types/jsonwebtoken": "^7.2.3",
"@types/node": "^8.0.30",
"@types/shelljs": "^0.7.4",
"@types/shelljs": "^0.8.0",
"@types/supertest": "^2.0.3",
"concurrently": "^3.5.0",
"nodemon": "^1.12.1",

View File

@ -69,9 +69,9 @@
"@types/express-serve-static-core" "*"
"@types/mime" "*"
"@types/shelljs@^0.7.4":
version "0.7.4"
resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.7.4.tgz#137b5f31306eaff4de120ffe5b9d74b297809cfc"
"@types/shelljs@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.0.tgz#0caa56b68baae4f68f44e0dd666ab30b098e3632"
dependencies:
"@types/glob" "*"
"@types/node" "*"

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

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

@ -15,6 +15,7 @@ export class ComposeMessageComponent {
@HostBinding('style.position') position = 'absolute';
details: string;
message: string;
sending = false;
constructor(private router: Router) {}

View File

@ -18,11 +18,11 @@ nav a {
border-radius: 4px;
}
nav a:visited, a:link {
color: #607D8B;
color: #607d8b;
}
nav a:hover {
color: #039be5;
background-color: #CFD8DC;
background-color: #cfd8dc;
}
nav a.active {
color: #039be5;

View File

@ -33,11 +33,11 @@ h4 {
color: #eee;
max-height: 120px;
min-width: 120px;
background-color: #607D8B;
background-color: #607d8b;
border-radius: 2px;
}
.module:hover {
background-color: #EEE;
background-color: #eee;
cursor: pointer;
color: #607d8b;
}

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

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

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

@ -933,29 +933,18 @@ As always, strive for consistency.
<a href="#toc">Back to top</a>
{@a 02-06}
{@a 05-02}
### Directive selectors
### Component selectors
#### Style 02-06
#### Style 05-02
<div class="s-rule do">
**Do** Use lower camel case for naming the selectors of directives.
</div>
<div class="s-why">
**Why?** Keeps the names of the properties defined in the directives that are bound to the view consistent with the attribute names.
**Do** use _dashed-case_ or _kebab-case_ for naming the element selectors of components.
</div>
@ -966,16 +955,40 @@ As always, strive for consistency.
**Why?** The Angular HTML parser is case sensitive and recognizes lower camel case.
**Why?** Keeps the element names consistent with the specification for [Custom Elements](https://www.w3.org/TR/custom-elements/).
</div>
<code-example path="styleguide/src/05-02/app/heroes/shared/hero-button/hero-button.component.avoid.ts" region="example" title="app/heroes/shared/hero-button/hero-button.component.ts">
</code-example>
<code-tabs>
<code-pane title="app/heroes/shared/hero-button/hero-button.component.ts" path="styleguide/src/05-02/app/heroes/shared/hero-button/hero-button.component.ts" region="example">
</code-pane>
<code-pane title="app/app.component.html" path="styleguide/src/05-02/app/app.component.html">
</code-pane>
</code-tabs>
<a href="#toc">Back to top</a>
{@a 02-07}
### Custom prefix for components
### Component custom prefix
#### Style 02-07
@ -1078,11 +1091,51 @@ For example, the prefix `toh` represents from **T**our **o**f **H**eroes and the
<a href="#toc">Back to top</a>
{@a 02-06}
### Directive selectors
#### Style 02-06
<div class="s-rule do">
**Do** Use lower camel case for naming the selectors of directives.
</div>
<div class="s-why">
**Why?** Keeps the names of the properties defined in the directives that are bound to the view consistent with the attribute names.
</div>
<div class="s-why-last">
**Why?** The Angular HTML parser is case sensitive and recognizes lower camel case.
</div>
<a href="#toc">Back to top</a>
{@a 02-08}
### Custom prefix for directives
### Directive custom prefix
#### Style 02-08
@ -3056,9 +3109,9 @@ module are referenced across the entire application.
**Avoid** providing services in shared modules. Services are usually
**Consider** _not_ providing services in shared modules. Services are usually
singletons that are provided once for the entire application or
in a particular feature module.
in a particular feature module. There are exceptions, however. For example, in the sample code that follows, notice that the `SharedModule` provides `FilterTextService`. This is acceptable here because the service is stateless;that is, the consumers of the service aren't impacted by new instances.
</div>
@ -3710,59 +3763,6 @@ A typical *lazy loaded folder* contains a *routing component*, its child compone
## Components
{@a 05-02}
### Component selector names
#### Style 05-02
<div class="s-rule do">
**Do** use _dashed-case_ or _kebab-case_ for naming the element selectors of components.
</div>
<div class="s-why-last">
**Why?** Keeps the element names consistent with the specification for [Custom Elements](https://www.w3.org/TR/custom-elements/).
</div>
<code-example path="styleguide/src/05-02/app/heroes/shared/hero-button/hero-button.component.avoid.ts" region="example" title="app/heroes/shared/hero-button/hero-button.component.ts">
</code-example>
<code-tabs>
<code-pane title="app/heroes/shared/hero-button/hero-button.component.ts" path="styleguide/src/05-02/app/heroes/shared/hero-button/hero-button.component.ts" region="example">
</code-pane>
<code-pane title="app/app.component.html" path="styleguide/src/05-02/app/app.component.html">
</code-pane>
</code-tabs>
<a href="#toc">Back to top</a>
{@a 05-03}
### Components as elements

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
@ -399,7 +398,7 @@ But a component is more than just its class.
A component interacts with the DOM and with other components.
The _class-only_ tests can tell you about class behavior.
They cannot tell you if the component is going to render properly,
respond to user input and gestures, or integrate with its parent and and child components.
respond to user input and gestures, or integrate with its parent and child components.
None of the _class-only_ tests above can answer key questions about how the
components actually behave on screen.
@ -2367,9 +2366,9 @@ The [override metadata object](#metadata-override-object) is a generic defined a
<code-example format="." language="javascript">
type MetadataOverride<T> = {
add?: T;
remove?: T;
set?: T;
add?: Partial<T>;
remove?: Partial<T>;
set?: Partial<T>;
};
</code-example>
@ -2725,9 +2724,9 @@ appropriate to the method, that is, the parameter of an `@NgModule`,
<code-example format="." language="javascript">
type MetadataOverride<T> = {
add?: T;
remove?: T;
set?: T;
add?: Partial<T>;
remove?: Partial<T>;
set?: Partial<T>;
};
</code-example>
@ -3379,11 +3378,11 @@ next to their corresponding helper files.
{@a q-e2e}
#### Why not rely on E2E tests of DOM integration?
The component DOM tests describe in this guide often require extensive setup and
advanced techniques where as the [class-only test](#component-class-testing)
were comparatively simple.
The component DOM tests described in this guide often require extensive setup and
advanced techniques whereas the [unit tests](#component-class-testing)
are comparatively simple.
Why not defer DOM integration tests to end-to-end (E2E) testing?
#### Why not defer DOM integration tests to end-to-end (E2E) testing?
E2E tests are great for high-level validation of the entire system.
But they can't give you the comprehensive test coverage that you'd expect from unit tests.
@ -3400,4 +3399,4 @@ accidental corruption of remote resources.
It can even be hard to navigate to the component you want to test.
Because of these many obstacles, you should test DOM interaction
with unit testing techniques as much as possible.
with unit testing techniques as much as possible.

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

View File

@ -12,5 +12,19 @@
"message": "Watch ng-conf live stream <br/>Apr 18th-20th, 2018",
"imageUrl": "generated/images/marketing/home/ng-conf.png",
"linkUrl": "https://www.ng-conf.org/livestream/"
},
{
"startDate": "2018-06-01",
"endDate": "2018-08-15",
"message": "Join us for Angular Mix<br/>October 10th-12th, 2018",
"imageUrl": "generated/images/marketing/home/angular-mix.png",
"linkUrl": "https://angularmix.com/"
},
{
"startDate": "2018-08-15",
"endDate": "2018-11-06",
"message": "Join us for Angular Connect<br/>November 6th-7th, 2018",
"imageUrl": "generated/images/marketing/home/angular-connect.png",
"linkUrl": "https://angularconnect.com/"
}
]

View File

@ -618,5 +618,32 @@
"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

@ -160,7 +160,7 @@
"desc": "Lightweight yet powerful IDE, perfectly equipped for complex client-side development and server-side development with Node.js",
"logo": "",
"rev": true,
"title": "Webstorm",
"title": "WebStorm",
"url": "https://www.jetbrains.com/webstorm/"
},
"ab3": {
@ -175,7 +175,13 @@
"rev": true,
"title": "Angular IDE by Webclipse",
"url": "https://www.genuitec.com/products/angular-ide"
}
},
"amexio-canvas": {
"desc": "Amexio Canvas is Drag and Drop Environment to create Fully Responsive Web and Smart Device HTML5/Angular Apps. Code will be auto generated and hot deployed by the Canvas for live testing. Out of the box 50+ Material Design Theme support. Commit your code to GitHub public or private repository.",
"rev": true,
"title": "Amexio Canvas Web Based Drag and Drop IDE by MetaMagic",
"url": "https://amexio.tech/"
}
}
},
"Tooling": {
@ -345,6 +351,13 @@
"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,
@ -370,6 +383,13 @@
"url": "http://www.amexio.tech/",
"logo": "http://www.amexio.org/amexio-logo.png"
},
"bm": {
"desc": "A lightweight Material Design library for Angular, based upon Google's Material Components for the Web",
"logo": "https://blox.src.zone/assets/bloxmaterial.03ecfe4fa0147a781487749dc1cc4580.svg",
"rev": true,
"title": "Blox Material",
"url": "https://github.com/src-zone/material"
},
"essentialjs2": {
"desc": "Essential JS 2 for Angular is a collection modern TypeScript based true Angular Components. It has support for Ahead Of Time (AOT) compilation and Tree-Shaking. All the components are developed from the ground up to be lightweight, responsive, modular and touch friendly.",
"rev": true,
@ -528,13 +548,6 @@
"title": "Pluralsight",
"url": "https://www.pluralsight.com/search?q=angular+2&categories=all"
},
"ab": {
"desc": "Take this introduction to Angular course, to learn the fundamentals in just two days, free of charge.",
"logo": "",
"rev": true,
"title": "Rangle.io",
"url": "https://rangle.io/services/javascript-training/training-angular1-angular2-with-ngupgrade/"
},
"ab3": {
"desc": "Angular courses hosted by Udemy",
"logo": "",

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

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

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

@ -82,31 +82,7 @@
};
</script>
<script>
if (window.document.documentMode) {
// polyfill IE11 in a blocking way
var s = document.createElement('script');
s.src = 'generated/ie-polyfills.min.js';
document.head.appendChild(s);
} else if (!Object.assign) {
// polyfill other non-evergreen browsers in a blocking way
var polyfillUrl = "https://cdn.polyfill.io/v2/polyfill.min.js?features=default,Array.prototype.find&flags=gated&unknown=polyfill";
// send a blocking XHR to fetch the polyfill
// then append it to the document so that its eval-ed synchronously
// this is required because the method used for IE is not reliable with other non-evergreen browsers
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", function() {
var s = document.createElement('script');
s.type = 'text/javascript';
var code = this.responseText;
s.appendChild(document.createTextNode(code));
document.head.appendChild(s);
});
xhr.open("GET", polyfillUrl, false);
xhr.send();
}
</script>
<script nomodule src="generated/ie-polyfills.min.js"></script>
<script>
//load CE polyfill

View File

@ -27,7 +27,7 @@ aio-shell.page-docs {
}
.sidenav-content button {
min-width: 50px;
min-width: 24px;
}
#guide-change-log h2::before {

View File

@ -93,15 +93,21 @@ aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
// HOME NAV-LINK
.nav-link.home img {
position: relative;
margin-top: -21px;
top: 12px;
height: 40px;
.nav-link.home {
cursor: pointer;
margin: 0 16px 0 0;
padding: 21px 0;
@media(max-width: 992px) {
&:hover {
transform: scale(1.1);
img {
position: relative;
margin-top: -21px;
top: 12px;
height: 40px;
@media(max-width: 992px) {
&:hover {
transform: scale(1.1);
}
}
}
}
@ -143,10 +149,6 @@ aio-top-menu {
padding: 24px 16px;
cursor: pointer;
&.home{
margin-right: 20px;
}
&:focus {
background: rgba($white, 0.15);
border-radius: 4px;

View File

@ -3,8 +3,9 @@ code-example, code-tabs {
display: block;
}
code-example,
code-tabs mat-tab-body {
code-example {
&:not(.no-box) {
background-color: rgba($backgroundgray, 0.2);
border: 0.5px solid $lightgray;
@ -27,6 +28,16 @@ code-tabs mat-tab-body {
}
}
code-example, code-tabs {
.mat-card {
padding: 0;
border-radius: 5px;
}
code {
overflow: auto;
}
}
// TERMINAL / SHELL TEXT STYLES
code-example.code-shell, code-example[language=sh], code-example[language=bash] {
@ -86,8 +97,9 @@ aio-code pre {
.copy-button {
position: absolute;
top: -8px;
right: -32px;
top: -7px;
right: -19px;
padding: 0;
color: $blue-grey-200;
background-color: transparent;
@ -106,6 +118,9 @@ aio-code pre {
}
.code-tab-group .mat-tab-label {
&:hover {
background: rgba(black, 0.04);
}
white-space: nowrap;
}

View File

@ -56,8 +56,8 @@ export class MockTocService {
}
export class MockElementsLoader {
loadContainingCustomElements =
jasmine.createSpy('MockElementsLoader#loadContainingCustomElements');
loadContainedCustomElements =
jasmine.createSpy('MockElementsLoader#loadContainedCustomElements');
}
@NgModule({

View File

@ -119,6 +119,17 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage])
addNotYetDocumentedProperty.docTypes = API_DOC_TYPES;
})
.config(function(mergeDecoratorDocs) {
mergeDecoratorDocs.propertiesToMerge = [
'shortDescription',
'description',
'security',
'deprecated',
'see',
'usageNotes',
];
})
.config(function(checkContentRules, EXPORT_DOC_TYPES) {
// Min length rules
const createMinLengthRule = require('./content-rules/minLength');

View File

@ -1,7 +1,7 @@
module.exports = function generateApiListDoc() {
return {
$runAfter: ['extra-docs-added'],
$runAfter: ['extra-docs-added', 'computeStability'],
$runBefore: ['rendering-docs'],
outputFolder: null,
$validate: {outputFolder: {presence: true}},

View File

@ -13,7 +13,7 @@ describe('generateApiListDoc processor', () => {
it('should run after the correct processor', () => {
const processor = processorFactory();
expect(processor.$runAfter).toEqual(['extra-docs-added']);
expect(processor.$runAfter).toEqual(['extra-docs-added', 'computeStability']);
});
it('should run before the correct processor', () => {

View File

@ -1,3 +1,5 @@
const {mergeProperties} = require('../../helpers/utils');
/**
* Decorators in the Angular code base are made up from three code items:
*
@ -47,6 +49,7 @@ module.exports = function mergeDecoratorDocs(log) {
return {
$runAfter: ['processing-docs'],
$runBefore: ['docs-processed'],
propertiesToMerge: [],
makeDecoratorCalls: [
{type: '', description: 'toplevel', functionName: 'makeDecorator'},
{type: 'Prop', description: 'property', functionName: 'makePropDecorator'},
@ -55,6 +58,7 @@ module.exports = function mergeDecoratorDocs(log) {
$process: function(docs) {
var makeDecoratorCalls = this.makeDecoratorCalls;
var propertiesToMerge = this.propertiesToMerge;
var docsToMerge = Object.create(null);
docs.forEach(function(doc) {
@ -88,9 +92,9 @@ module.exports = function mergeDecoratorDocs(log) {
log.debug(
'mergeDecoratorDocs: merging', doc.name, 'into', decoratorDoc.name,
callMember.description.substring(0, 50));
// Merge the documentation found in this call signature into the original decorator
decoratorDoc.description = callMember.description;
decoratorDoc.usageNotes = callMember.usageNotes;
mergeProperties(decoratorDoc, callMember, propertiesToMerge);
// remove doc from its module doc's exports
doc.moduleDoc.exports =

View File

@ -9,17 +9,21 @@ describe('mergeDecoratorDocs processor', () => {
const injector = dgeni.configureInjector();
processor = injector.get('mergeDecoratorDocs');
// Note that we do not include usageNotes in the tests.
processor.propertiesToMerge = ['description', 'shortDescription'];
moduleDoc = {};
decoratorDoc = {
name: 'Component',
docType: 'const',
description: 'A description of the metadata for the Component decorator',
shortDescription: 'decorator - short description',
description: 'decorator - description',
symbol: {
valueDeclaration: { initializer: { expression: { text: 'makeDecorator' }, arguments: [{ text: 'X' }] } }
},
members: [
{ name: 'templateUrl', description: 'A description of the templateUrl property' }
{ name: 'templateUrl', description: 'templateUrl - description' }
],
moduleDoc
};
@ -27,15 +31,15 @@ describe('mergeDecoratorDocs processor', () => {
metadataDoc = {
name: 'ComponentDecorator',
docType: 'interface',
description: 'A description of the interface for the call signature for the Component decorator',
description: 'call interface - description',
members: [
{
isCallMember: true,
description: 'The actual description of the call signature',
usageNotes: 'Use it like this...'
description: 'call interface - call member - description',
usageNotes: 'call interface - call member - usageNotes',
},
{
description: 'Some other member'
description: 'call interface - non call member - description'
}
],
moduleDoc
@ -65,10 +69,13 @@ describe('mergeDecoratorDocs processor', () => {
expect(decoratorDoc.decoratorType).toEqual('X');
});
it('should copy across properties from the call signature doc', () => {
it('should copy across specified properties from the call signature doc', () => {
processor.$process([decoratorDoc, metadataDoc, otherDoc]);
expect(decoratorDoc.description).toEqual('The actual description of the call signature');
expect(decoratorDoc.usageNotes).toEqual('Use it like this...');
expect(decoratorDoc.description).toEqual('call interface - call member - description');
// Since usageNotes is not in `propertiesToMerge` it will not get copied over in these tests.
expect(decoratorDoc.usageNotes).toBeUndefined();
// Since `shortDescription` does not exist on the call-member this will not get overridden.
expect(decoratorDoc.shortDescription).toEqual('decorator - short description');
});
it('should remove the metadataDoc from the module exports', () => {

View File

@ -34,6 +34,7 @@ module.exports = function checkContentRules(log, createDocMessage) {
$runAfter: ['tags-extracted'],
$runBefore: ['processing-docs'],
$process(docs) {
const logMessage = this.failOnContentErrors ? log.error.bind(log) : log.warn.bind(log);
const errors = [];
docs.forEach(doc => {
const docErrors = [];
@ -55,10 +56,10 @@ module.exports = function checkContentRules(log, createDocMessage) {
});
if (errors.length) {
log.error('Content contains errors');
logMessage('Content contains errors');
errors.forEach(docError => {
const errors = docError.errors.join('\n ');
log.error(createDocMessage(errors + '\n ', docError.doc));
logMessage(createDocMessage(errors + '\n ', docError.doc));
});
if (this.failOnContentErrors) {
throw new Error('Stopping due to content errors.');

View File

@ -67,10 +67,11 @@ describe('checkContentRules processor', function() {
expect(descriptionSpy3).toHaveBeenCalledWith(docs[0], 'description', 'test doc 1');
});
it('should log errors if the rule returns error messages', () => {
it('should log warnings if the rule returns error messages and `failOnContentErrors` is false', () => {
const nameSpy1 = jasmine.createSpy('name 1').and.returnValue('name error message');
const descriptionSpy1 = jasmine.createSpy('description 1').and.returnValue('description error message');
processor.failOnContentErrors = false;
processor.docTypeRules = {
'test1': {
name: [nameSpy1],
@ -85,6 +86,32 @@ describe('checkContentRules processor', function() {
processor.$process(docs);
expect(logger.warn).toHaveBeenCalledTimes(2);
expect(logger.warn).toHaveBeenCalledWith('Content contains errors');
expect(logger.warn).toHaveBeenCalledWith(`name error message
description error message
- doc "test-1" (test1) `);
});
it('should log errors and then throw if `failOnContentErrors` is true and errors are found', () => {
const nameSpy1 = jasmine.createSpy('name 1').and.returnValue('name error message');
const descriptionSpy1 = jasmine.createSpy('description 1').and.returnValue('description error message');
processor.failOnContentErrors = true;
processor.docTypeRules = {
'test1': {
name: [nameSpy1],
description: [descriptionSpy1]
}
};
const docs = [
{ docType: 'test1', description: 'test doc 1', name: 'test-1' },
{ docType: 'test2', description: 'test doc 2', name: 'test-2' }
];
expect(() => processor.$process(docs)).toThrowError('Stopping due to content errors.');
expect(logger.error).toHaveBeenCalledTimes(2);
expect(logger.error).toHaveBeenCalledWith('Content contains errors');
expect(logger.error).toHaveBeenCalledWith(`name error message
@ -92,17 +119,4 @@ describe('checkContentRules processor', function() {
- doc "test-1" (test1) `);
});
it('should throw an error if `failOnContentErrors` is true and errors are found', () => {
const errorRule = jasmine.createSpy('error rule').and.returnValue('some error');
processor.docTypeRules = {
'test': { description: [errorRule] }
};
processor.failOnContentErrors = true;
const docs = [
{ docType: 'test', description: 'test doc' },
];
expect(() => processor.$process(docs)).toThrowError('Stopping due to content errors.');
});
});

View File

@ -96,5 +96,19 @@ module.exports = {
attrMap[key] === false ? '' :
attrMap[key] === true ? ` ${key}` :
` ${key}="${attrMap[key].replace(/"/g, '&quot;')}"`).join('');
}
},
/**
* Merge the specified properties from the source to the target document
* @param {Document} target The document to receive the properties from the source
* @param {Document} source The document from which to get the properties to merge
* @param {string[]} properties A collection of the names of the properties to merge
*/
mergeProperties(target, source, properties) {
properties.forEach(property => {
if (source.hasOwnProperty(property)) {
target[property] = source[property];
}
});
},
};

View File

@ -1,4 +1,4 @@
const { mapObject, parseAttributes, renderAttributes } = require('./utils');
const { mergeProperties, mapObject, parseAttributes, renderAttributes } = require('./utils');
describe('utils', () => {
describe('mapObject', () => {
@ -96,4 +96,34 @@ describe('utils', () => {
expect(renderAttributes({ })).toEqual('');
});
});
describe('mergeProperties', () => {
it('should write specified properties from the source to the target', () => {
const source = { a: 1, b: 2, c: 3 };
const target = { };
mergeProperties(target, source, ['a', 'b']);
expect(target).toEqual({ a: 1, b: 2 });
});
it('should not overwrite target properties that are not specified', () => {
const source = { a: 1, b: 2, c: 3 };
const target = { b: 10 };
mergeProperties(target, source, ['a']);
expect(target).toEqual({ a: 1, b: 10 });
});
it('should not overwrite target properties that are specified but do not exist in the source', () => {
const source = { a: 1 };
const target = { b: 10 };
mergeProperties(target, source, ['a', 'b']);
expect(target).toEqual({ a: 1, b: 10 });
});
it('should overwrite target properties even if they are `undefined` in the source', () => {
const source = { a: undefined };
const target = { a: 10 };
mergeProperties(target, source, ['a']);
expect(target).toEqual({ a: undefined });
});
});
});

View File

@ -37,7 +37,7 @@ That repository defines dependencies on specific versions of
all the tools. You can run the tools Bazel installed, for
example rather than `yarn install` (which depends on whatever
version you have installed on your machine), you can
`bazel run @yarn//:yarn`.
`bazel run @nodejs//:yarn`.
Bazel accepts a lot of options. We check in some options in the
`.bazelrc` file. See the [bazelrc doc]. For example, if you don't

View File

@ -123,6 +123,16 @@ You can automatically format your code by running:
$ gulp format
```
There is a handy [clang-format extension](https://marketplace.visualstudio.com/items?itemName=xaver.clang-format)
for Visual Studio Code. Use the following settings to format your code when you save a file:
```json
{
"editor.formatOnSave": true,
"clang-format.executable": "${workspaceRoot}/node_modules/.bin/clang-format"
}
```
## Linting/verifying your source code
You can check that your code is properly formatted and adheres to coding style by running:

View File

@ -12,7 +12,7 @@ The owner of the component is then responsible for the secondary / component-lev
The caretaker should be able to determine which component the issue belongs to.
The components have a clear piece of source code associated with it within the `/packages/` folder of this repo.
* `comp: aio` - the angular.io application
* `comp: docs-infra` - the angular.io application
* `comp: animations`
* `comp: bazel` - @angular/bazel rules
* `comp: benchpress`

View File

@ -1,3 +0,0 @@
# Workaround https://github.com/bazelbuild/bazel/issues/3645
# Limit Bazel to consuming 3072K of RAM
build --local_resources=3072,2.0,1.0

View File

@ -17,3 +17,25 @@ filegroup(
],
),
)
ANGULAR_TESTING = [
"node_modules/@angular/*/bundles/*-testing.umd.js",
# We use AOT, so the compiler and the dynamic platform-browser should be
# visible only in tests
"node_modules/@angular/compiler/bundles/*.umd.js",
"node_modules/@angular/platform-browser-dynamic/bundles/*.umd.js",
]
filegroup(
name = "angular_bundles",
srcs = glob(
["node_modules/@angular/*/bundles/*.umd.js"],
exclude = ANGULAR_TESTING,
),
)
filegroup(
name = "angular_test_bundles",
testonly = 1,
srcs = glob(ANGULAR_TESTING),
)

View File

@ -1,24 +1,77 @@
workspace(name = "bazel_integration_test")
#
# Download Bazel toolchain dependencies as needed by build actions
#
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",
)
load("@build_bazel_rules_nodejs//:defs.bzl", "node_repositories")
http_archive(
name = "io_bazel_rules_webtesting",
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/0.15.0.zip",
strip_prefix = "rules_typescript-0.15.0",
sha256 = "1aa75917330b820cb239b3c10a936a10f0a46fe215063d4492dd76dc6e1616f4",
)
http_archive(
name = "io_bazel_rules_go",
url = "https://github.com/bazelbuild/rules_go/releases/download/0.11.0/rules_go-0.11.0.tar.gz",
sha256 = "f70c35a8c779bb92f7521ecb5a1c6604e9c3edd431e50b6376d7497abc8ad3c1",
)
http_archive(
name = "io_bazel_rules_sass",
url = "https://github.com/bazelbuild/rules_sass/archive/0.1.0.zip",
strip_prefix = "rules_sass-0.1.0",
sha256 = "b243c4d64f054c174051785862ab079050d90b37a1cef7da93821c6981cb9ad4",
)
#
# Load and install our dependencies downloaded above.
#
load("@build_bazel_rules_nodejs//:defs.bzl", "check_bazel_version", "node_repositories")
check_bazel_version("0.14.0")
node_repositories(package_json = ["//:package.json"])
local_repository(
name = "build_bazel_rules_typescript",
path = "node_modules/@bazel/typescript",
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
go_rules_dependencies()
go_register_toolchains()
load("@io_bazel_rules_webtesting//web:repositories.bzl", "browser_repositories", "web_test_repositories")
web_test_repositories()
browser_repositories(
chromium = True,
firefox = True,
)
load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace")
ts_setup_workspace()
load("@io_bazel_rules_sass//sass:sass_repositories.bzl", "sass_repositories")
sass_repositories()
#
# Point Bazel to WORKSPACEs that live in subdirectories
#
local_repository(
name = "angular",
path = "node_modules/@angular/bazel",
@ -28,14 +81,3 @@ local_repository(
name = "rxjs",
path = "node_modules/rxjs/src",
)
http_archive(
name = "io_bazel_rules_sass",
url = "https://github.com/bazelbuild/rules_sass/archive/0.0.3.zip",
strip_prefix = "rules_sass-0.0.3",
sha256 = "8fa98e7b48a5837c286a1ea254b5a5c592fced819ee9fe4fdd759768d97be868",
)
load("@io_bazel_rules_sass//sass:sass.bzl", "sass_repositories")
sass_repositories()

View File

@ -9,17 +9,26 @@
"@angular/compiler": "file:../../dist/packages-dist/compiler",
"@angular/core": "file:../../dist/packages-dist/core",
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",
"rxjs": "file:../../node_modules/rxjs",
"zone.js": "file:../../node_modules/zone.js"
},
"devDependencies": {
"@angular/bazel": "file:../../dist/packages-dist/bazel",
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
"typescript": "file:../../node_modules/typescript",
"@types/source-map": "0.5.1"
"@types/jasmine": "2.8.7",
"@types/source-map": "0.5.1",
"concurrently": "3.5.1",
"http-server": "0.11.1",
"protractor": "file:../../node_modules/protractor",
"typescript": "file:../../node_modules/typescript"
},
"scripts": {
"postinstall": "ngc -p angular.tsconfig.json",
"test": "bazel build //... --noshow_progress"
"postinstall": "concurrently \"webdriver-manager update $CHROMEDRIVER_VERSION_ARG\" \"ngc -p angular.tsconfig.json\"",
"test": "bazel build //... --noshow_progress && yarn e2e",
"pree2e": "bazel build test/...",
"e2e": "yarn e2e-prodserver && yarn e2e-devserver",
"e2e-prodserver": "concurrently \"bazel run //src:prodserver\" \"while ! nc -z 127.0.0.1 5432; do sleep 1; done && protractor\" --kill-others --success first",
"e2e-devserver": "concurrently \"bazel run //src:devserver\" \"while ! nc -z 127.0.0.1 5432; do sleep 1; done && protractor\" --kill-others --success first"
}
}

View File

@ -0,0 +1,9 @@
exports.config = {
specs: ['bazel-bin/test/e2e/*.spec.js'],
capabilities: {browserName: 'chrome', chromeOptions: {args: ['--no-sandbox']}},
directConnect: true,
baseUrl: 'http://localhost:5432/',
framework: 'jasmine',
getPageTimeout: 60 * 1000,
allScriptsTimeout: 60 * 1000,
};

View File

@ -9,3 +9,52 @@ ng_module(
tsconfig = ":tsconfig.json",
deps = ["//src/hello-world"],
)
load("@build_bazel_rules_typescript//:defs.bzl", "ts_devserver")
ts_devserver(
name = "devserver",
additional_root_paths = [
"bazel_integration_test/node_modules/zone.js/dist",
],
entry_module = "bazel_integration_test/src/main",
scripts = ["//:angular_bundles"],
serving_path = "/bundle.min.js",
static_files = [
"//:node_modules/zone.js/dist/zone.min.js",
"index.html",
],
deps = ["//src"],
)
load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary", "rollup_bundle")
rollup_bundle(
name = "bundle",
entry_point = "src/main",
deps = ["//src"],
)
# Needed because the prodserver only loads static files that appear under this
# package.
genrule(
name = "zone.js",
srcs = ["//:node_modules/zone.js/dist/zone.min.js"],
outs = ["zone.min.js"],
cmd = "cp $< $@",
)
nodejs_binary(
name = "prodserver",
args = [
"./src",
"-p",
"5432",
],
data = [
"index.html",
":bundle",
":zone.js",
],
entry_point = "http-server/bin/http-server",
)

View File

@ -0,0 +1,19 @@
import {Component} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {Observable} from 'rxjs';
import {map, startWith} from 'rxjs/operators';
@Component({
selector: 'app-component',
template: `
<hello-world-app></hello-world-app>
<div>The current time is {{ time$ | async }}</div>
`})
export class AppComponent {
constructor(private http: HttpClient) {
}
time$ = this.http.get('http://worldclockapi.com/api/json/pst/now').pipe(
map((result: any) => result.currentDateTime),
startWith(['...']));
}

View File

@ -1,9 +1,14 @@
import {HelloWorldModule} from './hello-world/hello-world.module';
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {HttpClientModule} from '@angular/common/http';
import {CommonModule} from '@angular/common';
import {AppComponent} from './app.component';
import {HelloWorldModule} from './hello-world/hello-world.module';
@NgModule({
imports: [BrowserModule, HelloWorldModule]
imports: [CommonModule, BrowserModule, HttpClientModule, HelloWorldModule],
declarations: [AppComponent],
bootstrap: [AppComponent],
})
export class AppModule {}

View File

@ -2,6 +2,7 @@ package(default_visibility = ["//visibility:public"])
load("@angular//:index.bzl", "ng_module", "ng_package")
load("@io_bazel_rules_sass//sass:sass.bzl", "sass_binary")
load("@build_bazel_rules_typescript//:defs.bzl", "ts_library", "ts_web_test_suite")
sass_binary(
name = "hello-world-styles",
@ -10,8 +11,11 @@ sass_binary(
ng_module(
name = "hello-world",
srcs = glob(["*.ts"]),
assets = [":hello-world-styles.css"],
srcs = glob(
["*.ts"],
exclude = ["*.spec.ts"],
),
assets = [":hello-world-styles"],
tsconfig = "//src:tsconfig.json",
# FIXME(alexeagle): the rxjs dep should come from Angular, but if we use the
# npm distro of angular there is no ts_library rule to propagate the dep.
@ -23,3 +27,24 @@ ng_package(
entry_point = "src/hello-world/index.js",
deps = [":hello-world"],
)
ts_library(
name = "test_lib",
testonly = 1,
srcs = glob(["*.spec.ts"]),
deps = [":hello-world"],
)
ts_web_test_suite(
name = "test",
bootstrap = ["//:node_modules/zone.js/dist/zone-testing-bundle.js"],
browsers = [
"@io_bazel_rules_webtesting//browsers:chromium-local",
"@io_bazel_rules_webtesting//browsers:firefox-local",
],
deps = [
":test_lib",
"//:angular_bundles",
"//:angular_test_bundles",
],
)

View File

@ -0,0 +1,45 @@
import {DebugElement} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing';
import {HelloWorldComponent} from './hello-world.component';
import {HelloWorldModuleNgSummary} from './hello-world.module.ngsummary';
// TODO(alexeagle): this helper should be in @angular/platform-browser-dynamic/testing
try {
TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
} catch {
// Ignore exceptions when calling it multiple times.
}
describe('BannerComponent (inline template)', () => {
let comp: HelloWorldComponent;
let fixture: ComponentFixture<HelloWorldComponent>;
let el: HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [HelloWorldComponent], // declare the test component
aotSummaries: HelloWorldModuleNgSummary,
});
TestBed.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(HelloWorldComponent);
comp = fixture.componentInstance;
el = fixture.debugElement.query(By.css('div')).nativeElement;
});
it('should display original title', () => {
fixture.detectChanges();
expect(el.textContent).toContain(comp.name);
});
it('should display a different test title', () => {
comp.name = 'Test';
fixture.detectChanges();
expect(el.textContent).toContain('Test');
});
});

View File

@ -8,7 +8,7 @@ import {Component, NgModule} from '@angular/core';
<input type="text" [value]="name" (input)="name = $event.target.value"/>
`,
// TODO: might be better to point to .scss so this looks valid at design-time
styleUrls: ['./hello-world-styles.css'],
styleUrls: ['./hello-world.component.css'],
})
export class HelloWorldComponent {
name: string = 'world';

View File

@ -3,6 +3,6 @@ import {NgModule} from '@angular/core';
@NgModule({
declarations: [HelloWorldComponent],
bootstrap: [HelloWorldComponent],
exports: [HelloWorldComponent],
})
export class HelloWorldModule {}

View File

@ -0,0 +1,12 @@
<!doctype html>
<html>
<head>
<title>Bazel Integration Test</title>
</head>
<body>
<app-component></app-component>
<script src="/zone.min.js"></script>
<script src="/bundle.min.js"></script>
</body>
</html>

View File

@ -0,0 +1,7 @@
load("@build_bazel_rules_typescript//:defs.bzl", "ts_library")
ts_library(
name = "e2e",
testonly = 1,
srcs = glob(["*.ts"]),
)

View File

@ -0,0 +1,12 @@
import {browser, by, element, ExpectedConditions} from 'protractor';
describe('angular example application', () => {
it('should display: Hello World!', (done) => {
browser.get('');
const div = element(by.css('div'));
div.getText().then(t => expect(t).toEqual(`Hello world!`));
element(by.css('input')).sendKeys('!');
div.getText().then(t => expect(t).toEqual(`Hello world!!`));
done();
});
});

View File

@ -0,0 +1,10 @@
# Make TypeScript and Angular compilation fast, by keeping a few copies of the
# compiler running as daemons, and cache SourceFile AST's to reduce parse time.
build --strategy=TypeScriptCompile=worker
build --strategy=AngularTemplateCompile=worker
test --test_output=errors
# Workaround https://github.com/bazelbuild/bazel/issues/3645
# Limit Bazel to consuming 3072K of RAM
build --local_resources=3072,2.0,1.0

File diff suppressed because it is too large Load Diff

View File

@ -128,7 +128,7 @@
chokidar "^1.4.2"
minimist "^1.2.0"
reflect-metadata "^0.1.2"
tsickle "^0.27.2"
tsickle "^0.29.0"
"@angular/compiler@file:../../dist/packages-dist/compiler":
version "6.0.0-rc.5"
@ -5599,14 +5599,8 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
rxjs@^6.0.0-beta.3, rxjs@^6.0.0-turbo-rc.4:
rxjs@^6.0.0-beta.3, rxjs@^6.0.0-turbo-rc.4, "rxjs@file:../../node_modules/rxjs":
version "6.0.0"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.0.0.tgz#d647e029b5854617f994c82fe57a4c6747b352da"
dependencies:
tslib "^1.9.0"
"rxjs@file:../../node_modules/rxjs":
version "6.0.0-uncanny-rc.7"
dependencies:
tslib "^1.9.0"
@ -6538,9 +6532,9 @@ tsconfig@^7.0.0:
strip-bom "^3.0.0"
strip-json-comments "^2.0.0"
tsickle@^0.27.2:
version "0.27.2"
resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.27.2.tgz#f33d46d046f73dd5c155a37922e422816e878736"
tsickle@^0.29.0:
version "0.29.0"
resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.29.0.tgz#812806554bb46c1aa16eb0fe2a051da95ca8f5a4"
dependencies:
minimist "^1.2.0"
mkdirp "^0.5.1"

View File

@ -3,45 +3,45 @@
"@angular/animations@file:../../dist/packages-dist/animations":
version "6.0.0-beta.7-54970933bb"
version "6.0.0-rc.5"
dependencies:
tslib "^1.9.0"
"@angular/common@file:../../dist/packages-dist/common":
version "6.0.0-beta.7-54970933bb"
version "6.0.0-rc.5"
dependencies:
tslib "^1.9.0"
"@angular/compiler-cli@file:../../dist/packages-dist/compiler-cli":
version "6.0.0-beta.7-54970933bb"
version "6.0.0-rc.5"
dependencies:
chokidar "^1.4.2"
minimist "^1.2.0"
reflect-metadata "^0.1.2"
tsickle "^0.27.2"
tsickle "^0.29.0"
"@angular/compiler@file:../../dist/packages-dist/compiler":
version "6.0.0-beta.7-54970933bb"
version "6.0.0-rc.5"
dependencies:
tslib "^1.9.0"
"@angular/core@file:../../dist/packages-dist/core":
version "6.0.0-beta.7-54970933bb"
version "6.0.0-rc.5"
dependencies:
tslib "^1.9.0"
"@angular/platform-browser-dynamic@file:../../dist/packages-dist/platform-browser-dynamic":
version "6.0.0-beta.7-54970933bb"
version "6.0.0-rc.5"
dependencies:
tslib "^1.9.0"
"@angular/platform-browser@file:../../dist/packages-dist/platform-browser":
version "6.0.0-beta.7-54970933bb"
version "6.0.0-rc.5"
dependencies:
tslib "^1.9.0"
"@angular/platform-server@file:../../dist/packages-dist/platform-server":
version "6.0.0-beta.7-54970933bb"
version "6.0.0-rc.5"
dependencies:
domino "^2.0.1"
tslib "^1.9.0"
@ -1881,7 +1881,9 @@ rx@4.1.0:
resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
"rxjs@file:../../node_modules/rxjs":
version "6.0.0-alpha.3"
version "6.0.0"
dependencies:
tslib "^1.9.0"
safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.1"
@ -2223,9 +2225,9 @@ tree-kill@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36"
tsickle@^0.27.2:
version "0.27.2"
resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.27.2.tgz#f33d46d046f73dd5c155a37922e422816e878736"
tsickle@^0.29.0:
version "0.29.0"
resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.29.0.tgz#812806554bb46c1aa16eb0fe2a051da95ca8f5a4"
dependencies:
minimist "^1.2.0"
mkdirp "^0.5.1"
@ -2247,7 +2249,7 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
"typescript@file:../../node_modules/typescript":
version "2.6.2"
version "2.7.2"
ua-parser-js@0.7.12:
version "0.7.12"
@ -2454,4 +2456,4 @@ yeast@0.1.2:
resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
"zone.js@file:../../node_modules/zone.js":
version "0.8.20"
version "0.8.26"

View File

@ -3,40 +3,40 @@
"@angular/animations@file:../../dist/packages-dist/animations":
version "6.0.0-rc.1-461a08bdc1"
version "6.0.0-rc.5"
dependencies:
tslib "^1.9.0"
"@angular/common@file:../../dist/packages-dist/common":
version "6.0.0-rc.1-461a08bdc1"
version "6.0.0-rc.5"
dependencies:
tslib "^1.9.0"
"@angular/compiler-cli@file:../../dist/packages-dist/compiler-cli":
version "6.0.0-rc.1-461a08bdc1"
version "6.0.0-rc.5"
dependencies:
chokidar "^1.4.2"
minimist "^1.2.0"
reflect-metadata "^0.1.2"
tsickle "^0.27.2"
tsickle "^0.29.0"
"@angular/compiler@file:../../dist/packages-dist/compiler":
version "6.0.0-rc.1-461a08bdc1"
version "6.0.0-rc.5"
dependencies:
tslib "^1.9.0"
"@angular/core@file:../../dist/packages-dist/core":
version "6.0.0-rc.1-461a08bdc1"
version "6.0.0-rc.5"
dependencies:
tslib "^1.9.0"
"@angular/platform-browser@file:../../dist/packages-dist/platform-browser":
version "6.0.0-rc.1-461a08bdc1"
version "6.0.0-rc.5"
dependencies:
tslib "^1.9.0"
"@angular/platform-server@file:../../dist/packages-dist/platform-server":
version "6.0.0-rc.1-461a08bdc1"
version "6.0.0-rc.5"
dependencies:
domino "^2.0.1"
tslib "^1.9.0"
@ -1835,7 +1835,7 @@ rx@4.1.0:
resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
"rxjs@file:../../node_modules/rxjs":
version "6.0.0-rc.0"
version "6.0.0"
dependencies:
tslib "^1.9.0"
@ -2180,9 +2180,9 @@ tree-kill@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36"
tsickle@^0.27.2:
version "0.27.2"
resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.27.2.tgz#f33d46d046f73dd5c155a37922e422816e878736"
tsickle@^0.29.0:
version "0.29.0"
resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.29.0.tgz#812806554bb46c1aa16eb0fe2a051da95ca8f5a4"
dependencies:
minimist "^1.2.0"
mkdirp "^0.5.1"
@ -2424,4 +2424,4 @@ yeast@0.1.2:
resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
"zone.js@file:../../node_modules/zone.js":
version "0.8.20"
version "0.8.26"

View File

@ -92,6 +92,16 @@ module.exports = function(config) {
'**/*.js': ['sourcemap'],
},
// Bazel inter-op: Allow tests to request resources from either
// /base/node_modules/path/to/thing
// or
// /base/angular/node_modules/path/to/thing
// This can be removed when all karma tests are run under Bazel, then we
// don't need this entire config file.
proxies: {
'/base/angular/': '/base/',
},
reporters: ['internal-angular'],
sauceLabs: {
testName: 'Angular2',

View File

@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "6.0.3",
"version": "6.0.5",
"private": true,
"branchPattern": "2.0.*",
"description": "Angular - a web framework for modern web apps",
@ -110,7 +110,7 @@
"source-map": "0.5.7",
"source-map-support": "0.4.18",
"systemjs": "0.18.10",
"tsickle": "^0.27.2",
"tsickle": "^0.29.0",
"tslint": "5.7.0",
"tslint-eslint-rules": "4.1.1",
"tsutils": "2.20.0",

View File

@ -38,8 +38,8 @@ export class AnimationTransitionFactory {
build(
driver: AnimationDriver, element: any, currentState: any, nextState: any,
enterClassName: string, leaveClassName: string, currentOptions?: AnimationOptions,
nextOptions?: AnimationOptions,
subInstructions?: ElementInstructionMap): AnimationTransitionInstruction {
nextOptions?: AnimationOptions, subInstructions?: ElementInstructionMap,
skipAstBuild?: boolean): AnimationTransitionInstruction {
const errors: any[] = [];
const transitionAnimationParams = this.ast.options && this.ast.options.params || EMPTY_OBJECT;
@ -55,9 +55,10 @@ export class AnimationTransitionFactory {
const animationOptions = {params: {...transitionAnimationParams, ...nextAnimationParams}};
const timelines = buildAnimationTimelines(
driver, element, this.ast.animation, enterClassName, leaveClassName, currentStateStyles,
nextStateStyles, animationOptions, subInstructions, errors);
const timelines = skipAstBuild ? [] : buildAnimationTimelines(
driver, element, this.ast.animation, enterClassName,
leaveClassName, currentStateStyles, nextStateStyles,
animationOptions, subInstructions, errors);
let totalTime = 0;
timelines.forEach(tl => { totalTime = Math.max(tl.duration + tl.delay, totalTime); });

View File

@ -10,6 +10,10 @@ import {AUTO_STYLE, AnimationEvent, AnimationPlayer, NoopAnimationPlayer, ɵAnim
import {AnimationStyleNormalizer} from '../../src/dsl/style_normalization/animation_style_normalizer';
import {AnimationDriver} from '../../src/render/animation_driver';
export function isBrowser() {
return (typeof window !== 'undefined' && typeof window.document !== 'undefined');
}
export function optimizeGroupPlayer(players: AnimationPlayer[]): AnimationPlayer {
switch (players.length) {
case 0:
@ -138,7 +142,7 @@ let _query: (element: any, selector: string, multi: boolean) => any[] =
return [];
};
if (typeof Element != 'undefined') {
if (isBrowser()) {
// this is well supported in all browsers
_contains = (elm1: any, elm2: any) => { return elm1.contains(elm2) as boolean; };

View File

@ -104,7 +104,6 @@ export class StateValue {
export const VOID_VALUE = 'void';
export const DEFAULT_STATE_VALUE = new StateValue(VOID_VALUE);
export const DELETED_STATE_VALUE = new StateValue('DELETED');
export class AnimationTransitionNamespace {
public players: TransitionAnimationPlayer[] = [];
@ -208,8 +207,6 @@ export class AnimationTransitionNamespace {
if (!fromState) {
fromState = DEFAULT_STATE_VALUE;
} else if (fromState === DELETED_STATE_VALUE) {
return player;
}
const isRemoval = toState.value === VOID_VALUE;
@ -743,10 +740,10 @@ export class TransitionAnimationEngine {
private _buildInstruction(
entry: QueueInstruction, subTimelines: ElementInstructionMap, enterClassName: string,
leaveClassName: string) {
leaveClassName: string, skipBuildAst?: boolean) {
return entry.transition.build(
this.driver, entry.element, entry.fromState.value, entry.toState.value, enterClassName,
leaveClassName, entry.fromState.options, entry.toState.options, subTimelines);
leaveClassName, entry.fromState.options, entry.toState.options, subTimelines, skipBuildAst);
}
destroyInnerAnimations(containerElement: any) {
@ -773,10 +770,6 @@ export class TransitionAnimationEngine {
}
});
}
const stateMap = this.statesByElement.get(element);
if (stateMap) {
Object.keys(stateMap).forEach(triggerName => stateMap[triggerName] = DELETED_STATE_VALUE);
}
}
finishActiveQueriedAnimationOnElement(element: any) {
@ -969,17 +962,24 @@ export class TransitionAnimationEngine {
}
}
if (!bodyNode || !this.driver.containsElement(bodyNode, element)) {
player.destroy();
const nodeIsOrphaned = !bodyNode || !this.driver.containsElement(bodyNode, element);
const leaveClassName = leaveNodeMapIds.get(element) !;
const enterClassName = enterNodeMapIds.get(element) !;
const instruction = this._buildInstruction(
entry, subTimelines, enterClassName, leaveClassName, nodeIsOrphaned) !;
if (instruction.errors && instruction.errors.length) {
erroneousTransitions.push(instruction);
return;
}
const leaveClassName = leaveNodeMapIds.get(element) !;
const enterClassName = enterNodeMapIds.get(element) !;
const instruction =
this._buildInstruction(entry, subTimelines, enterClassName, leaveClassName) !;
if (instruction.errors && instruction.errors.length) {
erroneousTransitions.push(instruction);
// even though the element may not be apart of the DOM, it may
// still be added at a later point (due to the mechanics of content
// projection and/or dynamic component insertion) therefore it's
// important we still style the element.
if (nodeIsOrphaned) {
player.onStart(() => eraseStyles(element, instruction.fromStyles));
player.onDestroy(() => setStyles(element, instruction.toStyles));
skippedPlayers.push(player);
return;
}

View File

@ -10,7 +10,7 @@ import {AnimationPlayer, ɵStyleData} from '@angular/animations';
import {allowPreviousPlayerStylesMerge, balancePreviousStylesIntoKeyframes, copyStyles} from '../../util';
import {AnimationDriver} from '../animation_driver';
import {CssKeyframesDriver} from '../css_keyframes/css_keyframes_driver';
import {containsElement, invokeQuery, matchesElement, validateStyleProperty} from '../shared';
import {containsElement, invokeQuery, isBrowser, matchesElement, validateStyleProperty} from '../shared';
import {WebAnimationsPlayer} from './web_animations_player';
@ -75,5 +75,5 @@ export function supportsWebAnimations() {
}
function getElementAnimateFn(): any {
return (typeof Element !== 'undefined' && (<any>Element).prototype['animate']) || {};
return (isBrowser() && (<any>Element).prototype['animate']) || {};
}

View File

@ -20,7 +20,7 @@ function createDiv() {
{
describe('Animation', () => {
// these tests are only mean't to be run within the DOM (for now)
if (typeof Element == 'undefined') return;
if (isNode) return;
let rootElement: any;
let subElement1: any;

View File

@ -17,7 +17,7 @@ import {makeTrigger} from '../shared';
{
describe('AnimationTrigger', () => {
// these tests are only mean't to be run within the DOM (for now)
if (typeof Element == 'undefined') return;
if (isNode) return;
let element: any;
beforeEach(() => {

View File

@ -16,9 +16,7 @@ import {assertElementExistsInDom, createElement, findKeyframeDefinition, forceRe
const CSS_KEYFRAME_RULE_TYPE = 7;
describe('CssKeyframesDriver tests', () => {
if (typeof Element == 'undefined' || typeof document == 'undefined' ||
typeof(window as any)['AnimationEvent'] == 'undefined')
return;
if (isNode || typeof(window as any)['AnimationEvent'] == 'undefined') return;
describe('building keyframes', () => {
it('should build CSS keyframe style object containing the keyframe styles', () => {

View File

@ -14,7 +14,7 @@ import {assertStyle, createElement} from './shared';
const CSS_KEYFRAME_RULE_TYPE = 7;
describe('DirectStylePlayer tests', () => {
if (typeof Element == 'undefined' || typeof document == 'undefined') return;
if (isNode) return;
it('should apply the styling to the given element when the animation starts and remove when destroyed',
() => {

View File

@ -13,9 +13,7 @@ import {assertStyle, createElement, makeAnimationEvent, supportsAnimationEventCr
const EMPTY_FN = () => {};
{
describe('ElementAnimationStyleHandler', () => {
if (typeof Element == 'undefined' || typeof document == 'undefined' ||
typeof(window as any)['AnimationEvent'] == 'undefined')
return;
if (isNode || typeof(window as any)['AnimationEvent'] == 'undefined') return;
it('should add and remove an animation on to an element\'s styling', () => {
const element = createElement();

View File

@ -21,7 +21,7 @@ import {MockAnimationDriver, MockAnimationPlayer} from '../../testing/src/mock_a
}
// these tests are only mean't to be run within the DOM
if (typeof Element == 'undefined') return;
if (isNode) return;
describe('TimelineAnimationEngine', () => {
let element: any;

View File

@ -20,7 +20,7 @@ const DEFAULT_NAMESPACE_ID = 'id';
const driver = new MockAnimationDriver();
// these tests are only mean't to be run within the DOM
if (typeof Element == 'undefined') return;
if (isNode) return;
describe('TransitionAnimationEngine', () => {
let element: any;
@ -623,6 +623,23 @@ const DEFAULT_NAMESPACE_ID = 'id';
const TRIGGER = 'fooTrigger';
expect(() => { engine.trigger(ID, element, TRIGGER, 'something'); }).not.toThrow();
});
it('should still apply state-styling to an element even if it is not yet inserted into the DOM',
() => {
const engine = makeEngine();
const orphanElement = document.createElement('div');
orphanElement.classList.add('orphan');
registerTrigger(
orphanElement, engine, trigger('trig', [
state('go', style({opacity: 0.5})), transition('* => go', animate(1000))
]));
setProperty(orphanElement, engine, 'trig', 'go');
engine.flush();
expect(engine.players.length).toEqual(0);
expect(orphanElement.style.opacity).toEqual('0.5');
});
});
});
})();

View File

@ -12,7 +12,7 @@ import {WebAnimationsPlayer} from '../../../src/render/web_animations/web_animat
{
describe('WebAnimationsDriver', () => {
if (typeof Element == 'undefined' || typeof document == 'undefined') return;
if (isNode) return;
describe('when web-animations are not supported natively', () => {
it('should return an instance of a CssKeyframePlayer if scrubbing is not requested', () => {

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