Compare commits
207 Commits
6.1.1
...
7.0.0-beta
Author | SHA1 | Date | |
---|---|---|---|
4ce70b9edf | |||
1f1103913a | |||
01ec5fd6b0 | |||
be2cf4dfd6 | |||
eeb81b9370 | |||
b5f354f2fb | |||
a0a29fdd27 | |||
26066f282e | |||
b40c437379 | |||
82e2725154 | |||
c13901f4c8 | |||
6a2130117f | |||
4e45f2c481 | |||
78f477652e | |||
98f336c0fb | |||
9117fa199c | |||
0c4209f4b9 | |||
14ac7ad6b4 | |||
85106375ac | |||
ecb5dc03f9 | |||
bbb3f8fa60 | |||
09711507f9 | |||
3ac7070009 | |||
c869b143c6 | |||
e0314b5d90 | |||
1bb30147d3 | |||
fb2c5241fc | |||
97d8b5ed88 | |||
4a4d6fb0e6 | |||
2016afdbff | |||
c8c1aa7fc0 | |||
409860a4da | |||
2b128a47b9 | |||
209cc7e1b0 | |||
2d759927d4 | |||
7058072ff6 | |||
33fd7e0784 | |||
2befc65777 | |||
6f085f8610 | |||
5be186035f | |||
fba276d3d1 | |||
9c92a6fc7a | |||
6c4da9dcd3 | |||
b64fed1ba3 | |||
8bbce3feff | |||
6c359afce6 | |||
a3f1e2cb42 | |||
090824526b | |||
bfdbdc2ee6 | |||
638ff760a5 | |||
74518c4b2e | |||
ebf508fcd0 | |||
1039bea53b | |||
a2593cbfb1 | |||
70a3deb8a5 | |||
843479449d | |||
732026c3f5 | |||
ec6d6175d2 | |||
af9ced9026 | |||
c6e5b971d6 | |||
dbdbbdbe86 | |||
fefc860f35 | |||
02e201ab1a | |||
7bf5a43385 | |||
2fe05abbc4 | |||
2505c077d7 | |||
066fc6a0ca | |||
07ab98bbb0 | |||
16c03c0f38 | |||
3355502f2f | |||
02c15a2448 | |||
6861bc5b06 | |||
b8887ddf16 | |||
4933e103d3 | |||
7d006c5005 | |||
67ad59c245 | |||
397530ab24 | |||
a9881bb18e | |||
c1587029db | |||
2b906f652f | |||
c67f1bb38e | |||
637ae135c5 | |||
4eb8ac6de9 | |||
ba1e25f53f | |||
97b5cb2e3b | |||
795e1e8a38 | |||
aea8832243 | |||
1e7ca22078 | |||
26a15cc534 | |||
b0d86c1c2f | |||
dc0715142f | |||
4e264781ee | |||
79a9c71422 | |||
15cc85c54a | |||
725bae1921 | |||
eb999300d9 | |||
afa6b9e794 | |||
0822dc70f2 | |||
728d98d3a9 | |||
2f4abbf5a1 | |||
1000fb8406 | |||
b38931b484 | |||
1fb7111da1 | |||
c2c12e52fe | |||
28c7a4efbc | |||
4f741e74e1 | |||
6bacd32fbd | |||
183757daa2 | |||
adf510f986 | |||
74bce18190 | |||
3ba5220839 | |||
5982425436 | |||
140248ade0 | |||
e60737f63c | |||
7b89711402 | |||
f1223628a6 | |||
1b4269ad85 | |||
a224df43af | |||
d46a961509 | |||
20b453008f | |||
06a1974a48 | |||
3d7f555044 | |||
06af7943a4 | |||
fa70a2a650 | |||
bde4402675 | |||
7d6b258778 | |||
5342aeaafd | |||
1dd2eaa7d2 | |||
af07ffc2ad | |||
2b6e1f0f4b | |||
7a4fb44f8d | |||
88da8f3d52 | |||
01e6dab544 | |||
a9ecf4b929 | |||
166ddaadca | |||
1e5327872d | |||
367841d237 | |||
d5b73832bf | |||
7075c418f9 | |||
466e026f6f | |||
4976a58780 | |||
bafe1a0d2a | |||
c8a4fb1faf | |||
64516da6b0 | |||
6e2a1877ab | |||
aafd502bcb | |||
4cb1074850 | |||
76d8eb021c | |||
3f6fc00d73 | |||
5254d3447d | |||
4ee9db959a | |||
3f20a2fb5a | |||
e3834b7001 | |||
36648293a8 | |||
cd89eb8404 | |||
e99d860393 | |||
24789e9ad9 | |||
f94f9640d0 | |||
4d5167ec83 | |||
efc6684cd3 | |||
2ef777b0b2 | |||
fe14f180a6 | |||
87419097da | |||
9a6d26e05b | |||
6a797d5401 | |||
89e8b6fc0e | |||
f82b6b2ed7 | |||
a87d44c187 | |||
43d0e3dd72 | |||
5b32aa4486 | |||
844d510d3f | |||
2f70e90493 | |||
45cf5b5dad | |||
4ad2f11919 | |||
d7aa20d912 | |||
a673494412 | |||
07e6de5788 | |||
6f1685ab98 | |||
67588ec606 | |||
ee2c050521 | |||
185b932138 | |||
5e98421d33 | |||
8e65891985 | |||
7f59170f77 | |||
9ea112473b | |||
16f0ac38b8 | |||
15df853622 | |||
d3c0915598 | |||
ce98634dfd | |||
342678486d | |||
e8d4211d5c | |||
6a4d66d432 | |||
a3cf61b7cf | |||
a1b185b723 | |||
601064e41d | |||
e265ccd82c | |||
dd44f63c73 | |||
1d051c5841 | |||
323faf954b | |||
3169edd77a | |||
8de304c15a | |||
0d1d5898e3 | |||
6fe865b080 | |||
e0c0c44d99 | |||
13a0d527f6 | |||
ed7aa1c3e5 | |||
f902b5ec59 |
@ -48,7 +48,7 @@ jobs:
|
||||
|
||||
# Check BUILD.bazel formatting before we have a node_modules directory
|
||||
# Then we don't need any exclude pattern to avoid checking those files
|
||||
- run: 'buildifier -mode=check $(find . -type f \( -name BUILD.bazel -or -name BUILD \)) ||
|
||||
- run: 'buildifier -mode=check $(find . -type f \( -name "*.bzl" -or -name BUILD.bazel -or -name BUILD \)) ||
|
||||
(echo "BUILD files not formatted. Please run ''yarn buildifier''" ; exit 1)'
|
||||
# Run the skylark linter to check our Bazel rules
|
||||
# deprecated-api is disabled because we use actions.new_file(genfiles_dir)
|
||||
|
7
.github/angular-robot.yml
vendored
7
.github/angular-robot.yml
vendored
@ -62,6 +62,13 @@ merge:
|
||||
|
||||
# list of checks that will determine if the merge label can be added
|
||||
checks:
|
||||
|
||||
# require that the PR has reviews from all requested reviewers
|
||||
#
|
||||
# This enables us to request reviews from both eng and tech writers, or multiple eng folks, and prevents accidental merges.
|
||||
# Rather than merging PRs with pending reviews, if all PullApprove requirements are satisfied and additional reviews are not needed pending reviewers should be removed via GitHub UI (this also leaves an audit trail behind these decisions).
|
||||
requireReviews: true,
|
||||
|
||||
# whether the PR shouldn't have a conflict with the base branch
|
||||
noConflict: true
|
||||
# list of labels that a PR needs to have, checked with a regexp (e.g. "PR target:" will work for the label "PR target: master")
|
||||
|
59
CHANGELOG.md
59
CHANGELOG.md
@ -1,9 +1,62 @@
|
||||
<a name="6.1.1"></a>
|
||||
## [6.1.1](https://github.com/angular/angular/compare/6.1.0...6.1.1) (2018-08-02)
|
||||
<a name="7.0.0-beta.2"></a>
|
||||
# [7.0.0-beta.2](https://github.com/angular/angular/compare/7.0.0-beta.1...7.0.0-beta.2) (2018-08-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** correct type concatenated to devmode_js ([#25467](https://github.com/angular/angular/issues/25467)) ([fb2c524](https://github.com/angular/angular/commit/fb2c524))
|
||||
* **service-worker:** `Cache-Control: no-cache` on assets breaks service worker ([#25408](https://github.com/angular/angular/issues/25408)) ([01ec5fd](https://github.com/angular/angular/commit/01ec5fd)), closes [#25442](https://github.com/angular/angular/issues/25442)
|
||||
|
||||
|
||||
|
||||
<a name="7.0.0-beta.1"></a>
|
||||
# [7.0.0-beta.1](https://github.com/angular/angular/compare/7.0.0-beta.0...7.0.0-beta.1) (2018-08-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-cli:** use the oldProgram option in watch mode ([#21364](https://github.com/angular/angular/issues/21364)) ([c6e5b97](https://github.com/angular/angular/commit/c6e5b97)), closes [#21361](https://github.com/angular/angular/issues/21361)
|
||||
* **core:** In Testability.whenStable update callback, pass more complete ([#25010](https://github.com/angular/angular/issues/25010)) ([16c03c0](https://github.com/angular/angular/commit/16c03c0))
|
||||
* add mappings for ngfactory & ngsummary files to their module names in aot summary resolver ([#25335](https://github.com/angular/angular/issues/25335)) ([02e201a](https://github.com/angular/angular/commit/02e201a))
|
||||
* **router:** take base uri into account in `setUpLocationSync()` ([#20244](https://github.com/angular/angular/issues/20244)) ([ba1e25f](https://github.com/angular/angular/commit/ba1e25f)), closes [#20061](https://github.com/angular/angular/issues/20061)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **core:** add DoBootstrap interface. ([#24558](https://github.com/angular/angular/issues/24558)) ([732026c](https://github.com/angular/angular/commit/732026c)), closes [#24557](https://github.com/angular/angular/issues/24557)
|
||||
|
||||
|
||||
|
||||
<a name="6.1.2"></a>
|
||||
## [6.1.2](https://github.com/angular/angular/compare/6.1.1...6.1.2) (2018-08-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **router:** take base uri into account in `setUpLocationSync()` ([#20244](https://github.com/angular/angular/issues/20244)) ([ae9b4e6](https://github.com/angular/angular/commit/ae9b4e6)), closes [#20061](https://github.com/angular/angular/issues/20061)
|
||||
* add mappings for ngfactory & ngsummary files to their module names in aot summary resolver ([#25335](https://github.com/angular/angular/issues/25335)) ([054fbbe](https://github.com/angular/angular/commit/054fbbe))
|
||||
|
||||
|
||||
<a name="7.0.0-beta.0"></a>
|
||||
# [7.0.0-beta.0](https://github.com/angular/angular/compare/6.1.0...7.0.0-beta.0) (2018-08-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **bazel:** allow compile_strategy to be (privately) imported ([#25080](https://github.com/angular/angular/issues/25080)) ([0d1d589](https://github.com/angular/angular/commit/0d1d589))
|
||||
* **compiler:** update compiler to flatten nested template fns ([#24943](https://github.com/angular/angular/issues/24943)) ([fe14f18](https://github.com/angular/angular/commit/fe14f18))
|
||||
* **compiler-cli:** correct realPath to realpath. ([#25023](https://github.com/angular/angular/issues/25023)) ([01e6dab](https://github.com/angular/angular/commit/01e6dab))
|
||||
* **core:** throw error message when @Output not initialized ([#19116](https://github.com/angular/angular/issues/19116)) ([adf510f](https://github.com/angular/angular/commit/adf510f)), closes [#3664](https://github.com/angular/angular/issues/3664)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler:** add "original" placeholder value on extracted XMB ([#25079](https://github.com/angular/angular/issues/25079)) ([e99d860](https://github.com/angular/angular/commit/e99d860))
|
||||
|
||||
|
||||
|
||||
<a name="6.1.1"></a>
|
||||
## [6.1.1](https://github.com/angular/angular/compare/6.1.0...6.1.1) (2018-08-02)
|
||||
|
||||
* **compiler-cli:** correct tsickle dependency version to fix typescript 2.9 compatibility ([fec29fa](https://github.com/angular/angular/commit/317c7087c56b72aa74cd6d6a8f719e6e7fec29fa))
|
||||
|
||||
|
||||
@ -93,7 +146,7 @@
|
||||
|
||||
* **bazel:** turn on preserve-symlinks ([#24881](https://github.com/angular/angular/issues/24881)) ([c438b5e](https://github.com/angular/angular/commit/c438b5e))
|
||||
|
||||
### BREAKING CHANGES
|
||||
### Angular Labs (experimental feature) breaking change
|
||||
|
||||
* **bazel:** Use of @angular/bazel rules now requires calling ng_setup_workspace() in your WORKSPACE file.
|
||||
|
||||
|
49
WORKSPACE
49
WORKSPACE
@ -6,23 +6,30 @@ workspace(name = "angular")
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_nodejs",
|
||||
url = "https://github.com/bazelbuild/rules_nodejs/archive/20ff5892612f8359aec8aaf26dd3902a24976ada.zip",
|
||||
strip_prefix = "rules_nodejs-20ff5892612f8359aec8aaf26dd3902a24976ada",
|
||||
sha256 = "07da9d4c3e688a02745d0f50709a87744706d4f5d1959b799b0ac38e97acd622",
|
||||
urls = ["https://github.com/bazelbuild/rules_nodejs/archive/0.11.4.zip"],
|
||||
strip_prefix = "rules_nodejs-0.11.4",
|
||||
sha256 = "c31c4ead696944a50fad2b3ee9dfbbeffe31a8dcca0b21b9bf5b3e6c6b069801",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "bazel_skylib",
|
||||
urls = ["https://github.com/bazelbuild/bazel-skylib/archive/0.3.1.zip"],
|
||||
strip_prefix = "bazel-skylib-0.3.1",
|
||||
sha256 = "95518adafc9a2b656667bbf517a952e54ce7f350779d0dd95133db4eb5c27fb1",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "io_bazel_rules_webtesting",
|
||||
url = "https://github.com/bazelbuild/rules_webtesting/archive/7ffe970bbf380891754487f66c3d680c087d67f2.zip",
|
||||
strip_prefix = "rules_webtesting-7ffe970bbf380891754487f66c3d680c087d67f2",
|
||||
sha256 = "4fb0dca8c9a90547891b7ef486592775a523330fc4555c88cd8f09270055c2ce",
|
||||
url = "https://github.com/bazelbuild/rules_webtesting/archive/0.2.1.zip",
|
||||
strip_prefix = "rules_webtesting-0.2.1",
|
||||
sha256 = "7d490aadff9b5262e5251fa69427ab2ffd1548422467cb9f9e1d110e2c36f0fa",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_typescript",
|
||||
url = "https://github.com/bazelbuild/rules_typescript/archive/0.15.3.zip",
|
||||
strip_prefix = "rules_typescript-0.15.3",
|
||||
sha256 = "a2b26ac3fc13036011196063db1bf7f1eae81334449201dc28087ebfa3708c99",
|
||||
url = "https://github.com/bazelbuild/rules_typescript/archive/0.16.0.zip",
|
||||
strip_prefix = "rules_typescript-0.16.0",
|
||||
sha256 = "e65c5639a42e2f6d3f9d2bda62487d6b42734830dda45be1620c3e2b1115070c",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
@ -31,6 +38,13 @@ http_archive(
|
||||
sha256 = "feba3278c13cde8d67e341a837f69a029f698d7a27ddbb2a202be7a10b22142a",
|
||||
)
|
||||
|
||||
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",
|
||||
)
|
||||
|
||||
# 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
|
||||
@ -51,6 +65,14 @@ http_archive(
|
||||
sha256 = "e373d2ae24955c1254c495c9c421c009d88966565c35e4e8444c082cb1f0f48f",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "io_bazel_skydoc",
|
||||
# TODO: switch to upstream when https://github.com/bazelbuild/skydoc/pull/103 is merged
|
||||
url = "https://github.com/alexeagle/skydoc/archive/fe2e9f888d28e567fef62ec9d4a93c425526d701.zip",
|
||||
strip_prefix = "skydoc-fe2e9f888d28e567fef62ec9d4a93c425526d701",
|
||||
sha256 = "7bfb5545f59792a2745f2523b9eef363f9c3e7274791c030885e7069f8116016",
|
||||
)
|
||||
|
||||
# We have a source dependency on the Devkit repository, because it's built with
|
||||
# Bazel.
|
||||
# This allows us to edit sources and have the effect appear immediately without
|
||||
@ -137,3 +159,12 @@ yarn_install(
|
||||
package_json = "//tools/http-server:package.json",
|
||||
yarn_lock = "//tools/http-server:yarn.lock",
|
||||
)
|
||||
|
||||
##################################
|
||||
# Skylark documentation generation
|
||||
|
||||
load("@io_bazel_rules_sass//sass:sass_repositories.bzl", "sass_repositories")
|
||||
sass_repositories()
|
||||
|
||||
load("@io_bazel_skydoc//skylark:skylark.bzl", "skydoc_repositories")
|
||||
skydoc_repositories()
|
||||
|
@ -8,7 +8,7 @@ Everything in this folder is part of the documentation project. This includes
|
||||
|
||||
## Developer tasks
|
||||
|
||||
We use `yarn` to manage the dependencies and to run build tasks.
|
||||
We use [Yarn](https://yarnpkg.com) to manage the dependencies and to run build tasks.
|
||||
You should run all these tasks from the `angular/aio` folder.
|
||||
Here are the most important tasks you might need to use:
|
||||
|
||||
|
@ -16,6 +16,7 @@ describe('Form Validation Tests', function () {
|
||||
|
||||
tests('Template-Driven Form');
|
||||
bobTests();
|
||||
asyncValidationTests();
|
||||
crossValidationTests();
|
||||
});
|
||||
|
||||
@ -26,6 +27,7 @@ describe('Form Validation Tests', function () {
|
||||
|
||||
tests('Reactive Form');
|
||||
bobTests();
|
||||
asyncValidationTests();
|
||||
crossValidationTests();
|
||||
});
|
||||
});
|
||||
@ -45,6 +47,7 @@ let page: {
|
||||
errorMessages: ElementArrayFinder,
|
||||
heroFormButtons: ElementArrayFinder,
|
||||
heroSubmitted: ElementFinder,
|
||||
alterEgoErrors: ElementFinder,
|
||||
crossValidationErrorMessage: ElementFinder,
|
||||
};
|
||||
|
||||
@ -63,6 +66,7 @@ function getPage(sectionTag: string) {
|
||||
errorMessages: section.all(by.css('div.alert')),
|
||||
heroFormButtons: buttons,
|
||||
heroSubmitted: section.element(by.css('.submitted-message')),
|
||||
alterEgoErrors: section.element(by.css('.alter-ego-errors')),
|
||||
crossValidationErrorMessage: section.element(by.css('.cross-validation-error-message')),
|
||||
};
|
||||
}
|
||||
@ -156,6 +160,16 @@ function expectFormIsInvalid() {
|
||||
expect(page.form.getAttribute('class')).toMatch('ng-invalid');
|
||||
}
|
||||
|
||||
function triggerAlterEgoValidation() {
|
||||
// alterEgo has updateOn set to 'blur', click outside of the input to trigger the blur event
|
||||
element(by.css('app-root')).click()
|
||||
}
|
||||
|
||||
function waitForAlterEgoValidation() {
|
||||
// alterEgo async validation will be performed in 400ms
|
||||
browser.sleep(400);
|
||||
}
|
||||
|
||||
function bobTests() {
|
||||
const emsg = 'Name cannot be Bob.';
|
||||
|
||||
@ -177,6 +191,32 @@ function bobTests() {
|
||||
});
|
||||
}
|
||||
|
||||
function asyncValidationTests() {
|
||||
const emsg = 'Alter ego is already taken.';
|
||||
|
||||
it(`should produce "${emsg}" error after setting alterEgo to Eric`, function () {
|
||||
page.alterEgoInput.clear();
|
||||
page.alterEgoInput.sendKeys('Eric');
|
||||
|
||||
triggerAlterEgoValidation();
|
||||
waitForAlterEgoValidation();
|
||||
|
||||
expectFormIsInvalid();
|
||||
expect(page.alterEgoErrors.getText()).toBe(emsg);
|
||||
});
|
||||
|
||||
it('should be ok again with different values', function () {
|
||||
page.alterEgoInput.clear();
|
||||
page.alterEgoInput.sendKeys('John');
|
||||
|
||||
triggerAlterEgoValidation();
|
||||
waitForAlterEgoValidation();
|
||||
|
||||
expectFormIsValid();
|
||||
expect(page.alterEgoErrors.isPresent()).toBe(false);
|
||||
});
|
||||
}
|
||||
|
||||
function crossValidationTests() {
|
||||
const emsg = 'Name cannot match alter ego.';
|
||||
|
||||
@ -187,6 +227,9 @@ function crossValidationTests() {
|
||||
page.alterEgoInput.clear();
|
||||
page.alterEgoInput.sendKeys('Batman');
|
||||
|
||||
triggerAlterEgoValidation();
|
||||
waitForAlterEgoValidation();
|
||||
|
||||
expectFormIsInvalid();
|
||||
expect(page.crossValidationErrorMessage.getText()).toBe(emsg);
|
||||
});
|
||||
@ -198,6 +241,9 @@ function crossValidationTests() {
|
||||
page.alterEgoInput.clear();
|
||||
page.alterEgoInput.sendKeys('Superman');
|
||||
|
||||
triggerAlterEgoValidation();
|
||||
waitForAlterEgoValidation();
|
||||
|
||||
expectFormIsValid();
|
||||
expect(page.crossValidationErrorMessage.isPresent()).toBe(false);
|
||||
});
|
||||
|
@ -8,6 +8,7 @@ import { HeroFormTemplateComponent } from './template/hero-form-template.compone
|
||||
import { HeroFormReactiveComponent } from './reactive/hero-form-reactive.component';
|
||||
import { ForbiddenValidatorDirective } from './shared/forbidden-name.directive';
|
||||
import { IdentityRevealedValidatorDirective } from './shared/identity-revealed.directive';
|
||||
import { UniqueAlterEgoValidatorDirective } from './shared/alter-ego.directive';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -20,7 +21,8 @@ import { IdentityRevealedValidatorDirective } from './shared/identity-revealed.d
|
||||
HeroFormTemplateComponent,
|
||||
HeroFormReactiveComponent,
|
||||
ForbiddenValidatorDirective,
|
||||
IdentityRevealedValidatorDirective
|
||||
IdentityRevealedValidatorDirective,
|
||||
UniqueAlterEgoValidatorDirective
|
||||
],
|
||||
bootstrap: [ AppComponent ]
|
||||
})
|
||||
|
@ -0,0 +1,48 @@
|
||||
/* tslint:disable: member-ordering forin */
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { forbiddenNameValidator } from '../shared/forbidden-name.directive';
|
||||
import { UniqueAlterEgoValidator } from '../shared/alter-ego.directive';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-form-reactive',
|
||||
templateUrl: './hero-form-reactive.component.html',
|
||||
styleUrls: ['./hero-form-reactive.component.css'],
|
||||
})
|
||||
export class HeroFormReactiveComponent implements OnInit {
|
||||
|
||||
powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];
|
||||
|
||||
hero = { name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0] };
|
||||
|
||||
heroForm: FormGroup;
|
||||
|
||||
ngOnInit(): void {
|
||||
// #docregion async-validation
|
||||
this.heroForm = new FormGroup({
|
||||
'name': new FormControl(this.hero.name, [
|
||||
Validators.required,
|
||||
Validators.minLength(4),
|
||||
forbiddenNameValidator(/bob/i)
|
||||
]),
|
||||
'alterEgo': new FormControl(this.hero.alterEgo, {
|
||||
asyncValidators: [this.alterEgoValidator.validate.bind(this.alterEgoValidator)],
|
||||
updateOn: 'blur'
|
||||
}),
|
||||
'power': new FormControl(this.hero.power, Validators.required)
|
||||
});
|
||||
// #enddocregion async-validation
|
||||
}
|
||||
|
||||
get name() { return this.heroForm.get('name'); }
|
||||
|
||||
get power() { return this.heroForm.get('power'); }
|
||||
|
||||
get alterEgo() { return this.heroForm.get('alterEgo'); }
|
||||
|
||||
// #docregion async-validation
|
||||
constructor(private alterEgoValidator: UniqueAlterEgoValidator) {}
|
||||
// #enddocregion async-validation
|
||||
}
|
@ -35,6 +35,13 @@
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input id="alterEgo" class="form-control"
|
||||
formControlName="alterEgo" >
|
||||
|
||||
<div *ngIf="alterEgo.pending">Validating...</div>
|
||||
<div *ngIf="alterEgo.invalid" class="alert alert-danger alter-ego-errors">
|
||||
<div *ngIf="alterEgo.errors?.uniqueAlterEgo">
|
||||
Alter ego is already taken.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- #docregion cross-validation-error-message -->
|
||||
|
@ -4,6 +4,7 @@ import { Component, OnInit } from '@angular/core';
|
||||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { forbiddenNameValidator } from '../shared/forbidden-name.directive';
|
||||
import { identityRevealedValidator } from '../shared/identity-revealed.directive';
|
||||
import { UniqueAlterEgoValidator } from '../shared/alter-ego.directive';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hero-form-reactive',
|
||||
@ -25,7 +26,10 @@ export class HeroFormReactiveComponent implements OnInit {
|
||||
Validators.minLength(4),
|
||||
forbiddenNameValidator(/bob/i)
|
||||
]),
|
||||
'alterEgo': new FormControl(this.hero.alterEgo),
|
||||
'alterEgo': new FormControl(this.hero.alterEgo, {
|
||||
asyncValidators: [this.alterEgoValidator.validate.bind(this.alterEgoValidator)],
|
||||
updateOn: 'blur'
|
||||
}),
|
||||
'power': new FormControl(this.hero.power, Validators.required)
|
||||
}, { validators: identityRevealedValidator }); // <-- add custom validator at the FormGroup level
|
||||
}
|
||||
@ -33,4 +37,8 @@ export class HeroFormReactiveComponent implements OnInit {
|
||||
get name() { return this.heroForm.get('name'); }
|
||||
|
||||
get power() { return this.heroForm.get('power'); }
|
||||
|
||||
get alterEgo() { return this.heroForm.get('alterEgo'); }
|
||||
|
||||
constructor(private alterEgoValidator: UniqueAlterEgoValidator) { }
|
||||
}
|
||||
|
@ -0,0 +1,46 @@
|
||||
import { Directive, forwardRef, Injectable } from '@angular/core';
|
||||
import {
|
||||
AsyncValidator,
|
||||
AbstractControl,
|
||||
NG_ASYNC_VALIDATORS,
|
||||
ValidationErrors
|
||||
} from '@angular/forms';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { HeroesService } from './heroes.service';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
// #docregion async-validator
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class UniqueAlterEgoValidator implements AsyncValidator {
|
||||
constructor(private heroesService: HeroesService) {}
|
||||
|
||||
validate(
|
||||
ctrl: AbstractControl
|
||||
): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> {
|
||||
return this.heroesService.isAlterEgoTaken(ctrl.value).pipe(
|
||||
map(isTaken => (isTaken ? { uniqueAlterEgo: true } : null)),
|
||||
catchError(() => null)
|
||||
);
|
||||
}
|
||||
}
|
||||
// #enddocregion async-validator
|
||||
|
||||
// #docregion async-validator-directive
|
||||
@Directive({
|
||||
selector: '[appUniqueAlterEgo]',
|
||||
providers: [
|
||||
{
|
||||
provide: NG_ASYNC_VALIDATORS,
|
||||
useExisting: forwardRef(() => UniqueAlterEgoValidator),
|
||||
multi: true
|
||||
}
|
||||
]
|
||||
})
|
||||
export class UniqueAlterEgoValidatorDirective {
|
||||
constructor(private validator: UniqueAlterEgoValidator) {}
|
||||
|
||||
validate(control: AbstractControl) {
|
||||
this.validator.validate(control);
|
||||
}
|
||||
}
|
||||
// #enddocregion async-validator-directive
|
@ -0,0 +1,14 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { delay } from 'rxjs/operators';
|
||||
|
||||
const ALTER_EGOS = ['Eric'];
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class HeroesService {
|
||||
isAlterEgoTaken(alterEgo: string): Observable<boolean> {
|
||||
const isTaken = ALTER_EGOS.includes(alterEgo);
|
||||
|
||||
return of(isTaken).pipe(delay(400));
|
||||
}
|
||||
}
|
@ -35,8 +35,20 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label for="alterEgo">Alter Ego</label>
|
||||
<input id="alterEgo" class="form-control"
|
||||
name="alterEgo" [(ngModel)]="hero.alterEgo" >
|
||||
<!-- #docregion async-validation -->
|
||||
<input id="alterEgo" class="form-control" name="alterEgo"
|
||||
#alterEgo="ngModel"
|
||||
[(ngModel)]="hero.alterEgo"
|
||||
[ngModelOptions]="{ updateOn: 'blur' }"
|
||||
appUniqueAlterEgo>
|
||||
<!-- #enddocregion async-validation -->
|
||||
|
||||
<div *ngIf="alterEgo.pending">Validating...</div>
|
||||
<div *ngIf="alterEgo.invalid" class="alert alert-danger alter-ego-errors">
|
||||
<div *ngIf="alterEgo.errors?.uniqueAlterEgo">
|
||||
Alter ego is already taken.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- #docregion cross-validation-error-message -->
|
||||
|
@ -23,7 +23,10 @@ import { Hero, HeroService } from './shared';
|
||||
`,
|
||||
styles: [`
|
||||
.heroes {
|
||||
margin: 0 0 2em 0; list-style-type: none; padding: 0; width: 15em;
|
||||
margin: 0 0 2em 0;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
width: 15em;
|
||||
}
|
||||
.heroes li {
|
||||
cursor: pointer;
|
||||
|
@ -1,6 +1,9 @@
|
||||
/* #docregion */
|
||||
.heroes {
|
||||
margin: 0 0 2em 0; list-style-type: none; padding: 0; width: 15em;
|
||||
margin: 0 0 2em 0;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
width: 15em;
|
||||
}
|
||||
.heroes li {
|
||||
cursor: pointer;
|
||||
|
@ -0,0 +1,27 @@
|
||||
import { TestBed, async, tick, fakeAsync } from '@angular/core/testing';
|
||||
import { CanvasComponent } from './canvas.component';
|
||||
describe('CanvasComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
CanvasComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
beforeEach(() => {
|
||||
window['__zone_symbol__FakeAsyncTestMacroTask'] = [
|
||||
{
|
||||
source: 'HTMLCanvasElement.toBlob',
|
||||
callbackArgs: [{ size: 200 }]
|
||||
}
|
||||
];
|
||||
});
|
||||
it('should be able to generate blob data from canvas', fakeAsync(() => {
|
||||
const fixture = TestBed.createComponent(CanvasComponent);
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app.blobSize).toBeGreaterThan(0);
|
||||
}));
|
||||
});
|
||||
|
@ -0,0 +1,25 @@
|
||||
import { Component, AfterViewInit, ViewChild } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'sample-canvas',
|
||||
template: '<canvas #sampleCanvas width="200" height="200"></canvas>'
|
||||
})
|
||||
export class CanvasComponent implements AfterViewInit {
|
||||
blobSize: number;
|
||||
@ViewChild('sampleCanvas') sampleCanvas;
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngAfterViewInit() {
|
||||
const canvas = this.sampleCanvas.nativeElement;
|
||||
const context = canvas.getContext('2d');
|
||||
if (context) {
|
||||
context.clearRect(0, 0, 200, 200);
|
||||
context.fillStyle = '#FF1122';
|
||||
context.fillRect(0, 0, 200, 200);
|
||||
canvas.toBlob((blob: any) => {
|
||||
this.blobSize = blob.size;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
// #docplaster
|
||||
// #docregion
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
// #docregion added-imports
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Location } from '@angular/common';
|
||||
@ -17,7 +17,7 @@ import { HeroService } from '../hero.service';
|
||||
styleUrls: [ './hero-detail.component.css' ]
|
||||
})
|
||||
export class HeroDetailComponent implements OnInit {
|
||||
@Input() hero: Hero;
|
||||
hero: Hero;
|
||||
|
||||
// #docregion ctor
|
||||
constructor(
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Location } from '@angular/common';
|
||||
|
||||
@ -11,7 +11,7 @@ import { HeroService } from '../hero.service';
|
||||
styleUrls: [ './hero-detail.component.css' ]
|
||||
})
|
||||
export class HeroDetailComponent implements OnInit {
|
||||
@Input() hero: Hero;
|
||||
hero: Hero;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
|
@ -1,8 +1,7 @@
|
||||
# Introduction to components
|
||||
|
||||
<img src="generated/images/guide/architecture/hero-component.png" alt="Component" class="left">
|
||||
|
||||
A _component_ controls a patch of screen called a *view*. For example, individual components define and control each of the following views from the [Tutorial](tutorial/index):
|
||||
A *component* controls a patch of screen called a *view*.
|
||||
For example, individual components define and control each of the following views from the [Tutorial](tutorial):
|
||||
|
||||
* The app root with the navigation links.
|
||||
* The list of heroes.
|
||||
@ -11,38 +10,38 @@ A _component_ controls a patch of screen called a *view*. For example, individua
|
||||
You define a component's application logic—what it does to support the view—inside a class.
|
||||
The class interacts with the view through an API of properties and methods.
|
||||
|
||||
For example, the `HeroListComponent` has a `heroes` property that holds an array of heroes. It also has a `selectHero()` method that sets a `selectedHero` property when the user clicks to choose a hero from that list. The component acquires the heroes from a service, which is a TypeScript [parameter property](http://www.typescriptlang.org/docs/handbook/classes.html#parameter-properties) on the constructor. The service is provided to the component through the dependency injection system.
|
||||
For example, `HeroListComponent` has a `heroes` property that holds an array of heroes.
|
||||
Its `selectHero()` method sets a `selectedHero` property when the user clicks to choose a hero from that list.
|
||||
The component acquires the heroes from a service, which is a TypeScript [parameter property](http://www.typescriptlang.org/docs/handbook/classes.html#parameter-properties) on the constructor.
|
||||
The service is provided to the component through the dependency injection system.
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.ts" linenums="false" title="src/app/hero-list.component.ts (class)" region="class"></code-example>
|
||||
|
||||
Angular creates, updates, and destroys components as the user moves through the application. Your app can take action at each moment in this lifecycle through optional [lifecycle hooks](guide/lifecycle-hooks), like `ngOnInit()`.
|
||||
|
||||
<hr/>
|
||||
|
||||
## Component metadata
|
||||
|
||||
<img src="generated/images/guide/architecture/metadata.png" alt="Metadata" class="left">
|
||||
|
||||
The `@Component` decorator identifies the class immediately below it as a component class, and specifies its metadata. In the example code below, you can see that `HeroListComponent` is just a class, with no special Angular notation or syntax at all. It's not a component until you mark it as one with the `@Component` decorator.
|
||||
|
||||
The metadata for a component tells Angular where to get the major building blocks it needs to create and present the component and its view. In particular, it associates a _template_ with the component, either directly with inline code, or by reference. Together, the component and its template describe a _view_.
|
||||
The metadata for a component tells Angular where to get the major building blocks that it needs to create and present the component and its view. In particular, it associates a *template* with the component, either directly with inline code, or by reference. Together, the component and its template describe a *view*.
|
||||
|
||||
In addition to containing or pointing to the template, the `@Component` metadata configures, for example, how the component can be referenced in HTML and what services it requires.
|
||||
|
||||
Here's an example of basic metadata for `HeroListComponent`:
|
||||
Here's an example of basic metadata for `HeroListComponent`.
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.ts" linenums="false" title="src/app/hero-list.component.ts (metadata)" region="metadata"></code-example>
|
||||
|
||||
This example shows some of the most useful `@Component` configuration options:
|
||||
This example shows some of the most useful `@Component` configuration options:
|
||||
|
||||
* `selector`: A CSS selector that tells Angular to create and insert an instance of this component wherever it finds the corresponding tag in template HTML. For example, if an app's HTML contains `<app-hero-list></app-hero-list>`, then
|
||||
Angular inserts an instance of the `HeroListComponent` view between those tags.
|
||||
|
||||
* `templateUrl`: The module-relative address of this component's HTML template. Alternatively, you can provide the HTML template inline, as the value of the `template` property. This template defines the component's _host view_.
|
||||
* `templateUrl`: The module-relative address of this component's HTML template. Alternatively, you can provide the HTML template inline, as the value of the `template` property. This template defines the component's *host view*.
|
||||
|
||||
* `providers`: An array of **dependency injection providers** for services that the component requires. In the example, this tells Angular how to provide the `HeroService` instance that the component's constructor uses to get the list of heroes to display.
|
||||
* `providers`: An array of [providers](guide/glossary#provider) for services that the component requires. In the example, this tells Angular how to provide the `HeroService` instance that the component's constructor uses to get the list of heroes to display.
|
||||
|
||||
<hr/>
|
||||
|
||||
## Templates and views
|
||||
|
||||
@ -50,7 +49,7 @@ Angular inserts an instance of the `HeroListComponent` view between those tags.
|
||||
|
||||
You define a component's view with its companion template. A template is a form of HTML that tells Angular how to render the component.
|
||||
|
||||
Views are typically arranged hierarchically, allowing you to modify or show and hide entire UI sections or pages as a unit. The template immediately associated with a component defines that component's _host view_. The component can also define a _view hierarchy_, which contains _embedded views_, hosted by other components.
|
||||
Views are typically arranged hierarchically, allowing you to modify or show and hide entire UI sections or pages as a unit. The template immediately associated with a component defines that component's *host view*. The component can also define a *view hierarchy*, which contains *embedded views*, hosted by other components.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/architecture/component-tree.png" alt="Component tree" class="left">
|
||||
@ -60,43 +59,47 @@ A view hierarchy can include views from components in the same NgModule, but it
|
||||
|
||||
## Template syntax
|
||||
|
||||
A template looks like regular HTML, except that it also contains Angular [template syntax](guide/template-syntax), which alters the HTML based on your app's logic and the state of app and DOM data. Your template can use _data binding_ to coordinate the app and DOM data, _pipes_ to transform data before it is displayed, and _directives_ to apply app logic to what gets displayed.
|
||||
A template looks like regular HTML, except that it also contains Angular [template syntax](guide/template-syntax), which alters the HTML based on your app's logic and the state of app and DOM data. Your template can use *data binding* to coordinate the app and DOM data, *pipes* to transform data before it is displayed, and *directives* to apply app logic to what gets displayed.
|
||||
|
||||
For example, here is a template for the Tutorial's `HeroListComponent`:
|
||||
For example, here is a template for the Tutorial's `HeroListComponent`.
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.html" title="src/app/hero-list.component.html"></code-example>
|
||||
|
||||
This template uses typical HTML elements like `<h2>` and `<p>`, and also includes Angular template-syntax elements, `*ngFor`, `{{hero.name}}`, `(click)`, `[hero]`, and `<app-hero-detail>`. The template-syntax elements tell Angular how to render the HTML to the screen, using program logic and data.
|
||||
|
||||
* The `*ngFor` directive tells Angular to iterate over a list.
|
||||
* The `{{hero.name}}`, `(click)`, and `[hero]` bind program data to and from the DOM, responding to user input. See more about [data binding](#data-binding) below.
|
||||
* The `<app-hero-detail>` tag in the example is an element that represents a new component, `HeroDetailComponent`. The `HeroDetailComponent` (code not shown) is a child component of the `HeroListComponent` that defines the Hero-detail view. Notice how custom components like this mix seamlessly with native HTML in the same layouts.
|
||||
* The `*ngFor` directive tells Angular to iterate over a list.
|
||||
* `{{hero.name}}`, `(click)`, and `[hero]` bind program data to and from the DOM, responding to user input. See more about [data binding](#data-binding) below.
|
||||
* The `<app-hero-detail>` tag in the example is an element that represents a new component, `HeroDetailComponent`.
|
||||
`HeroDetailComponent` (code not shown) defines the hero-detail child view of `HeroListComponent`.
|
||||
Notice how custom components like this mix seamlessly with native HTML in the same layouts.
|
||||
|
||||
### Data binding
|
||||
|
||||
Without a framework, you would be responsible for pushing data values into the HTML controls and turning user responses into actions and value updates. Writing such push/pull logic by hand is tedious, error-prone, and a nightmare to read, as any experienced jQuery programmer can attest.
|
||||
Without a framework, you would be responsible for pushing data values into the HTML controls and turning user responses into actions and value updates. Writing such push and pull logic by hand is tedious, error-prone, and a nightmare to read, as any experienced jQuery programmer can attest.
|
||||
|
||||
Angular supports *two-way data binding*, a mechanism for coordinating parts of a template with parts of a component. Add binding markup to the template HTML to tell Angular how to connect both sides.
|
||||
Angular supports *two-way data binding*, a mechanism for coordinating the parts of a template with the parts of a component. Add binding markup to the template HTML to tell Angular how to connect both sides.
|
||||
|
||||
The following diagram shows the four forms of data binding markup. Each form has a direction—to the DOM, from the DOM, or in both directions.
|
||||
The following diagram shows the four forms of data binding markup. Each form has a direction: to the DOM, from the DOM, or both.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/architecture/databinding.png" alt="Data Binding" class="left">
|
||||
</figure>
|
||||
|
||||
This example from the `HeroListComponent` template uses three of these forms:
|
||||
This example from the `HeroListComponent` template uses three of these forms.
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.1.html" linenums="false" title="src/app/hero-list.component.html (binding)" region="binding"></code-example>
|
||||
|
||||
* The `{{hero.name}}` [*interpolation*](guide/displaying-data#interpolation)
|
||||
displays the component's `hero.name` property value within the `<li>` element.
|
||||
|
||||
* The `[hero]` [*property binding*](guide/template-syntax#property-binding) passes the value of `selectedHero` from
|
||||
the parent `HeroListComponent` to the `hero` property of the child `HeroDetailComponent`.
|
||||
* The `[hero]` [*property binding*](guide/template-syntax#property-binding) passes the value of
|
||||
`selectedHero` from the parent `HeroListComponent` to the `hero` property of the child `HeroDetailComponent`.
|
||||
|
||||
* The `(click)` [*event binding*](guide/user-input#binding-to-user-input-events) calls the component's `selectHero` method when the user clicks a hero's name.
|
||||
|
||||
**Two-way data binding** is an important fourth form that combines property and event binding in a single notation. Here's an example from the `HeroDetailComponent` template that uses two-way data binding with the `ngModel` directive:
|
||||
Two-way data binding (used mainly in [template-driven forms](guide/forms))
|
||||
combines property and event binding in a single notation.
|
||||
Here's an example from the `HeroDetailComponent` template that uses two-way data binding with the `ngModel` directive.
|
||||
|
||||
<code-example path="architecture/src/app/hero-detail.component.html" linenums="false" title="src/app/hero-detail.component.html (ngModel)" region="ngModel"></code-example>
|
||||
|
||||
@ -104,7 +107,7 @@ In two-way binding, a data property value flows to the input box from the compon
|
||||
The user's changes also flow back to the component, resetting the property to the latest value,
|
||||
as with event binding.
|
||||
|
||||
Angular processes *all* data bindings once per JavaScript event cycle,
|
||||
Angular processes *all* data bindings once for each JavaScript event cycle,
|
||||
from the root of the application component tree through all child components.
|
||||
|
||||
<figure>
|
||||
@ -119,17 +122,17 @@ Data binding plays an important role in communication between a template and its
|
||||
|
||||
### Pipes
|
||||
|
||||
Angular pipes let you declare display-value transformations in your template HTML. A class with the `@Pipe` decorator defines a function that transforms input values to output values for display in a view.
|
||||
Angular pipes let you declare display-value transformations in your template HTML. A class with the `@Pipe` decorator defines a function that transforms input values to output values for display in a view.
|
||||
|
||||
Angular defines various pipes, such as the [date](https://angular.io/api/common/DatePipe) pipe and [currency](https://angular.io/api/common/CurrencyPipe) pipe; for a complete list, see the [Pipes API list](https://angular.io/api?type=pipe). You can also define new pipes.
|
||||
Angular defines various pipes, such as the [date](https://angular.io/api/common/DatePipe) pipe and [currency](https://angular.io/api/common/CurrencyPipe) pipe; for a complete list, see the [Pipes API list](https://angular.io/api?type=pipe). You can also define new pipes.
|
||||
|
||||
To specify a value transformation in an HTML template, use the [pipe operator (|)](https://angular.io/guide/template-syntax#pipe):
|
||||
To specify a value transformation in an HTML template, use the [pipe operator (|)](https://angular.io/guide/template-syntax#pipe).
|
||||
|
||||
`{{interpolated_value | pipe_name}}`
|
||||
`{{interpolated_value | pipe_name}}`
|
||||
|
||||
You can chain pipes, sending the output of one pipe function to be transformed by another pipe function. A pipe can also take arguments that control how it performs its transformation. For example, you can pass the desired format to the `date` pipe:
|
||||
You can chain pipes, sending the output of one pipe function to be transformed by another pipe function. A pipe can also take arguments that control how it performs its transformation. For example, you can pass the desired format to the `date` pipe.
|
||||
|
||||
```
|
||||
```
|
||||
<!-- Default format: output 'Jun 15, 2015'-->
|
||||
<p>Today is {{today | date}}</p>
|
||||
|
||||
@ -140,33 +143,38 @@ Data binding plays an important role in communication between a template and its
|
||||
<p>The time is {{today | date:'shortTime'}}</p>
|
||||
```
|
||||
|
||||
<hr/>
|
||||
|
||||
### Directives
|
||||
|
||||
<img src="generated/images/guide/architecture/directive.png" alt="Directives" class="left">
|
||||
|
||||
Angular templates are *dynamic*. When Angular renders them, it transforms the DOM according to the instructions given by *directives*. A directive is a class with a `@Directive` decorator.
|
||||
Angular templates are *dynamic*. When Angular renders them, it transforms the DOM according to the instructions given by *directives*. A directive is a class with a `@Directive()` decorator.
|
||||
|
||||
A component is technically a directive - but components are so distinctive and central to Angular applications that Angular defines the `@Component` decorator, which extends the `@Directive` decorator with template-oriented features.
|
||||
A component is technically a directive.
|
||||
However, components are so distinctive and central to Angular applications that Angular
|
||||
defines the `@Component()` decorator, which extends the `@Directive()` decorator with
|
||||
template-oriented features.
|
||||
|
||||
There are two kinds of directives besides components: _structural_ and _attribute_ directives. Just as for components, the metadata for a directive associates the class with a `selector` that you use to insert it into HTML. In templates, directives typically appear within an element tag as attributes, either by name or as the target of an assignment or a binding.
|
||||
In addition to components, there are two other kinds of directives: *structural* and *attribute*.
|
||||
Angular defines a number of directives of both kinds, and you can define your own using the `@Directive()` decorator.
|
||||
|
||||
Just as for components, the metadata for a directive associates the decorated class with a `selector` element that you use to insert it into HTML. In templates, directives typically appear within an element tag as attributes, either by name or as the target of an assignment or a binding.
|
||||
|
||||
#### Structural directives
|
||||
|
||||
Structural directives alter layout by adding, removing, and replacing elements in DOM. The example template uses two built-in structural directives to add application logic to how the view is rendered:
|
||||
*Structural directives* alter layout by adding, removing, and replacing elements in the DOM.
|
||||
The example template uses two built-in structural directives to add application logic to how the view is rendered.
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.1.html" linenums="false" title="src/app/hero-list.component.html (structural)" region="structural"></code-example>
|
||||
|
||||
* [`*ngFor`](guide/displaying-data#ngFor) is an iterative; it tells Angular to stamp out one `<li>` per hero in the `heroes` list.
|
||||
* [`*ngIf`](guide/displaying-data#ngIf) is a conditional; it includes the `HeroDetail` component only if a selected hero exists.
|
||||
* [`*ngFor`](guide/displaying-data#ngFor) is an iterative; it tells Angular to stamp out one `<li>` per hero in the `heroes` list.
|
||||
* [`*ngIf`](guide/displaying-data#ngIf) is a conditional; it includes the `HeroDetail` component only if a selected hero exists.
|
||||
|
||||
#### Attribute directives
|
||||
|
||||
Attribute directives alter the appearance or behavior of an existing element.
|
||||
*Attribute directives* alter the appearance or behavior of an existing element.
|
||||
In templates they look like regular HTML attributes, hence the name.
|
||||
|
||||
The `ngModel` directive, which implements two-way data binding, is an example of an attribute directive. `ngModel` modifies the behavior of an existing element (typically an `<input>`) by setting its display value property and responding to change events.
|
||||
The `ngModel` directive, which implements two-way data binding, is an example of an attribute directive. `ngModel` modifies the behavior of an existing element (typically `<input>`) by setting its display value property and responding to change events.
|
||||
|
||||
<code-example path="architecture/src/app/hero-detail.component.html" linenums="false" title="src/app/hero-detail.component.html (ngModel)" region="ngModel"></code-example>
|
||||
|
||||
@ -175,6 +183,8 @@ Angular has more pre-defined directives that either alter the layout structure
|
||||
or modify aspects of DOM elements and components
|
||||
(for example, [ngStyle](guide/template-syntax#ngStyle) and [ngClass](guide/template-syntax#ngClass)).
|
||||
|
||||
You can also write your own directives. Components such as `HeroListComponent` are one kind of custom directive. You can also create custom structural and attribute directives.
|
||||
<div class="alert is-helpful">
|
||||
|
||||
<!-- PENDING: link to where to learn more about other kinds! -->
|
||||
Learn more in the [Attribute Directives](guide/attribute-directives) and [Structural Directives](guide/structural-directives) guides.
|
||||
|
||||
</div>
|
||||
|
@ -1,40 +1,39 @@
|
||||
# Introduction to modules
|
||||
|
||||
<img src="generated/images/guide/architecture/module.png" alt="Module" class="left">
|
||||
Angular apps are modular and Angular has its own modularity system called *NgModules*.
|
||||
NgModules are containers for a cohesive block of code dedicated to an application domain, a workflow, or a closely related set of capabilities. They can contain components, service providers, and other code files whose scope is defined by the containing NgModule. They can import functionality that is exported from other NgModules, and export selected functionality for use by other NgModules.
|
||||
|
||||
Angular apps are modular and Angular has its own modularity system called _NgModules_. An NgModule is a container for a cohesive block of code dedicated to an application domain, a workflow, or a closely related set of capabilities. It can contain components, service providers, and other code files whose scope is defined by the containing NgModule. It can import functionality that is exported from other NgModules, and export selected functionality for use by other NgModules.
|
||||
Every Angular app has at least one NgModule class, [the *root module*](guide/bootstrapping), which is conventionally named `AppModule` and resides in a file named `app.module.ts`. You launch your app by *bootstrapping* the root NgModule.
|
||||
|
||||
Every Angular app has at least one NgModule class, [the _root module_](guide/bootstrapping), which is conventionally named `AppModule` and resides in a file named `app.module.ts`. You launch your app by *bootstrapping* the root NgModule.
|
||||
|
||||
While a small application might have only one NgModule, most apps have many more _feature modules_. The _root_ NgModule for an app is so named because it can include child NgModules in a hierarchy of any depth.
|
||||
While a small application might have only one NgModule, most apps have many more *feature modules*. The *root* NgModule for an app is so named because it can include child NgModules in a hierarchy of any depth.
|
||||
|
||||
## NgModule metadata
|
||||
|
||||
An NgModule is defined as a class decorated with `@NgModule`. The `@NgModule` decorator is a function that takes a single metadata object, whose properties describe the module. The most important properties are as follows.
|
||||
An NgModule is defined by a class decorated with `@NgModule()`. The `@NgModule()` decorator is a function that takes a single metadata object, whose properties describe the module. The most important properties are as follows.
|
||||
|
||||
* `declarations`—The [components](guide/architecture-components), _directives_, and _pipes_ that belong to this NgModule.
|
||||
* `declarations`: The [components](guide/architecture-components), *directives*, and *pipes* that belong to this NgModule.
|
||||
|
||||
* `exports`—The subset of declarations that should be visible and usable in the _component templates_ of other NgModules.
|
||||
* `exports`: The subset of declarations that should be visible and usable in the *component templates* of other NgModules.
|
||||
|
||||
* `imports`—Other modules whose exported classes are needed by component templates declared in _this_ NgModule.
|
||||
* `imports`: Other modules whose exported classes are needed by component templates declared in *this* NgModule.
|
||||
|
||||
* `providers`—Creators of [services](guide/architecture-services) that this NgModule contributes to the global collection of services; they become accessible in all parts of the app. (You can also specify providers at the component level, which is often preferred.)
|
||||
* `providers`: Creators of [services](guide/architecture-services) that this NgModule contributes to the global collection of services; they become accessible in all parts of the app. (You can also specify providers at the component level, which is often preferred.)
|
||||
|
||||
* `bootstrap`—The main application view, called the _root component_, which hosts all other app views. Only the _root NgModule_ should set this `bootstrap` property.
|
||||
* `bootstrap`: The main application view, called the *root component*, which hosts all other app views. Only the *root NgModule* should set the `bootstrap` property.
|
||||
|
||||
Here's a simple root NgModule definition:
|
||||
Here's a simple root NgModule definition.
|
||||
|
||||
<code-example path="architecture/src/app/mini-app.ts" region="module" title="src/app/app.module.ts" linenums="false"></code-example>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
The `export` of `AppComponent` is just to show how to export; it isn't actually necessary in this example. A root NgModule has no reason to _export_ anything because other modules don't need to _import_ the root NgModule.
|
||||
The `export` property of `AppComponent` is included here for illustration; it isn't actually necessary in this example. A root NgModule has no reason to *export* anything because other modules don't need to *import* the root NgModule.
|
||||
|
||||
</div>
|
||||
|
||||
## NgModules and components
|
||||
|
||||
NgModules provide a _compilation context_ for their components. A root NgModule always has a root component that is created during bootstrap, but any NgModule can include any number of additional components, which can be loaded through the router or created through the template. The components that belong to an NgModule share a compilation context.
|
||||
NgModules provide a *compilation context* for their components. A root NgModule always has a root component that is created during bootstrap, but any NgModule can include any number of additional components, which can be loaded through the router or created through the template. The components that belong to an NgModule share a compilation context.
|
||||
|
||||
<figure>
|
||||
|
||||
@ -44,7 +43,7 @@ NgModules provide a _compilation context_ for their components. A root NgModule
|
||||
|
||||
<br class="clear">
|
||||
|
||||
A component and its template together define a _view_. A component can contain a _view hierarchy_, which allows you to define arbitrarily complex areas of the screen that can be created, modified, and destroyed as a unit. A view hierarchy can mix views defined in components that belong to different NgModules. This is often the case, especially for UI libraries.
|
||||
A component and its template together define a *view*. A component can contain a *view hierarchy*, which allows you to define arbitrarily complex areas of the screen that can be created, modified, and destroyed as a unit. A view hierarchy can mix views defined in components that belong to different NgModules. This is often the case, especially for UI libraries.
|
||||
|
||||
<figure>
|
||||
|
||||
@ -54,17 +53,17 @@ A component and its template together define a _view_. A component can contain a
|
||||
|
||||
<br class="clear">
|
||||
|
||||
When you create a component, it is associated directly with a single view, called the _host view_. The host view can be the root of a view hierarchy, which can contain _embedded views_, which are in turn the host views of other components. Those components can be in the same NgModule, or can be imported from other NgModules. Views in the tree can be nested to any depth.
|
||||
When you create a component, it's associated directly with a single view, called the *host view*. The host view can be the root of a view hierarchy, which can contain *embedded views*, which are in turn the host views of other components. Those components can be in the same NgModule, or can be imported from other NgModules. Views in the tree can be nested to any depth.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
The hierarchical structure of views is a key factor in the way Angular detects and responds to changes in the DOM and app data.
|
||||
**Note:** The hierarchical structure of views is a key factor in the way Angular detects and responds to changes in the DOM and app data.
|
||||
</div>
|
||||
|
||||
## NgModules and JavaScript modules
|
||||
|
||||
The NgModule system is different from and unrelated to the JavaScript (ES2015) module system for managing collections of JavaScript objects. These are two different and _complementary_ module systems. You can use them both to write your apps.
|
||||
The NgModule system is different from and unrelated to the JavaScript (ES2015) module system for managing collections of JavaScript objects. These are *complementary* module systems that you can use together to write your apps.
|
||||
|
||||
In JavaScript each _file_ is a module and all objects defined in the file belong to that module.
|
||||
In JavaScript each *file* is a module and all objects defined in the file belong to that module.
|
||||
The module declares some objects to be public by marking them with the `export` key word.
|
||||
Other JavaScript modules use *import statements* to access public objects from other modules.
|
||||
|
||||
@ -80,29 +79,28 @@ Other JavaScript modules use *import statements* to access public objects from o
|
||||
|
||||
<img src="generated/images/guide/architecture/library-module.png" alt="Component" class="left">
|
||||
|
||||
Angular ships as a collection of JavaScript modules. You can think of them as library modules. Each Angular library name begins with the `@angular` prefix. Install them with the `npm` package manager and import parts of them with JavaScript `import` statements.
|
||||
Angular loads as a collection of JavaScript modules. You can think of them as library modules. Each Angular library name begins with the `@angular` prefix. Install them with the `npm` package manager and import parts of them with JavaScript `import` statements.
|
||||
|
||||
<br class="clear">
|
||||
|
||||
For example, import Angular's `Component` decorator from the `@angular/core` library like this:
|
||||
For example, import Angular's `Component` decorator from the `@angular/core` library like this.
|
||||
|
||||
<code-example path="architecture/src/app/app.component.ts" region="import" linenums="false"></code-example>
|
||||
|
||||
You also import NgModules from Angular _libraries_ using JavaScript import statements.
|
||||
For example, the following code imports the `BrowserModule` NgModule from the `platform-browser` library:
|
||||
You also import NgModules from Angular *libraries* using JavaScript import statements.
|
||||
For example, the following code imports the `BrowserModule` NgModule from the `platform-browser` library.
|
||||
|
||||
<code-example path="architecture/src/app/mini-app.ts" region="import-browser-module" linenums="false"></code-example>
|
||||
|
||||
In the example of the simple root module above, the application module needs material from within the `BrowserModule`. To access that material, add it to the `@NgModule` metadata `imports` like this.
|
||||
In the example of the simple root module above, the application module needs material from within
|
||||
`BrowserModule`. To access that material, add it to the `@NgModule` metadata `imports` like this.
|
||||
|
||||
<code-example path="architecture/src/app/mini-app.ts" region="ngmodule-imports" linenums="false"></code-example>
|
||||
|
||||
In this way you're using both the Angular and JavaScript module systems _together_. Although it's easy to confuse the two systems, which share the common vocabulary of "imports" and "exports", you will become familiar with the different contexts in which they are used.
|
||||
In this way you're using the Angular and JavaScript module systems *together*. Although it's easy to confuse the two systems, which share the common vocabulary of "imports" and "exports", you will become familiar with the different contexts in which they are used.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Learn more from the [NgModules](guide/ngmodules) page.
|
||||
Learn more from the [NgModules](guide/ngmodules) guide.
|
||||
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
@ -1,48 +1,50 @@
|
||||
# Next steps: tools and techniques
|
||||
|
||||
Once you have understood the basic building blocks, you can begin to learn more about the features and tools that are available to help you develop and deliver Angular applications. Angular provides a lot more features and services that are covered in this documentation.
|
||||
After you understand the basic Angular building blocks, you can begin to learn more
|
||||
about the features and tools that are available to help you develop and deliver Angular applications.
|
||||
Here are some key features.
|
||||
|
||||
#### Responsive programming tools
|
||||
## Responsive programming tools
|
||||
|
||||
* [Lifecycle hooks](guide/lifecycle-hooks): Tap into key moments in the lifetime of a component, from its creation to its destruction, by implementing the lifecycle hook interfaces.
|
||||
* [Lifecycle hooks](guide/lifecycle-hooks): Tap into key moments in the lifetime of a component, from its creation to its destruction, by implementing the lifecycle hook interfaces.
|
||||
|
||||
* [Observables and event processing](guide/observables): How to use observables with components and services to publish and subscribe to messages of any type, such as user-interaction events and asynchronous operation results.
|
||||
* [Observables and event processing](guide/observables): How to use observables with components and services to publish and subscribe to messages of any type, such as user-interaction events and asynchronous operation results.
|
||||
|
||||
#### Client-server interaction tools
|
||||
## Client-server interaction tools
|
||||
|
||||
* [HTTP](guide/http): Communicate with a server to get data, save data, and invoke server-side actions with an HTTP client.
|
||||
* [HTTP](guide/http): Communicate with a server to get data, save data, and invoke server-side actions with an HTTP client.
|
||||
|
||||
* [Server-side Rendering](guide/universal): Angular Universal generates static application pages on the server through server-side rendering (SSR). This allows you to run your Angular app on the server in order to improve performance and show the first page quickly on mobile and low-powered devices, and also facilitate web crawlers.
|
||||
* [Server-side Rendering](guide/universal): Angular Universal generates static application pages on the server through server-side rendering (SSR). This allows you to run your Angular app on the server in order to improve performance and show the first page quickly on mobile and low-powered devices, and also facilitate web crawlers.
|
||||
|
||||
* [Service Workers](guide/service-worker-intro): A service worker is a script that runs in the web browser and manages caching for an application. Service workers function as a network proxy. They intercept outgoing HTTP requests and can, for example, deliver a cached response if one is available. You can significantly improve the user experience by using a service worker to reduce dependency on the network.
|
||||
* [Service Workers](guide/service-worker-intro): Use a service worker to reduce dependency on the network
|
||||
significantly improving the use experience.
|
||||
|
||||
#### Domain-specific libraries
|
||||
## Domain-specific libraries
|
||||
|
||||
* [Animations](guide/animations): Animate component behavior
|
||||
without deep knowledge of animation techniques or CSS with Angular's animation library.
|
||||
* [Animations](guide/animations): Use Angular's animation library to animate component behavior
|
||||
without deep knowledge of animation techniques or CSS.
|
||||
|
||||
* [Forms](guide/forms): Support complex data entry scenarios with HTML-based validation and dirty checking.
|
||||
* [Forms](guide/forms): Support complex data entry scenarios with HTML-based validation and dirty checking.
|
||||
|
||||
#### Support for the development cycle
|
||||
## Support for the development cycle
|
||||
|
||||
* [Testing Platform](guide/testing): Run unit tests on your application parts as they interact with the Angular framework.
|
||||
* [Testing platform](guide/testing): Run unit tests on your application parts as they interact with the Angular framework.
|
||||
|
||||
* [Internationalization](guide/i18n): Angular's internationalization (i18n) tools can help you make your app available in multiple languages.
|
||||
* [Internationalization](guide/i18n): Make your app available in multiple languages with Angular's internationalization (i18n) tools.
|
||||
|
||||
* [Compilation](guide/aot-compiler): Angular provides just-in-time (JIT) compilation for the development environment, and ahead-of-time (AOT) compilation for the production environment.
|
||||
* [Compilation](guide/aot-compiler): Angular provides just-in-time (JIT) compilation for the development environment, and ahead-of-time (AOT) compilation for the production environment.
|
||||
|
||||
* [Security guidelines](guide/security): Learn about Angular's built-in protections against common web-app vulnerabilities and attacks such as cross-site scripting attacks.
|
||||
* [Security guidelines](guide/security): Learn about Angular's built-in protections against common web-app vulnerabilities and attacks such as cross-site scripting attacks.
|
||||
|
||||
#### Setup and deployment tools
|
||||
## Setup and deployment tools
|
||||
|
||||
* [Setup for local development](guide/setup): Learn how to set up a new project for development with QuickStart.
|
||||
* [Setup for local development](guide/setup): Set up a new project for development with QuickStart.
|
||||
|
||||
* [Installation](guide/npm-packages): The [Angular CLI](https://cli.angular.io/), Angular applications, and Angular itself depend on features and functionality provided by libraries that are available as [npm](https://docs.npmjs.com/) packages.
|
||||
* [Installation](guide/npm-packages): The [Angular CLI](https://cli.angular.io/), Angular applications, and Angular itself depend on features and functionality provided by libraries that are available as [npm](https://docs.npmjs.com/) packages.
|
||||
|
||||
* [Typescript Configuration](guide/typescript-configuration): TypeScript is the primary language for Angular application development.
|
||||
* [TypeScript configuration](guide/typescript-configuration): TypeScript is the primary language for Angular application development.
|
||||
|
||||
* [Browser support](guide/browser-support): Learn how to make your apps compatible across a wide range of browsers.
|
||||
* [Browser support](guide/browser-support): Make your apps compatible across a wide range of browsers.
|
||||
|
||||
* [Deployment](guide/deployment): Learn techniques for deploying your Angular application to a remote server.
|
||||
* [Deployment](guide/deployment): Learn techniques for deploying your Angular application to a remote server.
|
||||
|
||||
<hr/>
|
||||
|
@ -1,22 +1,32 @@
|
||||
# Introduction to services and dependency injection
|
||||
|
||||
<img src="generated/images/guide/architecture/service.png" alt="Service" class="left">
|
||||
*Service* is a broad category encompassing any value, function, or feature that an app needs.
|
||||
A service is typically a class with a narrow, well-defined purpose.
|
||||
It should do something specific and do it well.
|
||||
|
||||
_Service_ is a broad category encompassing any value, function, or feature that an app needs. A service is typically a class with a narrow, well-defined purpose. It should do something specific and do it well.
|
||||
<br class="clear">
|
||||
Angular distinguishes components from services to increase modularity and reusability.
|
||||
By separating a component's view-related functionality from other kinds of processing,
|
||||
you can make your component classes lean and efficient.
|
||||
|
||||
Angular distinguishes components from services in order to increase modularity and reusability.
|
||||
Ideally, a component's job is to enable the user experience and nothing more.
|
||||
A component should present properties and methods for data binding,
|
||||
in order to mediate between the view (rendered by the template)
|
||||
and the application logic (which often includes some notion of a *model*).
|
||||
|
||||
* By separating a component's view-related functionality from other kinds of processing, you can make your component classes lean and efficient. Ideally, a component's job is to enable the user experience and nothing more. It should present properties and methods for data binding, in order to mediate between the view (rendered by the template) and the application logic (which often includes some notion of a _model_).
|
||||
A component can delegate certain tasks to services, such as fetching data from the server,
|
||||
validating user input, or logging directly to the console.
|
||||
By defining such processing tasks in an *injectable service class*, you make those tasks
|
||||
available to any component.
|
||||
You can also make your app more adaptable by injecting different providers of the same kind of service,
|
||||
as appropriate in different circumstances.
|
||||
|
||||
* A component should not need to define things like how to fetch data from the server, validate user input, or log directly to the console. Instead, it can delegate such tasks to services. By defining that kind of processing task in an injectable service class, you make it available to any component. You can also make your app more adaptable by injecting different providers of the same kind of service, as appropriate in different circumstances.
|
||||
|
||||
Angular doesn't *enforce* these principles. Angular does help you *follow* these principles by making it easy to factor your
|
||||
application logic into services and make those services available to components through *dependency injection*.
|
||||
Angular doesn't *enforce* these principles. Angular does help you *follow* these principles
|
||||
by making it easy to factor your application logic into services and make those services
|
||||
available to components through *dependency injection*.
|
||||
|
||||
## Service examples
|
||||
|
||||
Here's an example of a service class that logs to the browser console:
|
||||
Here's an example of a service class that logs to the browser console.
|
||||
|
||||
<code-example path="architecture/src/app/logger.service.ts" linenums="false" title="src/app/logger.service.ts (class)" region="class"></code-example>
|
||||
|
||||
@ -24,35 +34,41 @@ Services can depend on other services. For example, here's a `HeroService` that
|
||||
|
||||
<code-example path="architecture/src/app/hero.service.ts" linenums="false" title="src/app/hero.service.ts (class)" region="class"></code-example>
|
||||
|
||||
<hr/>
|
||||
|
||||
## Dependency injection
|
||||
## Dependency injection (DI)
|
||||
|
||||
<img src="generated/images/guide/architecture/dependency-injection.png" alt="Service" class="left">
|
||||
|
||||
DI is wired into the Angular framework and used everywhere to provide new components with the services or other things they need.
|
||||
Components consume services; that is, you can *inject* a service into a component, giving the component access to that service class.
|
||||
|
||||
To define a class as a service in Angular, use the `@Injectable` decorator to provide the metadata that allows Angular to inject it into a component as a *dependency*.
|
||||
To define a class as a service in Angular, use the `@Injectable()` decorator to provide the metadata that allows Angular to inject it into a component as a *dependency*.
|
||||
Similarly, use the `@Injectable()` decorator to indicate that a component or other class (such as another service, a pipe, or an NgModule) *has* a dependency.
|
||||
|
||||
Similarly, use the `@Injectable` decorator to indicate that a component or other class (such as another service, a pipe, or an NgModule) _has_ a dependency. A dependency doesn't have to be a service—it could be a function, for example, or a value.
|
||||
* The *injector* is the main mechanism. Angular creates an application-wide injector for you during the bootstrap process, and additional injectors as needed. You don't have to create injectors.
|
||||
|
||||
*Dependency injection* (often called DI) is wired into the Angular framework and used everywhere to provide new components with the services or other things they need.
|
||||
* An injector creates dependencies, and maintains a *container* of dependency instances that it reuses if possible.
|
||||
|
||||
* The *injector* is the main mechanism. You don't have to create an Angular injector. Angular creates an application-wide injector for you during the bootstrap process.
|
||||
* A *provider* is an object that tell an injector how to obtain or create a dependency.
|
||||
|
||||
* The injector maintains a *container* of dependency instances that it has already created, and reuses them if possible.
|
||||
For any dependency that you need in your app, you must register a provider with the app's injector,
|
||||
so that the injector can use the provider to create new instances.
|
||||
For a service, the provider is typically the service class itself.
|
||||
|
||||
* A *provider* is a recipe for creating a dependency. For a service, this is typically the service class itself. For any dependency you need in your app, you must register a provider with the app's injector, so that the injector can use it to create new instances.
|
||||
<div class="alert is-helpful">
|
||||
|
||||
When Angular creates a new instance of a component class, it determines which services or other dependencies that component needs by looking at the types of its constructor parameters. For example, the constructor of `HeroListComponent` needs a `HeroService`:
|
||||
A dependency doesn't have to be a service—it could be a function, for example, or a value.
|
||||
|
||||
</div>
|
||||
|
||||
When Angular creates a new instance of a component class, it determines which services or other dependencies that component needs by looking at the constructor parameter types. For example, the constructor of `HeroListComponent` needs `HeroService`.
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.ts" linenums="false" title="src/app/hero-list.component.ts (constructor)" region="ctor"></code-example>
|
||||
|
||||
When Angular discovers that a component depends on a service, it first checks if the injector already has any existing instances of that service. If a requested service instance does not yet exist, the injector makes one using the registered provider, and adds it to the injector before returning the service to Angular.
|
||||
When Angular discovers that a component depends on a service, it first checks if the injector has any existing instances of that service. If a requested service instance doesn't yet exist, the injector makes one using the registered provider, and adds it to the injector before returning the service to Angular.
|
||||
|
||||
When all requested services have been resolved and returned, Angular can call the component's constructor with those services as arguments.
|
||||
|
||||
The process of `HeroService` injection looks something like this:
|
||||
The process of `HeroService` injection looks something like this.
|
||||
|
||||
<figure>
|
||||
<img src="generated/images/guide/architecture/injector-injects.png" alt="Service" class="left">
|
||||
@ -60,35 +76,41 @@ The process of `HeroService` injection looks something like this:
|
||||
|
||||
### Providing services
|
||||
|
||||
You must register at least one *provider* of any service you are going to use. A service can register providers itself, making it available everywhere, or you can register providers with specific modules or components. You register providers in the metadata of the service (in the `@Injectable` decorator), or in the `@NgModule` or `@Component` metadata
|
||||
You must register at least one *provider* of any service you are going to use.
|
||||
The provider can be part of the service's own metadata, making that service available everywhere,
|
||||
or you can register providers with specific modules or components.
|
||||
You register providers in the metadata of the service (in the `@Injectable()` decorator),
|
||||
or in the `@NgModule()` or `@Component()` metadata
|
||||
|
||||
* By default, the Angular CLI command `ng generate service` registers a provider with the root injector for your service by including provider metadata in the `@Injectable` decorator. The tutorial uses this method to register the provider of HeroService class definition:
|
||||
* By default, the Angular CLI command `ng generate service` registers a provider with the root injector for your service by including provider metadata in the `@Injectable()` decorator. The tutorial uses this method to register the provider of HeroService class definition.
|
||||
|
||||
```
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
```
|
||||
```
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
```
|
||||
|
||||
When you provide the service at the root level, Angular creates a single, shared instance of HeroService and injects into any class that asks for it. Registering the provider in the `@Injectable` metadata also allows Angular to optimize an app by removing the service if it turns out not to be used after all.
|
||||
When you provide the service at the root level, Angular creates a single, shared instance of `HeroService`
|
||||
and injects it into any class that asks for it.
|
||||
Registering the provider in the `@Injectable()` metadata also allows Angular to optimize an app
|
||||
by removing the service from the compiled app if it isn't used.
|
||||
|
||||
* When you register a provider with a [specific NgModule](guide/architecture-modules), the same instance of a service is available to all components in that NgModule. To register at this level, use the `providers` property of the `@NgModule` decorator:
|
||||
* When you register a provider with a [specific NgModule](guide/architecture-modules), the same instance of a service is available to all components in that NgModule. To register at this level, use the `providers` property of the `@NgModule()` decorator,
|
||||
|
||||
```
|
||||
@NgModule({
|
||||
providers: [
|
||||
BackendService,
|
||||
Logger
|
||||
],
|
||||
...
|
||||
})
|
||||
```
|
||||
```
|
||||
@NgModule({
|
||||
providers: [
|
||||
BackendService,
|
||||
Logger
|
||||
],
|
||||
...
|
||||
})
|
||||
```
|
||||
|
||||
* When you register a provider at the component level, you get a new instance of the
|
||||
service with each new instance of that component. At the component level, register a service provider in the `providers` property of the `@Component` metadata:
|
||||
service with each new instance of that component.
|
||||
At the component level, register a service provider in the `providers` property of the `@Component()` metadata.
|
||||
|
||||
<code-example path="architecture/src/app/hero-list.component.ts" linenums="false" title="src/app/hero-list.component.ts (component providers)" region="providers"></code-example>
|
||||
<code-example path="architecture/src/app/hero-list.component.ts" linenums="false" title="src/app/hero-list.component.ts (component providers)" region="providers"></code-example>
|
||||
|
||||
For more detailed information, see the [Dependency Injection](guide/dependency-injection) section.
|
||||
|
||||
<hr/>
|
||||
|
@ -1,11 +1,11 @@
|
||||
# Architecture overview
|
||||
|
||||
Angular is a platform and framework for building client applications in HTML and TypeScript.
|
||||
Angular is itself written in TypeScript. It implements core and optional functionality as a set of TypeScript libraries that you import into your apps.
|
||||
Angular is written in TypeScript. It implements core and optional functionality as a set of TypeScript libraries that you import into your apps.
|
||||
|
||||
The basic building blocks of an Angular application are _NgModules_, which provide a compilation context for _components_. NgModules collect related code into functional sets; an Angular app is defined by a set of NgModules. An app always has at least a _root module_ that enables bootstrapping, and typically has many more _feature modules_.
|
||||
The basic building blocks of an Angular application are *NgModules*, which provide a compilation context for *components*. NgModules collect related code into functional sets; an Angular app is defined by a set of NgModules. An app always has at least a *root module* that enables bootstrapping, and typically has many more *feature modules*.
|
||||
|
||||
* Components define *views*, which are sets of screen elements that Angular can choose among and modify according to your program logic and data. Every app has at least a root component.
|
||||
* Components define *views*, which are sets of screen elements that Angular can choose among and modify according to your program logic and data.
|
||||
|
||||
* Components use *services*, which provide specific functionality not directly related to views. Service providers can be *injected* into components as *dependencies*, making your code modular, reusable, and efficient.
|
||||
|
||||
@ -13,19 +13,19 @@ Both components and services are simply classes, with *decorators* that mark the
|
||||
|
||||
* The metadata for a component class associates it with a *template* that defines a view. A template combines ordinary HTML with Angular *directives* and *binding markup* that allow Angular to modify the HTML before rendering it for display.
|
||||
|
||||
* The metadata for a service class provides the information Angular needs to make it available to components through *Dependency Injection (DI)*.
|
||||
* The metadata for a service class provides the information Angular needs to make it available to components through *dependency injection (DI)*.
|
||||
|
||||
An app's components typically define many views, arranged hierarchically. Angular provides the `Router` service to help you define navigation paths among views. The router provides sophisticated in-browser navigational capabilities.
|
||||
|
||||
## Modules
|
||||
|
||||
Angular defines the `NgModule`, which differs from and complements the JavaScript (ES2015) module. An NgModule declares a compilation context for a set of components that is dedicated to an application domain, a workflow, or a closely related set of capabilities. An NgModule can associate its components with related code, such as services, to form functional units.
|
||||
Angular *NgModules* differ from and complement JavaScript (ES2015) modules. An NgModule declares a compilation context for a set of components that is dedicated to an application domain, a workflow, or a closely related set of capabilities. An NgModule can associate its components with related code, such as services, to form functional units.
|
||||
|
||||
Every Angular app has a _root module_, conventionally named `AppModule`, which provides the bootstrap mechanism that launches the application. An app typically contains many functional modules.
|
||||
Every Angular app has a *root module*, conventionally named `AppModule`, which provides the bootstrap mechanism that launches the application. An app typically contains many functional modules.
|
||||
|
||||
Like JavaScript modules, NgModules can import functionality from other NgModules, and allow their own functionality to be exported and used by other NgModules. For example, to use the router service in your app, you import the `Router` NgModule.
|
||||
|
||||
Organizing your code into distinct functional modules helps in managing development of complex applications, and in designing for reusability. In addition, this technique lets you take advantage of _lazy-loading_—that is, loading modules on demand—in order to minimize the amount of code that needs to be loaded at startup.
|
||||
Organizing your code into distinct functional modules helps in managing development of complex applications, and in designing for reusability. In addition, this technique lets you take advantage of *lazy-loading*—that is, loading modules on demand—to minimize the amount of code that needs to be loaded at startup.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
@ -35,13 +35,13 @@ Organizing your code into distinct functional modules helps in managing developm
|
||||
|
||||
## Components
|
||||
|
||||
Every Angular application has at least one component, the *root component* that connects a component hierarchy with the page DOM. Each component defines a class that contains application data and logic, and is associated with an HTML *template* that defines a view to be displayed in a target environment.
|
||||
Every Angular application has at least one component, the *root component* that connects a component hierarchy with the page document object model (DOM). Each component defines a class that contains application data and logic, and is associated with an HTML *template* that defines a view to be displayed in a target environment.
|
||||
|
||||
The `@Component` decorator identifies the class immediately below it as a component, and provides the template and related component-specific metadata.
|
||||
The `@Component()` decorator identifies the class immediately below it as a component, and provides the template and related component-specific metadata.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Decorators are functions that modify JavaScript classes. Angular defines a number of such decorators that attach specific kinds of metadata to classes, so that it knows what those classes mean and how they should work.
|
||||
Decorators are functions that modify JavaScript classes. Angular defines a number of decorators that attach specific kinds of metadata to classes, so that the system knows what those classes mean and how they should work.
|
||||
|
||||
<a href="https://medium.com/google-developers/exploring-es7-decorators-76ecb65fb841#.x5c2ndtx0">Learn more about decorators on the web.</a>
|
||||
|
||||
@ -49,15 +49,18 @@ The `@Component` decorator identifies the class immediately below it as a compon
|
||||
|
||||
### Templates, directives, and data binding
|
||||
|
||||
A template combines HTML with Angular markup that can modify the HTML elements before they are displayed.
|
||||
Template *directives* provide program logic, and *binding markup* connects your application data and the document object model (DOM).
|
||||
A template combines HTML with Angular markup that can modify HTML elements before they are displayed.
|
||||
Template *directives* provide program logic, and *binding markup* connects your application data and the DOM.
|
||||
There are two types of data binding:
|
||||
|
||||
* *Event binding* lets your app respond to user input in the target environment by updating your application data.
|
||||
* *Property binding* lets you interpolate values that are computed from your application data into the HTML.
|
||||
|
||||
Before a view is displayed, Angular evaluates the directives and resolves the binding syntax in the template to modify the HTML elements and the DOM, according to your program data and logic. Angular supports *two-way data binding*, meaning that changes in the DOM, such as user choices, can also be reflected back into your program data.
|
||||
Before a view is displayed, Angular evaluates the directives and resolves the binding syntax in the template to modify the HTML elements and the DOM, according to your program data and logic. Angular supports *two-way data binding*, meaning that changes in the DOM, such as user choices, are also reflected in your program data.
|
||||
|
||||
Your templates can also use *pipes* to improve the user experience by transforming values for display. Use pipes to display, for example, dates and currency values in a way appropriate to the user's locale. Angular provides predefined pipes for common transformations, and you can also define your own.
|
||||
Your templates can use *pipes* to improve the user experience by transforming values for display.
|
||||
For example, use pipes to display dates and currency values that are appropriate for a user's locale.
|
||||
Angular provides predefined pipes for common transformations, and you can also define your own pipes.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
@ -70,13 +73,13 @@ Your templates can also use *pipes* to improve the user experience by transformi
|
||||
|
||||
## Services and dependency injection
|
||||
|
||||
For data or logic that is not associated with a specific view, and that you want to share across components, you create a *service* class. A service class definition is immediately preceded by the `@Injectable` decorator. The decorator provides the metadata that allows your service to be *injected* into client components as a dependency.
|
||||
For data or logic that isn't associated with a specific view, and that you want to share across components, you create a *service* class. A service class definition is immediately preceded by the `@Injectable()` decorator. The decorator provides the metadata that allows your service to be *injected* into client components as a dependency.
|
||||
|
||||
*Dependency injection* (or DI) lets you keep your component classes lean and efficient. They don't fetch data from the server, validate user input, or log directly to the console; they delegate such tasks to services.
|
||||
*Dependency injection* (DI) lets you keep your component classes lean and efficient. They don't fetch data from the server, validate user input, or log directly to the console; they delegate such tasks to services.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
For a more detailed discusssion, see [Introduction to services and DI](guide/architecture-services).
|
||||
For a more detailed discussion, see [Introduction to services and DI](guide/architecture-services).
|
||||
|
||||
</div>
|
||||
|
||||
@ -85,14 +88,16 @@ For data or logic that is not associated with a specific view, and that you want
|
||||
The Angular `Router` NgModule provides a service that lets you define a navigation path among the different application states and view hierarchies in your app. It is modeled on the familiar browser navigation conventions:
|
||||
|
||||
* Enter a URL in the address bar and the browser navigates to a corresponding page.
|
||||
|
||||
* Click links on the page and the browser navigates to a new page.
|
||||
|
||||
* Click the browser's back and forward buttons and the browser navigates backward and forward through the history of pages you've seen.
|
||||
|
||||
The router maps URL-like paths to views instead of pages. When a user performs an action, such as clicking a link, that would load a new page in the browser, the router intercepts the browser's behavior, and shows or hides view hierarchies.
|
||||
|
||||
If the router determines that the current application state requires particular functionality, and the module that defines it has not been loaded, the router can _lazy-load_ the module on demand.
|
||||
If the router determines that the current application state requires particular functionality, and the module that defines it hasn't been loaded, the router can *lazy-load* the module on demand.
|
||||
|
||||
The router interprets a link URL according to your app's view navigation rules and data state. You can navigate to new views when the user clicks a button, selects from a drop box, or in response to some other stimulus from any source. The Router logs activity in the browser's history journal, so the back and forward buttons work as well.
|
||||
The router interprets a link URL according to your app's view navigation rules and data state. You can navigate to new views when the user clicks a button or selects from a drop box, or in response to some other stimulus from any source. The router logs activity in the browser's history, so the back and forward buttons work as well.
|
||||
|
||||
To define navigation rules, you associate *navigation paths* with your components. A path uses a URL-like syntax that integrates your program data, in much the same way that template syntax integrates your views with your program data. You can then apply program logic to choose which views to show or to hide, in response to user input and your own access rules.
|
||||
|
||||
@ -119,19 +124,26 @@ You've learned the basics about the main building blocks of an Angular applicati
|
||||
|
||||
Each of these subjects is introduced in more detail in the following pages.
|
||||
|
||||
* [Modules](guide/architecture-modules)
|
||||
* [Components](guide/architecture-components)
|
||||
* [Templates](guide/architecture-components#templates-and-views)
|
||||
* [Metadata](guide/architecture-components#component-metadata)
|
||||
* [Introduction to Modules](guide/architecture-modules)
|
||||
|
||||
* [Introduction to Components](guide/architecture-components)
|
||||
|
||||
* [Templates and views](guide/architecture-components#templates-and-views)
|
||||
|
||||
* [Component metadata](guide/architecture-components#component-metadata)
|
||||
|
||||
* [Data binding](guide/architecture-components#data-binding)
|
||||
|
||||
* [Directives](guide/architecture-components#directives)
|
||||
|
||||
* [Pipes](guide/architecture-components#pipes)
|
||||
* [Services and dependency injection](guide/architecture-services)
|
||||
|
||||
* [Introduction to services and dependency injection](guide/architecture-services)
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Note that the code referenced on these pages is available as a <live-example></live-example>.
|
||||
</div>
|
||||
|
||||
When you are familiar with these fundamental building blocks, you can explore them in more detail in the documentation. To learn about more tools and techniques that are available to help you build and deploy Angular applications, see [Next steps](guide/architecture-next-steps).
|
||||
When you're familiar with these fundamental building blocks, you can explore them in more detail in the documentation. To learn about more tools and techniques that are available to help you build and deploy Angular applications, see [Next steps: tools and techniques](guide/architecture-next-steps).
|
||||
</div>
|
||||
|
@ -89,47 +89,51 @@ promise.then(() => {
|
||||
The following code snippets illustrate how the same kind of operation is defined using observables and promises.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Operation</th>
|
||||
<th>Observable</th>
|
||||
<th>Promise</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Creation</td>
|
||||
<td>
|
||||
<pre>new Observable((observer) => {
|
||||
observer.next(123);
|
||||
});</pre>
|
||||
</td>
|
||||
<td>
|
||||
<pre>new Promise((resolve, reject) => {
|
||||
resolve(123);
|
||||
});</pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Transform</td>
|
||||
<td><pre>obs.map((value) => value * 2 );</pre></td>
|
||||
<td><pre>promise.then((value) => value * 2);</pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Subscribe</td>
|
||||
<td>
|
||||
<pre>sub = obs.subscribe((value) => {
|
||||
console.log(value)
|
||||
});</pre>
|
||||
</td>
|
||||
<td>
|
||||
<pre>promise.then((value) => {
|
||||
console.log(value);
|
||||
});</pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Unsubscribe</td>
|
||||
<td><pre>sub.unsubscribe();</pre></td>
|
||||
<td>Implied by promise resolution.</td>
|
||||
</tr>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Operation</th>
|
||||
<th>Observable</th>
|
||||
<th>Promise</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Creation</td>
|
||||
<td>
|
||||
<pre>new Observable((observer) => {
|
||||
observer.next(123);
|
||||
});</pre>
|
||||
</td>
|
||||
<td>
|
||||
<pre>new Promise((resolve, reject) => {
|
||||
resolve(123);
|
||||
});</pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Transform</td>
|
||||
<td><pre>obs.map((value) => value * 2 );</pre></td>
|
||||
<td><pre>promise.then((value) => value * 2);</pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Subscribe</td>
|
||||
<td>
|
||||
<pre>sub = obs.subscribe((value) => {
|
||||
console.log(value)
|
||||
});</pre>
|
||||
</td>
|
||||
<td>
|
||||
<pre>promise.then((value) => {
|
||||
console.log(value);
|
||||
});</pre>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Unsubscribe</td>
|
||||
<td><pre>sub.unsubscribe();</pre></td>
|
||||
<td>Implied by promise resolution.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Observables compared to events API
|
||||
|
@ -410,7 +410,7 @@ Here you see the new and the old implementation side-by-side:
|
||||
|
||||
Tree shaking is the ability to remove code that is not referenced in an application from the final bundle. Tree-shakable providers give Angular the ability to remove services that are not used in your application from the final output. This significantly reduces the size of your bundles.
|
||||
|
||||
Ideally, if an application is not injecting a service, it should not be included in the final output. However, it turns out that the Angular compiler cannot identify at build time if the service will be required or not. Because it's always possible to inject a service directly using `injector.get(Service)`, Angular cannot identify all of the places in your code where this injection could happen, so it has no choice but to include the service in the injector regardless. Thus, services provided in modules are not tree-shakeable.
|
||||
Ideally, if an application is not injecting a service, it should not be included in the final output. However, it turns out that the Angular compiler cannot identify at build time if the service will be required or not. Because it's always possible to inject a service directly using `injector.get(Service)`, Angular cannot identify all of the places in your code where this injection could happen, so it has no choice but to include the service in the injector regardless. Thus, services provided in modules are not tree-shakable.
|
||||
|
||||
Let us consider an example of non-tree-shakable providers in Angular.
|
||||
|
||||
@ -424,13 +424,13 @@ This module can then be imported into your application module, to make the servi
|
||||
|
||||
When `ngc` runs, it compiles AppModule into a module factory, which contains definitions for all the providers declared in all the modules it includes. At runtime, this factory becomes an injector that instantiates these services.
|
||||
|
||||
Tree-shaking doesn't work in the method above because Angular cannot decide to exclude one chunk of code (the provider definition for the service within the module factory) based on whether another chunk of code (the service class) is used. To make services tree-shakeable, the information about how to construct an instance of the service (the provider definition) needs to be a part of the service class itself.
|
||||
Tree-shaking doesn't work in the method above because Angular cannot decide to exclude one chunk of code (the provider definition for the service within the module factory) based on whether another chunk of code (the service class) is used. To make services tree-shakable, the information about how to construct an instance of the service (the provider definition) needs to be a part of the service class itself.
|
||||
|
||||
#### Creating tree-shakable providers
|
||||
|
||||
To create providers that are tree-shakable, the information that used to be specified in the module should be specified in the `@Injectable` decorator on the service itself.
|
||||
|
||||
The following example shows the tree-shakeable equivalent to the `ServiceModule` example above:
|
||||
The following example shows the tree-shakable equivalent to the `ServiceModule` example above:
|
||||
|
||||
<code-example path="dependency-injection/src/app/tree-shaking/service.ts" title="src/app/tree-shaking/service.ts" linenums="false"> </code-example>
|
||||
|
||||
@ -690,7 +690,7 @@ If the factory function needs access to other DI tokens, it can use the inject f
|
||||
|
||||
<code-example>
|
||||
const TOKEN =
|
||||
new InjectionToken('tree-shakeable token',
|
||||
new InjectionToken('tree-shakable token',
|
||||
{ providedIn: 'root', factory: () =>
|
||||
new AppConfig(inject(Parameter1), inject(Parameter2)), });
|
||||
</code-example>
|
||||
|
@ -284,4 +284,71 @@ This completes the cross validation example. We managed to:
|
||||
- validate the form based on the values of two sibling controls,
|
||||
- show a descriptive error message after the user interacted with the form and the validation failed.
|
||||
|
||||
## Async Validation
|
||||
This section shows how to create asynchronous validators. It assumes some basic knowledge of creating [custom validators](guide/form-validation#custom-validators).
|
||||
|
||||
### The Basics
|
||||
Just like synchronous validators have the `ValidatorFn` and `Validator` interfaces, asynchronous validators have their own counterparts: `AsyncValidatorFn` and `AsyncValidator`.
|
||||
|
||||
They are very similar with the only difference being:
|
||||
|
||||
* They must return a Promise or an Observable,
|
||||
* The observable returned must be finite, meaning it must complete at some point. To convert an infinite observable into a finite one, pipe the observable through a filtering operator such as `first`, `last`, `take`, or `takeUntil`.
|
||||
|
||||
It is important to note that the asynchronous validation happens after the synchronous validation, and is performed only if the synchronous validation is successful. This check allows forms to avoid potentially expensive async validation processes such as an HTTP request if more basic validation methods fail.
|
||||
|
||||
After asynchronous validation begins, the form control enters a `pending` state. You can inspect the control's `pending` property and use it to give visual feedback about the ongoing validation.
|
||||
|
||||
A common UI pattern is to show a spinner while the async validation is being performed. The following example presents how to achieve this with template-driven forms:
|
||||
|
||||
```html
|
||||
<input [(ngModel)}="name" #model="ngModel" appSomeAsyncValidator>
|
||||
<app-spinner *ngIf="model.pending"></app-spinner>
|
||||
```
|
||||
|
||||
### Implementing Custom Async Validator
|
||||
In the following section, validation is performed asynchronously to ensure that our heroes pick an alter ego that is not already taken. New heroes are constantly enlisting and old heroes are leaving the service. That means that we do not have the list of available alter egos ahead of time.
|
||||
|
||||
To validate the potential alter ego, we need to consult a central database of all currently enlisted heroes. The process is asynchronous, so we need a special validator for that.
|
||||
|
||||
Let's start by creating the validator class.
|
||||
|
||||
<code-example path="form-validation/src/app/shared/alter-ego.directive.ts" region="async-validator" linenums="false"></code-example>
|
||||
|
||||
As you can see, the `UniqueAlterEgoValidator` class implements the `AsyncValidator` interface. In the constructor, we inject the `HeroesService` that has the following interface:
|
||||
|
||||
```typescript
|
||||
interface HeroesService {
|
||||
isAlterEgoTaken: (alterEgo: string) => Observable<boolean>;
|
||||
}
|
||||
```
|
||||
|
||||
In a real world application, the `HeroesService` is responsible for making an HTTP request to the hero database to check if the alter ego is available. From the validator's point of view, the actual implementation of the service is not important, so we can just code against the `HeroesService` interface.
|
||||
|
||||
As the validation begins, the `UniqueAlterEgoValidator` delegates to the `HeroesService` `isAlterEgoTaken()` method with the current control value. At this point the control is marked as `pending` and remains in this state until the observable chain returned from the `validate()` method completes.
|
||||
|
||||
The `isAlterEgoTaken()` method dispatches an HTTP request that checks if the alter ego is available, and returns `Observable<boolean>` as the result. We pipe the response through the `map` operator and transform it into a validation result. As always, we return `null` if the form is valid, and `ValidationErrors` if it is not. We make sure to handle any potential errors with the `catchError` operator.
|
||||
|
||||
Here we decided that `isAlterEgoTaken()` error is treated as a successful validation, because failure to make a validation request does not necessarily mean that the alter ego is invalid. You could handle the error differently and return the `ValidationError` object instead.
|
||||
|
||||
After some time passes, the observable chain completes and the async validation is done. The `pending` flag is set to `false`, and the form validity is updated.
|
||||
|
||||
### Note on performance
|
||||
|
||||
By default, all validators are run after every form value change. With synchronous validators, this will not likely have a noticeable impact on application performance. However, it's common for async validators to perform some kind of HTTP request to validate the control. Dispatching an HTTP request after every keystroke could put a strain on the backend API, and should be avoided if possible.
|
||||
|
||||
We can delay updating the form validity by changing the `updateOn` property from `change` (default) to `submit` or `blur`.
|
||||
|
||||
With template-driven forms:
|
||||
|
||||
```html
|
||||
<input [(ngModel)]="name" [ngModelOptions]="{updateOn: 'blur'}">
|
||||
```
|
||||
|
||||
With reactive forms:
|
||||
|
||||
```typescript
|
||||
new FormControl('', {updateOn: 'blur'});
|
||||
```
|
||||
|
||||
**You can run the <live-example></live-example> to see the complete reactive and template-driven example code.**
|
||||
|
@ -73,11 +73,11 @@ Typically you don’t interact with the compiler directly; rather, you use it in
|
||||
|
||||
**@angular/forms**: support for both [template-driven](guide/forms) and [reactive forms](guide/reactive-forms).
|
||||
|
||||
**@angular/http**: Angular's old, soon-to-be-deprecated, HTTP client.
|
||||
**@angular/http**: Angular's old, deprecated, HTTP client.
|
||||
|
||||
**@angular/platform-browser**: Everything DOM and browser related, especially
|
||||
the pieces that help render into the DOM.
|
||||
This package also includes the `bootstrapStatic()` method
|
||||
This package also includes the `bootstrapModuleFactory()` method
|
||||
for bootstrapping applications for production builds that pre-compile with [AOT](guide/aot-compiler).
|
||||
|
||||
**@angular/platform-browser-dynamic**: Includes [Providers](api/core/Provider)
|
||||
|
@ -6,8 +6,8 @@ This guide offers tips and techniques for unit and integration testing Angular a
|
||||
The guide presents tests of a sample CLI application that is much like the [_Tour of Heroes_ tutorial](tutorial).
|
||||
The sample application and all tests in this guide are available for inspection and experimentation:
|
||||
|
||||
* <live-example embedded-style>Sample app</live-example>
|
||||
* <live-example stackblitz="specs">Tests</live-example>
|
||||
- <live-example embedded-style>Sample app</live-example>
|
||||
- <live-example stackblitz="specs">Tests</live-example>
|
||||
|
||||
<hr>
|
||||
|
||||
@ -84,7 +84,7 @@ The test file extension **must be `.spec.ts`** so that tooling can identify it a
|
||||
|
||||
</div>
|
||||
|
||||
The `app.component.ts` and `app.component.spec.ts` files are siblings in the same folder.
|
||||
The `app.component.ts` and `app.component.spec.ts` files are siblings in the same folder.
|
||||
The root file names (`app.component`) are the same for both files.
|
||||
|
||||
Adopt these two conventions in your own projects for _every kind_ of test file.
|
||||
@ -153,7 +153,7 @@ when you use the `TestBed` testing utility to provide and create services.
|
||||
|
||||
#### Angular _TestBed_
|
||||
|
||||
The `TestBed` is the most important of the Angular testing utilities.
|
||||
The `TestBed` is the most important of the Angular testing utilities.
|
||||
The `TestBed` creates a dynamically-constructed Angular _test_ module that emulates
|
||||
an Angular [@NgModule](guide/ngmodules).
|
||||
|
||||
@ -403,11 +403,11 @@ respond to user input and gestures, or integrate with its parent and child compo
|
||||
None of the _class-only_ tests above can answer key questions about how the
|
||||
components actually behave on screen.
|
||||
|
||||
* Is `Lightswitch.clicked()` bound to anything such that the user can invoke it?
|
||||
* Is the `Lightswitch.message` displayed?
|
||||
* Can the user actually select the hero displayed by `DashboardHeroComponent`?
|
||||
* Is the hero name displayed as expected (i.e, in uppercase)?
|
||||
* Is the welcome message displayed by the template of `WelcomeComponent`?
|
||||
- Is `Lightswitch.clicked()` bound to anything such that the user can invoke it?
|
||||
- Is the `Lightswitch.message` displayed?
|
||||
- Can the user actually select the hero displayed by `DashboardHeroComponent`?
|
||||
- Is the hero name displayed as expected (i.e, in uppercase)?
|
||||
- Is the welcome message displayed by the template of `WelcomeComponent`?
|
||||
|
||||
These may not be troubling questions for the simple components illustrated above.
|
||||
But many components have complex interactions with the DOM elements
|
||||
@ -625,7 +625,7 @@ For example, the component might render first on the server as part of a strateg
|
||||
If it doesn't support `querySelector`, the previous test could fail.
|
||||
|
||||
The `DebugElement` offers query methods that work for all supported platforms.
|
||||
These query methods take a _predicate_ function that returns `true` when a node in the `DebugElement` tree matches the selection criteria.
|
||||
These query methods take a _predicate_ function that returns `true` when a node in the `DebugElement` tree matches the selection criteria.
|
||||
|
||||
You create a _predicate_ with the help of a `By` class imported from a
|
||||
library for the runtime platform. Here's the `By` import for the browser platform:
|
||||
@ -645,10 +645,10 @@ The following example re-implements the previous test with
|
||||
|
||||
Some noteworthy observations:
|
||||
|
||||
* The `By.css()` static method selects `DebugElement` nodes
|
||||
with a [standard CSS selector](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_started/Selectors "CSS selectors").
|
||||
* The query returns a `DebugElement` for the paragraph.
|
||||
* You must unwrap that result to get the paragraph element.
|
||||
- The `By.css()` static method selects `DebugElement` nodes
|
||||
with a [standard CSS selector](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_started/Selectors 'CSS selectors').
|
||||
- The query returns a `DebugElement` for the paragraph.
|
||||
- You must unwrap that result to get the paragraph element.
|
||||
|
||||
When you're filtering by CSS selector and only testing properties of a browser's _native element_, the `By.css` approach may be overkill.
|
||||
|
||||
@ -706,6 +706,7 @@ Your instinct is to write a test that immediately inspects the `<h1>` like this:
|
||||
</code-example>
|
||||
|
||||
_That test fails_ with the message:
|
||||
|
||||
```javascript
|
||||
expected '' to contain 'Test Tour of Heroes'.
|
||||
```
|
||||
@ -739,7 +740,6 @@ the component _before Angular initiates data binding and calls [lifecycle hooks]
|
||||
|
||||
Here's another test that changes the component's `title` property _before_ calling `fixture.detectChanges()`.
|
||||
|
||||
|
||||
<code-example
|
||||
path="testing/src/app/banner/banner.component.spec.ts"
|
||||
region="after-change">
|
||||
@ -1051,6 +1051,7 @@ The test must wait at least one full turn of the JavaScript engine before the
|
||||
value becomes available. The test must become _asynchronous_.
|
||||
|
||||
{@a fake-async}
|
||||
|
||||
#### Async test with _fakeAsync()_
|
||||
|
||||
The following test confirms the expected behavior when the service returns an `ErrorObservable`.
|
||||
@ -1061,6 +1062,7 @@ The following test confirms the expected behavior when the service returns an `E
|
||||
</code-example>
|
||||
|
||||
Note that the `it()` function receives an argument of the following form.
|
||||
|
||||
```javascript
|
||||
fakeAsync(() => { /* test body */ })`
|
||||
```
|
||||
@ -1081,6 +1083,54 @@ In this case, it waits for the error handler's `setTimeout()`;
|
||||
The `tick` function is one of the Angular testing utilities that you import with `TestBed`.
|
||||
It's a companion to `fakeAsync` and you can only call it within a `fakeAsync` body.
|
||||
|
||||
#### Support more macroTasks
|
||||
|
||||
By default `fakeAsync` supports the following `macroTasks`.
|
||||
|
||||
- setTimeout
|
||||
- setInterval
|
||||
- requestAnimationFrame
|
||||
- webkitRequestAnimationFrame
|
||||
- mozRequestAnimationFrame
|
||||
|
||||
If you run other `macroTask` such as `HTMLCanvasElement.toBlob()`, `Unknown macroTask scheduled in fake async test` error will be thrown.
|
||||
|
||||
<code-tabs>
|
||||
<code-pane
|
||||
path="testing/src/app/shared/canvas.component.spec.ts"
|
||||
title="src/app/shared/canvas.component.spec.ts" linenums="false">
|
||||
</code-pane>
|
||||
<code-pane
|
||||
path="testing/src/app/shared/canvas.component.ts"
|
||||
title="src/app/shared/canvas.component.ts" linenums="false">
|
||||
</code-pane>
|
||||
</code-tabs>
|
||||
|
||||
If you want to support such case, you need to define the `macroTask` you want to support in `beforeEach`.
|
||||
For example:
|
||||
|
||||
```javascript
|
||||
beforeEach(() => {
|
||||
window['__zone_symbol__FakeAsyncTestMacroTask'] = [
|
||||
{
|
||||
source: 'HTMLCanvasElement.toBlob',
|
||||
callbackArgs: [{ size: 200 }]
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
it('toBlob should be able to run in fakeAsync', fakeAsync(() => {
|
||||
const canvas: HTMLCanvasElement = document.getElementById('canvas') as HTMLCanvasElement;
|
||||
let blob = null;
|
||||
canvas.toBlob(function(b) {
|
||||
blob = b;
|
||||
});
|
||||
tick();
|
||||
expect(blob.size).toBe(200);
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
#### Async observables
|
||||
|
||||
You might be satisfied with the test coverage of these tests.
|
||||
@ -1213,8 +1263,7 @@ But it is occasionally necessary.
|
||||
For example, you can't call `async` or `fakeAsync` when testing
|
||||
code that involves the `intervalTimer()` or the RxJS `delay()` operator.
|
||||
|
||||
|
||||
Here are two mover versions of the previous test, written with `done()`.
|
||||
Here are two more versions of the previous test, written with `done()`.
|
||||
The first one subscribes to the `Observable` exposed to the template by the component's `quote` property.
|
||||
|
||||
<code-example
|
||||
@ -1345,7 +1394,6 @@ A _hot_ observable is already producing values _before_ you subscribe to it.
|
||||
The [_Router.events_](api/router/Router#events) observable,
|
||||
which reports router activity, is a _hot_ observable.
|
||||
|
||||
|
||||
RxJS marble testing is a rich subject, beyond the scope of this guide.
|
||||
Learn about it on the web, starting with the
|
||||
[official documentation](https://github.com/ReactiveX/rxjs/blob/master/doc/writing-marble-tests.md).
|
||||
@ -1391,9 +1439,9 @@ Here's the component's full definition:
|
||||
While testing a component this simple has little intrinsic value, it's worth knowing how.
|
||||
You can use one of these approaches:
|
||||
|
||||
* Test it as used by `DashboardComponent`.
|
||||
* Test it as a stand-alone component.
|
||||
* Test it as used by a substitute for `DashboardComponent`.
|
||||
- Test it as used by `DashboardComponent`.
|
||||
- Test it as a stand-alone component.
|
||||
- Test it as used by a substitute for `DashboardComponent`.
|
||||
|
||||
A quick look at the `DashboardComponent` constructor discourages the first approach:
|
||||
|
||||
@ -1513,6 +1561,7 @@ which is perfectly fine for _this component_.
|
||||
</code-example>
|
||||
|
||||
{@a click-helper}
|
||||
|
||||
#### _click()_ helper
|
||||
|
||||
Clicking a button, an anchor, or an arbitrary HTML element is a common test task.
|
||||
@ -1708,10 +1757,11 @@ by manipulating the `ActivatedRoute` injected into the component's constructor.
|
||||
You know how to spy on the `Router` and a data service.
|
||||
|
||||
You'll take a different approach with `ActivatedRoute` because
|
||||
* `paramMap` returns an `Observable` that can emit more than one value
|
||||
during a test.
|
||||
* You need the router helper function, `convertToParamMap()`, to create a `ParamMap`.
|
||||
* Other _routed components_ tests need a test double for `ActivatedRoute`.
|
||||
|
||||
- `paramMap` returns an `Observable` that can emit more than one value
|
||||
during a test.
|
||||
- You need the router helper function, `convertToParamMap()`, to create a `ParamMap`.
|
||||
- Other _routed components_ tests need a test double for `ActivatedRoute`.
|
||||
|
||||
These differences argue for a re-usable stub class.
|
||||
|
||||
@ -1730,8 +1780,8 @@ This sample puts `ActivatedRouteStub` in `testing/activated-route-stub.ts`.
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
Consider writing a more capable version of this stub class with
|
||||
the [_marble testing library_](#marble-testing).
|
||||
Consider writing a more capable version of this stub class with
|
||||
the [_marble testing library_](#marble-testing).
|
||||
|
||||
</div>
|
||||
|
||||
@ -1847,7 +1897,7 @@ The rest are stubs.
|
||||
|
||||
{@a no-errors-schema}
|
||||
|
||||
#### *NO\_ERRORS\_SCHEMA*
|
||||
#### _NO_ERRORS_SCHEMA_
|
||||
|
||||
In the second approach, add `NO_ERRORS_SCHEMA` to the `TestBed.schemas` metadata.
|
||||
|
||||
@ -1876,7 +1926,7 @@ in the component's template that matter for tests.
|
||||
|
||||
The `NO_ERRORS_SCHEMA` approach is the easier of the two but don't overuse it.
|
||||
|
||||
The `NO_ERRORS_SCHEMA` also prevents the compiler from telling you about the missing
|
||||
The `NO_ERRORS_SCHEMA` also prevents the compiler from telling you about the missing
|
||||
components and attributes that you omitted inadvertently or misspelled.
|
||||
You could waste hours chasing phantom bugs that the compiler would have caught in an instant.
|
||||
|
||||
@ -1949,12 +1999,12 @@ A little more setup triggers the initial data binding and gets references to the
|
||||
|
||||
Three points of special interest:
|
||||
|
||||
1. You can locate the anchor elements with an attached directive using `By.directive`.
|
||||
1. You can locate the anchor elements with an attached directive using `By.directive`.
|
||||
|
||||
1. The query returns `DebugElement` wrappers around the matching elements.
|
||||
1. The query returns `DebugElement` wrappers around the matching elements.
|
||||
|
||||
1. Each `DebugElement` exposes a dependency injector with the
|
||||
specific instance of the directive attached to that element.
|
||||
1. Each `DebugElement` exposes a dependency injector with the
|
||||
specific instance of the directive attached to that element.
|
||||
|
||||
The `AppComponent` links to validate are as follows:
|
||||
|
||||
@ -2029,11 +2079,11 @@ But there's plenty of template complexity even in this simple form.
|
||||
|
||||
Tests that exercise the component need ...
|
||||
|
||||
* to wait until a hero arrives before elements appear in the DOM.
|
||||
* a reference to the title text.
|
||||
* a reference to the name input box to inspect and set it.
|
||||
* references to the two buttons so they can click them.
|
||||
* spies for some of the component and router methods.
|
||||
- to wait until a hero arrives before elements appear in the DOM.
|
||||
- a reference to the title text.
|
||||
- a reference to the name input box to inspect and set it.
|
||||
- references to the two buttons so they can click them.
|
||||
- spies for some of the component and router methods.
|
||||
|
||||
Even a small form such as this one can produce a mess of tortured conditional setup and CSS element selection.
|
||||
|
||||
@ -2139,8 +2189,8 @@ Error: ViewDestroyedError: Attempt to use a destroyed view
|
||||
|
||||
A typical approach is to divide the setup logic into two separate `beforeEach()` functions:
|
||||
|
||||
1. An async `beforeEach()` that compiles the components
|
||||
1. A synchronous `beforeEach()` that performs the remaining setup.
|
||||
1. An async `beforeEach()` that compiles the components
|
||||
1. A synchronous `beforeEach()` that performs the remaining setup.
|
||||
|
||||
To follow this pattern, import the `async()` helper with the other testing symbols.
|
||||
|
||||
@ -2245,10 +2295,10 @@ which means you can also specify `providers` and `imports`.
|
||||
The `HeroDetailComponent` requires a lot of help despite its small size and simple construction.
|
||||
In addition to the support it receives from the default testing module `CommonModule`, it needs:
|
||||
|
||||
* `NgModel` and friends in the `FormsModule` to enable two-way data binding.
|
||||
* The `TitleCasePipe` from the `shared` folder.
|
||||
* Router services (which these tests are stubbing).
|
||||
* Hero data access services (also stubbed).
|
||||
- `NgModel` and friends in the `FormsModule` to enable two-way data binding.
|
||||
- The `TitleCasePipe` from the `shared` folder.
|
||||
- Router services (which these tests are stubbing).
|
||||
- Hero data access services (also stubbed).
|
||||
|
||||
One approach is to configure the testing module from the individual pieces as in this example:
|
||||
|
||||
@ -2375,7 +2425,7 @@ The [override metadata object](#metadata-override-object) is a generic defined a
|
||||
A metadata override object can either add-and-remove elements in metadata properties or completely reset those properties.
|
||||
This example resets the component's `providers` metadata.
|
||||
|
||||
The type parameter, `T`, is the kind of metadata you'd pass to the `@Component` decorator:
|
||||
The type parameter, `T`, is the kind of metadata you'd pass to the `@Component` decorator:
|
||||
|
||||
<code-example format="." language="javascript">
|
||||
selector?: string;
|
||||
@ -2475,20 +2525,20 @@ Here are some tests of this component:
|
||||
|
||||
A few techniques are noteworthy:
|
||||
|
||||
* The `By.directive` predicate is a great way to get the elements that have this directive _when their element types are unknown_.
|
||||
- The `By.directive` predicate is a great way to get the elements that have this directive _when their element types are unknown_.
|
||||
|
||||
* The <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:not">`:not` pseudo-class</a>
|
||||
in `By.css('h2:not([highlight])')` helps find `<h2>` elements that _do not_ have the directive.
|
||||
`By.css('*:not([highlight])')` finds _any_ element that does not have the directive.
|
||||
- The <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:not">`:not` pseudo-class</a>
|
||||
in `By.css('h2:not([highlight])')` helps find `<h2>` elements that _do not_ have the directive.
|
||||
`By.css('*:not([highlight])')` finds _any_ element that does not have the directive.
|
||||
|
||||
* `DebugElement.styles` affords access to element styles even in the absence of a real browser, thanks to the `DebugElement` abstraction.
|
||||
But feel free to exploit the `nativeElement` when that seems easier or more clear than the abstraction.
|
||||
- `DebugElement.styles` affords access to element styles even in the absence of a real browser, thanks to the `DebugElement` abstraction.
|
||||
But feel free to exploit the `nativeElement` when that seems easier or more clear than the abstraction.
|
||||
|
||||
* Angular adds a directive to the injector of the element to which it is applied.
|
||||
The test for the default color uses the injector of the second `<h2>` to get its `HighlightDirective` instance
|
||||
and its `defaultColor`.
|
||||
- Angular adds a directive to the injector of the element to which it is applied.
|
||||
The test for the default color uses the injector of the second `<h2>` to get its `HighlightDirective` instance
|
||||
and its `defaultColor`.
|
||||
|
||||
* `DebugElement.properties` affords access to the artificial custom property that is set by the directive.
|
||||
- `DebugElement.properties` affords access to the artificial custom property that is set by the directive.
|
||||
|
||||
<hr>
|
||||
|
||||
@ -2531,13 +2581,13 @@ Consider adding component tests such as this one:
|
||||
|
||||
Debug specs in the browser in the same way that you debug an application.
|
||||
|
||||
1. Reveal the karma browser window (hidden earlier).
|
||||
1. Click the **DEBUG** button; it opens a new browser tab and re-runs the tests.
|
||||
1. Open the browser's “Developer Tools” (`Ctrl-Shift-I` on windows; `Command-Option-I` in OSX).
|
||||
1. Pick the "sources" section.
|
||||
1. Open the `1st.spec.ts` test file (Control/Command-P, then start typing the name of the file).
|
||||
1. Set a breakpoint in the test.
|
||||
1. Refresh the browser, and it stops at the breakpoint.
|
||||
1. Reveal the karma browser window (hidden earlier).
|
||||
1. Click the **DEBUG** button; it opens a new browser tab and re-runs the tests.
|
||||
1. Open the browser's “Developer Tools” (`Ctrl-Shift-I` on windows; `Command-Option-I` in OSX).
|
||||
1. Pick the "sources" section.
|
||||
1. Open the `1st.spec.ts` test file (Control/Command-P, then start typing the name of the file).
|
||||
1. Set a breakpoint in the test.
|
||||
1. Refresh the browser, and it stops at the breakpoint.
|
||||
|
||||
<figure>
|
||||
<img src='generated/images/guide/testing/karma-1st-spec-debug.png' alt="Karma debugging">
|
||||
@ -2910,7 +2960,6 @@ Here are the most important static methods, in order of likely utility.
|
||||
</tr>
|
||||
</table
|
||||
|
||||
|
||||
A few of the `TestBed` instance methods are not covered by static `TestBed` _class_ methods.
|
||||
These are rarely needed.
|
||||
|
||||
@ -3334,9 +3383,9 @@ The following example finds all `DebugElements` with a reference to a template l
|
||||
|
||||
The Angular `By` class has three static methods for common predicates:
|
||||
|
||||
* `By.all` - return all elements.
|
||||
* `By.css(selector)` - return elements with matching CSS selectors.
|
||||
* `By.directive(directive)` - return elements that Angular matched to an instance of the directive class.
|
||||
- `By.all` - return all elements.
|
||||
- `By.css(selector)` - return elements with matching CSS selectors.
|
||||
- `By.directive(directive)` - return elements that Angular matched to an instance of the directive class.
|
||||
|
||||
<code-example path="testing/src/app/hero/hero-list.component.spec.ts" region="by" title="app/hero/hero-list.component.spec.ts" linenums="false"></code-example>
|
||||
|
||||
@ -3353,11 +3402,11 @@ The Angular `By` class has three static methods for common predicates:
|
||||
It's a good idea to put unit test spec files in the same folder
|
||||
as the application source code files that they test:
|
||||
|
||||
* Such tests are easy to find.
|
||||
* You see at a glance if a part of your application lacks tests.
|
||||
* Nearby tests can reveal how a part works in context.
|
||||
* When you move the source (inevitable), you remember to move the test.
|
||||
* When you rename the source file (inevitable), you remember to rename the test file.
|
||||
- Such tests are easy to find.
|
||||
- You see at a glance if a part of your application lacks tests.
|
||||
- Nearby tests can reveal how a part works in context.
|
||||
- When you move the source (inevitable), you remember to move the test.
|
||||
- When you rename the source file (inevitable), you remember to rename the test file.
|
||||
|
||||
<hr>
|
||||
|
||||
@ -3376,6 +3425,7 @@ Of course specs that test the test helpers belong in the `test` folder,
|
||||
next to their corresponding helper files.
|
||||
|
||||
{@a q-e2e}
|
||||
|
||||
#### Why not rely on E2E tests of DOM integration?
|
||||
|
||||
The component DOM tests described in this guide often require extensive setup and
|
||||
|
@ -16,6 +16,12 @@
|
||||
"rev": true,
|
||||
"title": "Angular Conferences and Angular Camps in Moscow, Russia.",
|
||||
"url": "https://angular-ru.github.io/"
|
||||
},
|
||||
"made-with-angular": {
|
||||
"desc": "A showcase of web apps built with Angular.",
|
||||
"rev": true,
|
||||
"title": "Made with Angular",
|
||||
"url": "https://www.madewithangular.com/"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -260,6 +266,13 @@
|
||||
"title": "Nx",
|
||||
"logo": "https://nrwl.io/assets/nx-logo.png",
|
||||
"url": "https://nrwl.io/nx"
|
||||
},
|
||||
"uijar": {
|
||||
"desc": "A drop in module to automatically create a living style guide based on the test you write for your components.",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "UI-jar - Test Driven Style Guide Development",
|
||||
"url": "https://github.com/ui-jar/ui-jar"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -421,8 +434,8 @@
|
||||
"-KLIzGEp8Mh5W-FkiQnL": {
|
||||
"desc": "Your quick, no-nonsense guide to building real-world apps with Angular",
|
||||
"rev": true,
|
||||
"title": "Learning Angular",
|
||||
"url": "https://www.packtpub.com/web-development/learning-angular-2"
|
||||
"title": "Learning Angular - Second Edition",
|
||||
"url": "https://www.packtpub.com/web-development/learning-angular-second-edition"
|
||||
},
|
||||
"3ab": {
|
||||
"desc": "More than 15 books from O'Reilly about Angular",
|
||||
@ -503,6 +516,13 @@
|
||||
"rev": true,
|
||||
"title": "The Angular Guide by Wishtack (Français)",
|
||||
"url": "https://guide-angular.wishtack.io/"
|
||||
},
|
||||
"ab5": {
|
||||
"desc": "How to build Angular applications using NGRX",
|
||||
"logo": "",
|
||||
"rev": true,
|
||||
"title": "Architecting Angular Applications with NGRX",
|
||||
"url": "https://www.packtpub.com/web-development/architecting-angular-applications-redux"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -629,12 +649,24 @@
|
||||
"rev": true,
|
||||
"title": "AngularFirebase.com",
|
||||
"url": "https://angularfirebase.com/"
|
||||
},
|
||||
"loiane-angulartraining": {
|
||||
"desc": "Free Angular course in Portuguese.",
|
||||
"rev": true,
|
||||
"title": "Loiane Training (Português)",
|
||||
"url": "https://loiane.training/course/angular/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Workshops & Onsite Training": {
|
||||
"order": 2,
|
||||
"resources": {
|
||||
"-acceleb": {
|
||||
"desc": "Customized, Instructor-Led Angular Training",
|
||||
"rev": true,
|
||||
"title": "Accelebrate",
|
||||
"url": "https://www.accelebrate.com/angular-training"
|
||||
},
|
||||
"-KLIBoFWStce29UCwkvY": {
|
||||
"desc": "Private Angular Training and Mentoring",
|
||||
"rev": true,
|
||||
|
@ -291,7 +291,8 @@ That header is in the `httpOptions` constant defined in the `HeroService`.
|
||||
|
||||
<code-example
|
||||
path="toh-pt6/src/app/hero.service.ts"
|
||||
region="http-options">
|
||||
region="http-options"
|
||||
title="src/app/hero.service.ts">
|
||||
</code-example>
|
||||
|
||||
Refresh the browser, change a hero name and save your change. Navigating to the previous view is implemented in the `save()` method defined in `HeroDetailComponent`.
|
||||
|
@ -68,7 +68,7 @@ function runE2e() {
|
||||
// that they should run under. Then run each app/spec collection sequentially.
|
||||
function findAndRunE2eTests(filter, outputFile, shard) {
|
||||
|
||||
const shardParts = shard ? shard.split('/') : [0,1];
|
||||
const shardParts = shard ? shard.split('/') : [0, 1];
|
||||
const shardModulo = parseInt(shardParts[0], 10);
|
||||
const shardDivider = parseInt(shardParts[1], 10);
|
||||
|
||||
@ -82,11 +82,17 @@ function findAndRunE2eTests(filter, outputFile, shard) {
|
||||
const status = { passed: [], failed: [] };
|
||||
return getE2eSpecs(EXAMPLES_PATH, filter)
|
||||
.then(e2eSpecPaths => {
|
||||
console.log('All e2e specs:');
|
||||
logSpecs(e2eSpecPaths);
|
||||
|
||||
Object.keys(e2eSpecPaths).forEach(key => {
|
||||
const value = e2eSpecPaths[key];
|
||||
e2eSpecPaths[key] = value.filter((p, index) => index % shardDivider === shardModulo);
|
||||
});
|
||||
|
||||
console.log(`E2e specs for shard ${shardParts.join('/')}:`);
|
||||
logSpecs(e2eSpecPaths);
|
||||
|
||||
return e2eSpecPaths.systemjs.reduce((promise, specPath) => {
|
||||
return promise.then(() => {
|
||||
const examplePath = path.dirname(specPath);
|
||||
@ -313,4 +319,16 @@ function loadExampleConfig(exampleFolder) {
|
||||
return config;
|
||||
}
|
||||
|
||||
// Log the specs (for debugging purposes).
|
||||
// `e2eSpecPaths` is of type: `{[type: string]: string[]}`
|
||||
// (where `type` is `systemjs`, `cli, etc.)
|
||||
function logSpecs(e2eSpecPaths) {
|
||||
Object.keys(e2eSpecPaths).forEach(type => {
|
||||
const paths = e2eSpecPaths[type];
|
||||
|
||||
console.log(` ${type.toUpperCase()}:`);
|
||||
console.log(paths.map(p => ` ${p}`).join('\n'));
|
||||
});
|
||||
}
|
||||
|
||||
runE2e();
|
||||
|
@ -8,11 +8,27 @@ module.exports = function computeApiBreadCrumbs(API_DOC_TYPES_TO_RENDER) {
|
||||
if (API_DOC_TYPES_TO_RENDER.indexOf(doc.docType) !== -1) {
|
||||
doc.breadCrumbs = [];
|
||||
doc.breadCrumbs.push({ text: 'API', path: '/api' });
|
||||
if (doc.moduleDoc) doc.breadCrumbs.push({ text: '@angular/' + doc.moduleDoc.id, path: doc.moduleDoc.path });
|
||||
if (isSecondaryEntryPoint(doc)) {
|
||||
doc.breadCrumbs.push(createPackageBreadcrumb(doc));
|
||||
}
|
||||
if (doc.moduleDoc) {
|
||||
if (isSecondaryEntryPoint(doc.moduleDoc)) {
|
||||
doc.breadCrumbs.push(createPackageBreadcrumb(doc.moduleDoc));
|
||||
}
|
||||
doc.breadCrumbs.push({ text: '@angular/' + doc.moduleDoc.id, path: doc.moduleDoc.path });
|
||||
}
|
||||
doc.breadCrumbs.push({ text: doc.name, path: doc.path });
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function isSecondaryEntryPoint(doc) {
|
||||
return doc.docType === 'package' && !doc.isPrimaryPackage;
|
||||
}
|
||||
|
||||
function createPackageBreadcrumb(doc) {
|
||||
return { text: doc.packageInfo.primary.name, path: doc.packageInfo.primary.path };
|
||||
}
|
||||
|
@ -17,11 +17,17 @@ describe('angular-api-package: computeApiBreadCrumbs processor', () => {
|
||||
const API_DOC_TYPES_TO_RENDER = ['class', 'interface', 'package'];
|
||||
const processor = processorFactory(API_DOC_TYPES_TO_RENDER);
|
||||
|
||||
const httpPackage = { docType: 'package', name: '@angular/http', id: 'http', path: 'http', isPrimaryPackage: true };
|
||||
const httpTestingPackage = { docType: 'package', name: '@angular/http/testing', id: 'http/testing', path: 'http/testing', packageInfo: { primary: httpPackage } };
|
||||
const testRequestClass = { docType: 'class', name: 'TestRequest', path: 'http/testing/test-request', moduleDoc: httpTestingPackage };
|
||||
|
||||
const docs = [
|
||||
{ docType: 'class', name: 'ClassA', path: 'module-1/class-a', moduleDoc: { id: 'moduleOne', path: 'module-1' } },
|
||||
{ docType: 'interface', name: 'InterfaceB', path: 'module-2/interface-b', moduleDoc: { id: 'moduleTwo', path: 'module-2' } },
|
||||
{ docType: 'guide', name: 'Guide One', path: 'guide/guide-1' },
|
||||
{ docType: 'package', name: 'testing', id: 'http/testing', path: 'http/testing' },
|
||||
httpPackage,
|
||||
httpTestingPackage,
|
||||
testRequestClass
|
||||
];
|
||||
processor.$process(docs);
|
||||
|
||||
@ -38,7 +44,18 @@ describe('angular-api-package: computeApiBreadCrumbs processor', () => {
|
||||
expect(docs[2].breadCrumbs).toBeUndefined();
|
||||
expect(docs[3].breadCrumbs).toEqual([
|
||||
{ text: 'API', path: '/api' },
|
||||
{ text: 'testing', path: 'http/testing' },
|
||||
{ text: '@angular/http', path: 'http' },
|
||||
]);
|
||||
expect(docs[4].breadCrumbs).toEqual([
|
||||
{ text: 'API', path: '/api' },
|
||||
{ text: '@angular/http', path: 'http' },
|
||||
{ text: '@angular/http/testing', path: 'http/testing' },
|
||||
]);
|
||||
expect(docs[5].breadCrumbs).toEqual([
|
||||
{ text: 'API', path: '/api' },
|
||||
{ text: '@angular/http', path: 'http' },
|
||||
{ text: '@angular/http/testing', path: 'http/testing' },
|
||||
{ text: 'TestRequest', path: 'http/testing/test-request' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% import "lib/githubLinks.html" as github -%}
|
||||
{% set comma = joiner(',') %}
|
||||
{% set slash = joiner('/') %}
|
||||
{% set breadcrumbDelimiter = joiner('>') %}
|
||||
<article>
|
||||
{$ github.githubLinks(doc, versionInfo) $}
|
||||
<div class="breadcrumb">
|
||||
@ -14,14 +14,11 @@
|
||||
]
|
||||
}
|
||||
</script>
|
||||
{% for crumb in doc.breadCrumbs %}{% if not loop.last %} {$ slash() $} {% if crumb.path %}<a href="{$ crumb.path $}">{$ crumb.text $}</a>{% else %}{$ crumb.text $}{% endif %}{% endif %}{% endfor %}
|
||||
{% for crumb in doc.breadCrumbs %}{% if not loop.last %} {$ breadcrumbDelimiter() $} {% if crumb.path %}<a href="{$ crumb.path $}">{$ crumb.text $}</a>{% else %}{$ crumb.text $}{% endif %}{% endif %}{% endfor %}
|
||||
</div>
|
||||
<header class="api-header">
|
||||
<h1>{$ doc.name $}</h1>
|
||||
<label class="api-type-label {$ doc.docType $}">{$ doc.docType $}</label>
|
||||
{% if doc.deprecated !== undefined %}<label class="api-status-label deprecated">deprecated</label>{% endif %}
|
||||
{% if doc.experimental !== undefined %}<label class="api-status-label experimental">experimental</label>{% endif %}
|
||||
{% if doc.stable !== undefined %}<label class="api-status-label stable">stable</label>{% endif %}
|
||||
{% if doc.pipeOptions.pure === 'false' %}<label class="api-status-label impure-pipe">impure</label>{% endif %}
|
||||
</header>
|
||||
<aio-toc class="embedded"></aio-toc>
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% extends 'base.template.html' -%}
|
||||
|
||||
{% macro listItems(items, title) %}
|
||||
{% macro listItems(items, title, overridePath) %}
|
||||
{% if items.length %}
|
||||
<section class="export-list">
|
||||
<h3>{$ title $}</h3>
|
||||
@ -8,7 +8,7 @@
|
||||
{% for item in items %}
|
||||
<tr>
|
||||
<td><code class="code-anchor">
|
||||
<a href="{$ item.path $}">{$ item.name $}</a></code></td>
|
||||
<a href="{$ overridePath or item.path $}">{$ item.name $}</a></code></td>
|
||||
<td>{% if item.shortDescription %}{$ item.shortDescription | marked $}{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@ -24,12 +24,13 @@
|
||||
|
||||
{% include "includes/see-also.html" %}
|
||||
|
||||
{% if doc.isPrimaryPackage %}
|
||||
<h2>Entry points</h2>
|
||||
{$ listItems([doc.packageInfo.primary], 'Primary') $}
|
||||
{$ listItems([doc.packageInfo.primary], 'Primary', '#primary-entry-point-exports') $}
|
||||
{$ listItems(doc.packageInfo.secondary, 'Secondary') $}
|
||||
{% endif %}
|
||||
|
||||
|
||||
<h2>Exports</h2>
|
||||
<h2>{% if doc.isPrimaryPackage %}Primary entry{% else %}Entry{% endif %} point exports</h2>
|
||||
{$ listItems(doc.classes, 'Classes') $}
|
||||
{$ listItems(doc.decorators, 'Decorators') $}
|
||||
{$ listItems(doc.functions, 'Functions') $}
|
||||
|
13
build.sh
13
build.sh
@ -8,8 +8,8 @@ source ${currentDir}/scripts/ci/_travis-fold.sh
|
||||
# TODO(i): wrap into subshell, so that we don't pollute CWD, but not yet to minimize diff collision with Jason
|
||||
cd ${currentDir}
|
||||
|
||||
PACKAGES=(core
|
||||
compiler
|
||||
PACKAGES=(compiler
|
||||
core
|
||||
common
|
||||
animations
|
||||
platform-browser
|
||||
@ -27,7 +27,8 @@ PACKAGES=(core
|
||||
service-worker
|
||||
elements)
|
||||
|
||||
TSC_PACKAGES=(compiler-cli
|
||||
TSC_PACKAGES=(compiler
|
||||
compiler-cli
|
||||
language-service
|
||||
benchpress)
|
||||
|
||||
@ -239,7 +240,13 @@ compilePackage() {
|
||||
# For TSC_PACKAGES items
|
||||
if containsElement "${3}" "${TSC_PACKAGES[@]}"; then
|
||||
echo "====== [${3}]: COMPILING: ${TSC} -p ${1}/tsconfig-build.json"
|
||||
local package_name=$(basename "${2}")
|
||||
$TSC -p ${1}/tsconfig-build.json
|
||||
if [[ "${3}" = "compiler" ]]; then
|
||||
if [[ "${package_name}" = "testing" ]]; then
|
||||
echo "$(cat ${LICENSE_BANNER}) ${N} export * from './${package_name}/${package_name}'" > ${2}/../${package_name}.d.ts
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "====== [${3}]: COMPILING: ${NGC} -p ${1}/tsconfig-build.json"
|
||||
local package_name=$(basename "${2}")
|
||||
|
@ -68,7 +68,26 @@ new as of May 2017 and not very stable yet.
|
||||
You can use [ibazel] to get a "watch mode" that continuously
|
||||
keeps the outputs up-to-date as you save sources.
|
||||
|
||||
### Various Flags Used For Tests
|
||||
|
||||
If you're experiencing problems with seemingly unrelated tests failing, it may be because you're not using the proper flags with your Bazel test runs in Angular.
|
||||
|
||||
See also: [`//tools/bazel.rc`](https://github.com/angular/angular/blob/master/tools/bazel.rc) where `--define=ivy=false` is defined as default.
|
||||
|
||||
- `--config=debug`: build and launch in debug mode (see [debugging](#debugging) instructions below)
|
||||
- `--define=compile=<option>` Controls if ivy or legacy mode is enabled. This is done by generating the [`src/ivy_switch.ts`](https://github.com/angular/angular/blob/master/packages/core/src/ivy_switch.ts) file from [`ivy_switch_legacy.ts`](https://github.com/angular/angular/blob/master/packages/core/src/ivy_switch_legacy.ts) (default), [`ivy_switch_jit.ts`](https://github.com/angular/angular/blob/master/packages/core/src/ivy_switch_jit.ts), or [`ivy_switch_local.ts`](https://github.com/angular/angular/blob/master/packages/core/src/ivy_switch_local.ts).
|
||||
- `legacy`: (default behavior) compile against View Engine, e.g. `--define=compile=legacy`
|
||||
- `jit`: Compile in ivy JIT mode, e.g. `--define=compile=jit`
|
||||
- `local`: Compile in ivy AOT move, e.g. `--define=compile=local`
|
||||
- `--test_tag_filters=<tag>`: filter tests down to tags defined in the `tag` config
|
||||
of your rules in any given `BUILD.bazel`.
|
||||
- `ivy-jit`: This flag should be set for tests that should be excuted with ivy JIT, e.g. `--test_tag_filters=ivy-jit`. For this, you may have to include `--define=compile=jit`.
|
||||
- `ivy-local`: Only run tests that have to do with ivy AOT. For this, you may have to include `--define=compile=local`, e.g. `--test_tag_filters=ivy-local`..
|
||||
- `ivy-only`: Only run ivy related tests, e.g. `--test_tag_filters=ivy-only`.
|
||||
|
||||
|
||||
### Debugging a Node Test
|
||||
<a id="debugging"></a>
|
||||
|
||||
- Open chrome at: [chrome://inspect](chrome://inspect)
|
||||
- Click on `Open dedicated DevTools for Node` to launch a debugger.
|
||||
@ -82,7 +101,7 @@ First time setup:
|
||||
- Go to Debug > Add configuration (in the menu bar) to open `launch.json`
|
||||
- Add the following to the `configurations` array:
|
||||
|
||||
```
|
||||
```json
|
||||
{
|
||||
"name": "Attach (inspect)",
|
||||
"type": "node",
|
||||
@ -107,6 +126,7 @@ First time setup:
|
||||
},
|
||||
```
|
||||
|
||||
**Setting breakpoints directly in your code files may not work in VSCode**. This is because the files you're actually debugging are built files that exist in a `./private/...` folder.
|
||||
The easiest way to debug a test for now is to add a `debugger` statement in the code
|
||||
and launch the bazel corresponding test (`bazel test <target> --config=debug`).
|
||||
|
||||
@ -153,7 +173,7 @@ Note that Bazel has a `--stamp` argument to `bazel build`, but this has no effec
|
||||
Bazel supports fetching action results from a cache, allowing a clean build to pick up artifacts from prior builds.
|
||||
This makes builds incremental, even on CI.
|
||||
It works because Bazel assigns a content-based hash to all action inputs, which is used as the cache key for the action outputs.
|
||||
Thanks the the hermeticity property, we can skip executing an action if the inputs hash is already present in the cache.
|
||||
Thanks to the hermeticity property, we can skip executing an action if the inputs hash is already present in the cache.
|
||||
|
||||
Of course, non-hermeticity in an action can cause problems.
|
||||
At worst, you can fetch a broken artifact from the cache, making your build non-reproducible.
|
||||
|
@ -14,11 +14,17 @@ Ctrl + Shift + j.
|
||||
By default the debug tools are disabled. You can enable debug tools as follows:
|
||||
|
||||
```typescript
|
||||
import {ApplicationRef} from '@angular/core';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import {enableDebugTools} from '@angular/platform-browser';
|
||||
|
||||
bootstrap(Application).then((appRef) => {
|
||||
enableDebugTools(appRef);
|
||||
});
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
.then(moduleRef => {
|
||||
const applicationRef = moduleRef.injector.get(ApplicationRef);
|
||||
const appComponent = applicationRef.components[0];
|
||||
enableDebugTools(appComponent);
|
||||
})
|
||||
```
|
||||
|
||||
### Using debug tools
|
||||
|
@ -4,7 +4,7 @@
|
||||
# found in the LICENSE file at https://angular.io/license
|
||||
""" Public API surface is re-exported here.
|
||||
|
||||
This API is exported for user's building angular from source in downstream
|
||||
This API is exported for users building angular from source in downstream
|
||||
projects. The rules from packages/bazel are re-exported here as well
|
||||
as the ng_setup_workspace repository rule needed when building angular
|
||||
from source downstream. Alternately, this API is available from the
|
||||
@ -12,11 +12,13 @@ from source downstream. Alternately, this API is available from the
|
||||
used in a downstream project.
|
||||
"""
|
||||
|
||||
load("//packages/bazel:index.bzl",
|
||||
load(
|
||||
"//packages/bazel:index.bzl",
|
||||
_ng_module = "ng_module",
|
||||
_ng_package = "ng_package",
|
||||
_protractor_web_test = "protractor_web_test",
|
||||
_protractor_web_test_suite = "protractor_web_test_suite")
|
||||
_protractor_web_test_suite = "protractor_web_test_suite",
|
||||
)
|
||||
load("//tools:ng_setup_workspace.bzl", _ng_setup_workspace = "ng_setup_workspace")
|
||||
|
||||
ng_module = _ng_module
|
||||
|
@ -24,9 +24,8 @@ 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",
|
||||
# We use AOT, so the dynamic platform-browser should be visible only in tests
|
||||
# NOTE that we still need to include the compiler because the core depends on it
|
||||
"node_modules/@angular/platform-browser-dynamic/bundles/*.umd.js",
|
||||
]
|
||||
|
||||
|
@ -6,23 +6,30 @@ workspace(name = "bazel_integration_test")
|
||||
|
||||
http_archive(
|
||||
name = "build_bazel_rules_nodejs",
|
||||
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.10.1.zip",
|
||||
strip_prefix = "rules_nodejs-0.10.1",
|
||||
sha256 = "634206524d90dc03c52392fa3f19a16637d2bcf154910436fe1d669a0d9d7b9c",
|
||||
urls = ["https://github.com/bazelbuild/rules_nodejs/archive/0.11.4.zip"],
|
||||
strip_prefix = "rules_nodejs-0.11.4",
|
||||
sha256 = "c31c4ead696944a50fad2b3ee9dfbbeffe31a8dcca0b21b9bf5b3e6c6b069801",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "bazel_skylib",
|
||||
urls = ["https://github.com/bazelbuild/bazel-skylib/archive/0.3.1.zip"],
|
||||
strip_prefix = "bazel-skylib-0.3.1",
|
||||
sha256 = "95518adafc9a2b656667bbf517a952e54ce7f350779d0dd95133db4eb5c27fb1",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "io_bazel_rules_webtesting",
|
||||
url = "https://github.com/bazelbuild/rules_webtesting/archive/8fd9ce0fd9254bde251da0bc373d6cd08e811434.zip",
|
||||
strip_prefix = "rules_webtesting-8fd9ce0fd9254bde251da0bc373d6cd08e811434",
|
||||
sha256 = "4baee95fcfadfbaf868707af8accfd1cb98c5d13f808908e0152468bfb47f0f7",
|
||||
url = "https://github.com/bazelbuild/rules_webtesting/archive/0.2.1.zip",
|
||||
strip_prefix = "rules_webtesting-0.2.1",
|
||||
sha256 = "7d490aadff9b5262e5251fa69427ab2ffd1548422467cb9f9e1d110e2c36f0fa",
|
||||
)
|
||||
|
||||
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",
|
||||
url = "https://github.com/bazelbuild/rules_typescript/archive/0.16.0.zip",
|
||||
strip_prefix = "rules_typescript-0.16.0",
|
||||
sha256 = "e65c5639a42e2f6d3f9d2bda62487d6b42734830dda45be1620c3e2b1115070c",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
|
@ -32,6 +32,7 @@ ts_library(
|
||||
name = "test_lib",
|
||||
testonly = 1,
|
||||
srcs = glob(["*.spec.ts"]),
|
||||
tsconfig = "//src:tsconfig.json",
|
||||
deps = [":hello-world"],
|
||||
)
|
||||
|
||||
|
@ -5,6 +5,7 @@ ts_library(
|
||||
name = "e2e",
|
||||
testonly = 1,
|
||||
srcs = ["app.spec.ts"],
|
||||
tsconfig = ":tsconfig.json",
|
||||
)
|
||||
|
||||
ts_library(
|
||||
|
@ -3,24 +3,24 @@
|
||||
|
||||
|
||||
"@angular/animations@file:../../dist/packages-dist/animations":
|
||||
version "6.1.0-beta.3"
|
||||
version "6.1.0"
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
"@angular/bazel@file:../../dist/packages-dist/bazel":
|
||||
version "6.1.0-beta.3"
|
||||
version "6.1.0"
|
||||
dependencies:
|
||||
"@bazel/typescript" "^0.15.0"
|
||||
"@types/node" "6.0.84"
|
||||
protobufjs "5.0.0"
|
||||
|
||||
"@angular/common@file:../../dist/packages-dist/common":
|
||||
version "6.1.0-beta.3"
|
||||
version "6.1.0"
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
"@angular/compiler-cli@file:../../dist/packages-dist/compiler-cli":
|
||||
version "6.1.0-beta.3"
|
||||
version "6.1.0"
|
||||
dependencies:
|
||||
chokidar "^1.4.2"
|
||||
convert-source-map "^1.5.1"
|
||||
@ -31,22 +31,22 @@
|
||||
tsickle "^0.32.1"
|
||||
|
||||
"@angular/compiler@file:../../dist/packages-dist/compiler":
|
||||
version "6.1.0-beta.3"
|
||||
version "6.1.0"
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
"@angular/core@file:../../dist/packages-dist/core":
|
||||
version "6.1.0-beta.3"
|
||||
version "6.1.0"
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
"@angular/platform-browser-dynamic@file:../../dist/packages-dist/platform-browser-dynamic":
|
||||
version "6.1.0-beta.3"
|
||||
version "6.1.0"
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
"@angular/platform-browser@file:../../dist/packages-dist/platform-browser":
|
||||
version "6.1.0-beta.3"
|
||||
version "6.1.0"
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
@ -1410,9 +1410,9 @@ tough-cookie@~2.3.3:
|
||||
dependencies:
|
||||
punycode "^1.4.1"
|
||||
|
||||
tsickle@^0.30.0:
|
||||
version "0.30.0"
|
||||
resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.30.0.tgz#7941146ae92933854a8742fa1047606c4536649b"
|
||||
tsickle@^0.32.1:
|
||||
version "0.32.1"
|
||||
resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.32.1.tgz#f16e94ba80b32fc9ebe320dc94fbc2ca7f3521a5"
|
||||
dependencies:
|
||||
jasmine-diff "^0.1.3"
|
||||
minimist "^1.2.0"
|
||||
|
@ -19,6 +19,9 @@ node_modules/zone.js/dist/zone_externs.js
|
||||
--js node_modules/rxjs/operators/package.json
|
||||
--js node_modules/rxjs/_esm2015/operators/index.js
|
||||
|
||||
--js node_modules/@angular/compiler/package.json
|
||||
--js node_modules/@angular/compiler/fesm2015/compiler.js
|
||||
|
||||
--js node_modules/@angular/core/package.json
|
||||
--js node_modules/@angular/core/fesm2015/core.js
|
||||
--js node_modules/@angular/core/src/testability/testability.externs.js
|
||||
|
@ -17,6 +17,9 @@ node_modules/zone.js/dist/zone_externs.js
|
||||
--module_resolution=node
|
||||
--package_json_entry_names es2015
|
||||
|
||||
--js node_modules/@angular/compiler/package.json
|
||||
--js node_modules/@angular/compiler/fesm2015/compiler.js
|
||||
|
||||
--js node_modules/@angular/core/package.json
|
||||
--js node_modules/@angular/core/fesm2015/core.js
|
||||
--js node_modules/@angular/core/src/testability/testability.externs.js
|
||||
|
@ -9,6 +9,7 @@
|
||||
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
|
||||
"@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",
|
||||
"@angular/platform-server": "file:../../dist/packages-dist/platform-server",
|
||||
"google-closure-compiler": "git+https://github.com/alexeagle/closure-compiler.git#packagejson.dist",
|
||||
"rxjs": "file:../../node_modules/rxjs",
|
||||
|
@ -3,40 +3,48 @@
|
||||
|
||||
|
||||
"@angular/animations@file:../../dist/packages-dist/animations":
|
||||
version "6.0.0-beta.7-8203e0365a"
|
||||
version "7.0.0-beta.1-d2d510089c"
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
"@angular/common@file:../../dist/packages-dist/common":
|
||||
version "6.0.0-beta.7-8203e0365a"
|
||||
version "7.0.0-beta.1-d2d510089c"
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
"@angular/compiler-cli@file:../../dist/packages-dist/compiler-cli":
|
||||
version "6.0.0-beta.7-8203e0365a"
|
||||
version "7.0.0-beta.1-d2d510089c"
|
||||
dependencies:
|
||||
chokidar "^1.4.2"
|
||||
convert-source-map "^1.5.1"
|
||||
magic-string "^0.25.0"
|
||||
minimist "^1.2.0"
|
||||
reflect-metadata "^0.1.2"
|
||||
tsickle "^0.27.2"
|
||||
source-map "^0.6.1"
|
||||
tsickle "^0.32.1"
|
||||
|
||||
"@angular/compiler@file:../../dist/packages-dist/compiler":
|
||||
version "6.0.0-beta.7-8203e0365a"
|
||||
version "7.0.0-beta.1-d2d510089c"
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
"@angular/core@file:../../dist/packages-dist/core":
|
||||
version "6.0.0-beta.7-8203e0365a"
|
||||
version "7.0.0-beta.1-d2d510089c"
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
"@angular/platform-browser-dynamic@file:../../dist/packages-dist/platform-browser-dynamic":
|
||||
version "7.0.0-beta.1-d2d510089c"
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
"@angular/platform-browser@file:../../dist/packages-dist/platform-browser":
|
||||
version "6.0.0-beta.7-8203e0365a"
|
||||
version "7.0.0-beta.1-d2d510089c"
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
"@angular/platform-server@file:../../dist/packages-dist/platform-server":
|
||||
version "6.0.0-beta.7-8203e0365a"
|
||||
version "7.0.0-beta.1-d2d510089c"
|
||||
dependencies:
|
||||
domino "^2.0.1"
|
||||
tslib "^1.9.0"
|
||||
@ -510,6 +518,10 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
|
||||
|
||||
convert-source-map@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5"
|
||||
|
||||
cookie@0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
|
||||
@ -612,6 +624,10 @@ dev-ip@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/dev-ip/-/dev-ip-1.0.1.tgz#a76a3ed1855be7a012bb8ac16cb80f3c00dc28f0"
|
||||
|
||||
diff@^3.2.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
|
||||
|
||||
domino@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/domino/-/domino-2.0.1.tgz#9e1d63215d0fe8dcb8202bff07effa1a216db504"
|
||||
@ -1203,6 +1219,12 @@ jasmine-core@~2.8.0:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.8.0.tgz#bcc979ae1f9fd05701e45e52e65d3a5d63f1a24e"
|
||||
|
||||
jasmine-diff@^0.1.3:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/jasmine-diff/-/jasmine-diff-0.1.3.tgz#93ccc2dcc41028c5ddd4606558074839f2deeaa8"
|
||||
dependencies:
|
||||
diff "^3.2.0"
|
||||
|
||||
jasmine@^2.5.3:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-2.8.0.tgz#6b089c0a11576b1f16df11b80146d91d4e8b8a3e"
|
||||
@ -1319,6 +1341,12 @@ lodash@^4.11.1, lodash@^4.5.1:
|
||||
version "4.17.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
|
||||
|
||||
magic-string@^0.25.0:
|
||||
version "0.25.0"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.0.tgz#1f3696f9931ff0a1ed4c132250529e19cad6759b"
|
||||
dependencies:
|
||||
sourcemap-codec "^1.4.1"
|
||||
|
||||
micromatch@2.3.11, micromatch@^2.1.5:
|
||||
version "2.3.11"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
|
||||
@ -1835,7 +1863,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-alpha.4"
|
||||
version "6.0.0"
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
@ -2016,10 +2044,14 @@ source-map@^0.5.1, source-map@^0.5.6:
|
||||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||
|
||||
source-map@^0.6.0:
|
||||
source-map@^0.6.0, source-map@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
|
||||
sourcemap-codec@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.1.tgz#c8fd92d91889e902a07aee392bdd2c5863958ba2"
|
||||
|
||||
spawn-command@^0.0.2-1:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2.tgz#9544e1a43ca045f8531aac1a48cb29bdae62338e"
|
||||
@ -2180,10 +2212,11 @@ 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.32.1:
|
||||
version "0.32.1"
|
||||
resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.32.1.tgz#f16e94ba80b32fc9ebe320dc94fbc2ca7f3521a5"
|
||||
dependencies:
|
||||
jasmine-diff "^0.1.3"
|
||||
minimist "^1.2.0"
|
||||
mkdirp "^0.5.1"
|
||||
source-map "^0.6.0"
|
||||
@ -2204,7 +2237,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.7.2"
|
||||
version "2.9.2"
|
||||
|
||||
ua-parser-js@0.7.12:
|
||||
version "0.7.12"
|
||||
@ -2424,4 +2457,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"
|
||||
|
@ -84,7 +84,7 @@ module.exports = function(config) {
|
||||
'dist/all/@angular/elements/schematics/**',
|
||||
'dist/all/@angular/examples/**/e2e_test/*',
|
||||
'dist/all/@angular/language-service/**',
|
||||
'dist/all/@angular/router/test/**',
|
||||
'dist/all/@angular/router/**/test/**',
|
||||
'dist/all/@angular/platform-browser/testing/e2e_util.js',
|
||||
'dist/all/angular1_router.js',
|
||||
'dist/examples/**/e2e_test/**',
|
||||
|
11
package.json
11
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "6.1.1",
|
||||
"version": "7.0.0-beta.2",
|
||||
"private": true,
|
||||
"branchPattern": "2.0.*",
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
@ -19,7 +19,7 @@
|
||||
"preskylint": "bazel build --noshow_progress @io_bazel//src/tools/skylark/java/com/google/devtools/skylark/skylint:Skylint",
|
||||
"skylint": "find . -type f -name \"*.bzl\" ! -path \"*/node_modules/*\" ! -path \"./dist/*\" | xargs $(bazel info bazel-bin)/external/io_bazel/src/tools/skylark/java/com/google/devtools/skylark/skylint/Skylint --disable-checks=deprecated-api",
|
||||
"prebuildifier": "bazel build --noshow_progress @com_github_bazelbuild_buildtools//buildifier",
|
||||
"buildifier": "find . -type f \\( -name BUILD -or -name BUILD.bazel \\) ! -path \"*/node_modules/*\" | xargs $(bazel info bazel-bin)/external/com_github_bazelbuild_buildtools/buildifier/*/buildifier",
|
||||
"buildifier": "find . -type f \\( -name \"*.bzl\" -or -name BUILD -or -name BUILD.bazel \\) ! -path \"*/node_modules/*\" | xargs $(bazel info bazel-bin)/external/com_github_bazelbuild_buildtools/buildifier/*/buildifier",
|
||||
"preinstall": "node tools/yarn/check-yarn.js",
|
||||
"postinstall": "yarn update-webdriver && node ./tools/postinstall-patches.js && yarn patch-types",
|
||||
"//patch-types": "work-around for issue https://github.com/angular/angular/issues/25051",
|
||||
@ -46,12 +46,14 @@
|
||||
"@types/base64-js": "1.2.5",
|
||||
"@types/chai": "^4.1.2",
|
||||
"@types/chokidar": "1.7.3",
|
||||
"@types/convert-source-map": "^1.5.1",
|
||||
"@types/diff": "^3.2.2",
|
||||
"@types/fs-extra": "4.0.2",
|
||||
"@types/hammerjs": "2.0.35",
|
||||
"@types/jasmine": "^2.8.8",
|
||||
"@types/jasminewd2": "^2.0.3",
|
||||
"@types/minimist": "^1.2.0",
|
||||
"@types/mock-fs": "^3.6.30",
|
||||
"@types/node": "6.0.88",
|
||||
"@types/selenium-webdriver": "3.0.7",
|
||||
"@types/shelljs": "^0.7.8",
|
||||
@ -74,6 +76,7 @@
|
||||
"cldr-data-downloader": "0.3.2",
|
||||
"cldrjs": "0.5.0",
|
||||
"conventional-changelog": "1.1.0",
|
||||
"convert-source-map": "^1.5.1",
|
||||
"cors": "2.8.4",
|
||||
"diff": "^3.5.0",
|
||||
"domino": "2.0.1",
|
||||
@ -99,7 +102,9 @@
|
||||
"karma-sauce-launcher": "^1.2.0",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"madge": "0.5.0",
|
||||
"magic-string": "^0.25.0",
|
||||
"minimist": "1.2.0",
|
||||
"mock-fs": "^4.5.0",
|
||||
"mutation-observer": "^1.0.3",
|
||||
"node-uuid": "1.4.8",
|
||||
"protobufjs": "5.0.0",
|
||||
@ -112,7 +117,7 @@
|
||||
"selenium-webdriver": "3.5.0",
|
||||
"semver": "5.4.1",
|
||||
"shelljs": "^0.8.1",
|
||||
"source-map": "0.5.7",
|
||||
"source-map": "^0.6.1",
|
||||
"source-map-support": "0.4.18",
|
||||
"systemjs": "0.18.10",
|
||||
"tsickle": "0.32",
|
||||
|
@ -9,31 +9,42 @@ import {AnimationMetadata, AnimationOptions} from './animation_metadata';
|
||||
import {AnimationPlayer} from './players/animation_player';
|
||||
|
||||
/**
|
||||
* AnimationBuilder is an injectable service that is available when the {@link
|
||||
* BrowserAnimationsModule BrowserAnimationsModule} or {@link NoopAnimationsModule
|
||||
* NoopAnimationsModule} modules are used within an application.
|
||||
* An injectable service that produces an animation sequence programmatically within an
|
||||
* Angular component or directive.
|
||||
* Provided by the `BrowserAnimationsModule` or `NoopAnimationsModule`.
|
||||
*
|
||||
* The purpose of this service is to produce an animation sequence programmatically within an
|
||||
* angular component or directive.
|
||||
* @usageNotes
|
||||
*
|
||||
* Programmatic animations are first built and then a player is created when the build animation is
|
||||
* attached to an element.
|
||||
* To use this service, add it to your component or directive as a dependency.
|
||||
* The service is instantiated along with your component.
|
||||
*
|
||||
* Apps do not typically need to create their own animation players, but if you
|
||||
* do need to, follow these steps:
|
||||
*
|
||||
* 1. Use the `build()` method to create a programmatic animation using the
|
||||
* `animate()` function. The method returns an `AnimationFactory` instance.
|
||||
*
|
||||
* 2. Use the factory object to create an `AnimationPlayer` and attach it to a DOM element.
|
||||
*
|
||||
* 3. Use the player object to control the animation programmatically.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```ts
|
||||
* // remember to include the BrowserAnimationsModule module for this to work...
|
||||
* // import the service from BrowserAnimationsModule
|
||||
* import {AnimationBuilder} from '@angular/animations';
|
||||
*
|
||||
* // require the service as a dependency
|
||||
* class MyCmp {
|
||||
* constructor(private _builder: AnimationBuilder) {}
|
||||
*
|
||||
* makeAnimation(element: any) {
|
||||
* // first build the animation
|
||||
* // first define a reusable animation
|
||||
* const myAnimation = this._builder.build([
|
||||
* style({ width: 0 }),
|
||||
* animate(1000, style({ width: '100px' }))
|
||||
* ]);
|
||||
*
|
||||
* // then create a player from it
|
||||
* // use the returned factory object to create a player
|
||||
* const player = myAnimation.create(element);
|
||||
*
|
||||
* player.play();
|
||||
@ -41,22 +52,29 @@ import {AnimationPlayer} from './players/animation_player';
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* When an animation is built an instance of {@link AnimationFactory AnimationFactory} will be
|
||||
* returned. Using that an {@link AnimationPlayer AnimationPlayer} can be created which can then be
|
||||
* used to start the animation.
|
||||
*
|
||||
* @experimental Animation support is experimental.
|
||||
*/
|
||||
export abstract class AnimationBuilder {
|
||||
/**
|
||||
* Builds a factory for producing a defined animation.
|
||||
* @param animation A reusable animation definition.
|
||||
* @returns A factory object that can create a player for the defined animation.
|
||||
* @see `animate()`
|
||||
*/
|
||||
abstract build(animation: AnimationMetadata|AnimationMetadata[]): AnimationFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* An instance of `AnimationFactory` is returned from {@link AnimationBuilder#build
|
||||
* AnimationBuilder.build}.
|
||||
* A factory object returned from the `AnimationBuilder`.`build()` method.
|
||||
*
|
||||
* @experimental Animation support is experimental.
|
||||
*/
|
||||
export abstract class AnimationFactory {
|
||||
/**
|
||||
* Creates an `AnimationPlayer` instance for the reusable animation defined by
|
||||
* the `AnimationBuilder`.`build()` method that created this factory.
|
||||
* Attaches the new player a DOM element.
|
||||
* @param element The DOM element to which to attach the animation.
|
||||
* @param options A set of options that can include a time delay and
|
||||
* additional developer-defined parameters.
|
||||
*/
|
||||
abstract create(element: any, options?: AnimationOptions): AnimationPlayer;
|
||||
}
|
||||
|
@ -38,11 +38,33 @@
|
||||
* @experimental Animation support is experimental.
|
||||
*/
|
||||
export interface AnimationEvent {
|
||||
/**
|
||||
* The name of the state from which the animation is triggered.
|
||||
*/
|
||||
fromState: string;
|
||||
/**
|
||||
* The name of the state in which the animation completes.
|
||||
*/
|
||||
toState: string;
|
||||
/**
|
||||
* The time it takes the animation to complete, in milliseconds.
|
||||
*/
|
||||
totalTime: number;
|
||||
/**
|
||||
* The animation phase in which the callback was invoked, one of
|
||||
* "start" or "done".
|
||||
*/
|
||||
phaseName: string;
|
||||
/**
|
||||
* The element to which the animation is attached.
|
||||
*/
|
||||
element: any;
|
||||
/**
|
||||
* Internal.
|
||||
*/
|
||||
triggerName: string;
|
||||
/**
|
||||
* Internal.
|
||||
*/
|
||||
disabled: boolean;
|
||||
}
|
||||
|
@ -9,6 +9,14 @@
|
||||
import {scheduleMicroTask} from '../util';
|
||||
import {AnimationPlayer} from './animation_player';
|
||||
|
||||
/**
|
||||
* A programmatic controller for a group of reusable animations.
|
||||
* Used internally to control animations.
|
||||
*
|
||||
* @see `AnimationPlayer`
|
||||
* @see `{@link animations/group group()}`
|
||||
*
|
||||
*/
|
||||
export class AnimationGroupPlayer implements AnimationPlayer {
|
||||
private _onDoneFns: Function[] = [];
|
||||
private _onStartFns: Function[] = [];
|
||||
|
@ -8,37 +8,110 @@
|
||||
import {scheduleMicroTask} from '../util';
|
||||
|
||||
/**
|
||||
* AnimationPlayer controls an animation sequence that was produced from a programmatic animation.
|
||||
* (see {@link AnimationBuilder AnimationBuilder} for more information on how to create programmatic
|
||||
* animations.)
|
||||
* Provides programmatic control of a reusable animation sequence,
|
||||
* built using the `build()` method of `AnimationBuilder`. The `build()` method
|
||||
* returns a factory, whose `create()` method instantiates and initializes this interface.
|
||||
*
|
||||
* @see `AnimationBuilder`
|
||||
* @see `AnimationFactory`
|
||||
* @see `animate()`
|
||||
*
|
||||
* @experimental Animation support is experimental.
|
||||
*/
|
||||
export interface AnimationPlayer {
|
||||
/**
|
||||
* Provides a callback to invoke when the animation finishes.
|
||||
* @param fn The callback function.
|
||||
* @see `finish()`
|
||||
*/
|
||||
onDone(fn: () => void): void;
|
||||
/**
|
||||
* Provides a callback to invoke when the animation starts.
|
||||
* @param fn The callback function.
|
||||
* @see `run()`
|
||||
*/
|
||||
onStart(fn: () => void): void;
|
||||
/**
|
||||
* Provides a callback to invoke after the animation is destroyed.
|
||||
* @param fn The callback function.
|
||||
* @see `destroy()`
|
||||
* @see `beforeDestroy()`
|
||||
*/
|
||||
onDestroy(fn: () => void): void;
|
||||
/**
|
||||
* Initializes the animation.
|
||||
*/
|
||||
init(): void;
|
||||
/**
|
||||
* Reports whether the animation has started.
|
||||
* @returns True if the animation has started, false otherwise.
|
||||
*/
|
||||
hasStarted(): boolean;
|
||||
/**
|
||||
* Runs the animation, invoking the `onStart()` callback.
|
||||
*/
|
||||
play(): void;
|
||||
/**
|
||||
* Pauses the animation.
|
||||
*/
|
||||
pause(): void;
|
||||
/**
|
||||
* Restarts the paused animation.
|
||||
*/
|
||||
restart(): void;
|
||||
/**
|
||||
* Ends the animation, invoking the `onDone()` callback.
|
||||
*/
|
||||
finish(): void;
|
||||
/**
|
||||
* Destroys the animation, after invoking the `beforeDestroy()` callback.
|
||||
* Calls the `onDestroy()` callback when destruction is completed.
|
||||
*/
|
||||
destroy(): void;
|
||||
/**
|
||||
* Resets the animation to its initial state.
|
||||
*/
|
||||
reset(): void;
|
||||
/**
|
||||
* Sets the position of the animation.
|
||||
* @param position A 0-based offset into the duration, in milliseconds.
|
||||
*/
|
||||
setPosition(position: any /** TODO #9100 */): void;
|
||||
/**
|
||||
* Reports the current position of the animation.
|
||||
* @returns A 0-based offset into the duration, in milliseconds.
|
||||
*/
|
||||
getPosition(): number;
|
||||
/**
|
||||
* The parent of this player, if any.
|
||||
*/
|
||||
parentPlayer: AnimationPlayer|null;
|
||||
/**
|
||||
* The total run time of the animation, in milliseconds.
|
||||
*/
|
||||
readonly totalTime: number;
|
||||
/**
|
||||
* Provides a callback to invoke before the animation is destroyed.
|
||||
*/
|
||||
beforeDestroy?: () => any;
|
||||
/** @internal */
|
||||
/** @internal
|
||||
* Internal
|
||||
*/
|
||||
triggerCallback?: (phaseName: string) => void;
|
||||
/** @internal */
|
||||
/** @internal
|
||||
* Internal
|
||||
*/
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental Animation support is experimental.
|
||||
* An empty programmatic controller for reusable animations.
|
||||
* Used internally when animations are disabled, to avoid
|
||||
* checking for the null case when an animation player is expected.
|
||||
*
|
||||
* @see `animate()`
|
||||
* @see `AnimationPlayer`
|
||||
* @see `GroupPlayer`
|
||||
*
|
||||
*/
|
||||
export class NoopAnimationPlayer implements AnimationPlayer {
|
||||
private _onDoneFns: Function[] = [];
|
||||
|
@ -14,6 +14,7 @@ npm_package(
|
||||
"package.json",
|
||||
"//packages/bazel/src:package_assets",
|
||||
],
|
||||
packages = ["//packages/bazel/docs"],
|
||||
# Re-host //packages/bazel/ which is just // in the public distro
|
||||
replacements = {
|
||||
"//packages/bazel/": "//",
|
||||
|
16
packages/bazel/docs/BUILD.bazel
Normal file
16
packages/bazel/docs/BUILD.bazel
Normal file
@ -0,0 +1,16 @@
|
||||
load("@io_bazel_skydoc//skylark:skylark.bzl", "skylark_doc")
|
||||
|
||||
skylark_doc(
|
||||
name = "docs",
|
||||
srcs = [
|
||||
"//packages/bazel/src:ng_module.bzl",
|
||||
"//packages/bazel/src:ng_rollup_bundle.bzl",
|
||||
"//packages/bazel/src:ng_setup_workspace.bzl",
|
||||
"//packages/bazel/src/ng_package:ng_package.bzl",
|
||||
"//packages/bazel/src/protractor:protractor_web_test.bzl",
|
||||
],
|
||||
format = "html",
|
||||
site_root = "/bazel-builds",
|
||||
strip_prefix = "packages/bazel/",
|
||||
visibility = ["//packages/bazel:__subpackages__"],
|
||||
)
|
@ -10,12 +10,16 @@ Users should not load files under "/src"
|
||||
load("//packages/bazel/src:ng_module.bzl", _ng_module = "ng_module")
|
||||
load("//packages/bazel/src:ng_setup_workspace.bzl", _ng_setup_workspace = "ng_setup_workspace")
|
||||
load("//packages/bazel/src/ng_package:ng_package.bzl", _ng_package = "ng_package")
|
||||
load("//packages/bazel/src/protractor:protractor_web_test.bzl",
|
||||
_protractor_web_test = "protractor_web_test",
|
||||
_protractor_web_test_suite = "protractor_web_test_suite")
|
||||
load(
|
||||
"//packages/bazel/src/protractor:protractor_web_test.bzl",
|
||||
_protractor_web_test = "protractor_web_test",
|
||||
_protractor_web_test_suite = "protractor_web_test_suite",
|
||||
)
|
||||
|
||||
ng_module = _ng_module
|
||||
ng_package = _ng_package
|
||||
protractor_web_test = _protractor_web_test
|
||||
protractor_web_test_suite = _protractor_web_test_suite
|
||||
ng_setup_workspace = _ng_setup_workspace
|
||||
# DO NOT ADD PUBLIC API without including in the documentation generation
|
||||
# Run `bazel build //packages/bazel/docs` to verify
|
||||
|
@ -19,6 +19,7 @@ nodejs_binary(
|
||||
# dependency @build_bazel_rules_nodejs_rollup_deps. We don't need any
|
||||
# additional npm dependencies when we run rollup or uglify.
|
||||
entry_point = "build_bazel_rules_nodejs_rollup_deps/node_modules/rollup/bin/rollup",
|
||||
install_source_map_support = False,
|
||||
node_modules = "@build_bazel_rules_nodejs_rollup_deps//:node_modules",
|
||||
)
|
||||
|
||||
@ -26,4 +27,5 @@ nodejs_binary(
|
||||
name = "modify_tsconfig",
|
||||
data = ["modify_tsconfig.js"],
|
||||
entry_point = "angular/packages/bazel/src/modify_tsconfig.js",
|
||||
install_source_map_support = False,
|
||||
)
|
||||
|
@ -28,75 +28,78 @@ ESM5Info = provider(
|
||||
)
|
||||
|
||||
def _map_closure_path(file):
|
||||
result = file.short_path[:-len(".closure.js")]
|
||||
# short_path is meant to be used when accessing runfiles in a binary, where
|
||||
# the CWD is inside the current repo. Therefore files in external repo have a
|
||||
# short_path of ../external/wkspc/path/to/package
|
||||
# We want to strip the first two segments from such paths.
|
||||
if (result.startswith("../")):
|
||||
result = "/".join(result.split("/")[2:])
|
||||
return result + ".js"
|
||||
result = file.short_path[:-len(".closure.js")]
|
||||
|
||||
# short_path is meant to be used when accessing runfiles in a binary, where
|
||||
# the CWD is inside the current repo. Therefore files in external repo have a
|
||||
# short_path of ../external/wkspc/path/to/package
|
||||
# We want to strip the first two segments from such paths.
|
||||
if (result.startswith("../")):
|
||||
result = "/".join(result.split("/")[2:])
|
||||
return result + ".js"
|
||||
|
||||
def _join(array):
|
||||
return "/".join([p for p in array if p])
|
||||
return "/".join([p for p in array if p])
|
||||
|
||||
def _esm5_outputs_aspect(target, ctx):
|
||||
if not hasattr(target, "typescript"):
|
||||
return []
|
||||
if not hasattr(target, "typescript"):
|
||||
return []
|
||||
|
||||
# We create a new tsconfig.json file that will have our compilation settings
|
||||
tsconfig = ctx.actions.declare_file("%s_esm5.tsconfig.json" % target.label.name)
|
||||
# We create a new tsconfig.json file that will have our compilation settings
|
||||
tsconfig = ctx.actions.declare_file("%s_esm5.tsconfig.json" % target.label.name)
|
||||
|
||||
workspace = target.label.workspace_root if target.label.workspace_root else ""
|
||||
workspace = target.label.workspace_root if target.label.workspace_root else ""
|
||||
|
||||
# re-root the outputs under a ".esm5" directory so the path don't collide
|
||||
out_dir = ctx.label.name + ".esm5"
|
||||
if workspace:
|
||||
out_dir = out_dir + "/" + workspace
|
||||
# re-root the outputs under a ".esm5" directory so the path don't collide
|
||||
out_dir = ctx.label.name + ".esm5"
|
||||
if workspace:
|
||||
out_dir = out_dir + "/" + workspace
|
||||
|
||||
outputs = [ctx.actions.declare_file(_join([out_dir, _map_closure_path(f)]))
|
||||
for f in target.typescript.replay_params.outputs
|
||||
if not f.short_path.endswith(".externs.js")]
|
||||
outputs = [
|
||||
ctx.actions.declare_file(_join([out_dir, _map_closure_path(f)]))
|
||||
for f in target.typescript.replay_params.outputs
|
||||
if not f.short_path.endswith(".externs.js")
|
||||
]
|
||||
|
||||
ctx.actions.run(
|
||||
executable = ctx.executable._modify_tsconfig,
|
||||
inputs = [target.typescript.replay_params.tsconfig],
|
||||
outputs = [tsconfig],
|
||||
arguments = [
|
||||
target.typescript.replay_params.tsconfig.path,
|
||||
tsconfig.path,
|
||||
_join([workspace, target.label.package, ctx.label.name + ".esm5"]),
|
||||
ctx.bin_dir.path
|
||||
],
|
||||
)
|
||||
ctx.actions.run(
|
||||
executable = ctx.executable._modify_tsconfig,
|
||||
inputs = [target.typescript.replay_params.tsconfig],
|
||||
outputs = [tsconfig],
|
||||
arguments = [
|
||||
target.typescript.replay_params.tsconfig.path,
|
||||
tsconfig.path,
|
||||
_join([workspace, target.label.package, ctx.label.name + ".esm5"]),
|
||||
ctx.bin_dir.path,
|
||||
],
|
||||
)
|
||||
|
||||
ctx.actions.run(
|
||||
progress_message = "Compiling TypeScript (ES5 with ES Modules) %s" % target.label,
|
||||
inputs = target.typescript.replay_params.inputs + [tsconfig],
|
||||
outputs = outputs,
|
||||
arguments = [tsconfig.path],
|
||||
executable = target.typescript.replay_params.compiler,
|
||||
execution_requirements = {
|
||||
# TODO(alexeagle): enable worker mode for these compilations
|
||||
"supports-workers": "0",
|
||||
},
|
||||
)
|
||||
ctx.actions.run(
|
||||
progress_message = "Compiling TypeScript (ES5 with ES Modules) %s" % target.label,
|
||||
inputs = target.typescript.replay_params.inputs + [tsconfig],
|
||||
outputs = outputs,
|
||||
arguments = [tsconfig.path],
|
||||
executable = target.typescript.replay_params.compiler,
|
||||
execution_requirements = {
|
||||
# TODO(alexeagle): enable worker mode for these compilations
|
||||
"supports-workers": "0",
|
||||
},
|
||||
)
|
||||
|
||||
root_dir = _join([
|
||||
ctx.bin_dir.path,
|
||||
workspace,
|
||||
target.label.package,
|
||||
ctx.label.name + ".esm5",
|
||||
])
|
||||
root_dir = _join([
|
||||
ctx.bin_dir.path,
|
||||
workspace,
|
||||
target.label.package,
|
||||
ctx.label.name + ".esm5",
|
||||
])
|
||||
|
||||
transitive_output={root_dir: depset(outputs)}
|
||||
for dep in ctx.rule.attr.deps:
|
||||
if ESM5Info in dep:
|
||||
transitive_output.update(dep[ESM5Info].transitive_output)
|
||||
transitive_output = {root_dir: depset(outputs)}
|
||||
for dep in ctx.rule.attr.deps:
|
||||
if ESM5Info in dep:
|
||||
transitive_output.update(dep[ESM5Info].transitive_output)
|
||||
|
||||
return [ESM5Info(
|
||||
transitive_output = transitive_output,
|
||||
)]
|
||||
return [ESM5Info(
|
||||
transitive_output = transitive_output,
|
||||
)]
|
||||
|
||||
# Downstream rules can use this aspect to access the ESM5 output flavor.
|
||||
# Only terminal rules (those which expect never to be used in deps[]) should do
|
||||
@ -104,12 +107,13 @@ def _esm5_outputs_aspect(target, ctx):
|
||||
esm5_outputs_aspect = aspect(
|
||||
implementation = _esm5_outputs_aspect,
|
||||
# Recurse to the deps of any target we visit
|
||||
attr_aspects = ['deps'],
|
||||
attr_aspects = ["deps"],
|
||||
attrs = {
|
||||
"_modify_tsconfig": attr.label(
|
||||
default = Label("//packages/bazel/src:modify_tsconfig"),
|
||||
executable = True,
|
||||
cfg = "host"),
|
||||
cfg = "host",
|
||||
),
|
||||
# We must list tsc_wrapped here to ensure it's built before the action runs
|
||||
# For some reason, having the compiler output as an input to the action above
|
||||
# is not sufficient.
|
||||
@ -128,43 +132,44 @@ esm5_outputs_aspect = aspect(
|
||||
)
|
||||
|
||||
def esm5_root_dir(ctx):
|
||||
return ctx.label.name + ".esm5"
|
||||
return ctx.label.name + ".esm5"
|
||||
|
||||
def flatten_esm5(ctx):
|
||||
"""Merge together the .esm5 folders from the dependencies.
|
||||
"""Merge together the .esm5 folders from the dependencies.
|
||||
|
||||
Two different dependencies A and B may have outputs like
|
||||
`bazel-bin/path/to/A.esm5/path/to/lib.js`
|
||||
`bazel-bin/path/to/B.esm5/path/to/main.js`
|
||||
Two different dependencies A and B may have outputs like
|
||||
`bazel-bin/path/to/A.esm5/path/to/lib.js`
|
||||
`bazel-bin/path/to/B.esm5/path/to/main.js`
|
||||
|
||||
In order to run rollup on this app, in case main.js contains `import from './lib'`
|
||||
they need to be together in the same root directory, so if we depend on both A and B
|
||||
we need the outputs to be
|
||||
`bazel-bin/path/to/my_rule.esm5/path/to/lib.js`
|
||||
`bazel-bin/path/to/my_rule.esm5/path/to/main.js`
|
||||
In order to run rollup on this app, in case main.js contains `import from './lib'`
|
||||
they need to be together in the same root directory, so if we depend on both A and B
|
||||
we need the outputs to be
|
||||
`bazel-bin/path/to/my_rule.esm5/path/to/lib.js`
|
||||
`bazel-bin/path/to/my_rule.esm5/path/to/main.js`
|
||||
|
||||
Args:
|
||||
ctx: the skylark rule execution context
|
||||
Args:
|
||||
ctx: the skylark rule execution context
|
||||
|
||||
Returns:
|
||||
list of flattened files
|
||||
"""
|
||||
esm5_sources = []
|
||||
result = []
|
||||
for dep in ctx.attr.deps:
|
||||
if ESM5Info in dep:
|
||||
transitive_output = dep[ESM5Info].transitive_output
|
||||
esm5_sources.extend(transitive_output.values())
|
||||
for f in depset(transitive = esm5_sources).to_list():
|
||||
path = f.short_path[f.short_path.find(".esm5") + len(".esm5"):]
|
||||
if (path.startswith("../")):
|
||||
path = "external/" + path[3:]
|
||||
rerooted_file = ctx.actions.declare_file("/".join([esm5_root_dir(ctx), path]))
|
||||
result.append(rerooted_file)
|
||||
# print("copy", f.short_path, "to", rerooted_file.short_path)
|
||||
ctx.actions.expand_template(
|
||||
output = rerooted_file,
|
||||
template = f,
|
||||
substitutions = {},
|
||||
)
|
||||
return result
|
||||
Returns:
|
||||
list of flattened files
|
||||
"""
|
||||
esm5_sources = []
|
||||
result = []
|
||||
for dep in ctx.attr.deps:
|
||||
if ESM5Info in dep:
|
||||
transitive_output = dep[ESM5Info].transitive_output
|
||||
esm5_sources.extend(transitive_output.values())
|
||||
for f in depset(transitive = esm5_sources).to_list():
|
||||
path = f.short_path[f.short_path.find(".esm5") + len(".esm5"):]
|
||||
if (path.startswith("../")):
|
||||
path = "external/" + path[3:]
|
||||
rerooted_file = ctx.actions.declare_file("/".join([esm5_root_dir(ctx), path]))
|
||||
result.append(rerooted_file)
|
||||
|
||||
# print("copy", f.short_path, "to", rerooted_file.short_path)
|
||||
ctx.actions.expand_template(
|
||||
output = rerooted_file,
|
||||
template = f,
|
||||
substitutions = {},
|
||||
)
|
||||
return result
|
||||
|
@ -2,273 +2,277 @@
|
||||
#
|
||||
# Use of this source code is governed by an MIT-style license that can be
|
||||
# found in the LICENSE file at https://angular.io/license
|
||||
"""Implementation of the ng_module rule.
|
||||
"""Run Angular's AOT template compiler
|
||||
"""
|
||||
|
||||
load(":rules_typescript.bzl",
|
||||
"tsc_wrapped_tsconfig",
|
||||
load(
|
||||
":rules_typescript.bzl",
|
||||
"COMMON_ATTRIBUTES",
|
||||
"COMMON_OUTPUTS",
|
||||
"compile_ts",
|
||||
"DEPS_ASPECTS",
|
||||
"compile_ts",
|
||||
"ts_providers_dict_to_struct",
|
||||
"tsc_wrapped_tsconfig",
|
||||
)
|
||||
|
||||
def _compile_strategy(ctx):
|
||||
"""Detect which strategy should be used to implement ng_module.
|
||||
def compile_strategy(ctx):
|
||||
"""Detect which strategy should be used to implement ng_module.
|
||||
|
||||
Depending on the value of the 'compile' define flag or the '_global_mode' attribute, ng_module
|
||||
can be implemented in various ways. This function reads the configuration passed by the user and
|
||||
determines which mode is active.
|
||||
Depending on the value of the 'compile' define flag or the '_global_mode' attribute, ng_module
|
||||
can be implemented in various ways. This function reads the configuration passed by the user and
|
||||
determines which mode is active.
|
||||
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
|
||||
Returns:
|
||||
one of 'legacy', 'local', 'jit', or 'global' depending on the configuration in ctx
|
||||
"""
|
||||
Returns:
|
||||
one of 'legacy', 'local', 'jit', or 'global' depending on the configuration in ctx
|
||||
"""
|
||||
|
||||
strategy = 'legacy'
|
||||
if 'compile' in ctx.var:
|
||||
strategy = ctx.var['compile']
|
||||
strategy = "legacy"
|
||||
if "compile" in ctx.var:
|
||||
strategy = ctx.var["compile"]
|
||||
|
||||
if strategy not in ['legacy', 'local', 'jit']:
|
||||
fail("Unknown --define=compile value '%s'" % strategy)
|
||||
if strategy not in ["legacy", "local", "jit"]:
|
||||
fail("Unknown --define=compile value '%s'" % strategy)
|
||||
|
||||
if strategy == 'legacy' and hasattr(ctx.attr, '_global_mode') and ctx.attr._global_mode:
|
||||
strategy = 'global'
|
||||
if strategy == "legacy" and hasattr(ctx.attr, "_global_mode") and ctx.attr._global_mode:
|
||||
strategy = "global"
|
||||
|
||||
return strategy
|
||||
return strategy
|
||||
|
||||
def _compiler_name(ctx):
|
||||
"""Selects a user-visible name depending on the current compilation strategy.
|
||||
"""Selects a user-visible name depending on the current compilation strategy.
|
||||
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
|
||||
Returns:
|
||||
the name of the current compiler to be displayed in build output
|
||||
"""
|
||||
Returns:
|
||||
the name of the current compiler to be displayed in build output
|
||||
"""
|
||||
|
||||
strategy = _compile_strategy(ctx)
|
||||
if strategy == 'legacy':
|
||||
return 'ngc'
|
||||
elif strategy == 'global':
|
||||
return 'ngc.ivy'
|
||||
elif strategy == 'local':
|
||||
return 'ngtsc'
|
||||
elif strategy == 'jit':
|
||||
return 'tsc'
|
||||
else:
|
||||
fail('unreachable')
|
||||
strategy = compile_strategy(ctx)
|
||||
if strategy == "legacy":
|
||||
return "ngc"
|
||||
elif strategy == "global":
|
||||
return "ngc.ivy"
|
||||
elif strategy == "local":
|
||||
return "ngtsc"
|
||||
elif strategy == "jit":
|
||||
return "tsc"
|
||||
else:
|
||||
fail("unreachable")
|
||||
|
||||
def _enable_ivy_value(ctx):
|
||||
"""Determines the value of the enableIvy option in the generated tsconfig.
|
||||
"""Determines the value of the enableIvy option in the generated tsconfig.
|
||||
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
|
||||
Returns:
|
||||
the value of enableIvy that needs to be set in angularCompilerOptions in the generated tsconfig
|
||||
"""
|
||||
Returns:
|
||||
the value of enableIvy that needs to be set in angularCompilerOptions in the generated tsconfig
|
||||
"""
|
||||
|
||||
strategy = _compile_strategy(ctx)
|
||||
if strategy == 'legacy':
|
||||
return False
|
||||
elif strategy == 'global':
|
||||
return True
|
||||
elif strategy == 'local':
|
||||
return 'ngtsc'
|
||||
elif strategy == 'jit':
|
||||
return 'tsc'
|
||||
else:
|
||||
fail('unreachable')
|
||||
strategy = compile_strategy(ctx)
|
||||
if strategy == "legacy":
|
||||
return False
|
||||
elif strategy == "global":
|
||||
return True
|
||||
elif strategy == "local":
|
||||
return "ngtsc"
|
||||
elif strategy == "jit":
|
||||
return "tsc"
|
||||
else:
|
||||
fail("unreachable")
|
||||
|
||||
def _include_ng_files(ctx):
|
||||
"""Determines whether Angular outputs will be produced by the current compilation strategy.
|
||||
"""Determines whether Angular outputs will be produced by the current compilation strategy.
|
||||
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
|
||||
Returns:
|
||||
true iff the current compilation strategy will produce View Engine compilation outputs (such as
|
||||
factory files), false otherwise
|
||||
"""
|
||||
Returns:
|
||||
true iff the current compilation strategy will produce View Engine compilation outputs (such as
|
||||
factory files), false otherwise
|
||||
"""
|
||||
|
||||
strategy = _compile_strategy(ctx)
|
||||
return strategy == 'legacy' or strategy == 'global'
|
||||
strategy = compile_strategy(ctx)
|
||||
return strategy == "legacy" or strategy == "global"
|
||||
|
||||
def _basename_of(ctx, file):
|
||||
ext_len = len(".ts")
|
||||
if file.short_path.endswith(".ng.html"):
|
||||
ext_len = len(".ng.html")
|
||||
elif file.short_path.endswith(".html"):
|
||||
ext_len = len(".html")
|
||||
return file.short_path[len(ctx.label.package) + 1:-ext_len]
|
||||
ext_len = len(".ts")
|
||||
if file.short_path.endswith(".ng.html"):
|
||||
ext_len = len(".ng.html")
|
||||
elif file.short_path.endswith(".html"):
|
||||
ext_len = len(".html")
|
||||
return file.short_path[len(ctx.label.package) + 1:-ext_len]
|
||||
|
||||
# Return true if run with bazel (the open-sourced version of blaze), false if
|
||||
# run with blaze.
|
||||
def _is_bazel():
|
||||
return not hasattr(native, "genmpm")
|
||||
return not hasattr(native, "genmpm")
|
||||
|
||||
def _flat_module_out_file(ctx):
|
||||
"""Provide a default for the flat_module_out_file attribute.
|
||||
"""Provide a default for the flat_module_out_file attribute.
|
||||
|
||||
We cannot use the default="" parameter of ctx.attr because the value is calculated
|
||||
from other attributes (name)
|
||||
We cannot use the default="" parameter of ctx.attr because the value is calculated
|
||||
from other attributes (name)
|
||||
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
|
||||
Returns:
|
||||
a basename used for the flat module out (no extension)
|
||||
"""
|
||||
if hasattr(ctx.attr, "flat_module_out_file") and ctx.attr.flat_module_out_file:
|
||||
return ctx.attr.flat_module_out_file
|
||||
return "%s_public_index" % ctx.label.name
|
||||
Returns:
|
||||
a basename used for the flat module out (no extension)
|
||||
"""
|
||||
if hasattr(ctx.attr, "flat_module_out_file") and ctx.attr.flat_module_out_file:
|
||||
return ctx.attr.flat_module_out_file
|
||||
return "%s_public_index" % ctx.label.name
|
||||
|
||||
def _should_produce_flat_module_outs(ctx):
|
||||
"""Should we produce flat module outputs.
|
||||
"""Should we produce flat module outputs.
|
||||
|
||||
We only produce flat module outs when we expect the ng_module is meant to be published,
|
||||
based on the presence of the module_name attribute.
|
||||
We only produce flat module outs when we expect the ng_module is meant to be published,
|
||||
based on the presence of the module_name attribute.
|
||||
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
|
||||
Returns:
|
||||
true iff we should run the bundle_index_host to produce flat module metadata and bundle index
|
||||
"""
|
||||
return _is_bazel() and ctx.attr.module_name
|
||||
Returns:
|
||||
true iff we should run the bundle_index_host to produce flat module metadata and bundle index
|
||||
"""
|
||||
return _is_bazel() and ctx.attr.module_name
|
||||
|
||||
# Calculate the expected output of the template compiler for every source in
|
||||
# in the library. Most of these will be produced as empty files but it is
|
||||
# unknown, without parsing, which will be empty.
|
||||
def _expected_outs(ctx):
|
||||
include_ng_files = _include_ng_files(ctx)
|
||||
include_ng_files = _include_ng_files(ctx)
|
||||
|
||||
devmode_js_files = []
|
||||
closure_js_files = []
|
||||
declaration_files = []
|
||||
summary_files = []
|
||||
metadata_files = []
|
||||
devmode_js_files = []
|
||||
closure_js_files = []
|
||||
declaration_files = []
|
||||
summary_files = []
|
||||
metadata_files = []
|
||||
|
||||
factory_basename_set = depset([_basename_of(ctx, src) for src in ctx.files.factories])
|
||||
factory_basename_set = depset([_basename_of(ctx, src) for src in ctx.files.factories])
|
||||
|
||||
for src in ctx.files.srcs + ctx.files.assets:
|
||||
package_prefix = ctx.label.package + "/" if ctx.label.package else ""
|
||||
for src in ctx.files.srcs + ctx.files.assets:
|
||||
package_prefix = ctx.label.package + "/" if ctx.label.package else ""
|
||||
|
||||
# Strip external repository name from path if src is from external repository
|
||||
# If src is from external repository, it's short_path will be ../<external_repo_name>/...
|
||||
short_path = src.short_path if src.short_path[0:2] != ".." else "/".join(src.short_path.split("/")[2:])
|
||||
# Strip external repository name from path if src is from external repository
|
||||
# If src is from external repository, it's short_path will be ../<external_repo_name>/...
|
||||
short_path = src.short_path if src.short_path[0:2] != ".." else "/".join(src.short_path.split("/")[2:])
|
||||
|
||||
if short_path.endswith(".ts") and not short_path.endswith(".d.ts"):
|
||||
basename = short_path[len(package_prefix):-len(".ts")]
|
||||
if include_ng_files and (len(factory_basename_set) == 0 or basename in factory_basename_set):
|
||||
devmode_js = [
|
||||
".ngfactory.js",
|
||||
".ngsummary.js",
|
||||
".js",
|
||||
]
|
||||
summaries = [".ngsummary.json"]
|
||||
metadata = [".metadata.json"]
|
||||
else:
|
||||
devmode_js = [".js"]
|
||||
summaries = []
|
||||
metadata = []
|
||||
elif include_ng_files and short_path.endswith(".css"):
|
||||
basename = short_path[len(package_prefix):-len(".css")]
|
||||
devmode_js = [
|
||||
".css.shim.ngstyle.js",
|
||||
".css.ngstyle.js",
|
||||
]
|
||||
summaries = []
|
||||
metadata = []
|
||||
if short_path.endswith(".ts") and not short_path.endswith(".d.ts"):
|
||||
basename = short_path[len(package_prefix):-len(".ts")]
|
||||
if include_ng_files and (len(factory_basename_set) == 0 or basename in factory_basename_set):
|
||||
devmode_js = [
|
||||
".ngfactory.js",
|
||||
".ngsummary.js",
|
||||
".js",
|
||||
]
|
||||
summaries = [".ngsummary.json"]
|
||||
metadata = [".metadata.json"]
|
||||
else:
|
||||
devmode_js = [".js"]
|
||||
if not _is_bazel():
|
||||
devmode_js += [".ngfactory.js"]
|
||||
summaries = []
|
||||
metadata = []
|
||||
elif include_ng_files and short_path.endswith(".css"):
|
||||
basename = short_path[len(package_prefix):-len(".css")]
|
||||
devmode_js = [
|
||||
".css.shim.ngstyle.js",
|
||||
".css.ngstyle.js",
|
||||
]
|
||||
summaries = []
|
||||
metadata = []
|
||||
else:
|
||||
continue
|
||||
|
||||
filter_summaries = ctx.attr.filter_summaries
|
||||
closure_js = [f.replace(".js", ".closure.js") for f in devmode_js if not filter_summaries or not f.endswith(".ngsummary.js")]
|
||||
declarations = [f.replace(".js", ".d.ts") for f in devmode_js]
|
||||
|
||||
devmode_js_files += [ctx.actions.declare_file(basename + ext) for ext in devmode_js]
|
||||
closure_js_files += [ctx.actions.declare_file(basename + ext) for ext in closure_js]
|
||||
declaration_files += [ctx.actions.declare_file(basename + ext) for ext in declarations]
|
||||
summary_files += [ctx.actions.declare_file(basename + ext) for ext in summaries]
|
||||
if not _is_bazel():
|
||||
metadata_files += [ctx.actions.declare_file(basename + ext) for ext in metadata]
|
||||
|
||||
# We do this just when producing a flat module index for a publishable ng_module
|
||||
if include_ng_files and _should_produce_flat_module_outs(ctx):
|
||||
flat_module_out = _flat_module_out_file(ctx)
|
||||
devmode_js_files.append(ctx.actions.declare_file("%s.js" % flat_module_out))
|
||||
closure_js_files.append(ctx.actions.declare_file("%s.closure.js" % flat_module_out))
|
||||
bundle_index_typings = ctx.actions.declare_file("%s.d.ts" % flat_module_out)
|
||||
declaration_files.append(bundle_index_typings)
|
||||
metadata_files.append(ctx.actions.declare_file("%s.metadata.json" % flat_module_out))
|
||||
else:
|
||||
continue
|
||||
bundle_index_typings = None
|
||||
|
||||
filter_summaries = ctx.attr.filter_summaries
|
||||
closure_js = [f.replace(".js", ".closure.js") for f in devmode_js if not filter_summaries or not f.endswith(".ngsummary.js")]
|
||||
declarations = [f.replace(".js", ".d.ts") for f in devmode_js]
|
||||
# TODO(alxhub): i18n is only produced by the legacy compiler currently. This should be re-enabled
|
||||
# when ngtsc can extract messages
|
||||
if include_ng_files:
|
||||
i18n_messages_files = [ctx.new_file(ctx.genfiles_dir, ctx.label.name + "_ngc_messages.xmb")]
|
||||
else:
|
||||
i18n_messages_files = []
|
||||
|
||||
devmode_js_files += [ctx.actions.declare_file(basename + ext) for ext in devmode_js]
|
||||
closure_js_files += [ctx.actions.declare_file(basename + ext) for ext in closure_js]
|
||||
declaration_files += [ctx.actions.declare_file(basename + ext) for ext in declarations]
|
||||
summary_files += [ctx.actions.declare_file(basename + ext) for ext in summaries]
|
||||
if not _is_bazel():
|
||||
metadata_files += [ctx.actions.declare_file(basename + ext) for ext in metadata]
|
||||
|
||||
# We do this just when producing a flat module index for a publishable ng_module
|
||||
if include_ng_files and _should_produce_flat_module_outs(ctx):
|
||||
flat_module_out = _flat_module_out_file(ctx)
|
||||
devmode_js_files.append(ctx.actions.declare_file("%s.js" % flat_module_out))
|
||||
closure_js_files.append(ctx.actions.declare_file("%s.closure.js" % flat_module_out))
|
||||
bundle_index_typings = ctx.actions.declare_file("%s.d.ts" % flat_module_out)
|
||||
declaration_files.append(bundle_index_typings)
|
||||
metadata_files.append(ctx.actions.declare_file("%s.metadata.json" % flat_module_out))
|
||||
else:
|
||||
bundle_index_typings = None
|
||||
|
||||
# TODO(alxhub): i18n is only produced by the legacy compiler currently. This should be re-enabled
|
||||
# when ngtsc can extract messages
|
||||
if include_ng_files:
|
||||
i18n_messages_files = [ctx.new_file(ctx.genfiles_dir, ctx.label.name + "_ngc_messages.xmb")]
|
||||
else:
|
||||
i18n_messages_files = []
|
||||
|
||||
return struct(
|
||||
closure_js = closure_js_files,
|
||||
devmode_js = devmode_js_files,
|
||||
declarations = declaration_files,
|
||||
summaries = summary_files,
|
||||
metadata = metadata_files,
|
||||
bundle_index_typings = bundle_index_typings,
|
||||
i18n_messages = i18n_messages_files,
|
||||
)
|
||||
return struct(
|
||||
closure_js = closure_js_files,
|
||||
devmode_js = devmode_js_files,
|
||||
declarations = declaration_files,
|
||||
summaries = summary_files,
|
||||
metadata = metadata_files,
|
||||
bundle_index_typings = bundle_index_typings,
|
||||
i18n_messages = i18n_messages_files,
|
||||
)
|
||||
|
||||
def _ngc_tsconfig(ctx, files, srcs, **kwargs):
|
||||
outs = _expected_outs(ctx)
|
||||
include_ng_files = _include_ng_files(ctx)
|
||||
if "devmode_manifest" in kwargs:
|
||||
expected_outs = outs.devmode_js + outs.declarations + outs.summaries + outs.metadata
|
||||
else:
|
||||
expected_outs = outs.closure_js
|
||||
outs = _expected_outs(ctx)
|
||||
include_ng_files = _include_ng_files(ctx)
|
||||
if "devmode_manifest" in kwargs:
|
||||
expected_outs = outs.devmode_js + outs.declarations + outs.summaries + outs.metadata
|
||||
else:
|
||||
expected_outs = outs.closure_js
|
||||
|
||||
angular_compiler_options = {
|
||||
"enableResourceInlining": ctx.attr.inline_resources,
|
||||
"generateCodeForLibraries": False,
|
||||
"allowEmptyCodegenFiles": True,
|
||||
# Summaries are only enabled if Angular outputs are to be produced.
|
||||
"enableSummariesForJit": include_ng_files,
|
||||
"enableIvy": _enable_ivy_value(ctx),
|
||||
"fullTemplateTypeCheck": ctx.attr.type_check,
|
||||
# FIXME: wrong place to de-dupe
|
||||
"expectedOut": depset([o.path for o in expected_outs]).to_list()
|
||||
}
|
||||
angular_compiler_options = {
|
||||
"enableResourceInlining": ctx.attr.inline_resources,
|
||||
"generateCodeForLibraries": False,
|
||||
"allowEmptyCodegenFiles": True,
|
||||
# Summaries are only enabled if Angular outputs are to be produced.
|
||||
"enableSummariesForJit": include_ng_files,
|
||||
"enableIvy": _enable_ivy_value(ctx),
|
||||
"fullTemplateTypeCheck": ctx.attr.type_check,
|
||||
# FIXME: wrong place to de-dupe
|
||||
"expectedOut": depset([o.path for o in expected_outs]).to_list(),
|
||||
}
|
||||
|
||||
if _should_produce_flat_module_outs(ctx):
|
||||
angular_compiler_options["flatModuleId"] = ctx.attr.module_name
|
||||
angular_compiler_options["flatModuleOutFile"] = _flat_module_out_file(ctx)
|
||||
angular_compiler_options["flatModulePrivateSymbolPrefix"] = "_".join(
|
||||
[ctx.workspace_name] + ctx.label.package.split("/") + [ctx.label.name, ""])
|
||||
if _should_produce_flat_module_outs(ctx):
|
||||
angular_compiler_options["flatModuleId"] = ctx.attr.module_name
|
||||
angular_compiler_options["flatModuleOutFile"] = _flat_module_out_file(ctx)
|
||||
angular_compiler_options["flatModulePrivateSymbolPrefix"] = "_".join(
|
||||
[ctx.workspace_name] + ctx.label.package.split("/") + [ctx.label.name, ""],
|
||||
)
|
||||
|
||||
return dict(tsc_wrapped_tsconfig(ctx, files, srcs, **kwargs), **{
|
||||
"angularCompilerOptions": angular_compiler_options
|
||||
})
|
||||
return dict(tsc_wrapped_tsconfig(ctx, files, srcs, **kwargs), **{
|
||||
"angularCompilerOptions": angular_compiler_options,
|
||||
})
|
||||
|
||||
def _collect_summaries_aspect_impl(target, ctx):
|
||||
results = depset(target.angular.summaries if hasattr(target, "angular") else [])
|
||||
results = depset(target.angular.summaries if hasattr(target, "angular") else [])
|
||||
|
||||
# If we are visiting empty-srcs ts_library, this is a re-export
|
||||
srcs = ctx.rule.attr.srcs if hasattr(ctx.rule.attr, "srcs") else []
|
||||
# If we are visiting empty-srcs ts_library, this is a re-export
|
||||
srcs = ctx.rule.attr.srcs if hasattr(ctx.rule.attr, "srcs") else []
|
||||
|
||||
# "re-export" rules should expose all the files of their deps
|
||||
if not srcs and hasattr(ctx.rule.attr, "deps"):
|
||||
for dep in ctx.rule.attr.deps:
|
||||
if (hasattr(dep, "angular")):
|
||||
results = depset(dep.angular.summaries, transitive = [results])
|
||||
# "re-export" rules should expose all the files of their deps
|
||||
if not srcs and hasattr(ctx.rule.attr, "deps"):
|
||||
for dep in ctx.rule.attr.deps:
|
||||
if (hasattr(dep, "angular")):
|
||||
results = depset(dep.angular.summaries, transitive = [results])
|
||||
|
||||
return struct(collect_summaries_aspect_result = results)
|
||||
return struct(collect_summaries_aspect_result = results)
|
||||
|
||||
_collect_summaries_aspect = aspect(
|
||||
implementation = _collect_summaries_aspect_impl,
|
||||
@ -278,213 +282,233 @@ _collect_summaries_aspect = aspect(
|
||||
# Extra options passed to Node when running ngc.
|
||||
_EXTRA_NODE_OPTIONS_FLAGS = [
|
||||
# Expose the v8 garbage collection API to JS.
|
||||
"--node_options=--expose-gc"
|
||||
"--node_options=--expose-gc",
|
||||
]
|
||||
|
||||
def ngc_compile_action(ctx, label, inputs, outputs, messages_out, tsconfig_file,
|
||||
node_opts, locale=None, i18n_args=[]):
|
||||
"""Helper function to create the ngc action.
|
||||
def ngc_compile_action(
|
||||
ctx,
|
||||
label,
|
||||
inputs,
|
||||
outputs,
|
||||
messages_out,
|
||||
tsconfig_file,
|
||||
node_opts,
|
||||
locale = None,
|
||||
i18n_args = []):
|
||||
"""Helper function to create the ngc action.
|
||||
|
||||
This is exposed for google3 to wire up i18n replay rules, and is not intended
|
||||
as part of the public API.
|
||||
This is exposed for google3 to wire up i18n replay rules, and is not intended
|
||||
as part of the public API.
|
||||
|
||||
Args:
|
||||
ctx: skylark context
|
||||
label: the label of the ng_module being compiled
|
||||
inputs: passed to the ngc action's inputs
|
||||
outputs: passed to the ngc action's outputs
|
||||
messages_out: produced xmb files
|
||||
tsconfig_file: tsconfig file with settings used for the compilation
|
||||
node_opts: list of strings, extra nodejs options.
|
||||
locale: i18n locale, or None
|
||||
i18n_args: additional command-line arguments to ngc
|
||||
Args:
|
||||
ctx: skylark context
|
||||
label: the label of the ng_module being compiled
|
||||
inputs: passed to the ngc action's inputs
|
||||
outputs: passed to the ngc action's outputs
|
||||
messages_out: produced xmb files
|
||||
tsconfig_file: tsconfig file with settings used for the compilation
|
||||
node_opts: list of strings, extra nodejs options.
|
||||
locale: i18n locale, or None
|
||||
i18n_args: additional command-line arguments to ngc
|
||||
|
||||
Returns:
|
||||
the parameters of the compilation which will be used to replay the ngc action for i18N.
|
||||
"""
|
||||
Returns:
|
||||
the parameters of the compilation which will be used to replay the ngc action for i18N.
|
||||
"""
|
||||
|
||||
include_ng_files = _include_ng_files(ctx)
|
||||
include_ng_files = _include_ng_files(ctx)
|
||||
|
||||
mnemonic = "AngularTemplateCompile"
|
||||
progress_message = "Compiling Angular templates (%s) %s" % (_compiler_name(ctx), label)
|
||||
mnemonic = "AngularTemplateCompile"
|
||||
progress_message = "Compiling Angular templates (%s) %s" % (_compiler_name(ctx), label)
|
||||
|
||||
if locale:
|
||||
mnemonic = "AngularI18NMerging"
|
||||
supports_workers = "0"
|
||||
progress_message = ("Recompiling Angular templates (ngc) %s for locale %s" %
|
||||
(label, locale))
|
||||
else:
|
||||
supports_workers = str(int(ctx.attr._supports_workers))
|
||||
if locale:
|
||||
mnemonic = "AngularI18NMerging"
|
||||
supports_workers = "0"
|
||||
progress_message = ("Recompiling Angular templates (ngc) %s for locale %s" %
|
||||
(label, locale))
|
||||
else:
|
||||
supports_workers = str(int(ctx.attr._supports_workers))
|
||||
|
||||
arguments = (list(_EXTRA_NODE_OPTIONS_FLAGS) +
|
||||
["--node_options=%s" % opt for opt in node_opts])
|
||||
# One at-sign makes this a params-file, enabling the worker strategy.
|
||||
# Two at-signs escapes the argument so it's passed through to ngc
|
||||
# rather than the contents getting expanded.
|
||||
if supports_workers == "1":
|
||||
arguments += ["@@" + tsconfig_file.path]
|
||||
else:
|
||||
arguments += ["-p", tsconfig_file.path]
|
||||
arguments = (list(_EXTRA_NODE_OPTIONS_FLAGS) +
|
||||
["--node_options=%s" % opt for opt in node_opts])
|
||||
|
||||
arguments += i18n_args
|
||||
# One at-sign makes this a params-file, enabling the worker strategy.
|
||||
# Two at-signs escapes the argument so it's passed through to ngc
|
||||
# rather than the contents getting expanded.
|
||||
if supports_workers == "1":
|
||||
arguments += ["@@" + tsconfig_file.path]
|
||||
else:
|
||||
arguments += ["-p", tsconfig_file.path]
|
||||
|
||||
ctx.actions.run(
|
||||
progress_message = progress_message,
|
||||
mnemonic = mnemonic,
|
||||
inputs = inputs,
|
||||
outputs = outputs,
|
||||
arguments = arguments,
|
||||
executable = ctx.executable.compiler,
|
||||
execution_requirements = {
|
||||
"supports-workers": supports_workers,
|
||||
},
|
||||
)
|
||||
arguments += i18n_args
|
||||
|
||||
if include_ng_files and messages_out != None:
|
||||
ctx.actions.run(
|
||||
inputs = list(inputs),
|
||||
outputs = messages_out,
|
||||
executable = ctx.executable._ng_xi18n,
|
||||
arguments = (_EXTRA_NODE_OPTIONS_FLAGS +
|
||||
[tsconfig_file.path] +
|
||||
# The base path is bin_dir because of the way the ngc
|
||||
# compiler host is configured. So we need to explicitly
|
||||
# point to genfiles/ to redirect the output.
|
||||
["../genfiles/" + messages_out[0].short_path]),
|
||||
progress_message = "Extracting Angular 2 messages (ng_xi18n)",
|
||||
mnemonic = "Angular2MessageExtractor")
|
||||
|
||||
if not locale and not ctx.attr.no_i18n:
|
||||
return struct(
|
||||
label = label,
|
||||
tsconfig = tsconfig_file,
|
||||
progress_message = progress_message,
|
||||
mnemonic = mnemonic,
|
||||
inputs = inputs,
|
||||
outputs = outputs,
|
||||
compiler = ctx.executable.compiler,
|
||||
arguments = arguments,
|
||||
executable = ctx.executable.compiler,
|
||||
execution_requirements = {
|
||||
"supports-workers": supports_workers,
|
||||
},
|
||||
)
|
||||
|
||||
return None
|
||||
if include_ng_files and messages_out != None:
|
||||
ctx.actions.run(
|
||||
inputs = list(inputs),
|
||||
outputs = messages_out,
|
||||
executable = ctx.executable._ng_xi18n,
|
||||
arguments = (_EXTRA_NODE_OPTIONS_FLAGS +
|
||||
[tsconfig_file.path] +
|
||||
# The base path is bin_dir because of the way the ngc
|
||||
# compiler host is configured. So we need to explicitly
|
||||
# point to genfiles/ to redirect the output.
|
||||
["../genfiles/" + messages_out[0].short_path]),
|
||||
progress_message = "Extracting Angular 2 messages (ng_xi18n)",
|
||||
mnemonic = "Angular2MessageExtractor",
|
||||
)
|
||||
|
||||
if not locale and not ctx.attr.no_i18n:
|
||||
return struct(
|
||||
label = label,
|
||||
tsconfig = tsconfig_file,
|
||||
inputs = inputs,
|
||||
outputs = outputs,
|
||||
compiler = ctx.executable.compiler,
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
def _compile_action(ctx, inputs, outputs, messages_out, tsconfig_file, node_opts):
|
||||
# Give the Angular compiler all the user-listed assets
|
||||
file_inputs = list(ctx.files.assets)
|
||||
# Give the Angular compiler all the user-listed assets
|
||||
file_inputs = list(ctx.files.assets)
|
||||
|
||||
# The compiler only needs to see TypeScript sources from the npm dependencies,
|
||||
# but may need to look at package.json and ngsummary.json files as well.
|
||||
if hasattr(ctx.attr, "node_modules"):
|
||||
file_inputs += [f for f in ctx.files.node_modules
|
||||
if f.path.endswith(".ts") or f.path.endswith(".json")]
|
||||
# The compiler only needs to see TypeScript sources from the npm dependencies,
|
||||
# but may need to look at package.json and ngsummary.json files as well.
|
||||
if hasattr(ctx.attr, "node_modules"):
|
||||
file_inputs += [
|
||||
f
|
||||
for f in ctx.files.node_modules
|
||||
if f.path.endswith(".ts") or f.path.endswith(".json")
|
||||
]
|
||||
|
||||
# If the user supplies a tsconfig.json file, the Angular compiler needs to read it
|
||||
if hasattr(ctx.attr, "tsconfig") and ctx.file.tsconfig:
|
||||
file_inputs.append(ctx.file.tsconfig)
|
||||
# If the user supplies a tsconfig.json file, the Angular compiler needs to read it
|
||||
if hasattr(ctx.attr, "tsconfig") and ctx.file.tsconfig:
|
||||
file_inputs.append(ctx.file.tsconfig)
|
||||
|
||||
# Collect the inputs and summary files from our deps
|
||||
action_inputs = depset(file_inputs,
|
||||
transitive = [inputs] + [dep.collect_summaries_aspect_result for dep in ctx.attr.deps
|
||||
if hasattr(dep, "collect_summaries_aspect_result")])
|
||||
|
||||
return ngc_compile_action(ctx, ctx.label, action_inputs, outputs, messages_out, tsconfig_file, node_opts)
|
||||
|
||||
|
||||
def _prodmode_compile_action(ctx, inputs, outputs, tsconfig_file, node_opts):
|
||||
outs = _expected_outs(ctx)
|
||||
return _compile_action(ctx, inputs, outputs + outs.closure_js, outs.i18n_messages, tsconfig_file, node_opts)
|
||||
|
||||
def _devmode_compile_action(ctx, inputs, outputs, tsconfig_file, node_opts):
|
||||
outs = _expected_outs(ctx)
|
||||
compile_action_outputs = outputs + outs.devmode_js + outs.declarations + outs.summaries + outs.metadata
|
||||
_compile_action(ctx, inputs, compile_action_outputs, None, tsconfig_file, node_opts)
|
||||
|
||||
def _ts_expected_outs(ctx, label):
|
||||
# rules_typescript expects a function with two arguments, but our
|
||||
# implementation doesn't use the label
|
||||
_ignored = [label]
|
||||
return _expected_outs(ctx)
|
||||
|
||||
def ng_module_impl(ctx, ts_compile_actions):
|
||||
"""Implementation function for the ng_module rule.
|
||||
|
||||
This is exposed so that google3 can have its own entry point that re-uses this
|
||||
and is not meant as a public API.
|
||||
|
||||
Args:
|
||||
ctx: the skylark rule context
|
||||
ts_compile_actions: generates all the actions to run an ngc compilation
|
||||
|
||||
Returns:
|
||||
the result of the ng_module rule as a dict, suitable for
|
||||
conversion by ts_providers_dict_to_struct
|
||||
"""
|
||||
|
||||
include_ng_files = _include_ng_files(ctx)
|
||||
|
||||
providers = ts_compile_actions(
|
||||
ctx, is_library=True, compile_action=_prodmode_compile_action,
|
||||
devmode_compile_action=_devmode_compile_action,
|
||||
tsc_wrapped_tsconfig=_ngc_tsconfig,
|
||||
outputs = _ts_expected_outs)
|
||||
|
||||
outs = _expected_outs(ctx)
|
||||
|
||||
if include_ng_files:
|
||||
providers["angular"] = {
|
||||
"summaries": outs.summaries,
|
||||
"metadata": outs.metadata
|
||||
}
|
||||
providers["ngc_messages"] = outs.i18n_messages
|
||||
|
||||
if include_ng_files and _should_produce_flat_module_outs(ctx):
|
||||
if len(outs.metadata) > 1:
|
||||
fail("expecting exactly one metadata output for " + str(ctx.label))
|
||||
|
||||
providers["angular"]["flat_module_metadata"] = struct(
|
||||
module_name = ctx.attr.module_name,
|
||||
metadata_file = outs.metadata[0],
|
||||
typings_file = outs.bundle_index_typings,
|
||||
flat_module_out_file = _flat_module_out_file(ctx),
|
||||
# Collect the inputs and summary files from our deps
|
||||
action_inputs = depset(
|
||||
file_inputs,
|
||||
transitive = [inputs] + [
|
||||
dep.collect_summaries_aspect_result
|
||||
for dep in ctx.attr.deps
|
||||
if hasattr(dep, "collect_summaries_aspect_result")
|
||||
],
|
||||
)
|
||||
|
||||
return providers
|
||||
return ngc_compile_action(ctx, ctx.label, action_inputs, outputs, messages_out, tsconfig_file, node_opts)
|
||||
|
||||
def _prodmode_compile_action(ctx, inputs, outputs, tsconfig_file, node_opts):
|
||||
outs = _expected_outs(ctx)
|
||||
return _compile_action(ctx, inputs, outputs + outs.closure_js, outs.i18n_messages, tsconfig_file, node_opts)
|
||||
|
||||
def _devmode_compile_action(ctx, inputs, outputs, tsconfig_file, node_opts):
|
||||
outs = _expected_outs(ctx)
|
||||
compile_action_outputs = outputs + outs.devmode_js + outs.declarations + outs.summaries + outs.metadata
|
||||
_compile_action(ctx, inputs, compile_action_outputs, None, tsconfig_file, node_opts)
|
||||
|
||||
def _ts_expected_outs(ctx, label):
|
||||
# rules_typescript expects a function with two arguments, but our
|
||||
# implementation doesn't use the label
|
||||
_ignored = [label]
|
||||
return _expected_outs(ctx)
|
||||
|
||||
def ng_module_impl(ctx, ts_compile_actions):
|
||||
"""Implementation function for the ng_module rule.
|
||||
|
||||
This is exposed so that google3 can have its own entry point that re-uses this
|
||||
and is not meant as a public API.
|
||||
|
||||
Args:
|
||||
ctx: the skylark rule context
|
||||
ts_compile_actions: generates all the actions to run an ngc compilation
|
||||
|
||||
Returns:
|
||||
the result of the ng_module rule as a dict, suitable for
|
||||
conversion by ts_providers_dict_to_struct
|
||||
"""
|
||||
|
||||
include_ng_files = _include_ng_files(ctx)
|
||||
|
||||
providers = ts_compile_actions(
|
||||
ctx,
|
||||
is_library = True,
|
||||
compile_action = _prodmode_compile_action,
|
||||
devmode_compile_action = _devmode_compile_action,
|
||||
tsc_wrapped_tsconfig = _ngc_tsconfig,
|
||||
outputs = _ts_expected_outs,
|
||||
)
|
||||
|
||||
outs = _expected_outs(ctx)
|
||||
|
||||
if include_ng_files:
|
||||
providers["angular"] = {
|
||||
"summaries": outs.summaries,
|
||||
"metadata": outs.metadata,
|
||||
}
|
||||
providers["ngc_messages"] = outs.i18n_messages
|
||||
|
||||
if include_ng_files and _should_produce_flat_module_outs(ctx):
|
||||
if len(outs.metadata) > 1:
|
||||
fail("expecting exactly one metadata output for " + str(ctx.label))
|
||||
|
||||
providers["angular"]["flat_module_metadata"] = struct(
|
||||
module_name = ctx.attr.module_name,
|
||||
metadata_file = outs.metadata[0],
|
||||
typings_file = outs.bundle_index_typings,
|
||||
flat_module_out_file = _flat_module_out_file(ctx),
|
||||
)
|
||||
|
||||
return providers
|
||||
|
||||
def _ng_module_impl(ctx):
|
||||
return ts_providers_dict_to_struct(ng_module_impl(ctx, compile_ts))
|
||||
return ts_providers_dict_to_struct(ng_module_impl(ctx, compile_ts))
|
||||
|
||||
NG_MODULE_ATTRIBUTES = {
|
||||
"srcs": attr.label_list(allow_files = [".ts"]),
|
||||
|
||||
"deps": attr.label_list(aspects = DEPS_ASPECTS + [_collect_summaries_aspect]),
|
||||
|
||||
"assets": attr.label_list(allow_files = [
|
||||
".css",
|
||||
# TODO(alexeagle): change this to ".ng.html" when usages updated
|
||||
".html",
|
||||
]),
|
||||
|
||||
# Note: DEPS_ASPECTS is already a list, we add the cast to workaround
|
||||
# https://github.com/bazelbuild/skydoc/issues/21
|
||||
"deps": attr.label_list(
|
||||
doc = "Targets that are imported by this target",
|
||||
aspects = list(DEPS_ASPECTS) + [_collect_summaries_aspect],
|
||||
),
|
||||
"assets": attr.label_list(
|
||||
doc = ".html and .css files needed by the Angular compiler",
|
||||
allow_files = [
|
||||
".css",
|
||||
# TODO(alexeagle): change this to ".ng.html" when usages updated
|
||||
".html",
|
||||
],
|
||||
),
|
||||
"factories": attr.label_list(
|
||||
allow_files = [".ts", ".html"],
|
||||
mandatory = False),
|
||||
|
||||
mandatory = False,
|
||||
),
|
||||
"filter_summaries": attr.bool(default = False),
|
||||
|
||||
"type_check": attr.bool(default = True),
|
||||
|
||||
"inline_resources": attr.bool(default = True),
|
||||
|
||||
"no_i18n": attr.bool(default = False),
|
||||
|
||||
"compiler": attr.label(
|
||||
default = Label("//packages/bazel/src/ngc-wrapped"),
|
||||
executable = True,
|
||||
cfg = "host",
|
||||
),
|
||||
|
||||
"_ng_xi18n": attr.label(
|
||||
default = Label("//packages/bazel/src/ngc-wrapped:xi18n"),
|
||||
executable = True,
|
||||
cfg = "host",
|
||||
),
|
||||
|
||||
"_supports_workers": attr.bool(default = True),
|
||||
}
|
||||
|
||||
@ -495,9 +519,8 @@ NG_MODULE_RULE_ATTRS = dict(dict(COMMON_ATTRIBUTES, **NG_MODULE_ATTRIBUTES), **{
|
||||
# The default assumes the user specified a target "node_modules" in their
|
||||
# root BUILD file.
|
||||
"node_modules": attr.label(
|
||||
default = Label("@//:node_modules")
|
||||
default = Label("@//:node_modules"),
|
||||
),
|
||||
|
||||
"entry_point": attr.string(),
|
||||
|
||||
# Default is %{name}_public_index
|
||||
@ -515,7 +538,13 @@ ng_module = rule(
|
||||
attrs = NG_MODULE_RULE_ATTRS,
|
||||
outputs = COMMON_OUTPUTS,
|
||||
)
|
||||
"""
|
||||
Run the Angular AOT template compiler.
|
||||
|
||||
This rule extends the [ts_library] rule.
|
||||
|
||||
[ts_library]: http://tsetse.info/api/build_defs.html#ts_library
|
||||
"""
|
||||
|
||||
# TODO(alxhub): this rule causes legacy ngc to produce Ivy outputs from global analysis information.
|
||||
# It exists to facilitate testing of the Ivy runtime until ngtsc is mature enough to be used
|
||||
|
@ -22,5 +22,6 @@ nodejs_binary(
|
||||
name = "packager",
|
||||
data = ["lib"],
|
||||
entry_point = "angular/packages/bazel/src/ng_package/packager.js",
|
||||
install_source_map_support = False,
|
||||
node_modules = "@angular_packager_deps//:node_modules",
|
||||
)
|
||||
|
@ -2,45 +2,58 @@
|
||||
#
|
||||
# Use of this source code is governed by an MIT-style license that can be
|
||||
# found in the LICENSE file at https://angular.io/license
|
||||
"""Implementation of the ng_package rule.
|
||||
"""Package Angular libraries for npm distribution
|
||||
|
||||
If all users of an Angular library use Bazel (e.g. internal usage in your company)
|
||||
then you should simply add your library to the `deps` of the consuming application.
|
||||
|
||||
These rules exist for compatibility with non-Bazel consumers of your library.
|
||||
|
||||
It packages your library following the Angular Package Format, see the
|
||||
specification of this format at https://goo.gl/jB3GVv
|
||||
"""
|
||||
|
||||
load("@build_bazel_rules_nodejs//:internal/collect_es6_sources.bzl", "collect_es6_sources")
|
||||
load("@build_bazel_rules_nodejs//:internal/rollup/rollup_bundle.bzl",
|
||||
"write_rollup_config",
|
||||
"rollup_module_mappings_aspect",
|
||||
"run_uglify",
|
||||
"ROLLUP_ATTRS")
|
||||
load("@build_bazel_rules_nodejs//:internal/npm_package/npm_package.bzl",
|
||||
"NPM_PACKAGE_ATTRS",
|
||||
"NPM_PACKAGE_OUTPUTS",
|
||||
"create_package")
|
||||
load(
|
||||
"@build_bazel_rules_nodejs//:internal/rollup/rollup_bundle.bzl",
|
||||
"ROLLUP_ATTRS",
|
||||
"rollup_module_mappings_aspect",
|
||||
"run_uglify",
|
||||
"write_rollup_config",
|
||||
)
|
||||
load(
|
||||
"@build_bazel_rules_nodejs//:internal/npm_package/npm_package.bzl",
|
||||
"NPM_PACKAGE_ATTRS",
|
||||
"NPM_PACKAGE_OUTPUTS",
|
||||
"create_package",
|
||||
)
|
||||
load("@build_bazel_rules_nodejs//:internal/node.bzl", "sources_aspect")
|
||||
load("//packages/bazel/src:esm5.bzl", "esm5_outputs_aspect", "flatten_esm5", "esm5_root_dir")
|
||||
load("//packages/bazel/src:esm5.bzl", "esm5_outputs_aspect", "esm5_root_dir", "flatten_esm5")
|
||||
|
||||
# Convert from some-dash-case to someCamelCase
|
||||
def _convert_dash_case_to_camel_case(s):
|
||||
parts = s.split("-")
|
||||
# First letter in the result is always unchanged
|
||||
return s[0] + "".join([p.title() for p in parts])[1:]
|
||||
parts = s.split("-")
|
||||
|
||||
# First letter in the result is always unchanged
|
||||
return s[0] + "".join([p.title() for p in parts])[1:]
|
||||
|
||||
# Convert from a package name on npm to an identifier that's a legal global symbol
|
||||
# @angular/core -> ng.core
|
||||
# @angular/platform-browser-dynamic/testing -> ng.platformBrowserDynamic.testing
|
||||
def _global_name(package_name):
|
||||
# strip npm scoped package qualifier
|
||||
start = 1 if package_name.startswith("@") else 0
|
||||
parts = package_name[start:].split("/")
|
||||
result_parts = []
|
||||
for p in parts:
|
||||
# Special case for angular's short name
|
||||
if p == "angular":
|
||||
result_parts.append("ng")
|
||||
else:
|
||||
result_parts.append(_convert_dash_case_to_camel_case(p))
|
||||
return ".".join(result_parts)
|
||||
# strip npm scoped package qualifier
|
||||
start = 1 if package_name.startswith("@") else 0
|
||||
parts = package_name[start:].split("/")
|
||||
result_parts = []
|
||||
for p in parts:
|
||||
# Special case for angular's short name
|
||||
if p == "angular":
|
||||
result_parts.append("ng")
|
||||
else:
|
||||
result_parts.append(_convert_dash_case_to_camel_case(p))
|
||||
return ".".join(result_parts)
|
||||
|
||||
WELL_KNOWN_GLOBALS = { p: _global_name(p) for p in [
|
||||
WELL_KNOWN_GLOBALS = {p: _global_name(p) for p in [
|
||||
"@angular/upgrade",
|
||||
"@angular/upgrade/static",
|
||||
"@angular/forms",
|
||||
@ -77,242 +90,259 @@ WELL_KNOWN_GLOBALS = { p: _global_name(p) for p in [
|
||||
]}
|
||||
|
||||
def _rollup(ctx, bundle_name, rollup_config, entry_point, inputs, js_output, format = "es", package_name = "", include_tslib = False):
|
||||
map_output = ctx.actions.declare_file(js_output.basename + ".map", sibling = js_output)
|
||||
map_output = ctx.actions.declare_file(js_output.basename + ".map", sibling = js_output)
|
||||
|
||||
args = ctx.actions.args()
|
||||
args.add("--config", rollup_config)
|
||||
args = ctx.actions.args()
|
||||
args.add("--config", rollup_config)
|
||||
|
||||
args.add("--input", entry_point)
|
||||
args.add("--output.file", js_output)
|
||||
args.add("--output.format", format)
|
||||
if package_name:
|
||||
args.add("--output.name", _global_name(package_name))
|
||||
args.add("--amd.id", package_name)
|
||||
args.add("--input", entry_point)
|
||||
args.add("--output.file", js_output)
|
||||
args.add("--output.format", format)
|
||||
if package_name:
|
||||
args.add("--output.name", _global_name(package_name))
|
||||
args.add("--amd.id", package_name)
|
||||
|
||||
# Note: if the input has external source maps then we need to also install and use
|
||||
# `rollup-plugin-sourcemaps`, which will require us to use rollup.config.js file instead
|
||||
# of command line args
|
||||
args.add("--sourcemap")
|
||||
# Note: if the input has external source maps then we need to also install and use
|
||||
# `rollup-plugin-sourcemaps`, which will require us to use rollup.config.js file instead
|
||||
# of command line args
|
||||
args.add("--sourcemap")
|
||||
|
||||
globals = dict(WELL_KNOWN_GLOBALS, **ctx.attr.globals)
|
||||
external = globals.keys()
|
||||
if not include_tslib:
|
||||
external.append("tslib")
|
||||
args.add_joined("--external", external, join_with=",")
|
||||
globals = dict(WELL_KNOWN_GLOBALS, **ctx.attr.globals)
|
||||
external = globals.keys()
|
||||
if not include_tslib:
|
||||
external.append("tslib")
|
||||
args.add_joined("--external", external, join_with = ",")
|
||||
|
||||
args.add_joined(
|
||||
"--globals",
|
||||
["%s:%s" % g for g in globals.items()],
|
||||
join_with=",")
|
||||
args.add_joined(
|
||||
"--globals",
|
||||
["%s:%s" % g for g in globals.items()],
|
||||
join_with = ",",
|
||||
)
|
||||
|
||||
args.add("--silent")
|
||||
args.add("--silent")
|
||||
|
||||
other_inputs = [ctx.executable._rollup, rollup_config]
|
||||
if ctx.file.license_banner:
|
||||
other_inputs.append(ctx.file.license_banner)
|
||||
if ctx.version_file:
|
||||
other_inputs.append(ctx.version_file)
|
||||
ctx.actions.run(
|
||||
progress_message = "ng_package: Rollup %s %s" % (bundle_name, ctx.label),
|
||||
mnemonic = "AngularPackageRollup",
|
||||
inputs = inputs.to_list() + other_inputs,
|
||||
outputs = [js_output, map_output],
|
||||
executable = ctx.executable._rollup,
|
||||
arguments = [args],
|
||||
)
|
||||
return struct(
|
||||
js = js_output,
|
||||
map = map_output,
|
||||
)
|
||||
other_inputs = [ctx.executable._rollup, rollup_config]
|
||||
if ctx.file.license_banner:
|
||||
other_inputs.append(ctx.file.license_banner)
|
||||
if ctx.version_file:
|
||||
other_inputs.append(ctx.version_file)
|
||||
ctx.actions.run(
|
||||
progress_message = "ng_package: Rollup %s %s" % (bundle_name, ctx.label),
|
||||
mnemonic = "AngularPackageRollup",
|
||||
inputs = inputs.to_list() + other_inputs,
|
||||
outputs = [js_output, map_output],
|
||||
executable = ctx.executable._rollup,
|
||||
arguments = [args],
|
||||
)
|
||||
return struct(
|
||||
js = js_output,
|
||||
map = map_output,
|
||||
)
|
||||
|
||||
# convert from [{js: js_file1, map: map_file1}, ...] to
|
||||
# [js_filepath1, map_filepath1, ...]
|
||||
def _flatten_paths(directory):
|
||||
result = []
|
||||
for f in directory:
|
||||
result.append(f.js.path)
|
||||
if f.map:
|
||||
result.append(f.map.path)
|
||||
return result
|
||||
|
||||
result = []
|
||||
for f in directory:
|
||||
result.append(f.js.path)
|
||||
if f.map:
|
||||
result.append(f.map.path)
|
||||
return result
|
||||
|
||||
# takes an depset of files and returns an array that doesn't contain any generated files by ngc
|
||||
def _filter_out_generated_files(files):
|
||||
result = []
|
||||
for file in files:
|
||||
if (not(file.path.endswith(".ngfactory.js") or file.path.endswith(".ngsummary.js") or file.path.endswith(".ngstyle.js"))):
|
||||
result.append(file)
|
||||
return depset(result)
|
||||
|
||||
result = []
|
||||
for file in files:
|
||||
if (not (file.path.endswith(".ngfactory.js") or file.path.endswith(".ngsummary.js") or file.path.endswith(".ngstyle.js"))):
|
||||
result.append(file)
|
||||
return depset(result)
|
||||
|
||||
def _esm2015_root_dir(ctx):
|
||||
return ctx.label.name + ".es6"
|
||||
|
||||
return ctx.label.name + ".es6"
|
||||
|
||||
# ng_package produces package that is npm-ready.
|
||||
def _ng_package_impl(ctx):
|
||||
npm_package_directory = ctx.actions.declare_directory("%s.ng_pkg" % ctx.label.name)
|
||||
npm_package_directory = ctx.actions.declare_directory("%s.ng_pkg" % ctx.label.name)
|
||||
|
||||
esm_2015_files = _filter_out_generated_files(collect_es6_sources(ctx))
|
||||
esm5_sources = _filter_out_generated_files(flatten_esm5(ctx))
|
||||
esm_2015_files = _filter_out_generated_files(collect_es6_sources(ctx))
|
||||
esm5_sources = _filter_out_generated_files(flatten_esm5(ctx))
|
||||
|
||||
# These accumulators match the directory names where the files live in the
|
||||
# Angular package format.
|
||||
fesm2015 = []
|
||||
fesm5 = []
|
||||
esm2015 = []
|
||||
esm5 = []
|
||||
bundles = []
|
||||
# These accumulators match the directory names where the files live in the
|
||||
# Angular package format.
|
||||
fesm2015 = []
|
||||
fesm5 = []
|
||||
esm2015 = []
|
||||
esm5 = []
|
||||
bundles = []
|
||||
|
||||
# For Angular Package Format v6, we put all the individual .js files in the
|
||||
# esm5/ and esm2015/ folders.
|
||||
for f in esm5_sources.to_list():
|
||||
if f.path.endswith(".js"):
|
||||
esm5.append(struct(js = f, map = None))
|
||||
for f in esm_2015_files.to_list():
|
||||
if f.path.endswith(".js"):
|
||||
esm2015.append(struct(js = f, map = None))
|
||||
# For Angular Package Format v6, we put all the individual .js files in the
|
||||
# esm5/ and esm2015/ folders.
|
||||
for f in esm5_sources.to_list():
|
||||
if f.path.endswith(".js"):
|
||||
esm5.append(struct(js = f, map = None))
|
||||
for f in esm_2015_files.to_list():
|
||||
if f.path.endswith(".js"):
|
||||
esm2015.append(struct(js = f, map = None))
|
||||
|
||||
# We infer the entry points to be:
|
||||
# - ng_module rules in the deps (they have an "angular" provider)
|
||||
# - in this package or a subpackage
|
||||
# - those that have a module_name attribute (they produce flat module metadata)
|
||||
flat_module_metadata = []
|
||||
# Name given in the package.json name field, eg. @angular/core/testing
|
||||
package_name = ""
|
||||
deps_in_package = [d for d in ctx.attr.deps if d.label.package.startswith(ctx.label.package)]
|
||||
for dep in deps_in_package:
|
||||
# Intentionally evaluates to empty string for the main entry point
|
||||
entry_point = dep.label.package[len(ctx.label.package) + 1:]
|
||||
if hasattr(dep, "module_name"):
|
||||
package_name = dep.module_name
|
||||
if hasattr(dep, "angular") and hasattr(dep.angular, "flat_module_metadata"):
|
||||
flat_module_metadata.append(dep.angular.flat_module_metadata)
|
||||
flat_module_out_file = dep.angular.flat_module_metadata.flat_module_out_file + ".js"
|
||||
# We infer the entry points to be:
|
||||
# - ng_module rules in the deps (they have an "angular" provider)
|
||||
# - in this package or a subpackage
|
||||
# - those that have a module_name attribute (they produce flat module metadata)
|
||||
flat_module_metadata = []
|
||||
|
||||
# Name given in the package.json name field, eg. @angular/core/testing
|
||||
package_name = ""
|
||||
deps_in_package = [d for d in ctx.attr.deps if d.label.package.startswith(ctx.label.package)]
|
||||
for dep in deps_in_package:
|
||||
# Intentionally evaluates to empty string for the main entry point
|
||||
entry_point = dep.label.package[len(ctx.label.package) + 1:]
|
||||
if hasattr(dep, "module_name"):
|
||||
package_name = dep.module_name
|
||||
if hasattr(dep, "angular") and hasattr(dep.angular, "flat_module_metadata"):
|
||||
flat_module_metadata.append(dep.angular.flat_module_metadata)
|
||||
flat_module_out_file = dep.angular.flat_module_metadata.flat_module_out_file + ".js"
|
||||
else:
|
||||
# fallback to a reasonable default
|
||||
flat_module_out_file = "index.js"
|
||||
|
||||
es2015_entry_point = "/".join([p for p in [
|
||||
ctx.bin_dir.path,
|
||||
ctx.label.package,
|
||||
_esm2015_root_dir(ctx),
|
||||
ctx.label.package,
|
||||
entry_point,
|
||||
flat_module_out_file,
|
||||
] if p])
|
||||
|
||||
es5_entry_point = "/".join([p for p in [
|
||||
ctx.label.package,
|
||||
entry_point,
|
||||
flat_module_out_file,
|
||||
] if p])
|
||||
|
||||
if entry_point:
|
||||
# TODO jasonaden says there is no particular reason these filenames differ
|
||||
prefix = primary_entry_point_name(ctx.attr.name, ctx.attr.entry_point, ctx.attr.entry_point_name)
|
||||
umd_output_filename = "-".join([prefix] + entry_point.split("/"))
|
||||
fesm_output_filename = entry_point.replace("/", "__")
|
||||
fesm2015_output = ctx.actions.declare_file("fesm2015/%s.js" % fesm_output_filename)
|
||||
fesm5_output = ctx.actions.declare_file("%s.js" % fesm_output_filename)
|
||||
umd_output = ctx.actions.declare_file("%s.umd.js" % umd_output_filename)
|
||||
min_output = ctx.actions.declare_file("%s.umd.min.js" % umd_output_filename)
|
||||
else:
|
||||
fesm2015_output = ctx.outputs.fesm2015
|
||||
fesm5_output = ctx.outputs.fesm5
|
||||
umd_output = ctx.outputs.umd
|
||||
min_output = ctx.outputs.umd_min
|
||||
|
||||
esm2015_config = write_rollup_config(ctx, [], "/".join([ctx.bin_dir.path, ctx.label.package, _esm2015_root_dir(ctx)]), filename = "_%s.rollup_esm2015.conf.js")
|
||||
esm5_config = write_rollup_config(ctx, [], "/".join([ctx.bin_dir.path, ctx.label.package, esm5_root_dir(ctx)]), filename = "_%s.rollup_esm5.conf.js")
|
||||
|
||||
fesm2015.append(_rollup(ctx, "fesm2015", esm2015_config, es2015_entry_point, esm_2015_files + ctx.files.node_modules, fesm2015_output))
|
||||
fesm5.append(_rollup(ctx, "fesm5", esm5_config, es5_entry_point, esm5_sources + ctx.files.node_modules, fesm5_output))
|
||||
|
||||
bundles.append(
|
||||
_rollup(
|
||||
ctx,
|
||||
"umd",
|
||||
esm5_config,
|
||||
es5_entry_point,
|
||||
esm5_sources + ctx.files.node_modules,
|
||||
umd_output,
|
||||
format = "umd",
|
||||
package_name = package_name,
|
||||
include_tslib = True,
|
||||
),
|
||||
)
|
||||
uglify_sourcemap = run_uglify(
|
||||
ctx,
|
||||
umd_output,
|
||||
min_output,
|
||||
config_name = entry_point.replace("/", "_"),
|
||||
)
|
||||
bundles.append(struct(js = min_output, map = uglify_sourcemap))
|
||||
|
||||
packager_inputs = (
|
||||
ctx.files.srcs +
|
||||
ctx.files.data +
|
||||
esm5_sources.to_list() +
|
||||
depset(transitive = [
|
||||
d.typescript.transitive_declarations
|
||||
for d in ctx.attr.deps
|
||||
if hasattr(d, "typescript")
|
||||
]).to_list() +
|
||||
[f.js for f in fesm2015 + fesm5 + esm2015 + esm5 + bundles] +
|
||||
[f.map for f in fesm2015 + fesm5 + esm2015 + esm5 + bundles if f.map]
|
||||
)
|
||||
|
||||
packager_args = ctx.actions.args()
|
||||
packager_args.use_param_file("%s", use_always = True)
|
||||
|
||||
# The order of arguments matters here, as they are read in order in packager.ts.
|
||||
packager_args.add(npm_package_directory.path)
|
||||
packager_args.add(ctx.label.package)
|
||||
packager_args.add_joined([ctx.bin_dir.path, ctx.label.package], join_with = "/")
|
||||
packager_args.add_joined([ctx.genfiles_dir.path, ctx.label.package], join_with = "/")
|
||||
|
||||
# Marshal the metadata into a JSON string so we can parse the data structure
|
||||
# in the TypeScript program easily.
|
||||
metadata_arg = {}
|
||||
for m in flat_module_metadata:
|
||||
packager_inputs.extend([m.metadata_file])
|
||||
metadata_arg[m.module_name] = {
|
||||
"index": m.typings_file.path.replace(".d.ts", ".js"),
|
||||
"typings": m.typings_file.path,
|
||||
"metadata": m.metadata_file.path,
|
||||
}
|
||||
packager_args.add(str(metadata_arg))
|
||||
|
||||
if ctx.file.readme_md:
|
||||
packager_inputs.append(ctx.file.readme_md)
|
||||
packager_args.add(ctx.file.readme_md.path)
|
||||
else:
|
||||
# fallback to a reasonable default
|
||||
flat_module_out_file = "index.js"
|
||||
# placeholder
|
||||
packager_args.add("")
|
||||
|
||||
es2015_entry_point = "/".join([p for p in [
|
||||
ctx.bin_dir.path,
|
||||
ctx.label.package,
|
||||
_esm2015_root_dir(ctx),
|
||||
ctx.label.package,
|
||||
entry_point,
|
||||
flat_module_out_file,
|
||||
] if p])
|
||||
packager_args.add_joined(_flatten_paths(fesm2015), join_with = ",")
|
||||
packager_args.add_joined(_flatten_paths(fesm5), join_with = ",")
|
||||
packager_args.add_joined(_flatten_paths(esm2015), join_with = ",")
|
||||
packager_args.add_joined(_flatten_paths(esm5), join_with = ",")
|
||||
packager_args.add_joined(_flatten_paths(bundles), join_with = ",")
|
||||
packager_args.add_joined([s.path for s in ctx.files.srcs], join_with = ",")
|
||||
|
||||
es5_entry_point = "/".join([p for p in [
|
||||
ctx.label.package,
|
||||
entry_point,
|
||||
flat_module_out_file,
|
||||
] if p])
|
||||
# TODO: figure out a better way to gather runfiles providers from the transitive closure.
|
||||
packager_args.add_joined([d.path for d in ctx.files.data], join_with = ",")
|
||||
|
||||
if entry_point:
|
||||
# TODO jasonaden says there is no particular reason these filenames differ
|
||||
prefix = primary_entry_point_name(ctx.attr.name, ctx.attr.entry_point, ctx.attr.entry_point_name)
|
||||
umd_output_filename = "-".join([prefix] + entry_point.split("/"))
|
||||
fesm_output_filename = entry_point.replace("/", "__")
|
||||
fesm2015_output = ctx.actions.declare_file("fesm2015/%s.js" % fesm_output_filename)
|
||||
fesm5_output = ctx.actions.declare_file("%s.js" % fesm_output_filename)
|
||||
umd_output = ctx.actions.declare_file("%s.umd.js" % umd_output_filename)
|
||||
min_output = ctx.actions.declare_file("%s.umd.min.js" % umd_output_filename)
|
||||
if ctx.file.license_banner:
|
||||
packager_inputs.append(ctx.file.license_banner)
|
||||
packager_args.add(ctx.file.license_banner)
|
||||
else:
|
||||
fesm2015_output = ctx.outputs.fesm2015
|
||||
fesm5_output = ctx.outputs.fesm5
|
||||
umd_output = ctx.outputs.umd
|
||||
min_output = ctx.outputs.umd_min
|
||||
# placeholder
|
||||
packager_args.add("")
|
||||
|
||||
esm2015_config = write_rollup_config(ctx, [], "/".join([ctx.bin_dir.path, ctx.label.package, _esm2015_root_dir(ctx)]), filename="_%s.rollup_esm2015.conf.js")
|
||||
esm5_config = write_rollup_config(ctx, [], "/".join([ctx.bin_dir.path, ctx.label.package, esm5_root_dir(ctx)]), filename="_%s.rollup_esm5.conf.js")
|
||||
ctx.actions.run(
|
||||
progress_message = "Angular Packaging: building npm package %s" % str(ctx.label),
|
||||
mnemonic = "AngularPackage",
|
||||
inputs = packager_inputs,
|
||||
outputs = [npm_package_directory],
|
||||
executable = ctx.executable._ng_packager,
|
||||
arguments = [packager_args],
|
||||
)
|
||||
|
||||
fesm2015.append(_rollup(ctx, "fesm2015", esm2015_config, es2015_entry_point, esm_2015_files + ctx.files.node_modules, fesm2015_output))
|
||||
fesm5.append(_rollup(ctx, "fesm5", esm5_config, es5_entry_point, esm5_sources + ctx.files.node_modules, fesm5_output))
|
||||
devfiles = depset()
|
||||
if ctx.attr.include_devmode_srcs:
|
||||
for d in ctx.attr.deps:
|
||||
devfiles = depset(transitive = [devfiles, d.files, d.node_sources])
|
||||
|
||||
bundles.append(
|
||||
_rollup(ctx, "umd", esm5_config, es5_entry_point, esm5_sources + ctx.files.node_modules, umd_output,
|
||||
format = "umd", package_name = package_name, include_tslib = True))
|
||||
uglify_sourcemap = run_uglify(ctx, umd_output, min_output,
|
||||
config_name = entry_point.replace("/", "_"))
|
||||
bundles.append(struct(js = min_output, map = uglify_sourcemap))
|
||||
|
||||
packager_inputs = (
|
||||
ctx.files.srcs +
|
||||
ctx.files.data +
|
||||
esm5_sources.to_list() +
|
||||
depset(transitive = [d.typescript.transitive_declarations
|
||||
for d in ctx.attr.deps
|
||||
if hasattr(d, "typescript")]).to_list() +
|
||||
[f.js for f in fesm2015 + fesm5 + esm2015 + esm5 + bundles] +
|
||||
[f.map for f in fesm2015 + fesm5 + esm2015 + esm5 + bundles if f.map])
|
||||
|
||||
packager_args = ctx.actions.args()
|
||||
packager_args.use_param_file("%s", use_always = True)
|
||||
|
||||
# The order of arguments matters here, as they are read in order in packager.ts.
|
||||
packager_args.add(npm_package_directory.path)
|
||||
packager_args.add(ctx.label.package)
|
||||
packager_args.add_joined([ctx.bin_dir.path, ctx.label.package], join_with="/")
|
||||
packager_args.add_joined([ctx.genfiles_dir.path, ctx.label.package], join_with="/")
|
||||
|
||||
# Marshal the metadata into a JSON string so we can parse the data structure
|
||||
# in the TypeScript program easily.
|
||||
metadata_arg = {}
|
||||
for m in flat_module_metadata:
|
||||
packager_inputs.extend([m.metadata_file])
|
||||
metadata_arg[m.module_name] = {
|
||||
"index": m.typings_file.path.replace(".d.ts", ".js"),
|
||||
"typings": m.typings_file.path,
|
||||
"metadata": m.metadata_file.path,
|
||||
}
|
||||
packager_args.add(str(metadata_arg))
|
||||
|
||||
if ctx.file.readme_md:
|
||||
packager_inputs.append(ctx.file.readme_md)
|
||||
packager_args.add(ctx.file.readme_md.path)
|
||||
else:
|
||||
# placeholder
|
||||
packager_args.add("")
|
||||
|
||||
packager_args.add_joined(_flatten_paths(fesm2015), join_with=",")
|
||||
packager_args.add_joined(_flatten_paths(fesm5), join_with=",")
|
||||
packager_args.add_joined(_flatten_paths(esm2015), join_with=",")
|
||||
packager_args.add_joined(_flatten_paths(esm5), join_with=",")
|
||||
packager_args.add_joined(_flatten_paths(bundles), join_with=",")
|
||||
packager_args.add_joined([s.path for s in ctx.files.srcs], join_with=",")
|
||||
|
||||
# TODO: figure out a better way to gather runfiles providers from the transitive closure.
|
||||
packager_args.add_joined([d.path for d in ctx.files.data], join_with=",")
|
||||
|
||||
if ctx.file.license_banner:
|
||||
packager_inputs.append(ctx.file.license_banner)
|
||||
packager_args.add(ctx.file.license_banner)
|
||||
else:
|
||||
# placeholder
|
||||
packager_args.add("")
|
||||
|
||||
ctx.actions.run(
|
||||
progress_message = "Angular Packaging: building npm package %s" % str(ctx.label),
|
||||
mnemonic = "AngularPackage",
|
||||
inputs = packager_inputs,
|
||||
outputs = [npm_package_directory],
|
||||
executable = ctx.executable._ng_packager,
|
||||
arguments = [packager_args],
|
||||
)
|
||||
|
||||
devfiles = depset()
|
||||
if ctx.attr.include_devmode_srcs:
|
||||
for d in ctx.attr.deps:
|
||||
devfiles = depset(transitive = [devfiles, d.files, d.node_sources])
|
||||
|
||||
# Re-use the create_package function from the nodejs npm_package rule.
|
||||
package_dir = create_package(
|
||||
ctx,
|
||||
devfiles.to_list(),
|
||||
[npm_package_directory] + ctx.files.packages)
|
||||
return [DefaultInfo(
|
||||
files = depset([package_dir])
|
||||
)]
|
||||
# Re-use the create_package function from the nodejs npm_package rule.
|
||||
package_dir = create_package(
|
||||
ctx,
|
||||
devfiles.to_list(),
|
||||
[npm_package_directory] + ctx.files.packages,
|
||||
)
|
||||
return [DefaultInfo(
|
||||
files = depset([package_dir]),
|
||||
)]
|
||||
|
||||
NG_PACKAGE_ATTRS = dict(NPM_PACKAGE_ATTRS, **dict(ROLLUP_ATTRS, **{
|
||||
"srcs": attr.label_list(allow_files = True),
|
||||
@ -327,22 +357,29 @@ NG_PACKAGE_ATTRS = dict(NPM_PACKAGE_ATTRS, **dict(ROLLUP_ATTRS, **{
|
||||
),
|
||||
"include_devmode_srcs": attr.bool(default = False),
|
||||
"readme_md": attr.label(allow_single_file = FileType([".md"])),
|
||||
"globals": attr.string_dict(default={}),
|
||||
"globals": attr.string_dict(default = {}),
|
||||
"entry_point_name": attr.string(
|
||||
doc = "Name to use when generating bundle files for the primary entry-point.",
|
||||
doc = "Name to use when generating bundle files for the primary entry-point.",
|
||||
),
|
||||
"_ng_packager": attr.label(
|
||||
default=Label("//packages/bazel/src/ng_package:packager"),
|
||||
executable=True, cfg="host"),
|
||||
default = Label("//packages/bazel/src/ng_package:packager"),
|
||||
executable = True,
|
||||
cfg = "host",
|
||||
),
|
||||
"_rollup": attr.label(
|
||||
default=Label("@build_bazel_rules_nodejs//internal/rollup"),
|
||||
executable=True, cfg="host"),
|
||||
default = Label("@build_bazel_rules_nodejs//internal/rollup"),
|
||||
executable = True,
|
||||
cfg = "host",
|
||||
),
|
||||
"_rollup_config_tmpl": attr.label(
|
||||
default=Label("@build_bazel_rules_nodejs//internal/rollup:rollup.config.js"),
|
||||
allow_single_file=True),
|
||||
default = Label("@build_bazel_rules_nodejs//internal/rollup:rollup.config.js"),
|
||||
allow_single_file = True,
|
||||
),
|
||||
"_uglify": attr.label(
|
||||
default=Label("@build_bazel_rules_nodejs//internal/rollup:uglify"),
|
||||
executable=True, cfg="host"),
|
||||
default = Label("@build_bazel_rules_nodejs//internal/rollup:uglify"),
|
||||
executable = True,
|
||||
cfg = "host",
|
||||
),
|
||||
}))
|
||||
|
||||
# Angular wants these named after the entry_point,
|
||||
@ -352,35 +389,63 @@ NG_PACKAGE_ATTRS = dict(NPM_PACKAGE_ATTRS, **dict(ROLLUP_ATTRS, **{
|
||||
# some/path/to/my/package/index.js
|
||||
# we assume the files should be named "package.*.js"
|
||||
def primary_entry_point_name(name, entry_point, entry_point_name):
|
||||
if entry_point_name:
|
||||
# If an explicit entry_point_name is given, use that.
|
||||
return entry_point_name
|
||||
elif entry_point.find("/") >= 0:
|
||||
# If the entry_point has multiple path segments, use the second one.
|
||||
# E.g., for "@angular/cdk/a11y", use "cdk".
|
||||
return entry_point.split("/")[-2]
|
||||
else:
|
||||
# Fall back to the name of the ng_package rule.
|
||||
return name
|
||||
"""This is not a public API.
|
||||
|
||||
Compute the name of the primary entry point in the library.
|
||||
|
||||
Args:
|
||||
name: the name of the `ng_package` rule, as a fallback.
|
||||
entry_point: The starting point of the application, see rollup_bundle.
|
||||
entry_point_name: if set, this is the returned value.
|
||||
|
||||
Returns:
|
||||
name of the entry point, which will appear in the name of generated bundles
|
||||
"""
|
||||
if entry_point_name:
|
||||
# If an explicit entry_point_name is given, use that.
|
||||
return entry_point_name
|
||||
elif entry_point.find("/") >= 0:
|
||||
# If the entry_point has multiple path segments, use the second one.
|
||||
# E.g., for "@angular/cdk/a11y", use "cdk".
|
||||
return entry_point.split("/")[-2]
|
||||
else:
|
||||
# Fall back to the name of the ng_package rule.
|
||||
return name
|
||||
|
||||
def ng_package_outputs(name, entry_point, entry_point_name):
|
||||
basename = primary_entry_point_name(name, entry_point, entry_point_name)
|
||||
outputs = {
|
||||
"fesm5": "fesm5/%s.js" % basename,
|
||||
"fesm2015": "fesm2015/%s.js" % basename,
|
||||
"umd": "%s.umd.js" % basename,
|
||||
"umd_min": "%s.umd.min.js" % basename,
|
||||
}
|
||||
for key in NPM_PACKAGE_OUTPUTS:
|
||||
# NPM_PACKAGE_OUTPUTS is a "normal" dict-valued outputs so it looks like
|
||||
# "pack": "%{name}.pack",
|
||||
# But this is a function-valued outputs.
|
||||
# Bazel won't replace the %{name} token so we have to do it.
|
||||
outputs[key] = NPM_PACKAGE_OUTPUTS[key].replace("%{name}", name)
|
||||
return outputs
|
||||
"""This is not a public API.
|
||||
|
||||
This function computes the named outputs for an ng_package rule.
|
||||
|
||||
Args:
|
||||
name: value of the name attribute
|
||||
entry_point: value of the entry_point attribute
|
||||
entry_point_name: value of the entry_point_name attribute
|
||||
|
||||
Returns:
|
||||
dict of named outputs of the rule
|
||||
"""
|
||||
|
||||
basename = primary_entry_point_name(name, entry_point, entry_point_name)
|
||||
outputs = {
|
||||
"fesm5": "fesm5/%s.js" % basename,
|
||||
"fesm2015": "fesm2015/%s.js" % basename,
|
||||
"umd": "%s.umd.js" % basename,
|
||||
"umd_min": "%s.umd.min.js" % basename,
|
||||
}
|
||||
for key in NPM_PACKAGE_OUTPUTS:
|
||||
# NPM_PACKAGE_OUTPUTS is a "normal" dict-valued outputs so it looks like
|
||||
# "pack": "%{name}.pack",
|
||||
# But this is a function-valued outputs.
|
||||
# Bazel won't replace the %{name} token so we have to do it.
|
||||
outputs[key] = NPM_PACKAGE_OUTPUTS[key].replace("%{name}", name)
|
||||
return outputs
|
||||
|
||||
ng_package = rule(
|
||||
implementation = _ng_package_impl,
|
||||
attrs = NG_PACKAGE_ATTRS,
|
||||
outputs = ng_package_outputs,
|
||||
)
|
||||
"""
|
||||
ng_package produces an npm-ready package from an Angular library.
|
||||
"""
|
||||
|
@ -3,143 +3,177 @@
|
||||
# Use of this source code is governed by an MIT-style license that can be
|
||||
# found in the LICENSE file at https://angular.io/license
|
||||
|
||||
"""This provides a variant of rollup_bundle that works better for Angular apps.
|
||||
"""Rollup with Build Optimizer
|
||||
|
||||
It registers @angular-devkit/build-optimizer as a rollup plugin, to get
|
||||
This provides a variant of the [rollup_bundle] rule that works better for Angular apps.
|
||||
|
||||
It registers `@angular-devkit/build-optimizer` as a rollup plugin, to get
|
||||
better optimization. It also uses ESM5 format inputs, as this is what
|
||||
build-optimizer is hard-coded to look for and transform.
|
||||
|
||||
[rollup_bundle]: https://bazelbuild.github.io/rules_nodejs/rollup/rollup_bundle.html
|
||||
"""
|
||||
|
||||
load("@build_bazel_rules_nodejs//internal/rollup:rollup_bundle.bzl",
|
||||
"rollup_module_mappings_aspect",
|
||||
load(
|
||||
"@build_bazel_rules_nodejs//internal/rollup:rollup_bundle.bzl",
|
||||
"ROLLUP_ATTRS",
|
||||
"ROLLUP_OUTPUTS",
|
||||
"write_rollup_config",
|
||||
"rollup_module_mappings_aspect",
|
||||
"run_rollup",
|
||||
"run_sourcemapexplorer",
|
||||
"run_uglify",
|
||||
"run_sourcemapexplorer")
|
||||
"write_rollup_config",
|
||||
)
|
||||
load("@build_bazel_rules_nodejs//internal:collect_es6_sources.bzl", collect_es2015_sources = "collect_es6_sources")
|
||||
load(":esm5.bzl", "esm5_outputs_aspect", "flatten_esm5", "esm5_root_dir")
|
||||
load(":esm5.bzl", "esm5_outputs_aspect", "esm5_root_dir", "flatten_esm5")
|
||||
|
||||
PACKAGES=["packages/core/src", "packages/common/src", "packages/compiler/src", "external/rxjs"]
|
||||
PLUGIN_CONFIG="{sideEffectFreeModules: [\n%s]}" % ",\n".join(
|
||||
[" '.esm5/{0}'".format(p) for p in PACKAGES])
|
||||
BO_ROLLUP="angular_cli/packages/angular_devkit/build_optimizer/src/build-optimizer/rollup-plugin.js"
|
||||
BO_PLUGIN="require('%s').default(%s)" % (BO_ROLLUP, PLUGIN_CONFIG)
|
||||
PACKAGES = ["packages/core/src", "packages/common/src", "packages/compiler/src", "external/rxjs"]
|
||||
PLUGIN_CONFIG = "{sideEffectFreeModules: [\n%s]}" % ",\n".join(
|
||||
[" '.esm5/{0}'".format(p) for p in PACKAGES],
|
||||
)
|
||||
BO_ROLLUP = "angular_cli/packages/angular_devkit/build_optimizer/src/build-optimizer/rollup-plugin.js"
|
||||
BO_PLUGIN = "require('%s').default(%s)" % (BO_ROLLUP, PLUGIN_CONFIG)
|
||||
|
||||
def _use_plain_rollup(ctx):
|
||||
"""Determine whether to use the Angular or upstream versions of the rollup_bundle rule.
|
||||
"""Determine whether to use the Angular or upstream versions of the rollup_bundle rule.
|
||||
|
||||
In most modes, the Angular version of rollup is used. This runs build optimizer as part of its
|
||||
processing, which affects decorators and annotations.
|
||||
In most modes, the Angular version of rollup is used. This runs build optimizer as part of its
|
||||
processing, which affects decorators and annotations.
|
||||
|
||||
In JIT modes, an emulation of the upstream rollup_bundle rule is used. This avoids running
|
||||
build optimizer on code which isn't designed to be optimized by it.
|
||||
In JIT modes, an emulation of the upstream rollup_bundle rule is used. This avoids running
|
||||
build optimizer on code which isn't designed to be optimized by it.
|
||||
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
Args:
|
||||
ctx: skylark rule execution context
|
||||
|
||||
Returns:
|
||||
true iff the Angular version of rollup with build optimizer should be used, false otherwise
|
||||
"""
|
||||
Returns:
|
||||
true iff the Angular version of rollup with build optimizer should be used, false otherwise
|
||||
"""
|
||||
|
||||
if 'compile' not in ctx.var:
|
||||
return False
|
||||
|
||||
strategy = ctx.var['compile']
|
||||
return strategy == 'jit'
|
||||
if "compile" not in ctx.var:
|
||||
return False
|
||||
|
||||
strategy = ctx.var["compile"]
|
||||
return strategy == "jit"
|
||||
|
||||
def run_brotli(ctx, input, output):
|
||||
ctx.actions.run(
|
||||
executable = ctx.executable._brotli,
|
||||
inputs = [input],
|
||||
outputs = [output],
|
||||
arguments = ["--output=%s" % output.path, input.path],
|
||||
)
|
||||
"""Execute the Brotli compression utility.
|
||||
|
||||
Args:
|
||||
ctx: Bazel's rule execution context
|
||||
input: any file
|
||||
output: the compressed file
|
||||
"""
|
||||
ctx.actions.run(
|
||||
executable = ctx.executable._brotli,
|
||||
inputs = [input],
|
||||
outputs = [output],
|
||||
arguments = ["--output=%s" % output.path, input.path],
|
||||
)
|
||||
|
||||
# Borrowed from bazelbuild/rules_nodejs
|
||||
def _run_tsc(ctx, input, output):
|
||||
args = ctx.actions.args()
|
||||
args.add("--target", "es5")
|
||||
args.add("--allowJS")
|
||||
args.add(input)
|
||||
args.add("--outFile", output)
|
||||
args = ctx.actions.args()
|
||||
args.add("--target", "es5")
|
||||
args.add("--allowJS")
|
||||
args.add(input)
|
||||
args.add("--outFile", output)
|
||||
|
||||
ctx.action(
|
||||
executable = ctx.executable._tsc,
|
||||
inputs = [input],
|
||||
outputs = [output],
|
||||
arguments = [args]
|
||||
)
|
||||
ctx.action(
|
||||
executable = ctx.executable._tsc,
|
||||
inputs = [input],
|
||||
outputs = [output],
|
||||
arguments = [args],
|
||||
)
|
||||
|
||||
# Borrowed from bazelbuild/rules_nodejs, with the addition of brotli compression output
|
||||
def _plain_rollup_bundle(ctx):
|
||||
rollup_config = write_rollup_config(ctx)
|
||||
run_rollup(ctx, collect_es2015_sources(ctx), rollup_config, ctx.outputs.build_es6)
|
||||
_run_tsc(ctx, ctx.outputs.build_es6, ctx.outputs.build_es5)
|
||||
source_map = run_uglify(ctx, ctx.outputs.build_es5, ctx.outputs.build_es5_min)
|
||||
run_uglify(ctx, ctx.outputs.build_es5, ctx.outputs.build_es5_min_debug, debug = True)
|
||||
umd_rollup_config = write_rollup_config(ctx, filename = "_%s_umd.rollup.conf.js", output_format = "umd")
|
||||
run_rollup(ctx, collect_es2015_sources(ctx), umd_rollup_config, ctx.outputs.build_umd)
|
||||
run_sourcemapexplorer(ctx, ctx.outputs.build_es5_min, source_map, ctx.outputs.explore_html)
|
||||
rollup_config = write_rollup_config(ctx)
|
||||
run_rollup(ctx, collect_es2015_sources(ctx), rollup_config, ctx.outputs.build_es6)
|
||||
_run_tsc(ctx, ctx.outputs.build_es6, ctx.outputs.build_es5)
|
||||
source_map = run_uglify(ctx, ctx.outputs.build_es5, ctx.outputs.build_es5_min)
|
||||
run_uglify(ctx, ctx.outputs.build_es5, ctx.outputs.build_es5_min_debug, debug = True)
|
||||
umd_rollup_config = write_rollup_config(ctx, filename = "_%s_umd.rollup.conf.js", output_format = "umd")
|
||||
run_rollup(ctx, collect_es2015_sources(ctx), umd_rollup_config, ctx.outputs.build_umd)
|
||||
run_sourcemapexplorer(ctx, ctx.outputs.build_es5_min, source_map, ctx.outputs.explore_html)
|
||||
|
||||
run_brotli(ctx, ctx.outputs.build_es5_min, ctx.outputs.build_es5_min_compressed)
|
||||
files = [ctx.outputs.build_es5_min, source_map]
|
||||
return DefaultInfo(files = depset(files), runfiles = ctx.runfiles(files))
|
||||
run_brotli(ctx, ctx.outputs.build_es5_min, ctx.outputs.build_es5_min_compressed)
|
||||
files = [ctx.outputs.build_es5_min, source_map]
|
||||
return DefaultInfo(files = depset(files), runfiles = ctx.runfiles(files))
|
||||
|
||||
def _ng_rollup_bundle(ctx):
|
||||
# Escape and use the plain rollup rule if the compilation strategy requires it
|
||||
if _use_plain_rollup(ctx):
|
||||
return _plain_rollup_bundle(ctx)
|
||||
# Escape and use the plain rollup rule if the compilation strategy requires it
|
||||
if _use_plain_rollup(ctx):
|
||||
return _plain_rollup_bundle(ctx)
|
||||
|
||||
# We don't expect anyone to make use of this bundle yet, but it makes this rule
|
||||
# compatible with rollup_bundle which allows them to be easily swapped back and
|
||||
# forth.
|
||||
esm2015_rollup_config = write_rollup_config(ctx, filename = "_%s.rollup_es6.conf.js")
|
||||
run_rollup(ctx, collect_es2015_sources(ctx), esm2015_rollup_config, ctx.outputs.build_es6)
|
||||
# We don't expect anyone to make use of this bundle yet, but it makes this rule
|
||||
# compatible with rollup_bundle which allows them to be easily swapped back and
|
||||
# forth.
|
||||
esm2015_rollup_config = write_rollup_config(ctx, filename = "_%s.rollup_es6.conf.js")
|
||||
run_rollup(ctx, collect_es2015_sources(ctx), esm2015_rollup_config, ctx.outputs.build_es6)
|
||||
|
||||
esm5_sources = flatten_esm5(ctx)
|
||||
esm5_sources = flatten_esm5(ctx)
|
||||
|
||||
rollup_config = write_rollup_config(ctx, [BO_PLUGIN], "/".join([ctx.bin_dir.path, ctx.label.package, esm5_root_dir(ctx)]))
|
||||
rollup_sourcemap = run_rollup(ctx, esm5_sources, rollup_config, ctx.outputs.build_es5)
|
||||
rollup_config = write_rollup_config(ctx, [BO_PLUGIN], "/".join([ctx.bin_dir.path, ctx.label.package, esm5_root_dir(ctx)]))
|
||||
rollup_sourcemap = run_rollup(ctx, esm5_sources, rollup_config, ctx.outputs.build_es5)
|
||||
|
||||
sourcemap = run_uglify(ctx,
|
||||
ctx.outputs.build_es5,
|
||||
ctx.outputs.build_es5_min,
|
||||
comments = False,
|
||||
in_source_map = rollup_sourcemap)
|
||||
run_uglify(ctx,
|
||||
ctx.outputs.build_es5,
|
||||
ctx.outputs.build_es5_min_debug,
|
||||
debug = True, comments = False)
|
||||
sourcemap = run_uglify(
|
||||
ctx,
|
||||
ctx.outputs.build_es5,
|
||||
ctx.outputs.build_es5_min,
|
||||
comments = False,
|
||||
in_source_map = rollup_sourcemap,
|
||||
)
|
||||
run_uglify(
|
||||
ctx,
|
||||
ctx.outputs.build_es5,
|
||||
ctx.outputs.build_es5_min_debug,
|
||||
debug = True,
|
||||
comments = False,
|
||||
)
|
||||
|
||||
umd_rollup_config = write_rollup_config(ctx, filename = "_%s_umd.rollup.conf.js", output_format = "umd")
|
||||
run_rollup(ctx, collect_es2015_sources(ctx), umd_rollup_config, ctx.outputs.build_umd)
|
||||
umd_rollup_config = write_rollup_config(ctx, filename = "_%s_umd.rollup.conf.js", output_format = "umd")
|
||||
run_rollup(ctx, collect_es2015_sources(ctx), umd_rollup_config, ctx.outputs.build_umd)
|
||||
|
||||
run_brotli(ctx, ctx.outputs.build_es5_min, ctx.outputs.build_es5_min_compressed)
|
||||
run_brotli(ctx, ctx.outputs.build_es5_min, ctx.outputs.build_es5_min_compressed)
|
||||
|
||||
run_sourcemapexplorer(ctx, ctx.outputs.build_es5_min, sourcemap, ctx.outputs.explore_html)
|
||||
run_sourcemapexplorer(ctx, ctx.outputs.build_es5_min, sourcemap, ctx.outputs.explore_html)
|
||||
|
||||
return DefaultInfo(files=depset([ctx.outputs.build_es5_min, sourcemap]))
|
||||
return DefaultInfo(files = depset([ctx.outputs.build_es5_min, sourcemap]))
|
||||
|
||||
ng_rollup_bundle = rule(
|
||||
implementation = _ng_rollup_bundle,
|
||||
attrs = dict(ROLLUP_ATTRS, **{
|
||||
"deps": attr.label_list(aspects = [
|
||||
rollup_module_mappings_aspect,
|
||||
esm5_outputs_aspect,
|
||||
]),
|
||||
"deps": attr.label_list(
|
||||
doc = """Other targets that provide JavaScript files.
|
||||
Typically this will be `ts_library` or `ng_module` targets.""",
|
||||
aspects = [
|
||||
rollup_module_mappings_aspect,
|
||||
esm5_outputs_aspect,
|
||||
],
|
||||
),
|
||||
"_rollup": attr.label(
|
||||
executable = True,
|
||||
cfg = "host",
|
||||
default = Label("@angular//packages/bazel/src:rollup_with_build_optimizer")),
|
||||
default = Label("@angular//packages/bazel/src:rollup_with_build_optimizer"),
|
||||
),
|
||||
"_brotli": attr.label(
|
||||
executable = True,
|
||||
cfg = "host",
|
||||
default = Label("@org_brotli//:brotli")),
|
||||
default = Label("@org_brotli//:brotli"),
|
||||
),
|
||||
}),
|
||||
outputs = dict(ROLLUP_OUTPUTS, **{
|
||||
"build_es5_min_compressed": "%{name}.min.js.br",
|
||||
}),
|
||||
)
|
||||
"""
|
||||
Run [Rollup] with the [Build Optimizer] plugin.
|
||||
|
||||
This rule extends from the [rollup_bundle] rule, so attributes and outputs of
|
||||
that rule are used here too.
|
||||
|
||||
[Rollup]: https://rollupjs.org/
|
||||
[Build Optimizer]: https://www.npmjs.com/package/@angular-devkit/build-optimizer
|
||||
[rollup_bundle]: https://bazelbuild.github.io/rules_nodejs/rollup/rollup_bundle.html
|
||||
"""
|
||||
|
@ -6,15 +6,19 @@
|
||||
"Install toolchain dependencies"
|
||||
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "yarn_install")
|
||||
load("@build_bazel_rules_typescript//:defs.bzl", "check_rules_typescript_version")
|
||||
|
||||
def ng_setup_workspace():
|
||||
"""This repository rule should be called from your WORKSPACE file.
|
||||
"""This repository rule should be called from your WORKSPACE file.
|
||||
|
||||
It creates some additional Bazel external repositories that are used internally
|
||||
by the Angular rules.
|
||||
"""
|
||||
yarn_install(
|
||||
name = "angular_packager_deps",
|
||||
package_json = "@angular//packages/bazel/src/ng_package:package.json",
|
||||
yarn_lock = "@angular//packages/bazel/src/ng_package:yarn.lock",
|
||||
)
|
||||
It creates some additional Bazel external repositories that are used internally
|
||||
by the Angular rules.
|
||||
"""
|
||||
yarn_install(
|
||||
name = "angular_packager_deps",
|
||||
package_json = "@angular//packages/bazel/src/ng_package:package.json",
|
||||
yarn_lock = "@angular//packages/bazel/src/ng_package:yarn.lock",
|
||||
)
|
||||
|
||||
# 0.16.0: minimal version required to work with ng_module
|
||||
check_rules_typescript_version("0.16.0")
|
||||
|
@ -30,7 +30,7 @@ nodejs_binary(
|
||||
name = "ngc-wrapped",
|
||||
data = [
|
||||
":ngc_lib",
|
||||
"@build_bazel_rules_typescript//internal:worker_protocol.proto",
|
||||
"@build_bazel_rules_typescript//third_party/github.com/bazelbuild/bazel/src/main/protobuf:worker_protocol.proto",
|
||||
],
|
||||
entry_point = "angular/packages/bazel/src/ngc-wrapped/index.js",
|
||||
visibility = ["//visibility:public"],
|
||||
|
@ -76,7 +76,8 @@ export function runOneBuild(args: string[], inputs?: {[path: string]: string}):
|
||||
export function relativeToRootDirs(filePath: string, rootDirs: string[]): string {
|
||||
if (!filePath) return filePath;
|
||||
// NB: the rootDirs should have been sorted longest-first
|
||||
for (const dir of rootDirs || []) {
|
||||
for (let i = 0; i < rootDirs.length; i++) {
|
||||
const dir = rootDirs[i];
|
||||
const rel = path.posix.relative(dir, filePath);
|
||||
if (rel.indexOf('.') != 0) return rel;
|
||||
}
|
||||
@ -106,7 +107,9 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true,
|
||||
fileLoader = new CachedFileLoader(fileCache, allowNonHermeticReads);
|
||||
// Resolve the inputs to absolute paths to match TypeScript internals
|
||||
const resolvedInputs: {[path: string]: string} = {};
|
||||
for (const key of Object.keys(inputs)) {
|
||||
const inputKeys = Object.keys(inputs);
|
||||
for (let i = 0; i < inputKeys.length; i++) {
|
||||
const key = inputKeys[i];
|
||||
resolvedInputs[resolveNormalizedPath(key)] = inputs[key];
|
||||
}
|
||||
fileCache.updateCache(resolvedInputs);
|
||||
@ -124,7 +127,11 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true,
|
||||
|
||||
// Disable downleveling and Closure annotation if in Ivy mode.
|
||||
if (isInIvyMode) {
|
||||
compilerOpts.annotateForClosureCompiler = false;
|
||||
// In pass-through mode for TypeScript, we want to turn off decorator transpilation entirely.
|
||||
// This causes ngc to be have exactly like tsc.
|
||||
if (compilerOpts.enableIvy === 'tsc') {
|
||||
compilerOpts.annotateForClosureCompiler = false;
|
||||
}
|
||||
compilerOpts.annotationsAs = 'decorators';
|
||||
}
|
||||
|
||||
@ -207,7 +214,7 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true,
|
||||
if (fileName ===
|
||||
path.join(compilerOpts.baseUrl, bazelOpts.package, compilerOpts.flatModuleOutFile + '.ts'))
|
||||
return true;
|
||||
// Also handle the case when angular is build from source as an external repository
|
||||
// Also handle the case when angular is built from source as an external repository
|
||||
if (fileName ===
|
||||
path.join(
|
||||
compilerOpts.baseUrl, 'external/angular', bazelOpts.package,
|
||||
@ -306,8 +313,8 @@ export function compile({allowNonHermeticReads, allDepsCompiledWithBazel = true,
|
||||
fs.writeFileSync(bazelOpts.tsickleExternsPath, externs);
|
||||
}
|
||||
|
||||
for (const missing of writtenExpectedOuts) {
|
||||
originalWriteFile(missing, '', false);
|
||||
for (let i = 0; i < writtenExpectedOuts.length; i++) {
|
||||
originalWriteFile(writtenExpectedOuts[i], '', false);
|
||||
}
|
||||
|
||||
return {program, diagnostics};
|
||||
@ -323,7 +330,8 @@ function generateMetadataJson(
|
||||
program: ts.Program, files: string[], rootDirs: string[], bazelBin: string,
|
||||
tsHost: ts.CompilerHost) {
|
||||
const collector = new ng.MetadataCollector();
|
||||
for (const file of files) {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
const sourceFile = program.getSourceFile(file);
|
||||
if (sourceFile) {
|
||||
const metadata = collector.getMetadata(sourceFile);
|
||||
@ -353,7 +361,9 @@ function gatherDiagnosticsForInputsOnly(
|
||||
// program.getDeclarationDiagnostics() it somehow corrupts the emit.
|
||||
diagnostics.push(...tsProgram.getOptionsDiagnostics());
|
||||
diagnostics.push(...tsProgram.getGlobalDiagnostics());
|
||||
for (const sf of tsProgram.getSourceFiles().filter(f => isCompilationTarget(bazelOpts, f))) {
|
||||
const programFiles = tsProgram.getSourceFiles().filter(f => isCompilationTarget(bazelOpts, f));
|
||||
for (let i = 0; i < programFiles.length; i++) {
|
||||
const sf = programFiles[i];
|
||||
// Note: We only get the diagnostics for individual files
|
||||
// to e.g. not check libraries.
|
||||
diagnostics.push(...tsProgram.getSyntacticDiagnostics(sf));
|
||||
|
@ -2,12 +2,12 @@
|
||||
#
|
||||
# Use of this source code is governed by an MIT-style license that can be
|
||||
# found in the LICENSE file at https://angular.io/license
|
||||
"""Implementation of the protractor_web_test and protractor_web_test_suite rules.
|
||||
"""
|
||||
"Run end-to-end tests with Protractor"
|
||||
|
||||
load("@build_bazel_rules_nodejs//internal:node.bzl",
|
||||
"sources_aspect",
|
||||
load(
|
||||
"@build_bazel_rules_nodejs//internal:node.bzl",
|
||||
"expand_path_into_runfiles",
|
||||
"sources_aspect",
|
||||
)
|
||||
load("@io_bazel_rules_webtesting//web:web.bzl", "web_test_suite")
|
||||
load("@io_bazel_rules_webtesting//web/internal:constants.bzl", "DEFAULT_WRAPPED_TEST_TAGS")
|
||||
@ -16,69 +16,71 @@ load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary")
|
||||
_CONF_TMPL = "//packages/bazel/src/protractor:protractor.conf.js"
|
||||
|
||||
def _protractor_web_test_impl(ctx):
|
||||
configuration = ctx.actions.declare_file(
|
||||
"%s.conf.js" % ctx.label.name,
|
||||
sibling=ctx.outputs.executable)
|
||||
configuration = ctx.actions.declare_file(
|
||||
"%s.conf.js" % ctx.label.name,
|
||||
sibling = ctx.outputs.executable,
|
||||
)
|
||||
|
||||
files = depset(ctx.files.srcs)
|
||||
for d in ctx.attr.deps:
|
||||
if hasattr(d, "node_sources"):
|
||||
files = depset(transitive = [files, d.node_sources])
|
||||
elif hasattr(d, "files"):
|
||||
files = depset(transitive = [files, d.files])
|
||||
files = depset(ctx.files.srcs)
|
||||
for d in ctx.attr.deps:
|
||||
if hasattr(d, "node_sources"):
|
||||
files = depset(transitive = [files, d.node_sources])
|
||||
elif hasattr(d, "files"):
|
||||
files = depset(transitive = [files, d.files])
|
||||
|
||||
specs = [
|
||||
expand_path_into_runfiles(ctx, f.short_path)
|
||||
for f in files
|
||||
]
|
||||
specs = [
|
||||
expand_path_into_runfiles(ctx, f.short_path)
|
||||
for f in files
|
||||
]
|
||||
|
||||
configuration_sources = []
|
||||
if ctx.file.configuration:
|
||||
configuration_sources = [ctx.file.configuration]
|
||||
if hasattr(ctx.attr.configuration, "node_sources"):
|
||||
configuration_sources = ctx.attr.configuration.node_sources.to_list()
|
||||
configuration_sources = []
|
||||
if ctx.file.configuration:
|
||||
configuration_sources = [ctx.file.configuration]
|
||||
if hasattr(ctx.attr.configuration, "node_sources"):
|
||||
configuration_sources = ctx.attr.configuration.node_sources.to_list()
|
||||
|
||||
configuration_file = ctx.file.configuration
|
||||
if hasattr(ctx.attr.configuration, "typescript"):
|
||||
configuration_file = ctx.attr.configuration.typescript.es5_sources.to_list()[0]
|
||||
configuration_file = ctx.file.configuration
|
||||
if hasattr(ctx.attr.configuration, "typescript"):
|
||||
configuration_file = ctx.attr.configuration.typescript.es5_sources.to_list()[0]
|
||||
|
||||
on_prepare_sources = []
|
||||
if ctx.file.on_prepare:
|
||||
on_prepare_sources = [ctx.file.on_prepare]
|
||||
if hasattr(ctx.attr.on_prepare, "node_sources"):
|
||||
on_prepare_sources = ctx.attr.on_prepare.node_sources.to_list()
|
||||
on_prepare_sources = []
|
||||
if ctx.file.on_prepare:
|
||||
on_prepare_sources = [ctx.file.on_prepare]
|
||||
if hasattr(ctx.attr.on_prepare, "node_sources"):
|
||||
on_prepare_sources = ctx.attr.on_prepare.node_sources.to_list()
|
||||
|
||||
on_prepare_file = ctx.file.on_prepare
|
||||
if hasattr(ctx.attr.on_prepare, "typescript"):
|
||||
on_prepare_file = ctx.attr.on_prepare.typescript.es5_sources.to_list()[0]
|
||||
on_prepare_file = ctx.file.on_prepare
|
||||
if hasattr(ctx.attr.on_prepare, "typescript"):
|
||||
on_prepare_file = ctx.attr.on_prepare.typescript.es5_sources.to_list()[0]
|
||||
|
||||
protractor_executable_path = ctx.executable.protractor.short_path
|
||||
if protractor_executable_path.startswith('..'):
|
||||
protractor_executable_path = "external" + protractor_executable_path[2:]
|
||||
protractor_executable_path = ctx.executable.protractor.short_path
|
||||
if protractor_executable_path.startswith(".."):
|
||||
protractor_executable_path = "external" + protractor_executable_path[2:]
|
||||
|
||||
server_executable_path = ''
|
||||
if ctx.executable.server:
|
||||
server_executable_path = ctx.executable.server.short_path
|
||||
if server_executable_path.startswith('..'):
|
||||
server_executable_path = "external" + protractor_executable_path[2:]
|
||||
server_executable_path = ""
|
||||
if ctx.executable.server:
|
||||
server_executable_path = ctx.executable.server.short_path
|
||||
if server_executable_path.startswith(".."):
|
||||
server_executable_path = "external" + protractor_executable_path[2:]
|
||||
|
||||
ctx.actions.expand_template(
|
||||
output = configuration,
|
||||
template = ctx.file._conf_tmpl,
|
||||
substitutions = {
|
||||
"TMPL_config": expand_path_into_runfiles(ctx, configuration_file.short_path) if configuration_file else "",
|
||||
"TMPL_on_prepare": expand_path_into_runfiles(ctx, on_prepare_file.short_path) if on_prepare_file else "",
|
||||
"TMPL_workspace": ctx.workspace_name,
|
||||
"TMPL_server": server_executable_path,
|
||||
"TMPL_specs": "\n".join([" '%s'," % e for e in specs]),
|
||||
})
|
||||
ctx.actions.expand_template(
|
||||
output = configuration,
|
||||
template = ctx.file._conf_tmpl,
|
||||
substitutions = {
|
||||
"TMPL_config": expand_path_into_runfiles(ctx, configuration_file.short_path) if configuration_file else "",
|
||||
"TMPL_on_prepare": expand_path_into_runfiles(ctx, on_prepare_file.short_path) if on_prepare_file else "",
|
||||
"TMPL_workspace": ctx.workspace_name,
|
||||
"TMPL_server": server_executable_path,
|
||||
"TMPL_specs": "\n".join([" '%s'," % e for e in specs]),
|
||||
},
|
||||
)
|
||||
|
||||
runfiles = [configuration] + configuration_sources + on_prepare_sources
|
||||
runfiles = [configuration] + configuration_sources + on_prepare_sources
|
||||
|
||||
ctx.actions.write(
|
||||
output = ctx.outputs.executable,
|
||||
is_executable = True,
|
||||
content = """#!/usr/bin/env bash
|
||||
ctx.actions.write(
|
||||
output = ctx.outputs.executable,
|
||||
is_executable = True,
|
||||
content = """#!/usr/bin/env bash
|
||||
if [ -e "$RUNFILE_MANIFEST_FILE" ]; then
|
||||
while read line; do
|
||||
declare -a PARTS=($line)
|
||||
@ -101,19 +103,22 @@ echo "Protractor $PROTRACTOR_VERSION"
|
||||
|
||||
# Run the protractor binary
|
||||
$PROTRACTOR $CONF
|
||||
""".format(TMPL_protractor = protractor_executable_path,
|
||||
TMPL_conf = configuration.short_path))
|
||||
return [DefaultInfo(
|
||||
files = depset([ctx.outputs.executable]),
|
||||
runfiles = ctx.runfiles(
|
||||
files = runfiles,
|
||||
transitive_files = files,
|
||||
# Propagate protractor_bin and its runfiles
|
||||
collect_data = True,
|
||||
collect_default = True,
|
||||
),
|
||||
executable = ctx.outputs.executable,
|
||||
)]
|
||||
""".format(
|
||||
TMPL_protractor = protractor_executable_path,
|
||||
TMPL_conf = configuration.short_path,
|
||||
),
|
||||
)
|
||||
return [DefaultInfo(
|
||||
files = depset([ctx.outputs.executable]),
|
||||
runfiles = ctx.runfiles(
|
||||
files = runfiles,
|
||||
transitive_files = files,
|
||||
# Propagate protractor_bin and its runfiles
|
||||
collect_data = True,
|
||||
collect_default = True,
|
||||
),
|
||||
executable = ctx.outputs.executable,
|
||||
)]
|
||||
|
||||
_protractor_web_test = rule(
|
||||
implementation = _protractor_web_test_impl,
|
||||
@ -124,36 +129,43 @@ _protractor_web_test = rule(
|
||||
doc = "Protractor configuration file",
|
||||
allow_single_file = True,
|
||||
cfg = "data",
|
||||
aspects = [sources_aspect]),
|
||||
aspects = [sources_aspect],
|
||||
),
|
||||
"srcs": attr.label_list(
|
||||
doc = "A list of JavaScript test files",
|
||||
allow_files = [".js"]),
|
||||
allow_files = [".js"],
|
||||
),
|
||||
"on_prepare": attr.label(
|
||||
doc = """A file with a node.js script to run once before all tests run.
|
||||
If the script exports a function which returns a promise, protractor
|
||||
will wait for the promise to resolve before beginning tests.""",
|
||||
allow_single_file = True,
|
||||
cfg = "data",
|
||||
aspects = [sources_aspect]),
|
||||
aspects = [sources_aspect],
|
||||
),
|
||||
"deps": attr.label_list(
|
||||
doc = "Other targets which produce JavaScript such as `ts_library`",
|
||||
allow_files = True,
|
||||
aspects = [sources_aspect]),
|
||||
aspects = [sources_aspect],
|
||||
),
|
||||
"data": attr.label_list(
|
||||
doc = "Runtime dependencies",
|
||||
cfg = "data"),
|
||||
cfg = "data",
|
||||
),
|
||||
"server": attr.label(
|
||||
doc = "Optional server executable target",
|
||||
executable = True,
|
||||
cfg = "data",
|
||||
single_file = False,
|
||||
allow_files = True),
|
||||
allow_files = True,
|
||||
),
|
||||
"protractor": attr.label(
|
||||
doc = "Protractor executable target (set by protractor_web_test macro)",
|
||||
executable = True,
|
||||
cfg = "data",
|
||||
single_file = False,
|
||||
allow_files = True),
|
||||
allow_files = True,
|
||||
),
|
||||
"_conf_tmpl": attr.label(
|
||||
default = Label(_CONF_TMPL),
|
||||
allow_single_file = True,
|
||||
@ -162,180 +174,184 @@ _protractor_web_test = rule(
|
||||
)
|
||||
|
||||
def protractor_web_test(
|
||||
name,
|
||||
configuration = None,
|
||||
on_prepare = None,
|
||||
srcs = [],
|
||||
deps = [],
|
||||
data = [],
|
||||
server = None,
|
||||
tags = [],
|
||||
**kwargs):
|
||||
"""Runs a protractor test in a browser.
|
||||
name,
|
||||
configuration = None,
|
||||
on_prepare = None,
|
||||
srcs = [],
|
||||
deps = [],
|
||||
data = [],
|
||||
server = None,
|
||||
tags = [],
|
||||
**kwargs):
|
||||
"""Runs a protractor test in a browser.
|
||||
|
||||
Args:
|
||||
name: The name of the test
|
||||
configuration: Protractor configuration file.
|
||||
on_prepare: A file with a node.js script to run once before all tests run.
|
||||
If the script exports a function which returns a promise, protractor
|
||||
will wait for the promise to resolve before beginning tests.
|
||||
srcs: JavaScript source files
|
||||
deps: Other targets which produce JavaScript such as `ts_library`
|
||||
data: Runtime dependencies
|
||||
server: Optional server executable target
|
||||
tags: Standard Bazel tags, this macro adds one for ibazel
|
||||
**kwargs: passed through to `_protractor_web_test`
|
||||
"""
|
||||
Args:
|
||||
name: The name of the test
|
||||
configuration: Protractor configuration file.
|
||||
on_prepare: A file with a node.js script to run once before all tests run.
|
||||
If the script exports a function which returns a promise, protractor
|
||||
will wait for the promise to resolve before beginning tests.
|
||||
srcs: JavaScript source files
|
||||
deps: Other targets which produce JavaScript such as `ts_library`
|
||||
data: Runtime dependencies
|
||||
server: Optional server executable target
|
||||
tags: Standard Bazel tags, this macro adds one for ibazel
|
||||
**kwargs: passed through to `_protractor_web_test`
|
||||
"""
|
||||
|
||||
protractor_bin_name = name + "_protractor_bin"
|
||||
protractor_bin_name = name + "_protractor_bin"
|
||||
|
||||
nodejs_binary(
|
||||
name = protractor_bin_name,
|
||||
entry_point = "protractor/bin/protractor",
|
||||
data = srcs + deps + data,
|
||||
node_modules = "@//:node_modules",
|
||||
testonly = 1,
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
nodejs_binary(
|
||||
name = protractor_bin_name,
|
||||
entry_point = "protractor/bin/protractor",
|
||||
data = srcs + deps + data,
|
||||
node_modules = "@//:node_modules",
|
||||
testonly = 1,
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
# Our binary dependency must be in data[] for collect_data to pick it up
|
||||
# FIXME: maybe we can just ask :protractor_bin_name for its runfiles attr
|
||||
web_test_data = data + [":" + protractor_bin_name]
|
||||
if server:
|
||||
web_test_data += [server]
|
||||
# Our binary dependency must be in data[] for collect_data to pick it up
|
||||
# FIXME: maybe we can just ask :protractor_bin_name for its runfiles attr
|
||||
web_test_data = data + [":" + protractor_bin_name]
|
||||
if server:
|
||||
web_test_data += [server]
|
||||
|
||||
_protractor_web_test(
|
||||
name = name,
|
||||
configuration = configuration,
|
||||
on_prepare=on_prepare,
|
||||
srcs = srcs,
|
||||
deps = deps,
|
||||
data = web_test_data,
|
||||
server = server,
|
||||
protractor = protractor_bin_name,
|
||||
tags = tags + [
|
||||
# Users don't need to know that this tag is required to run under ibazel
|
||||
"ibazel_notify_changes",
|
||||
],
|
||||
**kwargs)
|
||||
_protractor_web_test(
|
||||
name = name,
|
||||
configuration = configuration,
|
||||
on_prepare = on_prepare,
|
||||
srcs = srcs,
|
||||
deps = deps,
|
||||
data = web_test_data,
|
||||
server = server,
|
||||
protractor = protractor_bin_name,
|
||||
tags = tags + [
|
||||
# Users don't need to know that this tag is required to run under ibazel
|
||||
"ibazel_notify_changes",
|
||||
],
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def protractor_web_test_suite(
|
||||
name,
|
||||
configuration = None,
|
||||
on_prepare = None,
|
||||
srcs = [],
|
||||
deps = [],
|
||||
data = [],
|
||||
server = None,
|
||||
browsers=["@io_bazel_rules_webtesting//browsers:chromium-local"],
|
||||
args=None,
|
||||
browser_overrides=None,
|
||||
config=None,
|
||||
flaky=None,
|
||||
local=None,
|
||||
shard_count=None,
|
||||
size=None,
|
||||
tags = [],
|
||||
test_suite_tags=None,
|
||||
timeout=None,
|
||||
visibility=None,
|
||||
web_test_data=[],
|
||||
wrapped_test_tags=None,
|
||||
**remaining_keyword_args):
|
||||
"""Defines a test_suite of web_test targets that wrap a protractor_web_test target.
|
||||
name,
|
||||
configuration = None,
|
||||
on_prepare = None,
|
||||
srcs = [],
|
||||
deps = [],
|
||||
data = [],
|
||||
server = None,
|
||||
browsers = ["@io_bazel_rules_webtesting//browsers:chromium-local"],
|
||||
args = None,
|
||||
browser_overrides = None,
|
||||
config = None,
|
||||
flaky = None,
|
||||
local = None,
|
||||
shard_count = None,
|
||||
size = None,
|
||||
tags = [],
|
||||
test_suite_tags = None,
|
||||
timeout = None,
|
||||
visibility = None,
|
||||
web_test_data = [],
|
||||
wrapped_test_tags = None,
|
||||
**remaining_keyword_args):
|
||||
"""Defines a test_suite of web_test targets that wrap a protractor_web_test target.
|
||||
|
||||
Args:
|
||||
name: The base name of the test.
|
||||
configuration: Protractor configuration file.
|
||||
on_prepare: A file with a node.js script to run once before all tests run.
|
||||
If the script exports a function which returns a promise, protractor
|
||||
will wait for the promise to resolve before beginning tests.
|
||||
srcs: JavaScript source files
|
||||
deps: Other targets which produce JavaScript such as `ts_library`
|
||||
data: Runtime dependencies
|
||||
server: Optional server executable target
|
||||
browsers: A sequence of labels specifying the browsers to use.
|
||||
args: Args for web_test targets generated by this extension.
|
||||
browser_overrides: Dictionary; optional; default is an empty dictionary. A
|
||||
dictionary mapping from browser names to browser-specific web_test
|
||||
attributes, such as shard_count, flakiness, timeout, etc. For example:
|
||||
{'//browsers:chrome-native': {'shard_count': 3, 'flaky': 1}
|
||||
'//browsers:firefox-native': {'shard_count': 1, 'timeout': 100}}.
|
||||
config: Label; optional; Configuration of web test features.
|
||||
flaky: A boolean specifying that the test is flaky. If set, the test will
|
||||
be retried up to 3 times (default: 0)
|
||||
local: boolean; optional.
|
||||
shard_count: The number of test shards to use per browser. (default: 1)
|
||||
size: A string specifying the test size. (default: 'large')
|
||||
tags: A list of test tag strings to apply to each generated web_test target.
|
||||
This macro adds a couple for ibazel.
|
||||
test_suite_tags: A list of tag strings for the generated test_suite.
|
||||
timeout: A string specifying the test timeout (default: computed from size)
|
||||
visibility: List of labels; optional.
|
||||
web_test_data: Data dependencies for the web_test.
|
||||
wrapped_test_tags: A list of test tag strings to use for the wrapped test
|
||||
**remaining_keyword_args: Arguments for the wrapped test target.
|
||||
"""
|
||||
# Check explicitly for None so that users can set this to the empty list
|
||||
if wrapped_test_tags == None:
|
||||
wrapped_test_tags = DEFAULT_WRAPPED_TEST_TAGS
|
||||
Args:
|
||||
name: The base name of the test.
|
||||
configuration: Protractor configuration file.
|
||||
on_prepare: A file with a node.js script to run once before all tests run.
|
||||
If the script exports a function which returns a promise, protractor
|
||||
will wait for the promise to resolve before beginning tests.
|
||||
srcs: JavaScript source files
|
||||
deps: Other targets which produce JavaScript such as `ts_library`
|
||||
data: Runtime dependencies
|
||||
server: Optional server executable target
|
||||
browsers: A sequence of labels specifying the browsers to use.
|
||||
args: Args for web_test targets generated by this extension.
|
||||
browser_overrides: Dictionary; optional; default is an empty dictionary. A
|
||||
dictionary mapping from browser names to browser-specific web_test
|
||||
attributes, such as shard_count, flakiness, timeout, etc. For example:
|
||||
{'//browsers:chrome-native': {'shard_count': 3, 'flaky': 1}
|
||||
'//browsers:firefox-native': {'shard_count': 1, 'timeout': 100}}.
|
||||
config: Label; optional; Configuration of web test features.
|
||||
flaky: A boolean specifying that the test is flaky. If set, the test will
|
||||
be retried up to 3 times (default: 0)
|
||||
local: boolean; optional.
|
||||
shard_count: The number of test shards to use per browser. (default: 1)
|
||||
size: A string specifying the test size. (default: 'large')
|
||||
tags: A list of test tag strings to apply to each generated web_test target.
|
||||
This macro adds a couple for ibazel.
|
||||
test_suite_tags: A list of tag strings for the generated test_suite.
|
||||
timeout: A string specifying the test timeout (default: computed from size)
|
||||
visibility: List of labels; optional.
|
||||
web_test_data: Data dependencies for the web_test.
|
||||
wrapped_test_tags: A list of test tag strings to use for the wrapped test
|
||||
**remaining_keyword_args: Arguments for the wrapped test target.
|
||||
"""
|
||||
|
||||
size = size or "large"
|
||||
# Check explicitly for None so that users can set this to the empty list
|
||||
if wrapped_test_tags == None:
|
||||
wrapped_test_tags = DEFAULT_WRAPPED_TEST_TAGS
|
||||
|
||||
wrapped_test_name = name + "_wrapped_test"
|
||||
protractor_bin_name = name + "_protractor_bin"
|
||||
size = size or "large"
|
||||
|
||||
# Users don't need to know that this tag is required to run under ibazel
|
||||
tags = tags + ["ibazel_notify_changes"]
|
||||
wrapped_test_name = name + "_wrapped_test"
|
||||
protractor_bin_name = name + "_protractor_bin"
|
||||
|
||||
nodejs_binary(
|
||||
name = protractor_bin_name,
|
||||
entry_point = "protractor/bin/protractor",
|
||||
data = srcs + deps + data,
|
||||
node_modules = "@//:node_modules",
|
||||
testonly = 1,
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
# Users don't need to know that this tag is required to run under ibazel
|
||||
tags = tags + ["ibazel_notify_changes"]
|
||||
|
||||
# Our binary dependency must be in data[] for collect_data to pick it up
|
||||
# FIXME: maybe we can just ask the :protractor_bin_name for its runfiles attr
|
||||
web_test_data = web_test_data + [":" + protractor_bin_name]
|
||||
if server:
|
||||
web_test_data += [server]
|
||||
nodejs_binary(
|
||||
name = protractor_bin_name,
|
||||
entry_point = "protractor/bin/protractor",
|
||||
data = srcs + deps + data,
|
||||
node_modules = "@//:node_modules",
|
||||
testonly = 1,
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
_protractor_web_test(
|
||||
name=wrapped_test_name,
|
||||
configuration=configuration,
|
||||
on_prepare=on_prepare,
|
||||
srcs=srcs,
|
||||
deps=deps,
|
||||
data=web_test_data,
|
||||
server=server,
|
||||
protractor=protractor_bin_name,
|
||||
args=args,
|
||||
flaky=flaky,
|
||||
local=local,
|
||||
shard_count=shard_count,
|
||||
size=size,
|
||||
tags=wrapped_test_tags,
|
||||
timeout=timeout,
|
||||
visibility=["//visibility:private"],
|
||||
**remaining_keyword_args)
|
||||
# Our binary dependency must be in data[] for collect_data to pick it up
|
||||
# FIXME: maybe we can just ask the :protractor_bin_name for its runfiles attr
|
||||
web_test_data = web_test_data + [":" + protractor_bin_name]
|
||||
if server:
|
||||
web_test_data += [server]
|
||||
|
||||
web_test_suite(
|
||||
name=name,
|
||||
launcher=":"+wrapped_test_name,
|
||||
args=args,
|
||||
browsers=browsers,
|
||||
browser_overrides=browser_overrides,
|
||||
config=config,
|
||||
data=web_test_data,
|
||||
flaky=flaky,
|
||||
local=local,
|
||||
shard_count=shard_count,
|
||||
size=size,
|
||||
tags=tags,
|
||||
test=wrapped_test_name,
|
||||
test_suite_tags=test_suite_tags,
|
||||
timeout=timeout,
|
||||
visibility=visibility)
|
||||
_protractor_web_test(
|
||||
name = wrapped_test_name,
|
||||
configuration = configuration,
|
||||
on_prepare = on_prepare,
|
||||
srcs = srcs,
|
||||
deps = deps,
|
||||
data = web_test_data,
|
||||
server = server,
|
||||
protractor = protractor_bin_name,
|
||||
args = args,
|
||||
flaky = flaky,
|
||||
local = local,
|
||||
shard_count = shard_count,
|
||||
size = size,
|
||||
tags = wrapped_test_tags,
|
||||
timeout = timeout,
|
||||
visibility = ["//visibility:private"],
|
||||
**remaining_keyword_args
|
||||
)
|
||||
|
||||
web_test_suite(
|
||||
name = name,
|
||||
launcher = ":" + wrapped_test_name,
|
||||
args = args,
|
||||
browsers = browsers,
|
||||
browser_overrides = browser_overrides,
|
||||
config = config,
|
||||
data = web_test_data,
|
||||
flaky = flaky,
|
||||
local = local,
|
||||
shard_count = shard_count,
|
||||
size = size,
|
||||
tags = tags,
|
||||
test = wrapped_test_name,
|
||||
test_suite_tags = test_suite_tags,
|
||||
timeout = timeout,
|
||||
visibility = visibility,
|
||||
)
|
||||
|
@ -1,26 +1,22 @@
|
||||
"""Allows different paths for these imports in google3.
|
||||
"""
|
||||
|
||||
load("@build_bazel_rules_typescript//internal:build_defs.bzl",
|
||||
load(
|
||||
"@build_bazel_rules_typescript//internal:build_defs.bzl",
|
||||
_tsc_wrapped_tsconfig = "tsc_wrapped_tsconfig",
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_typescript//internal:common/compilation.bzl",
|
||||
load(
|
||||
"@build_bazel_rules_typescript//internal:common/compilation.bzl",
|
||||
_COMMON_ATTRIBUTES = "COMMON_ATTRIBUTES",
|
||||
_COMMON_OUTPUTS = "COMMON_OUTPUTS",
|
||||
_compile_ts = "compile_ts",
|
||||
_DEPS_ASPECTS = "DEPS_ASPECTS",
|
||||
_compile_ts = "compile_ts",
|
||||
_ts_providers_dict_to_struct = "ts_providers_dict_to_struct",
|
||||
)
|
||||
|
||||
load("@build_bazel_rules_typescript//internal:common/json_marshal.bzl",
|
||||
_json_marshal = "json_marshal",
|
||||
)
|
||||
|
||||
tsc_wrapped_tsconfig = _tsc_wrapped_tsconfig
|
||||
COMMON_ATTRIBUTES = _COMMON_ATTRIBUTES
|
||||
COMMON_OUTPUTS = _COMMON_OUTPUTS
|
||||
compile_ts = _compile_ts
|
||||
DEPS_ASPECTS = _DEPS_ASPECTS
|
||||
ts_providers_dict_to_struct = _ts_providers_dict_to_struct
|
||||
json_marshal = _json_marshal
|
||||
|
@ -229,14 +229,8 @@ describe('@angular/core ng_package', () => {
|
||||
expect(shx.cat('testing.metadata.json'))
|
||||
.toContain(`"exports":[{"from":"./testing/testing"}],"flatModuleIndexRedirect":true`);
|
||||
});
|
||||
|
||||
it('should have an \'actual\' metadata.json file', () => {
|
||||
expect(shx.cat('testing/testing.metadata.json'))
|
||||
.toContain(`"metadata":{"async":{"__symbolic":"function"},`);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('fesm2015', () => {
|
||||
it('should have a fesm15 file in the /fesm2015 directory',
|
||||
() => { expect(shx.cat('fesm2015/testing.js')).toContain(`export {`); });
|
||||
|
@ -30,6 +30,6 @@ jasmine_node_test(
|
||||
":angular_core",
|
||||
"//packages/bazel/test/ngc-wrapped/empty:empty_tsconfig.json",
|
||||
"//packages/bazel/test/ngc-wrapped/empty:tsconfig.json",
|
||||
"@build_bazel_rules_typescript//internal:worker_protocol.proto",
|
||||
"@build_bazel_rules_typescript//third_party/github.com/bazelbuild/bazel/src/main/protobuf:worker_protocol.proto",
|
||||
],
|
||||
)
|
||||
|
@ -6,6 +6,7 @@ load("//tools/http-server:http_server.bzl", "http_server")
|
||||
ts_library(
|
||||
name = "app",
|
||||
srcs = ["app.ts"],
|
||||
tsconfig = ":tsconfig.json",
|
||||
)
|
||||
|
||||
ts_devserver(
|
||||
@ -33,6 +34,7 @@ ts_library(
|
||||
name = "ts_spec",
|
||||
testonly = True,
|
||||
srcs = ["test.spec.ts"],
|
||||
tsconfig = ":tsconfig.json",
|
||||
)
|
||||
|
||||
protractor_web_test_suite(
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es2015"]
|
||||
"lib": ["dom", "es2015"]
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ ts_library(
|
||||
name = "ts_spec",
|
||||
testonly = True,
|
||||
srcs = ["test.spec.ts"],
|
||||
tsconfig = ":tsconfig.json",
|
||||
)
|
||||
|
||||
ts_library(
|
||||
|
@ -28,13 +28,13 @@ export interface HttpParameterCodec {
|
||||
*
|
||||
*/
|
||||
export class HttpUrlEncodingCodec implements HttpParameterCodec {
|
||||
encodeKey(k: string): string { return standardEncoding(k); }
|
||||
encodeKey(key: string): string { return standardEncoding(key); }
|
||||
|
||||
encodeValue(v: string): string { return standardEncoding(v); }
|
||||
encodeValue(value: string): string { return standardEncoding(value); }
|
||||
|
||||
decodeKey(k: string): string { return decodeURIComponent(k); }
|
||||
decodeKey(key: string): string { return decodeURIComponent(key); }
|
||||
|
||||
decodeValue(v: string) { return decodeURIComponent(v); }
|
||||
decodeValue(value: string) { return decodeURIComponent(value); }
|
||||
}
|
||||
|
||||
|
||||
|
@ -51,22 +51,22 @@ export class NgClass implements DoCheck {
|
||||
private _ngEl: ElementRef, private _renderer: Renderer2) {}
|
||||
|
||||
@Input('class')
|
||||
set klass(v: string) {
|
||||
set klass(value: string) {
|
||||
this._removeClasses(this._initialClasses);
|
||||
this._initialClasses = typeof v === 'string' ? v.split(/\s+/) : [];
|
||||
this._initialClasses = typeof value === 'string' ? value.split(/\s+/) : [];
|
||||
this._applyClasses(this._initialClasses);
|
||||
this._applyClasses(this._rawClass);
|
||||
}
|
||||
|
||||
@Input()
|
||||
set ngClass(v: string|string[]|Set<string>|{[klass: string]: any}) {
|
||||
set ngClass(value: string|string[]|Set<string>|{[klass: string]: any}) {
|
||||
this._removeClasses(this._rawClass);
|
||||
this._applyClasses(this._initialClasses);
|
||||
|
||||
this._iterableDiffer = null;
|
||||
this._keyValueDiffer = null;
|
||||
|
||||
this._rawClass = typeof v === 'string' ? v.split(/\s+/) : v;
|
||||
this._rawClass = typeof value === 'string' ? value.split(/\s+/) : value;
|
||||
|
||||
if (this._rawClass) {
|
||||
if (isListLikeIterable(this._rawClass)) {
|
||||
|
@ -16,6 +16,8 @@ import {ComponentFactoryResolver, ComponentRef, Directive, Injector, Input, NgMo
|
||||
* `NgComponentOutlet` requires a component type, if a falsy value is set the view will clear and
|
||||
* any existing component will get destroyed.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* ### Fine tune control
|
||||
*
|
||||
* You can control the component creation process by using the following optional attributes:
|
||||
@ -50,7 +52,8 @@ import {ComponentFactoryResolver, ComponentRef, Directive, Injector, Input, NgMo
|
||||
* ngModuleFactory: moduleFactory;">
|
||||
* </ng-container>
|
||||
* ```
|
||||
* ## Example
|
||||
*
|
||||
* ### A simple example
|
||||
*
|
||||
* {@example common/ngComponentOutlet/ts/module.ts region='SimpleExample'}
|
||||
*
|
||||
|
@ -27,6 +27,8 @@ export class NgForOfContext<T> {
|
||||
* for each instantiated template inherits from the outer context with the given loop variable
|
||||
* set to the current item from the iterable.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* ### Local Variables
|
||||
*
|
||||
* `NgForOf` provides several exported values that can be aliased to local variables:
|
||||
|
@ -17,13 +17,16 @@ import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef, ɵstri
|
||||
* - `then` template is the inline template of `ngIf` unless bound to a different value.
|
||||
* - `else` template is blank unless it is bound.
|
||||
*
|
||||
* ## Most common usage
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* ### Most common usage
|
||||
*
|
||||
* The most common usage of the `ngIf` directive is to conditionally show the inline template as
|
||||
* seen in this example:
|
||||
* {@example common/ngIf/ts/module.ts region='NgIfSimple'}
|
||||
*
|
||||
* ## Showing an alternative template using `else`
|
||||
* ### Showing an alternative template using `else`
|
||||
*
|
||||
* If it is necessary to display a template when the `expression` is falsy use the `else` template
|
||||
* binding as shown. Note that the `else` binding points to a `<ng-template>` labeled `#elseBlock`.
|
||||
@ -32,7 +35,7 @@ import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef, ɵstri
|
||||
*
|
||||
* {@example common/ngIf/ts/module.ts region='NgIfElse'}
|
||||
*
|
||||
* ## Using non-inlined `then` template
|
||||
* ### Using non-inlined `then` template
|
||||
*
|
||||
* Usually the `then` template is the inlined template of the `ngIf`, but it can be changed using
|
||||
* a binding (just like `else`). Because `then` and `else` are bindings, the template references can
|
||||
@ -40,7 +43,7 @@ import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef, ɵstri
|
||||
*
|
||||
* {@example common/ngIf/ts/module.ts region='NgIfThenElse'}
|
||||
*
|
||||
* ## Storing conditional result in a variable
|
||||
* ### Storing conditional result in a variable
|
||||
*
|
||||
* A common pattern is that we need to show a set of properties from the same object. If the
|
||||
* object is undefined, then we have to use the safe-traversal-operator `?.` to guard against
|
||||
|
@ -41,10 +41,10 @@ export class NgStyle implements DoCheck {
|
||||
private _differs: KeyValueDiffers, private _ngEl: ElementRef, private _renderer: Renderer2) {}
|
||||
|
||||
@Input()
|
||||
set ngStyle(v: {[key: string]: string}) {
|
||||
this._ngStyle = v;
|
||||
if (!this._differ && v) {
|
||||
this._differ = this._differs.find(v).create();
|
||||
set ngStyle(values: {[key: string]: string}) {
|
||||
this._ngStyle = values;
|
||||
if (!this._differ && values) {
|
||||
this._differ = this._differs.find(values).create();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,11 +11,6 @@ import {Directive, EmbeddedViewRef, Input, OnChanges, SimpleChange, SimpleChange
|
||||
/**
|
||||
* @ngModule CommonModule
|
||||
*
|
||||
* @usageNotes
|
||||
* ```
|
||||
* <ng-container *ngTemplateOutlet="templateRefExp; context: contextExp"></ng-container>
|
||||
* ```
|
||||
*
|
||||
* @description
|
||||
*
|
||||
* Inserts an embedded view from a prepared `TemplateRef`.
|
||||
@ -24,13 +19,17 @@ import {Directive, EmbeddedViewRef, Input, OnChanges, SimpleChange, SimpleChange
|
||||
* `[ngTemplateOutletContext]` should be an object, the object's keys will be available for binding
|
||||
* by the local template `let` declarations.
|
||||
*
|
||||
* Note: using the key `$implicit` in the context object will set its value as default.
|
||||
* @usageNotes
|
||||
* ```
|
||||
* <ng-container *ngTemplateOutlet="templateRefExp; context: contextExp"></ng-container>
|
||||
* ```
|
||||
*
|
||||
* ## Example
|
||||
* Using the key `$implicit` in the context object will set its value as default.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example common/ngTemplateOutlet/ts/module.ts region='NgTemplateOutlet'}
|
||||
*
|
||||
*
|
||||
*/
|
||||
@Directive({selector: '[ngTemplateOutlet]'})
|
||||
export class NgTemplateOutlet implements OnChanges {
|
||||
|
@ -25,6 +25,8 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
|
||||
* For instance, if you call `location.go('/foo')`, the browser's URL will become
|
||||
* `example.com#/foo`.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example common/location/ts/hash_location_component.ts region='LocationComponent'}
|
||||
|
@ -27,7 +27,9 @@ export interface PopStateEvent {
|
||||
* Depending on which {@link LocationStrategy} is used, `Location` will either persist
|
||||
* to the URL's path or the URL's hash segment.
|
||||
*
|
||||
* Note: it's better to use {@link Router#navigate} service to trigger route changes. Use
|
||||
* @usageNotes
|
||||
*
|
||||
* It's better to use {@link Router#navigate} service to trigger route changes. Use
|
||||
* `Location` only if you need to interact with or create normalized URLs outside of
|
||||
* routing.
|
||||
*
|
||||
@ -39,6 +41,7 @@ export interface PopStateEvent {
|
||||
* - `/my/app/user/123/` **is not** normalized
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example common/location/ts/path_location_component.ts region='LocationComponent'}
|
||||
*
|
||||
*/
|
||||
|
@ -47,6 +47,8 @@ export abstract class LocationStrategy {
|
||||
* representing the URL prefix that should be preserved when generating and recognizing
|
||||
* URLs.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```typescript
|
||||
|
@ -34,6 +34,8 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
|
||||
* `location.go('/foo')`, the browser's URL will become
|
||||
* `example.com/my/app/foo`.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example common/location/ts/path_location_component.ts region='LocationComponent'}
|
||||
|
@ -67,4 +67,4 @@ export interface LocationChangeEvent {
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
export interface LocationChangeListener { (e: LocationChangeEvent): any; }
|
||||
export interface LocationChangeListener { (event: LocationChangeEvent): any; }
|
||||
|
@ -51,8 +51,9 @@ const _observableStrategy = new ObservableStrategy();
|
||||
* changes. When the component gets destroyed, the `async` pipe unsubscribes automatically to avoid
|
||||
* potential memory leaks.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* ## Examples
|
||||
* ### Examples
|
||||
*
|
||||
* This example binds a `Promise` to the view. Clicking the `Resolve` button resolves the
|
||||
* promise.
|
||||
@ -64,7 +65,6 @@ const _observableStrategy = new ObservableStrategy();
|
||||
*
|
||||
* {@example common/pipes/ts/async_pipe.ts region='AsyncPipeObservable'}
|
||||
*
|
||||
*
|
||||
*/
|
||||
@Pipe({name: 'async', pure: false})
|
||||
export class AsyncPipe implements OnDestroy, PipeTransform {
|
||||
|
@ -64,6 +64,8 @@ import {DateFormatter} from './intl';
|
||||
* - this pipe uses the Internationalization API. Therefore it is only reliable in Chrome and Opera
|
||||
* browsers.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* ### Examples
|
||||
*
|
||||
* Assuming `dateObj` is (year: 2010, month: 9, day: 3, hour: 12 PM, minute: 05, second: 08)
|
||||
|
@ -78,11 +78,12 @@ function formatNumber(
|
||||
* WARNING: this pipe uses the Internationalization API which is not yet available in all browsers
|
||||
* and may require a polyfill. See [Browser Support](guide/browser-support) for details.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example common/pipes/ts/number_pipe.ts region='DeprecatedNumberPipe'}
|
||||
*
|
||||
*
|
||||
*/
|
||||
@Pipe({name: 'number'})
|
||||
export class DeprecatedDecimalPipe implements PipeTransform {
|
||||
@ -106,6 +107,8 @@ export class DeprecatedDecimalPipe implements PipeTransform {
|
||||
* WARNING: this pipe uses the Internationalization API which is not yet available in all browsers
|
||||
* and may require a polyfill. See [Browser Support](guide/browser-support) for details.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example common/pipes/ts/percent_pipe.ts region='DeprecatedPercentPipe'}
|
||||
@ -140,6 +143,8 @@ export class DeprecatedPercentPipe implements PipeTransform {
|
||||
* WARNING: this pipe uses the Internationalization API which is not yet available in all browsers
|
||||
* and may require a polyfill. See [Browser Support](guide/browser-support) for details.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example common/pipes/ts/currency_pipe.ts region='DeprecatedCurrencyPipe'}
|
||||
|
@ -18,7 +18,9 @@ const _INTERPOLATION_REGEXP: RegExp = /#/g;
|
||||
*
|
||||
* Maps a value to a string that pluralizes the value according to locale rules.
|
||||
*
|
||||
* ## Example
|
||||
* @usageNotes
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example common/pipes/ts/i18n_pipe.ts region='I18nPluralPipeComponent'}
|
||||
*
|
||||
|
@ -18,7 +18,9 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
* If none of the keys of the `mapping` match the `value`, then the content
|
||||
* of the `other` key is returned when present, otherwise an empty string is returned.
|
||||
*
|
||||
* ## Example
|
||||
* @usageNotes
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example common/pipes/ts/i18n_pipe.ts region='I18nSelectPipeComponent'}
|
||||
*
|
||||
|
@ -18,9 +18,8 @@ import {Pipe, PipeTransform} from '@angular/core';
|
||||
*
|
||||
* The following component uses a JSON pipe to convert an object
|
||||
* to JSON format, and displays the string in both formats for comparison.
|
||||
|
||||
* {@example common/pipes/ts/json_pipe.ts region='JsonPipe'}
|
||||
*
|
||||
* {@example common/pipes/ts/json_pipe.ts region='JsonPipe'}
|
||||
*
|
||||
*/
|
||||
@Pipe({name: 'json', pure: false})
|
||||
|
@ -44,7 +44,6 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
*
|
||||
* <code-example path="common/pipes/ts/number_pipe.ts" region='NumberPipe'></code-example>
|
||||
*
|
||||
*
|
||||
*/
|
||||
@Pipe({name: 'number'})
|
||||
export class DecimalPipe implements PipeTransform {
|
||||
|
@ -15,6 +15,8 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
*
|
||||
* Creates a new `Array` or `String` containing a subset (slice) of the elements.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* All behavior is based on the expected behavior of the JavaScript API `Array.prototype.slice()`
|
||||
* and `String.prototype.slice()`.
|
||||
*
|
||||
@ -31,14 +33,15 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
*
|
||||
* produces the following:
|
||||
*
|
||||
* <li>b</li>
|
||||
* <li>c</li>
|
||||
* ```html
|
||||
* <li>b</li>
|
||||
* <li>c</li>
|
||||
* ```
|
||||
*
|
||||
* ## String Examples
|
||||
* ### String Examples
|
||||
*
|
||||
* {@example common/pipes/ts/slice_pipe.ts region='SlicePipe_string'}
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
@Pipe({name: 'slice', pure: false})
|
||||
|
@ -26,6 +26,7 @@ ts_library(
|
||||
deps = [
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/annotations",
|
||||
"//packages/compiler-cli/src/ngtsc/factories",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/transform",
|
||||
],
|
||||
@ -41,5 +42,8 @@ npm_package(
|
||||
"ivy-local",
|
||||
"release-with-framework",
|
||||
],
|
||||
deps = [":compiler-cli"],
|
||||
deps = [
|
||||
":compiler-cli",
|
||||
"//packages/compiler-cli/src/ngcc",
|
||||
],
|
||||
)
|
||||
|
@ -6,16 +6,16 @@
|
||||
"""
|
||||
|
||||
def _extract_flat_module_index(ctx):
|
||||
files = []
|
||||
for dep in ctx.attr.deps:
|
||||
if hasattr(dep, "angular"):
|
||||
metadata = dep.angular.flat_module_metadata
|
||||
files.extend([metadata.metadata_file, metadata.typings_file])
|
||||
return [DefaultInfo(files = depset(files))]
|
||||
files = []
|
||||
for dep in ctx.attr.deps:
|
||||
if hasattr(dep, "angular"):
|
||||
metadata = dep.angular.flat_module_metadata
|
||||
files.extend([metadata.metadata_file, metadata.typings_file])
|
||||
return [DefaultInfo(files = depset(files))]
|
||||
|
||||
extract_flat_module_index = rule(
|
||||
implementation = _extract_flat_module_index,
|
||||
attrs = {
|
||||
"deps": attr.label_list(),
|
||||
"deps": attr.label_list(),
|
||||
},
|
||||
)
|
||||
|
@ -5,6 +5,7 @@
|
||||
"main": "index.js",
|
||||
"typings": "index.d.ts",
|
||||
"bin": {
|
||||
"ivy-ngcc": "./src/ngcc/main-ngcc.js",
|
||||
"ngc": "./src/main.js",
|
||||
"ng-xi18n": "./src/extract_i18n.js"
|
||||
},
|
||||
@ -12,7 +13,10 @@
|
||||
"reflect-metadata": "^0.1.2",
|
||||
"minimist": "^1.2.0",
|
||||
"tsickle": "^0.32.1",
|
||||
"chokidar": "^1.4.2"
|
||||
"chokidar": "^1.4.2",
|
||||
"convert-source-map": "^1.5.1",
|
||||
"magic-string": "^0.25.0",
|
||||
"source-map": "^0.6.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=2.7.2 <2.10",
|
||||
|
21
packages/compiler-cli/src/ngcc/BUILD.bazel
Normal file
21
packages/compiler-cli/src/ngcc/BUILD.bazel
Normal file
@ -0,0 +1,21 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
|
||||
|
||||
ts_library(
|
||||
name = "ngcc",
|
||||
srcs = glob([
|
||||
"*.ts",
|
||||
"**/*.ts",
|
||||
]),
|
||||
module_name = "@angular/compiler-cli/src/ngcc",
|
||||
deps = [
|
||||
"//packages:types",
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/annotations",
|
||||
"//packages/compiler-cli/src/ngtsc/host",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/transform",
|
||||
],
|
||||
)
|
30
packages/compiler-cli/src/ngcc/README.md
Normal file
30
packages/compiler-cli/src/ngcc/README.md
Normal file
@ -0,0 +1,30 @@
|
||||
# Angular Compatibility Compiler (ngcc)
|
||||
|
||||
This compiler will convert `node_modules` compiled with `ngc`, into `node_modules` which
|
||||
appear to have been compiled with `ngtsc`.
|
||||
|
||||
This conversion will allow such "legacy" packages to be used by the Ivy rendering engine.
|
||||
|
||||
## Building
|
||||
|
||||
The project is built using Bazel:
|
||||
|
||||
```bash
|
||||
bazel build //packages/compiler-cli/src/ngcc
|
||||
```
|
||||
|
||||
## Unit Testing
|
||||
|
||||
The unit tests are built and run using Bazel:
|
||||
|
||||
```bash
|
||||
bazel test //packages/compiler-cli/src/ngcc/test
|
||||
```
|
||||
|
||||
## Integration Testing
|
||||
|
||||
There are tests that check the behaviour of the overall executable:
|
||||
|
||||
```bash
|
||||
bazel test //packages/compiler-cli/test/ngcc
|
||||
```
|
9
packages/compiler-cli/src/ngcc/index.ts
Normal file
9
packages/compiler-cli/src/ngcc/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export {mainNgcc} from './src/main';
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user