Compare commits

..

86 Commits
6.0.6 ... 6.0.8

Author SHA1 Message Date
024aba075c release: cut the v6.0.8 release 2018-07-11 13:43:59 -07:00
a86d038875 docs(forms): update API reference for form validators (#24734)
PR Close #24734
2018-07-10 18:52:40 -07:00
bc8a93e842 docs(forms): added missing backtick (#24451)
Fixed trivial markdown problem with a missing backtick.

PR Close #24451
2018-07-10 18:51:09 -07:00
4c18f637ae docs: unified string chaining (#22735)
PR Close #22735
2018-07-10 18:50:45 -07:00
fc189b2577 docs(aio): unified string chaining (#22735)
PR Close #22735
2018-07-10 18:50:45 -07:00
778ddb257a docs: fix incorrect forms selector references (#22631)
PR Close #22631
2018-07-10 18:50:18 -07:00
2d4f4b5d12 fix(router): add ability to recover from malformed url (#23283)
Fixes #21468

PR Close #23283
2018-07-10 18:48:53 -07:00
912742f1ca docs: fix typos referencing inline component styles (#22557)
PR Close #22557
2018-07-10 18:48:30 -07:00
0746485136 fix(common): do not round factional seconds (#24831)
fixes #24384

PR Close #24831
2018-07-10 18:48:06 -07:00
265489518e build: update to latest nodejs bazel rules (#24817)
PR Close #24817
2018-07-10 18:47:40 -07:00
00dd566b47 docs: correct project definition (#24807)
PR Close #24807
2018-07-10 18:47:20 -07:00
59fd91d785 Revert "build: make internal-angular karma reporter compatible with latest karma (#24803)" (#24809)
This reverts commit 98c509fecb.
Part of reverting PR #24803 on branch `6.0.x`, because that PR is a
follow-up to PR #19904, which was only merged into master.

PR Close #24809
2018-07-10 15:00:42 -07:00
56712aa771 Revert "build: remove unnecessary internal-angular karma reporter (#24803)" (#24809)
This reverts commit d7c4898081.
Part of reverting PR #24803 on branch `6.0.x`, because that PR is a
follow-up to PR #19904, which was only merged into master.

PR Close #24809
2018-07-10 15:00:42 -07:00
529e4b1565 docs: fix typo in Universal guide (#24812)
PR Close #24812
2018-07-10 11:12:45 -07:00
9ff4617956 docs: add tree-shakable providers (#24481)
PR Close #24481
2018-07-10 11:12:08 -07:00
8887d75723 docs: clarify wording in architecture overview (#24481)
Closes #23463
Closes #22158

PR Close #24481
2018-07-10 11:12:08 -07:00
7717ff187a fix(compiler-cli): Use typescript to resolve modules for metadata (#22856)
The current module resolution simply attaches .ts to the import/export path, which does
not work if the path is using Node / CommonJS behavior to resolve to an index.ts file.
This patch uses typescript's module resolution logic, and will attempt to load the original
typescript file if this resolution returns a .js or .d.ts file

PR Close #22856
2018-07-10 11:11:48 -07:00
98b18cd49f docs: unified console.log single string style (#22737)
PR Close #22737
2018-07-10 11:11:30 -07:00
fe23a6e77e docs(aio): unified console.log single string style (#22737)
PR Close #22737
2018-07-10 11:11:30 -07:00
d7c4898081 build: remove unnecessary internal-angular karma reporter (#24803)
The reporter was added in 87d56acda, with the purpose of fixing
source-map paths (which was apparently needed back then). Things have
moved around a lot since then and the custom reporter doesn't seem to be
necessary any more. By removing the reporter, we have one less thing to
worry about while upgrading karma; plus we get improvements in built-in
reporters for free.

Output with the custom reporter:
```
at someMethod (packages/core/.../some-file.ts:13:37)
```

Output with the built-in reporter:
```
at someMethod (packages/core/.../some-file.ts:13.37 <- dist/all/@angular/core/.../some-file.js:1:337)
```

PR Close #24803
2018-07-09 15:14:02 -07:00
98c509fecb build: make internal-angular karma reporter compatible with latest karma (#24803)
Due to changes in karma@1.0.0, `internal-angular` karma reporter stopped
showing browser logs (such as `console.log()` etc.).
Related to d571a5173.

PR Close #24803
2018-07-09 15:10:49 -07:00
a92f111b66 fix(common): use correct currency format for locale de-AT (#24658)
Fixes #24609
PR Close #24658
2018-07-09 15:10:07 -07:00
de1c44f6e3 fix(language-service): do not overwrite native Reflect (#24299)
Fixes #21420

PR Close #24299
2018-07-09 15:09:17 -07:00
183b079175 fix(service-worker): avoid network requests when looking up hashed resources in cache (#24127)
PR Close #24127
2018-07-06 13:50:38 -07:00
8f8caa13b7 refactor(service-worker): avoid unnecessary operations and remove unused code (#24127)
PR Close #24127
2018-07-06 13:50:38 -07:00
195dc0748b docs: update Angular Boot Camp description (#23653)
PR Close #23653
2018-07-06 13:49:57 -07:00
2c6f84b25d docs: refactored ng-container code (#22742)
PR Close #22742
2018-07-06 13:49:35 -07:00
8e726f7d7b build(bazel): update to rule_nodejs 0.10.0 (#24759)
PR Close #24759
2018-07-06 10:18:21 -07:00
2d1102f5bf docs: add workspace and related cli terms (#24633)
PR Close #24633
2018-07-06 10:13:40 -07:00
0437598609 docs(common): fix in the documentation of PUT (#24528)
PR Close #24528
2018-07-06 10:13:20 -07:00
bc2bf184a2 docs: describe rounding behaviour of 'DecimalPipe' (#24303)
PR Close #24303
2018-07-06 10:13:00 -07:00
b51ce62c58 docs(aio): added a link to Angular Zero online course (Traditional Chinese) (#24228)
PR Close #24228
2018-07-06 10:11:02 -07:00
25b532e819 docs: clarify faqs about services (#24079)
PR Close #24079
2018-07-06 10:10:41 -07:00
69c8226a9f docs: add app.module to changed documents (#23876)
PR Close #23876
2018-07-06 10:10:21 -07:00
5326537985 docs(router): add paramsInheritanceStrategy documentation (#22590)
PR Close #22590
2018-07-06 10:10:02 -07:00
ada486a1dd docs: fix typos in 'Httpclient' docs (#19127)
PR Close #19127
2018-07-06 10:09:41 -07:00
563e8e3e56 docs: fix documention for attributes directive (#24367)
fix:update documentation for attributes directive to fix error

PR Close #24367
2018-07-03 18:34:59 -04:00
dd931c73ec ci: fix broken build (#24747) 2018-07-03 13:30:17 -07:00
b8975a90ca fix(core): use addCustomEqualityTester instead of overriding toEqual (#22983)
This propagates other custom equality testers added by users. Additionally, if
an Angular project is using jasmine 2.6+, it will allow Jasmine's custom object
differ to print out pretty test error messages.

fixes #22939

PR Close #22983
2018-07-03 08:40:00 -07:00
f44161503a docs(aio): update contributors entry (#23786)
PR Close #23786
2018-07-02 15:45:39 -07:00
6c55a130b1 feat(core): add support for ShadowDOM v1 (#24718)
add a new ViewEncapsulation.ShadowDom option that uses the v1 Shadow DOM API to provide style encapsulation.

PR Close #24718
2018-07-02 14:37:42 -07:00
144a624088 docs: update HTTP error test example again (#24701)
This has somehow regressed since angular/angular#22844 was merged.

PR Close #24701
2018-07-02 14:37:18 -07:00
f561f5a460 docs: fix typo in pipes guide (#24452)
PR Close #24452
2018-07-02 14:36:55 -07:00
916914be13 docs: fix docregion in attribute directives for highlight directive (#23972)
Fixes #23503

PR Close #23972
2018-07-02 14:36:24 -07:00
9a98de941d fix(common): properly update collection reference in NgForOf (#24684)
closes #24155

PR Close #24684
2018-06-29 06:43:45 -07:00
0f1de35604 docs(core): rephrase doc for Injector.get (#24670)
PR Close #24670
2018-06-29 06:43:19 -07:00
d89f57f9d5 build(docs-infra): render short description of parameters in API docs (#24537)
PR Close #24537
2018-06-28 15:03:15 -07:00
ac5b69f783 fix(docs-infra): use clean package.json template when generating zips (#24691)
Closes #24689

PR Close #24691
2018-06-28 15:01:01 -07:00
5ddd6dcedd docs: include ts-loader as shared example dependency (#24691)
Closes #24671

PR Close #24691
2018-06-28 15:01:01 -07:00
ad68332fa0 docs: consistent spacing in tutorial html files (#23105) (#24497)
- Removed surrounding spaces in interpolation expressions following the styleguide
- Consistant spacing of two spaces in html

Fixes #23105

PR Close #24497
2018-06-28 17:56:20 -04:00
2880cf9ef1 release: cut the v6.0.7 release 2018-06-27 16:54:22 -07:00
64a8584a92 fix(common): use correct ICU plural for locale mk (#24659)
PR Close #24659
2018-06-27 15:03:34 -07:00
97897ab738 refactor(upgrade): fix examples for strictPropertyInitialization and remove internal comments (#18487)
PR Close #18487
2018-06-27 14:57:54 -07:00
9378f44d6d docs(aio): tech edits to upgrade-lazy (#18487)
PR Close #18487
2018-06-27 14:53:24 -07:00
afa46af4c6 docs(upgrade): use a class for upgraded service (#18487)
This makes the resulting use in Angular more ideomatic, since we can just
use the class type as the injection indicator.

PR Close #18487
2018-06-27 14:53:24 -07:00
dbffdcc442 docs(upgrade): fix sub-ordered-list syntax (#18487)
We must always use 1., 2. etc, to indicate ordered lists, even for sub-lists.
We can change the sublist to display as a., b. etc, via CSS.

PR Close #18487
2018-06-27 14:53:24 -07:00
06d68a1b9f docs(upgrade): add guide about downgradeModule() (#18487)
PR Close #18487
2018-06-27 14:53:24 -07:00
75dd3c5ca5 docs(upgrade): add API docs for downgradeModule() (#18487)
PR Close #18487
2018-06-27 14:53:24 -07:00
23f56198a5 docs(upgrade): add API docs for propagateDigest (#18487)
PR Close #18487
2018-06-27 14:53:24 -07:00
69167e4519 docs(upgrade): update API docs for upgrade/static (#18487)
PR Close #18487
2018-06-27 14:53:22 -07:00
10feafcf27 docs: minor fixes in docs-style-guide (#18487)
PR Close #18487
2018-06-27 14:49:14 -07:00
3f3fed95be test: minor improvements in examples e2e tests script (#18487)
PR Close #18487
2018-06-27 14:49:13 -07:00
94433f3b9e docs: fix unit tests in toh-pt6 (#24491)
Resolves #20373

PR Close #24491
2018-06-27 14:33:26 -07:00
aa753878e6 docs: add explanation for enableResourceInlining (#24644)
PR Close #24644
2018-06-27 14:31:54 -07:00
3513ae40cc docs(aio): fix adding to template driven forms (#23743)
PR Close #23743
2018-06-26 11:03:36 -07:00
979e7b6086 docs(aio): fix issues suggested by Brandon (#23743)
PR Close #23743
2018-06-26 11:03:36 -07:00
41f008d109 docs(aio): fix issues suggested by Kara (#23743)
PR Close #23743
2018-06-26 11:03:36 -07:00
c950410ee1 docs(aio): address pr review issues (#23743)
PR Close #23743
2018-06-26 11:03:36 -07:00
b0fa504d39 docs(aio): add cross field validation example (#23743)
PR Close #23743
2018-06-26 11:03:36 -07:00
426f9710a0 build(docs-infra): support hiding constructors of injectable classes (#24529)
Classes that are injectable often have constructors that should not be
called by the application developer. It is the responsibility of the
injector to instantiate the class and the constructor often contains
private implementation details that may need to change.

This commit removes constructors from the docs for API items that are
both:

a) Marked with an injectable decorator (e.g. Injectable, Directive,
Component, Pipe), and
b) Have no constructor description

This second rule allows the developer to override the removal if there
is something useful to say about the constructor.

Note that "normal" classes such as `angimations/HttpHeaders` do not have
their constructor removed, despite (at this time) having no description.

PR Close #24529
2018-06-26 10:58:11 -07:00
ae01c70bba ci: fix broken build do to bad merge (#24662)
PR Close #24662
2018-06-25 13:11:16 -07:00
3341a97154 docs: test doc for decorator templates (#23902)
PR Close #23902
2018-06-25 10:58:00 -07:00
2056e1f05c Revert "docs: test doc for decorator templates (#23902)"
This reverts commit 3938a8be75.
2018-06-25 10:54:47 -07:00
3938a8be75 docs: test doc for decorator templates (#23902)
PR Close #23902
2018-06-25 10:09:15 -07:00
393db94b8d docs: fix misdirected group links (#24569)
PR Close #24569
2018-06-25 10:03:43 -07:00
65744e4ae1 docs: test api doc for pipes (#24141)
PR Close #24141
2018-06-25 09:37:30 -07:00
56bc86992c test(aio): fix upgrade-phonecat examples e2e tests (#24583)
Closes #19625

PR Close #24583
2018-06-25 09:30:46 -07:00
9426c02648 build: upgrade AngularJS typings (#24583)
Previously, we were using [@types/angularjs][1], which is deprecated and
outdated (hasn't been updated for over two years). This PR switches to
[@types/angular][2], which is regularly updated (based on the
definitions on [DefinitelyTyped][3]).

[1]: https://www.npmjs.com/package/@types/angularjs
[2]: https://www.npmjs.com/package/@types/angular
[3]: https://github.com/DefinitelyTyped/DefinitelyTyped

PR Close #24583
2018-06-25 09:30:46 -07:00
0b356d4163 fix(animations): set animations styles properly on platform-server (#24624)
Animations styles weren't getting properly set on platform-server because of erroneous checks and absence of reflection of style property to attribute on the server.

The fix corrects the check for platform and explicitly reflects the style property to the attribute.

PR Close #24624
2018-06-25 07:58:12 -07:00
a886659444 docs: add guide-angular.wishtack.io to education resources (#24585)
PR Close #24585
2018-06-25 07:57:34 -07:00
3649958595 build(docs-infra): move overload short description above syntax (#24526)
PR Close #24526
2018-06-25 07:56:37 -07:00
8919577c54 docs: update Angular CLI option for sourcemaps (#24437)
PR Close #24437
2018-06-25 07:53:27 -07:00
84eff4219e docs: update lowercase pipe example in "AngularJS to Angular" guide (#24588)
PR Close #24588
2018-06-21 13:14:31 -07:00
c11e84ee18 build(docs-infra): redirect removed webpack guide to v5.angular.io (#24595)
The outdated webpack guide has been removed in #24478, but people might
still try to access it (via direct links or search-engine results).

Instead of returning 404, we will now redirect `/guide/webpack` to the
archived version of the guide at `v5.angular.io/guide/webpack`.

PR Close #24595
2018-06-20 16:51:34 -07:00
1ce5a495d3 docs(aio): add elana olson to contributor.json file (#24579)
Add new contributor, elana olson, to the contributors list.

PR Close #24579
2018-06-20 16:50:54 -07:00
1f532aaa5a feat(docs-infra): Add GitHub and Twitter external icon links to topnav toolbar (#24542)
PR Close #24542
2018-06-20 16:50:15 -07:00
167 changed files with 3582 additions and 1189 deletions

View File

@ -1,3 +1,35 @@
<a name="6.0.8"></a>
## [6.0.8](https://github.com/angular/angular/compare/6.0.7...6.0.8) (2018-07-11)
### Bug Fixes
* **common:** do not round factional seconds ([#24831](https://github.com/angular/angular/issues/24831)) ([0746485](https://github.com/angular/angular/commit/0746485)), closes [#24384](https://github.com/angular/angular/issues/24384)
* **common:** properly update collection reference in NgForOf ([#24684](https://github.com/angular/angular/issues/24684)) ([9a98de9](https://github.com/angular/angular/commit/9a98de9)), closes [#24155](https://github.com/angular/angular/issues/24155)
* **common:** use correct currency format for locale de-AT ([#24658](https://github.com/angular/angular/issues/24658)) ([a92f111](https://github.com/angular/angular/commit/a92f111)), closes [#24609](https://github.com/angular/angular/issues/24609)
* **compiler-cli:** Use typescript to resolve modules for metadata ([#22856](https://github.com/angular/angular/issues/22856)) ([7717ff1](https://github.com/angular/angular/commit/7717ff1))
* **core:** use addCustomEqualityTester instead of overriding toEqual ([#22983](https://github.com/angular/angular/issues/22983)) ([b8975a9](https://github.com/angular/angular/commit/b8975a9)), closes [#22939](https://github.com/angular/angular/issues/22939)
* **language-service:** do not overwrite native `Reflect` ([#24299](https://github.com/angular/angular/issues/24299)) ([de1c44f](https://github.com/angular/angular/commit/de1c44f)), closes [#21420](https://github.com/angular/angular/issues/21420)
* **router:** add ability to recover from malformed url ([#23283](https://github.com/angular/angular/issues/23283)) ([2d4f4b5](https://github.com/angular/angular/commit/2d4f4b5)), closes [#21468](https://github.com/angular/angular/issues/21468)
* **service-worker:** avoid network requests when looking up hashed resources in cache ([#24127](https://github.com/angular/angular/issues/24127)) ([183b079](https://github.com/angular/angular/commit/183b079))
### Features
* **core:** add support for ShadowDOM v1 ([#24718](https://github.com/angular/angular/issues/24718)) ([6c55a13](https://github.com/angular/angular/commit/6c55a13))
<a name="6.0.7"></a>
## [6.0.7](https://github.com/angular/angular/compare/6.0.6...6.0.7) (2018-06-27)
### Bug Fixes
* **animations:** set animations styles properly on platform-server ([#24624](https://github.com/angular/angular/issues/24624)) ([0b356d4](https://github.com/angular/angular/commit/0b356d4))
* **common:** use correct ICU plural for locale mk ([#24659](https://github.com/angular/angular/issues/24659)) ([64a8584](https://github.com/angular/angular/commit/64a8584))
<a name="6.0.6"></a> <a name="6.0.6"></a>
## [6.0.6](https://github.com/angular/angular/compare/6.0.5...6.0.6) (2018-06-20) ## [6.0.6](https://github.com/angular/angular/compare/6.0.5...6.0.6) (2018-06-20)

View File

@ -6,9 +6,9 @@ workspace(name = "angular")
http_archive( http_archive(
name = "build_bazel_rules_nodejs", name = "build_bazel_rules_nodejs",
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.9.1.zip", url = "https://github.com/bazelbuild/rules_nodejs/archive/0.10.1.zip",
strip_prefix = "rules_nodejs-0.9.1", strip_prefix = "rules_nodejs-0.10.1",
sha256 = "6139762b62b37c1fd171d7f22aa39566cb7dc2916f0f801d505a9aaf118c117f", sha256 = "634206524d90dc03c52392fa3f19a16637d2bcf154910436fe1d669a0d9d7b9c",
) )
http_archive( http_archive(

View File

@ -60,6 +60,8 @@ dist/
!rollup-config.js !rollup-config.js
aot-compiler/**/*.d.ts aot-compiler/**/*.d.ts
aot-compiler/**/*.factory.d.ts aot-compiler/**/*.factory.d.ts
upgrade-phonecat-2-hybrid/aot/**/*
!upgrade-phonecat-2-hybrid/aot/index.html
# i18n # i18n
!i18n/src/systemjs-text-plugin.js !i18n/src/systemjs-text-plugin.js

View File

@ -40,5 +40,7 @@ export class HighlightDirective {
// #docregion color-2 // #docregion color-2
@Input() appHighlight: string; @Input() appHighlight: string;
// #enddocregion color-2 // #enddocregion color-2
}
// #docregion
}
// #enddocregion

View File

@ -16,6 +16,7 @@ describe('Form Validation Tests', function () {
tests('Template-Driven Form'); tests('Template-Driven Form');
bobTests(); bobTests();
crossValidationTests();
}); });
describe('Reactive form', () => { describe('Reactive form', () => {
@ -25,6 +26,7 @@ describe('Form Validation Tests', function () {
tests('Reactive Form'); tests('Reactive Form');
bobTests(); bobTests();
crossValidationTests();
}); });
}); });
@ -42,7 +44,8 @@ let page: {
powerOption: ElementFinder, powerOption: ElementFinder,
errorMessages: ElementArrayFinder, errorMessages: ElementArrayFinder,
heroFormButtons: ElementArrayFinder, heroFormButtons: ElementArrayFinder,
heroSubmitted: ElementFinder heroSubmitted: ElementFinder,
crossValidationErrorMessage: ElementFinder,
}; };
function getPage(sectionTag: string) { function getPage(sectionTag: string) {
@ -59,7 +62,8 @@ function getPage(sectionTag: string) {
powerOption: section.element(by.css('#power option')), powerOption: section.element(by.css('#power option')),
errorMessages: section.all(by.css('div.alert')), errorMessages: section.all(by.css('div.alert')),
heroFormButtons: buttons, heroFormButtons: buttons,
heroSubmitted: section.element(by.css('.submitted-message')) heroSubmitted: section.element(by.css('.submitted-message')),
crossValidationErrorMessage: section.element(by.css('.cross-validation-error-message')),
}; };
} }
@ -172,3 +176,29 @@ function bobTests() {
expectFormIsValid(); expectFormIsValid();
}); });
} }
function crossValidationTests() {
const emsg = 'Name cannot match alter ego.';
it(`should produce "${emsg}" error after setting name and alter ego to the same value`, function () {
page.nameInput.clear();
page.nameInput.sendKeys('Batman');
page.alterEgoInput.clear();
page.alterEgoInput.sendKeys('Batman');
expectFormIsInvalid();
expect(page.crossValidationErrorMessage.getText()).toBe(emsg);
});
it('should be ok again with different values', function () {
page.nameInput.clear();
page.nameInput.sendKeys('Batman');
page.alterEgoInput.clear();
page.alterEgoInput.sendKeys('Superman');
expectFormIsValid();
expect(page.crossValidationErrorMessage.isPresent()).toBe(false);
});
}

View File

@ -7,7 +7,7 @@ import { AppComponent } from './app.component';
import { HeroFormTemplateComponent } from './template/hero-form-template.component'; import { HeroFormTemplateComponent } from './template/hero-form-template.component';
import { HeroFormReactiveComponent } from './reactive/hero-form-reactive.component'; import { HeroFormReactiveComponent } from './reactive/hero-form-reactive.component';
import { ForbiddenValidatorDirective } from './shared/forbidden-name.directive'; import { ForbiddenValidatorDirective } from './shared/forbidden-name.directive';
import { IdentityRevealedValidatorDirective } from './shared/identity-revealed.directive';
@NgModule({ @NgModule({
imports: [ imports: [
@ -19,7 +19,8 @@ import { ForbiddenValidatorDirective } from './shared/forbidden-name.directive';
AppComponent, AppComponent,
HeroFormTemplateComponent, HeroFormTemplateComponent,
HeroFormReactiveComponent, HeroFormReactiveComponent,
ForbiddenValidatorDirective ForbiddenValidatorDirective,
IdentityRevealedValidatorDirective
], ],
bootstrap: [ AppComponent ] bootstrap: [ AppComponent ]
}) })

View File

@ -0,0 +1,42 @@
/* 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';
@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;
// #docregion form-group
ngOnInit(): void {
// #docregion custom-validator
this.heroForm = new FormGroup({
'name': new FormControl(this.hero.name, [
Validators.required,
Validators.minLength(4),
forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
]),
'alterEgo': new FormControl(this.hero.alterEgo),
'power': new FormControl(this.hero.power, Validators.required)
});
// #enddocregion custom-validator
}
get name() { return this.heroForm.get('name'); }
get power() { return this.heroForm.get('power'); }
// #enddocregion form-group
}
// #enddocregion

View File

@ -0,0 +1,5 @@
/* #docregion cross-validation-error-css */
.cross-validation-error input {
border-left: 5px solid red;
}
/* #enddocregion cross-validation-error-css */

View File

@ -7,6 +7,7 @@
<div [hidden]="formDir.submitted"> <div [hidden]="formDir.submitted">
<div class="cross-validation" [class.cross-validation-error]="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)">
<div class="form-group"> <div class="form-group">
<label for="name">Name</label> <label for="name">Name</label>
@ -36,6 +37,13 @@
formControlName="alterEgo" > formControlName="alterEgo" >
</div> </div>
<!-- #docregion cross-validation-error-message -->
<div *ngIf="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)" class="cross-validation-error-message alert alert-danger">
Name cannot match alter ego.
</div>
<!-- #enddocregion cross-validation-error-message -->
</div>
<div class="form-group"> <div class="form-group">
<label for="power">Hero Power</label> <label for="power">Hero Power</label>
<select id="power" class="form-control" <select id="power" class="form-control"

View File

@ -1,13 +1,14 @@
/* tslint:disable: member-ordering forin */ /* tslint:disable: member-ordering forin */
// #docplaster
// #docregion // #docregion
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms'; import { FormControl, FormGroup, Validators } from '@angular/forms';
import { forbiddenNameValidator } from '../shared/forbidden-name.directive'; import { forbiddenNameValidator } from '../shared/forbidden-name.directive';
import { identityRevealedValidator } from '../shared/identity-revealed.directive';
@Component({ @Component({
selector: 'app-hero-form-reactive', selector: 'app-hero-form-reactive',
templateUrl: './hero-form-reactive.component.html' templateUrl: './hero-form-reactive.component.html',
styleUrls: ['./hero-form-reactive.component.css'],
}) })
export class HeroFormReactiveComponent implements OnInit { export class HeroFormReactiveComponent implements OnInit {
@ -17,24 +18,19 @@ export class HeroFormReactiveComponent implements OnInit {
heroForm: FormGroup; heroForm: FormGroup;
// #docregion form-group
ngOnInit(): void { ngOnInit(): void {
// #docregion custom-validator
this.heroForm = new FormGroup({ this.heroForm = new FormGroup({
'name': new FormControl(this.hero.name, [ 'name': new FormControl(this.hero.name, [
Validators.required, Validators.required,
Validators.minLength(4), Validators.minLength(4),
forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator. forbiddenNameValidator(/bob/i)
]), ]),
'alterEgo': new FormControl(this.hero.alterEgo), 'alterEgo': new FormControl(this.hero.alterEgo),
'power': new FormControl(this.hero.power, Validators.required) 'power': new FormControl(this.hero.power, Validators.required)
}); }, { validators: identityRevealedValidator }); // <-- add custom validator at the FormGroup level
// #enddocregion custom-validator
} }
get name() { return this.heroForm.get('name'); } get name() { return this.heroForm.get('name'); }
get power() { return this.heroForm.get('power'); } get power() { return this.heroForm.get('power'); }
// #enddocregion form-group
} }
// #enddocregion

View File

@ -0,0 +1,25 @@
// #docregion
import { Directive } from '@angular/core';
import { AbstractControl, FormGroup, NG_VALIDATORS, ValidationErrors, Validator, ValidatorFn } from '@angular/forms';
// #docregion cross-validation-validator
/** A hero's name can't match the hero's alter ego */
export const identityRevealedValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => {
const name = control.get('name');
const alterEgo = control.get('alterEgo');
return name && alterEgo && name.value === alterEgo.value ? { 'identityRevealed': true } : null;
};
// #enddocregion cross-validation-validator
// #docregion cross-validation-directive
@Directive({
selector: '[appIdentityRevealed]',
providers: [{ provide: NG_VALIDATORS, useExisting: IdentityRevealedValidatorDirective, multi: true }]
})
export class IdentityRevealedValidatorDirective implements Validator {
validate(control: AbstractControl): ValidationErrors {
return identityRevealedValidator(control)
}
}
// #enddocregion cross-validation-directive

View File

@ -0,0 +1,4 @@
/* #docregion */
.cross-validation-error input {
border-left: 5px solid red;
}

View File

@ -2,11 +2,11 @@
<div class="container"> <div class="container">
<h1>Template-Driven Form</h1> <h1>Template-Driven Form</h1>
<!-- #docregion form-tag--> <!-- #docregion cross-validation-register-validator -->
<form #heroForm="ngForm"> <form #heroForm="ngForm" appIdentityRevealed>
<!-- #enddocregion form-tag--> <!-- #enddocregion cross-validation-register-validator -->
<div [hidden]="heroForm.submitted"> <div [hidden]="heroForm.submitted">
<div class="cross-validation" [class.cross-validation-error]="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)">
<div class="form-group"> <div class="form-group">
<label for="name">Name</label> <label for="name">Name</label>
<!-- #docregion name-with-error-msg --> <!-- #docregion name-with-error-msg -->
@ -39,6 +39,13 @@
name="alterEgo" [(ngModel)]="hero.alterEgo" > name="alterEgo" [(ngModel)]="hero.alterEgo" >
</div> </div>
<!-- #docregion cross-validation-error-message -->
<div *ngIf="heroForm.errors?.identityRevealed && (heroForm.touched || heroForm.dirty)" class="cross-validation-error-message alert alert-danger">
Name cannot match alter ego.
</div>
<!-- #enddocregion cross-validation-error-message -->
</div>
<div class="form-group"> <div class="form-group">
<label for="power">Hero Power</label> <label for="power">Hero Power</label>
<select id="power" name="power" class="form-control" <select id="power" name="power" class="form-control"
@ -62,5 +69,4 @@
<button (click)="heroForm.resetForm({})">Add new hero</button> <button (click)="heroForm.resetForm({})">Add new hero</button>
</div> </div>
</form> </form>
</div> </div>

View File

@ -3,9 +3,11 @@
// #docregion // #docregion
import { Component } from '@angular/core'; import { Component } from '@angular/core';
// #docregion component
@Component({ @Component({
selector: 'app-hero-form-template', selector: 'app-hero-form-template',
templateUrl: './hero-form-template.component.html' templateUrl: './hero-form-template.component.html',
styleUrls: ['./hero-form-template.component.css'],
}) })
export class HeroFormTemplateComponent { export class HeroFormTemplateComponent {
@ -14,3 +16,4 @@ export class HeroFormTemplateComponent {
hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]}; hero = {name: 'Dr.', alterEgo: 'Dr. What', power: this.powers[0]};
} }
// #enddocregion

View File

@ -2,6 +2,7 @@
"description": "Validation", "description": "Validation",
"files":[ "files":[
"!**/*.d.ts", "!**/*.d.ts",
"!**/*.js" "!**/*.js",
"!**/*.[1].*"
] ]
} }

View File

@ -4,7 +4,8 @@ button {
font-size: 100%; font-size: 100%;
} }
code, .code { code,
.code {
background-color: #eee; background-color: #eee;
color: black; color: black;
font-family: Courier, sans-serif; font-family: Courier, sans-serif;
@ -21,14 +22,18 @@ div.code {
} }
hr { hr {
margin: 40px 0 margin: 40px 0;
} }
td, th { td,
th {
text-align: left; text-align: left;
vertical-align: top; vertical-align: top;
} }
/* #docregion p-span */ /* #docregion p-span */
p span { color: red; font-size: 70%; } p span {
color: red;
font-size: 70%;
}
/* #enddocregion p-span */ /* #enddocregion p-span */

View File

@ -132,7 +132,7 @@
<!-- #docregion select-span --> <!-- #docregion select-span -->
<select [(ngModel)]="hero"> <select [(ngModel)]="hero">
<span *ngFor="let h of heroes"> <span *ngFor="let h of heroes">
<span *ngIf="showSad || h?.emotion != 'sad'"> <span *ngIf="showSad || h?.emotion !== 'sad'">
<option [ngValue]="h">{{h.name}} ({{h?.emotion}})</option> <option [ngValue]="h">{{h.name}} ({{h?.emotion}})</option>
</span> </span>
</span> </span>
@ -147,7 +147,7 @@
<!-- #docregion select-ngcontainer --> <!-- #docregion select-ngcontainer -->
<select [(ngModel)]="hero"> <select [(ngModel)]="hero">
<ng-container *ngFor="let h of heroes"> <ng-container *ngFor="let h of heroes">
<ng-container *ngIf="showSad || h?.emotion != 'sad'"> <ng-container *ngIf="showSad || h?.emotion !== 'sad'">
<option [ngValue]="h">{{h.name}} ({{h?.emotion}})</option> <option [ngValue]="h">{{h.name}} ({{h?.emotion}})</option>
</ng-container> </ng-container>
</ng-container> </ng-container>

View File

@ -14,6 +14,7 @@ export class AppComponent {
heroTraits = ['honest', 'brave', 'considerate']; heroTraits = ['honest', 'brave', 'considerate'];
// flags for the table // flags for the table
attrDirs = true; attrDirs = true;
strucDirs = true; strucDirs = true;
divNgIf = false; divNgIf = false;

View File

@ -1,5 +1,6 @@
// #docregion // #docregion
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { Hero } from './hero'; import { Hero } from './hero';
@Component({ @Component({
@ -33,11 +34,15 @@ export class ConfusedHeroComponent {
export class UnknownHeroComponent { export class UnknownHeroComponent {
@Input() hero: Hero; @Input() hero: Hero;
get message() { get message() {
return this.hero && this.hero.name ? return this.hero && this.hero.name
`${this.hero.name} is strange and mysterious.` : ? `${this.hero.name} is strange and mysterious.`
'Are you feeling indecisive?'; : 'Are you feeling indecisive?';
} }
} }
export const heroComponents = export const heroComponents = [
[ HappyHeroComponent, SadHeroComponent, ConfusedHeroComponent, UnknownHeroComponent ]; HappyHeroComponent,
SadHeroComponent,
ConfusedHeroComponent,
UnknownHeroComponent
];

View File

@ -1,16 +1,36 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DashboardComponent } from './dashboard.component'; import { DashboardComponent } from './dashboard.component';
import { HeroSearchComponent } from '../hero-search/hero-search.component';
import { RouterTestingModule } from '@angular/router/testing';
import { of } from 'rxjs';
import { HEROES } from '../mock-heroes';
import { HeroService } from '../hero.service';
describe('DashboardComponent', () => { describe('DashboardComponent', () => {
let component: DashboardComponent; let component: DashboardComponent;
let fixture: ComponentFixture<DashboardComponent>; let fixture: ComponentFixture<DashboardComponent>;
let heroService;
let getHeroesSpy;
beforeEach(async(() => { beforeEach(async(() => {
heroService = jasmine.createSpyObj('HeroService', ['getHeroes']);
getHeroesSpy = heroService.getHeroes.and.returnValue( of(HEROES) );
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ DashboardComponent ] declarations: [
DashboardComponent,
HeroSearchComponent
],
imports: [
RouterTestingModule.withRoutes([])
],
providers: [
{ provide: HeroService, useValue: heroService }
]
}) })
.compileComponents(); .compileComponents();
})); }));
beforeEach(() => { beforeEach(() => {
@ -22,4 +42,17 @@ describe('DashboardComponent', () => {
it('should be created', () => { it('should be created', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
it('should display "Top Heroes" as headline', () => {
expect(fixture.nativeElement.querySelector('h3').textContent).toEqual('Top Heroes');
});
it('should call heroService', async(() => {
expect(getHeroesSpy.calls.any()).toBe(true);
}));
it('should display 4 links', async(() => {
expect(fixture.nativeElement.querySelectorAll('a').length).toEqual(4);
}));
}); });

View File

@ -1,14 +1,18 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { HeroSearchComponent } from './hero-search.component'; import { HeroSearchComponent } from './hero-search.component';
describe('HeroSearchComponent', () => { describe('HeroSearchComponent', () => {
let component: HeroSearchComponent; let component: HeroSearchComponent;
let fixture: ComponentFixture<HeroSearchComponent>; let fixture: ComponentFixture<HeroSearchComponent>;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ HeroSearchComponent ] declarations: [ HeroSearchComponent ],
imports: [RouterTestingModule.withRoutes([]), HttpClientTestingModule]
}) })
.compileComponents(); .compileComponents();
})); }));

View File

@ -40,7 +40,7 @@ export class HeroService {
// #enddocregion getHeroes-1 // #enddocregion getHeroes-1
.pipe( .pipe(
// #enddocregion getHeroes-2 // #enddocregion getHeroes-2
tap(heroes => this.log(`fetched heroes`)), tap(heroes => this.log('fetched heroes')),
// #docregion getHeroes-2 // #docregion getHeroes-2
catchError(this.handleError('getHeroes', [])) catchError(this.handleError('getHeroes', []))
); );
@ -151,7 +151,7 @@ export class HeroService {
// #docregion log // #docregion log
/** Log a HeroService message with the MessageService */ /** Log a HeroService message with the MessageService */
private log(message: string) { private log(message: string) {
this.messageService.add('HeroService: ' + message); this.messageService.add(`HeroService: ${message}`);
} }
// #enddocregion log // #enddocregion log
} }

View File

@ -1,6 +1,7 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { HeroesComponent } from './heroes.component'; import { HeroesComponent } from './heroes.component';
import { HttpClientTestingModule } from '@angular/common/http/testing';
describe('HeroesComponent', () => { describe('HeroesComponent', () => {
let component: HeroesComponent; let component: HeroesComponent;
@ -8,7 +9,8 @@ describe('HeroesComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ HeroesComponent ] declarations: [ HeroesComponent ],
imports: [RouterTestingModule.withRoutes([]), HttpClientTestingModule],
}) })
.compileComponents(); .compileComponents();
})); }));

View File

@ -30,7 +30,7 @@ export class HeroService {
getHeroes (): Observable<Hero[]> { getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl) return this.http.get<Hero[]>(this.heroesUrl)
.pipe( .pipe(
tap(heroes => this.log(`fetched heroes`)), tap(heroes => this.log('fetched heroes')),
catchError(this.handleError('getHeroes', [])) catchError(this.handleError('getHeroes', []))
); );
} }
@ -123,6 +123,6 @@ export class HeroService {
/** Log a HeroService message with the MessageService */ /** Log a HeroService message with the MessageService */
private log(message: string) { private log(message: string) {
this.messageService.add('HeroService: ' + message); this.messageService.add(`HeroService: ${message}`);
} }
} }

View File

@ -8,6 +8,7 @@
"experimentalDecorators": true, "experimentalDecorators": true,
"removeComments": false, "removeComments": false,
"noImplicitAny": false, "noImplicitAny": false,
"skipLibCheck": true,
"suppressImplicitAnyIndexErrors": true "suppressImplicitAnyIndexErrors": true
} }
} }

View File

@ -8,10 +8,8 @@
"experimentalDecorators": true, "experimentalDecorators": true,
"lib": [ "es2015", "dom" ], "lib": [ "es2015", "dom" ],
"noImplicitAny": true, "noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true, "skipLibCheck": true,
"typeRoots": [ "suppressImplicitAnyIndexErrors": true
"./node_modules/@types/"
]
}, },
"compileOnSave": true, "compileOnSave": true,
"exclude": [ "exclude": [

View File

@ -1,6 +1,6 @@
// #docregion // #docregion
import { platformBrowser } from '@angular/platform-browser'; import { platformBrowser } from '@angular/platform-browser';
import { AppModuleNgFactory } from '../aot/app/app.module.ngfactory'; import { AppModuleNgFactory } from './app.module.ngfactory';
platformBrowser().bootstrapModuleFactory(AppModuleNgFactory); platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);

View File

@ -3,7 +3,7 @@
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
// #enddocregion activatedroute // #enddocregion activatedroute
import { Observable } from 'rxjs'; import { Observable, of } from 'rxjs';
import { async, TestBed } from '@angular/core/testing'; import { async, TestBed } from '@angular/core/testing';
@ -21,7 +21,7 @@ function xyzPhoneData(): PhoneData {
class MockPhone { class MockPhone {
get(id: string): Observable<PhoneData> { get(id: string): Observable<PhoneData> {
return Observable.of(xyzPhoneData()); return of(xyzPhoneData());
} }
} }

View File

@ -2,7 +2,7 @@
// #docregion // #docregion
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable, of } from 'rxjs';
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SpyLocation } from '@angular/common/testing'; import { SpyLocation } from '@angular/common/testing';
@ -15,7 +15,7 @@ class ActivatedRouteMock {
class MockPhone { class MockPhone {
query(): Observable<PhoneData[]> { query(): Observable<PhoneData[]> {
return Observable.of([ return of([
{name: 'Nexus S', snippet: '', images: []}, {name: 'Nexus S', snippet: '', images: []},
{name: 'Motorola DROID', snippet: '', images: []} {name: 'Motorola DROID', snippet: '', images: []}
]); ]);

View File

@ -9,10 +9,8 @@
"lib": ["es2015", "dom"], "lib": ["es2015", "dom"],
"removeComments": false, "removeComments": false,
"noImplicitAny": true, "noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true, "skipLibCheck": true,
"typeRoots": [ "suppressImplicitAnyIndexErrors": true
"./node_modules/@types/"
]
}, },
"files": [ "files": [
@ -21,7 +19,6 @@
], ],
"angularCompilerOptions": { "angularCompilerOptions": {
"genDir": "aot",
"skipMetadataEmit" : true "skipMetadataEmit" : true
} }
} }

View File

@ -8,10 +8,8 @@
"experimentalDecorators": true, "experimentalDecorators": true,
"lib": [ "es2015", "dom" ], "lib": [ "es2015", "dom" ],
"noImplicitAny": true, "noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true, "skipLibCheck": true,
"typeRoots": [ "suppressImplicitAnyIndexErrors": true
"./node_modules/@types/"
]
}, },
"compileOnSave": true, "compileOnSave": true,
"exclude": [ "exclude": [

View File

@ -3,7 +3,7 @@
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
// #enddocregion activatedroute // #enddocregion activatedroute
import { Observable } from 'rxjs'; import { Observable, of } from 'rxjs';
import { async, TestBed } from '@angular/core/testing'; import { async, TestBed } from '@angular/core/testing';
@ -21,7 +21,7 @@ function xyzPhoneData(): PhoneData {
class MockPhone { class MockPhone {
get(id: string): Observable<PhoneData> { get(id: string): Observable<PhoneData> {
return Observable.of(xyzPhoneData()); return of(xyzPhoneData());
} }
} }

View File

@ -2,7 +2,7 @@
// #docregion routestuff // #docregion routestuff
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable, of } from 'rxjs';
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SpyLocation } from '@angular/common/testing'; import { SpyLocation } from '@angular/common/testing';
@ -17,7 +17,7 @@ class ActivatedRouteMock {
class MockPhone { class MockPhone {
query(): Observable<PhoneData[]> { query(): Observable<PhoneData[]> {
return Observable.of([ return of([
{name: 'Nexus S', snippet: '', images: []}, {name: 'Nexus S', snippet: '', images: []},
{name: 'Motorola DROID', snippet: '', images: []} {name: 'Motorola DROID', snippet: '', images: []}
]); ]);

View File

@ -8,10 +8,8 @@
"experimentalDecorators": true, "experimentalDecorators": true,
"lib": [ "es2015", "dom" ], "lib": [ "es2015", "dom" ],
"noImplicitAny": true, "noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true, "skipLibCheck": true,
"typeRoots": [ "suppressImplicitAnyIndexErrors": true
"./node_modules/@types/"
]
}, },
"compileOnSave": true, "compileOnSave": true,
"exclude": [ "exclude": [

View File

@ -895,7 +895,7 @@ For more information on pipes, see [Pipes](guide/pipes).
### lowercase ### lowercase
<code-example hideCopy> <code-example hideCopy>
&lt;div>{{movie.title | lowercase}}&lt;/div> &lt;td>{{movie.title | lowercase}}&lt;/td>
</code-example> </code-example>

View File

@ -97,6 +97,9 @@ You can control your app compilation by providing template compiler options in t
} }
} }
``` ```
### *enableResourceInlining*
This options tell the compiler to replace the `templateUrl` and `styleUrls` property in all `@Component` decorators with inlined contents in `template` and `styles` properties.
When enabled, the `.js` output of ngc will have no lazy-loaded `templateUrl` or `styleUrls`.
### *skipMetadataEmit* ### *skipMetadataEmit*

View File

@ -8,9 +8,10 @@ A _component_ controls a patch of screen called a *view*. For example, individua
* The list of heroes. * The list of heroes.
* The hero editor. * The hero editor.
You define a component's application logic&mdash;what it does to support the view&mdash;inside a class. The class interacts with the view through an API of properties and methods. You define a component's application logic&mdash;what it does to support the view&mdash;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 returns an array of heroes that it acquires from a service. `HeroListComponent` also has a `selectHero()` method that sets a `selectedHero` property when the user clicks to choose a hero from that list. 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.
<code-example path="architecture/src/app/hero-list.component.ts" linenums="false" title="src/app/hero-list.component.ts (class)" region="class"></code-example> <code-example path="architecture/src/app/hero-list.component.ts" linenums="false" title="src/app/hero-list.component.ts (class)" region="class"></code-example>
@ -39,8 +40,7 @@ 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 that the component's constructor requires a `HeroService` instance * `providers`: An array of **dependency injection providers** for services that the component requires. In the example, this tells Angular how provide the `HeroService` instance that the component's constructor uses to get the list of heroes to display.
in order to get the list of heroes to display.
<hr/> <hr/>

View File

@ -88,7 +88,8 @@ For example, import Angular's `Component` decorator from the `@angular/core` lib
<code-example path="architecture/src/app/app.component.ts" region="import" linenums="false"></code-example> <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: 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> <code-example path="architecture/src/app/mini-app.ts" region="import-browser-module" linenums="false"></code-example>

View File

@ -60,11 +60,29 @@ The process of `HeroService` injection looks something like this:
### Providing services ### Providing services
You must register at least one *provider* of any service you are going to use. You can register providers in modules or in components. 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
* When you add providers to the [root module](guide/architecture-modules), the same instance of a service is available to all components in your app. * 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:
<code-example path="architecture/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts (module providers)" region="providers"></code-example> ```
@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 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
],
...
})
```
* When you register a provider at the component level, you get a new instance of the * 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:

View File

@ -164,7 +164,7 @@ They are _not inherited_ by any components nested within the template nor by any
</div> </div>
The CLI defines an empty `styles` array when you create the component with the `--inline-styles` flag. The CLI defines an empty `styles` array when you create the component with the `--inline-style` flag.
<code-example language="sh" class="code-shell"> <code-example language="sh" class="code-shell">
ng generate component hero-app --inline-style ng generate component hero-app --inline-style
@ -189,7 +189,7 @@ They are _not inherited_ by any components nested within the template nor by any
<div class="l-sub-section"> <div class="l-sub-section">
You can specify more than one styles file or even a combination of `style` and `styleUrls`. You can specify more than one styles file or even a combination of `styles` and `styleUrls`.
</div> </div>
@ -280,12 +280,14 @@ To control how this encapsulation happens on a *per
component* basis, you can set the *view encapsulation mode* in the component metadata. component* basis, you can set the *view encapsulation mode* in the component metadata.
Choose from the following modes: Choose from the following modes:
* `Native` view encapsulation uses the browser's native shadow DOM implementation (see * `ShadowDom` view encapsulation uses the browser's native shadow DOM implementation (see
[Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM) [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM)
on the [MDN](https://developer.mozilla.org) site) on the [MDN](https://developer.mozilla.org) site)
to attach a shadow DOM to the component's host element, and then puts the component to attach a shadow DOM to the component's host element, and then puts the component
view inside that shadow DOM. The component's styles are included within the shadow DOM. view inside that shadow DOM. The component's styles are included within the shadow DOM.
* `Native` view encapsulation uses a now deprecated version of the browser's native shadow DOM implementation - [learn about the changes](https://hayato.io/2016/shadowdomv1/).
* `Emulated` view encapsulation (the default) emulates the behavior of shadow DOM by preprocessing * `Emulated` view encapsulation (the default) emulates the behavior of shadow DOM by preprocessing
(and renaming) the CSS code to effectively scope the CSS to the component's view. (and renaming) the CSS code to effectively scope the CSS to the component's view.
For details, see [Appendix 1](guide/component-styles#inspect-generated-css). For details, see [Appendix 1](guide/component-styles#inspect-generated-css).
@ -300,8 +302,8 @@ To set the components encapsulation mode, use the `encapsulation` property in th
<code-example path="component-styles/src/app/quest-summary.component.ts" region="encapsulation.native" title="src/app/quest-summary.component.ts" linenums="false"> <code-example path="component-styles/src/app/quest-summary.component.ts" region="encapsulation.native" title="src/app/quest-summary.component.ts" linenums="false">
</code-example> </code-example>
`Native` view encapsulation only works on browsers that have native support `ShadowDom` view encapsulation only works on browsers that have native support
for shadow DOM (see [Shadow DOM v0](http://caniuse.com/#feat=shadowdom) on the for shadow DOM (see [Shadow DOM v1](https://caniuse.com/#feat=shadowdomv1) on the
[Can I use](http://caniuse.com) site). The support is still limited, [Can I use](http://caniuse.com) site). The support is still limited,
which is why `Emulated` view encapsulation is the default mode and recommended which is why `Emulated` view encapsulation is the default mode and recommended
in most cases. in most cases.

View File

@ -152,7 +152,7 @@ Install `source-map-explorer`:
Build your app for production _including the source maps_ Build your app for production _including the source maps_
<code-example language="none" class="code-shell"> <code-example language="none" class="code-shell">
ng build --prod --sourcemaps ng build --prod --source-map
</code-example> </code-example>
List the generated bundles in the `dist/` folder. List the generated bundles in the `dist/` folder.

View File

@ -31,6 +31,8 @@ Here are a few essential commands for guide page authors.
1. http://localhost:4200/ &mdash; browse to the app running locally. 1. http://localhost:4200/ &mdash; browse to the app running locally.
You can combine `yarn docs-watch` and `yarn start` into one command with `yarn serve-and-sync`.
## Guide pages ## Guide pages
All but a few guide pages are [markdown](https://daringfireball.net/projects/markdown/syntax "markdown") files with an `.md` extension. All but a few guide pages are [markdown](https://daringfireball.net/projects/markdown/syntax "markdown") files with an `.md` extension.

View File

@ -92,7 +92,7 @@ built-in validators&mdash;this time, in function form. See below:
{@a reactive-component-class} {@a reactive-component-class}
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.ts" region="form-group" title="reactive/hero-form-reactive.component.ts (validator functions)" linenums="false"> <code-example path="form-validation/src/app/reactive/hero-form-reactive.component.1.ts" region="form-group" title="reactive/hero-form-reactive.component.ts (validator functions)" linenums="false">
</code-example> </code-example>
Note that: Note that:
@ -148,7 +148,7 @@ at which point the form uses the last value emitted for validation.
In reactive forms, custom validators are fairly simple to add. All you have to do is pass the function directly In reactive forms, custom validators are fairly simple to add. All you have to do is pass the function directly
to the `FormControl`. to the `FormControl`.
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.ts" region="custom-validator" title="reactive/hero-form-reactive.component.ts (validator functions)" linenums="false"> <code-example path="form-validation/src/app/reactive/hero-form-reactive.component.1.ts" region="custom-validator" title="reactive/hero-form-reactive.component.ts (validator functions)" linenums="false">
</code-example> </code-example>
### Adding to template-driven forms ### Adding to template-driven forms
@ -208,5 +208,80 @@ set the color of each form control's border.
</code-example> </code-example>
## Cross field validation
This section shows how to perform cross field validation. It assumes some basic knowledge of creating custom validators.
<div class="l-sub-section">
If you haven't created custom validators before, start by reviewing the [custom validators section](guide/form-validation#custom-validators).
</div>
In the following section, we will make sure that our heroes do not reveal their true identities by filling out the Hero Form. We will do that by validating that the hero names and alter egos do not match.
### Adding to reactive forms
The form has the following structure:
```javascript
const heroForm = new FormGroup({
'name': new FormControl(),
'alterEgo': new FormControl(),
'power': new FormControl()
});
```
Notice that the name and alterEgo are sibling controls. To evaluate both controls in a single custom validator, we should perform the validation in a common ancestor control: the `FormGroup`. That way, we can query the `FormGroup` for the child controls which will allow us to compare their values.
To add a validator to the `FormGroup`, pass the new validator in as the second argument on creation.
```javascript
const heroForm = new FormGroup({
'name': new FormControl(),
'alterEgo': new FormControl(),
'power': new FormControl()
}, { validators: identityRevealedValidator });
```
The validator code is as follows:
<code-example path="form-validation/src/app/shared/identity-revealed.directive.ts" region="cross-validation-validator" title="shared/identity-revealed.directive.ts" linenums="false">
</code-example>
The identity validator implements the `ValidatorFn` interface. It takes an Angular control object as an argument and returns either null if the form is valid, or `ValidationErrors` otherwise.
First we retrieve the child controls by calling the `FormGroup`'s [get](api/forms/AbstractControl#get) method. Then we simply compare the values of the `name` and `alterEgo` controls.
If the values do not match, the hero's identity remains secret, and we can safely return null. Otherwise, the hero's identity is revealed and we must mark the form as invalid by returning an error object.
Next, to provide better user experience, we show an appropriate error message when the form is invalid.
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.html" region="cross-validation-error-message" title="reactive/hero-form-template.component.html" linenums="false">
</code-example>
Note that we check if:
- the `FormGroup` has the cross validation error returned by the `identityRevealed` validator,
- the user is yet to [interact](guide/form-validation#why-check-dirty-and-touched) with the form.
### Adding to template driven forms
First we must create a directive that will wrap the validator function. We provide it as the validator using the `NG_VALIDATORS` token. If you are not sure why, or you do not fully understand the syntax, revisit the previous [section](guide/form-validation#adding-to-template-driven-forms).
<code-example path="form-validation/src/app/shared/identity-revealed.directive.ts" region="cross-validation-directive" title="shared/identity-revealed.directive.ts" linenums="false">
</code-example>
Next, we have to add the directive to the html template. Since the validator must be registered at the highest level in the form, we put the directive on the `form` tag.
<code-example path="form-validation/src/app/template/hero-form-template.component.html" region="cross-validation-register-validator" title="template/hero-form-template.component.html" linenums="false">
</code-example>
To provide better user experience, we show an appropriate error message when the form is invalid.
<code-example path="form-validation/src/app/template/hero-form-template.component.html" region="cross-validation-error-message" title="template/hero-form-template.component.html" linenums="false">
</code-example>
Note that we check if:
- the form has the cross validation error returned by the `identityRevealed` validator,
- the user is yet to [interact](guide/form-validation#why-check-dirty-and-touched) with the form.
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.
**You can run the <live-example></live-example> to see the complete reactive and template-driven example code.** **You can run the <live-example></live-example> to see the complete reactive and template-driven example code.**

View File

@ -152,7 +152,8 @@ nothing to distinguish it from any component you've written before.
Understanding this component requires only the Angular concepts covered in previous pages. Understanding this component requires only the Angular concepts covered in previous pages.
* The code imports the Angular core library and the `Hero` model you just created. * The code imports the Angular core library and the `Hero` model you just created.
* The `@Component` selector value of "hero-form" means you can drop this form in a parent template with a `<hero-form>` tag. * The `@Component` selector value of "app-hero-form" means you can drop this form in a parent
template with a `<app-hero-form>` tag.
* The `templateUrl` property points to a separate file for the template HTML. * The `templateUrl` property points to a separate file for the template HTML.
* You defined dummy data for `model` and `powers`, as befits a demo. * You defined dummy data for `model` and `powers`, as befits a demo.

View File

@ -114,19 +114,23 @@ The following class types can be declared:
A [decorator](guide/glossary#decorator) statement immediately before a field in a class definition that declares the type of that field. Some examples are `@Input` and `@Output`. A [decorator](guide/glossary#decorator) statement immediately before a field in a class definition that declares the type of that field. Some examples are `@Input` and `@Output`.
{@a cli}
## CLI ## CLI
The [Angular CLI](https://cli.angular.io/) is a command-line tool that can create a project, add files, and perform a variety of ongoing development tasks such as testing, bundling, and deployment. The [Angular CLI](https://cli.angular.io/) is a command-line tool for managing the Angular development cycle. Use it to create the initial filesystem scaffolding for a [workspace](guide/glossary#workspace) or [project](guide/glossary#project), and to run [schematics](guide/glossary#schematic) that add and modify code for initial generic versions of various elements. The tool supports all stages of the development cycle, including building, testing, bundling, and deployment.
Learn more in the [Getting Started](guide/quickstart) guide. * To begin using the CLI for a new project, see [Getting Started](guide/quickstart) guide.
* To learn more about the full capabilities of the CLI, see the [Angular CLI documentation].(https://github.com/angular/angular-cli/wiki).
{@a component} {@a component}
## Component ## Component
A class with the `@Component` [decorator](guide/glossary#decorator) that associates it with a companion [template](guide/glossary#template). A class with the `@Component` [decorator](guide/glossary#decorator) that associates it with a companion [template](guide/glossary#template). Together, the component and template define a [view](guide/glossary#view).
A component is a special type of [directive](guide/glossary#directive) that represents a [view](guide/glossary#view).The `@Component` decorator extends the `@Directive` decorator with template-oriented features. A component is a special type of [directive](guide/glossary#directive).
The `@Component` decorator extends the `@Directive` decorator with template-oriented features.
An Angular component class is responsible for exposing data and handling most of the view's display and user-interaction logic through [data binding](guide/glossary#data-binding). An Angular component class is responsible for exposing data and handling most of the view's display and user-interaction logic through [data binding](guide/glossary#data-binding).
@ -208,7 +212,8 @@ See [Class decorator](guide/glossary#class-decorator), [Class field decorator](g
A design pattern and mechanism for creating and delivering parts of an application (dependencies) to other parts of an application that require them. A design pattern and mechanism for creating and delivering parts of an application (dependencies) to other parts of an application that require them.
In Angular, dependencies are typically services, but can also be values, such as strings or functions. An [injector](guide/glossary#injector) for an app (created automatically during bootstrap) creates dependencies when needed, using a registered [provider](guide/glossary#provider) of the service or value. Different providers can provide different implementations of the same service. In Angular, dependencies are typically services, but can also be values, such as strings or functions.
An [injector](guide/glossary#injector) for an app (created automatically during bootstrap) instantiates dependencies when needed, using a configured [provider](guide/glossary#provider) of the service or value.
Learn more in the [Dependency Injection](guide/dependency-injection) guide. Learn more in the [Dependency Injection](guide/dependency-injection) guide.
@ -280,7 +285,7 @@ Compare [Custom element](guide/glossary#custom-element).
## Entry point ## Entry point
A JavaScript ID that makes parts of an NPM package available for import by other code. A JavaScript symbol that makes parts of an npm package available for import by other code.
The Angular [scoped packages](guide/glossary#scoped-package) each have an entry point named `index`. The Angular [scoped packages](guide/glossary#scoped-package) each have an entry point named `index`.
Within Angular, use [NgModules](guide/glossary#ngmodule) to achieve the same result. Within Angular, use [NgModules](guide/glossary#ngmodule) to achieve the same result.
@ -310,7 +315,17 @@ Both a [service](guide/glossary#service) and a [component](guide/glossary#compon
An object in the Angular [dependency-injection system](guide/glossary#dependency-injection) An object in the Angular [dependency-injection system](guide/glossary#dependency-injection)
that can find a named dependency in its cache or create a dependency that can find a named dependency in its cache or create a dependency
with a registered [provider](guide/glossary#provider). Injectors are created for NgModules automatically as part of the bootstrap process, and inherited through the component hierarchy. using a configured [provider](guide/glossary#provider).
Injectors are created for NgModules automatically as part of the bootstrap process
and are inherited through the component hierarchy.
* An injector provides a singleton instance of a dependency, and can inject this same instance in multiple components.
* A hierarchy of injectors at the NgModule and component level can provide different instances of a dependency to their own components and child components.
* You can configure injectors with different providers that can provide different implementations of the same dependency.
Learn more about the injector hierarchy in the [Dependency Injection guide](guide/hierarchical-dependency-injection).
## Input ## Input
@ -373,6 +388,17 @@ Lazy loading speeds up application load time by splitting the application into m
For example, dependencies can be lazy-loaded as needed&emdash;as opposed to "eager-loaded" modules that are required by the root module, and are thus loaded on launch. For example, dependencies can be lazy-loaded as needed&emdash;as opposed to "eager-loaded" modules that are required by the root module, and are thus loaded on launch.
Similarly, the [router](guide/glossary#router) can load child views only when the parent view is activated, and you can build custom elements that can be loaded into an Angular app when needed. Similarly, the [router](guide/glossary#router) can load child views only when the parent view is activated, and you can build custom elements that can be loaded into an Angular app when needed.
{@a library}
## Library
In Angular, a [project](guide/glossary#project) that provides functionality that can be included in other Angular apps. A library is not a complete Angular app, and it cannot run independently.
* Library developers can use the [CLI](guide/glossary#cli) to `generate` scaffolding for a new library in an existing [workspace](guide/glossary#workspace), and can publish a library as an `npm` package.
* App developers can use the [CLI](guide/glossary#cli) to `add` a published library for use with an app in the same [workspace](guide/glossary#workspace).
## Lifecycle hook ## Lifecycle hook
An interface that allows you to tap into the lifecycle of [directives](guide/glossary#directive) and [components](guide/glossary#component) as they are created, updated, and destroyed. An interface that allows you to tap into the lifecycle of [directives](guide/glossary#directive) and [components](guide/glossary#component) as they are created, updated, and destroyed.
@ -402,7 +428,7 @@ In general, a module collects a block of code dedicated to a single purpose. Ang
In JavaScript (ECMAScript), each file is a module and all objects defined in the file belong to that module. Objects can exported, making them public, and public objects can be imported for use by other modules. In JavaScript (ECMAScript), each file is a module and all objects defined in the file belong to that module. Objects can exported, making them public, and public objects can be imported for use by other modules.
Angular ships as a collection of JavaScript modules, or libraries. Each Angular library name begins with the `@angular` prefix. Install them with the NPM package manager and import parts of them with JavaScript `import` declarations. Angular ships as a collection of JavaScript modules, or libraries. Each Angular library name begins with the `@angular` prefix. Install them with the npm package manager and import parts of them with JavaScript `import` declarations.
Compare the Angular [NgModule](guide/glossary#ngmodule). Compare the Angular [NgModule](guide/glossary#ngmodule).
@ -470,8 +496,13 @@ To learn more, see the [pipes](guide/pipes) page.
## Polyfill ## Polyfill
An [NPM package](guide/npm-packages) that plugs gaps in a browser's JavaScript implementation. See the [Browser Support](guide/browser-support) guide for polyfills that support particular functionality for particular platforms. An [npm package](guide/npm-packages) that plugs gaps in a browser's JavaScript implementation. See the [Browser Support](guide/browser-support) guide for polyfills that support particular functionality for particular platforms.
{@a project}
## Project
In Angular, a folder within a [workspace](guide/glossary#workspace) that contains an Angular app or [library](guide/glossary#library). A workspace can contain multiple projects. All apps in a workspace can use libraries in the same workspace.
## Provider ## Provider
@ -531,9 +562,24 @@ For more information, see the [Routing & Navigation](guide/router) page.
{@a S} {@a S}
{@a schematic}
## Schematic
A scaffolding library that defines how to generate or transform a programming project by creating, modifying, refactoring, or moving files and code.
The Angular [CLI](guide/glossary#cli) uses schematics to generate and modify [Angular projects](guide/glossary#project) and parts of projects.
* Angular provides a set of schematics for use with the CLI.
For details, see [Angular CLI documentation].(https://github.com/angular/angular-cli/wiki).
* Library developers can create schematics that enable the CLI to generate their published libraries.
For more information, see https://www.npmjs.com/package/@angular-devkit/schematics.
## Scoped package ## Scoped package
A way to group related NPM packages. A way to group related npm packages.
NgModules are delivered within *scoped packages* whose names begin with the Angular *scope name* `@angular`. For example, `@angular/core`, `@angular/common`, `@angular/http`, and `@angular/router`. NgModules are delivered within *scoped packages* whose names begin with the Angular *scope name* `@angular`. For example, `@angular/core`, `@angular/common`, `@angular/http`, and `@angular/router`.
Import a scoped package in the same way that you import a normal package. Import a scoped package in the same way that you import a normal package.
@ -677,6 +723,12 @@ The view hierarchy does not imply a component hierarchy. Views that are embedded
See [Custom element](guide/glossary#custom-element) See [Custom element](guide/glossary#custom-element)
{@a workspace}
## Workspace
In Angular, a folder that contains [projects](guide/glossary#project) (that is, apps and libraries).
The [CLI](guide/glossary#cli) `new` command creates a workspace to contain projects. Commands such as `add` and `generate`, that create or operate on apps and libraries, must be executed from within a workspace folder.
{@a X} {@a X}

View File

@ -1034,7 +1034,7 @@ Call `request.flush()` with an error message, as seen in the following example.
<code-example <code-example
path="http/src/testing/http-client.spec.ts" path="http/src/testing/http-client.spec.ts"
region="network-error" region="404"
linenums="false"> linenums="false">
</code-example> </code-example>

View File

@ -219,7 +219,7 @@ configure services in root and feature modules respectively.
Angular doesn't recognize these names but Angular developers do. Angular doesn't recognize these names but Angular developers do.
Follow this convention when you write similar modules with configurable service providers. Follow this convention when you write similar modules with configurable service providers.
<!--KW--I don't understand how Angular doesn't understand these methods...-->
<hr/> <hr/>
@ -233,9 +233,8 @@ When you import an NgModule,
Angular adds the module's service providers (the contents of its `providers` list) Angular adds the module's service providers (the contents of its `providers` list)
to the application root injector. to the application root injector.
This makes the provider visible to every class in the application that knows the provider's lookup token, or knows its name. This makes the provider visible to every class in the application that knows the provider's lookup token, or name.
This is by design.
Extensibility through NgModule imports is a primary goal of the NgModule system. Extensibility through NgModule imports is a primary goal of the NgModule system.
Merging NgModule providers into the application injector Merging NgModule providers into the application injector
makes it easy for a module library to enrich the entire application with new services. makes it easy for a module library to enrich the entire application with new services.
@ -247,6 +246,8 @@ If the `HeroModule` provides the `HeroService` and the root `AppModule` imports
any class that knows the `HeroService` _type_ can inject that service, any class that knows the `HeroService` _type_ can inject that service,
not just the classes declared in the `HeroModule`. not just the classes declared in the `HeroModule`.
To limit access to a service, consider lazy loading the NgModule that provides that service. See [How do I restrict service scope to a module?](guide/ngmodule-faq#service-scope) for more information.
<hr/> <hr/>
{@a q-lazy-loaded-module-provider-visibility} {@a q-lazy-loaded-module-provider-visibility}
@ -288,6 +289,7 @@ The `AppModule` always wins.
<hr/> <hr/>
{@a service-scope}
## How do I restrict service scope to a module? ## How do I restrict service scope to a module?
@ -335,6 +337,8 @@ You can embed the child components in the top component's template.
Alternatively, make the top component a routing host by giving it a `<router-outlet>`. Alternatively, make the top component a routing host by giving it a `<router-outlet>`.
Define child routes and let the router load module components into that outlet. Define child routes and let the router load module components into that outlet.
Though you can limit access to a service by providing it in a lazy loaded module or providing it in a component, providing services in a component can lead to multiple instances of those services. Thus, the lazy loading is preferable.
<hr/> <hr/>
{@a q-root-component-or-module} {@a q-root-component-or-module}

View File

@ -3,7 +3,7 @@
Every application starts out with what seems like a simple task: get data, transform them, and show them to users. Every application starts out with what seems like a simple task: get data, transform them, and show them to users.
Getting data could be as simple as creating a local variable or as complex as streaming data over a WebSocket. Getting data could be as simple as creating a local variable or as complex as streaming data over a WebSocket.
Once data arrive, you could push their raw `toString` values directly to the view, Once data arrives, you could push their raw `toString` values directly to the view,
but that rarely makes for a good user experience. but that rarely makes for a good user experience.
For example, in most use cases, users prefer to see a date in a simple format like For example, in most use cases, users prefer to see a date in a simple format like
<samp>April 15, 1988</samp> rather than the raw string format <samp>April 15, 1988</samp> rather than the raw string format

View File

@ -241,7 +241,7 @@ even when the Universal web server is capable of handling those requests.
You'll have to change the services to make requests with absolute URLs when running on the server You'll have to change the services to make requests with absolute URLs when running on the server
and with relative URLs when running in the browser. and with relative URLs when running in the browser.
One solution is to provide the server's runtime origin under the Angular [`APP_BASE_REF` token](api/common/APP_BASE_HREF), One solution is to provide the server's runtime origin under the Angular [`APP_BASE_HREF` token](api/common/APP_BASE_HREF),
inject it into the service, and prepend the origin to the request URL. inject it into the service, and prepend the origin to the request URL.
Start by changing the `HeroService` constructor to take a second `origin` parameter that is optionally injected via the `APP_BASE_HREF` token. Start by changing the `HeroService` constructor to take a second `origin` parameter that is optionally injected via the `APP_BASE_HREF` token.

View File

@ -0,0 +1,352 @@
# Upgrading for Performance
<div class="alert is-helpful">
_Angular_ is the name for the Angular of today and tomorrow.<br />
_AngularJS_ is the name for all 1.x versions of Angular.
</div>
This guide describes some of the built-in tools for efficiently migrating AngularJS projects over to
the Angular platform, one piece at a time. It is very similar to
[Upgrading from AngularJS](guide/upgrade) with the exception that this one uses the {@link
downgradeModule downgradeModule()} helper function instead of the {@link UpgradeModule
UpgradeModule} class. This affects how the app is bootstrapped and how change detection is
propagated between the two frameworks. It allows you to upgrade incrementally while improving the
speed of your hybrid apps and leveraging the latest of Angular in AngularJS apps early in the
process of upgrading.
## Preparation
Before discussing how you can use `downgradeModule()` to create hybrid apps, there are things that
you can do to ease the upgrade process even before you begin upgrading. Because the steps are the
same regardless of how you upgrade, refer to the [Preparation](guide/upgrade#preparation) section of
[Upgrading from AngularJS](guide/upgrade).
## Upgrading with `ngUpgrade`
With the `ngUpgrade` library in Angular you can upgrade an existing AngularJS app incrementally by
building a hybrid app where you can run both frameworks side-by-side. In these hybrid apps you can
mix and match AngularJS and Angular components and services and have them interoperate seamlessly.
That means you don't have to do the upgrade work all at once as there is a natural coexistence
between the two frameworks during the transition period.
### How `ngUpgrade` Works
Regardless of whether you choose `downgradeModule()` or `UpgradeModule`, the basic principles of
upgrading, the mental model behind hybrid apps, and how you use the {@link upgrade/static
upgrade/static} utilities remain the same. For more information, see the
[How `ngUpgrade` Works](guide/upgrade#how-ngupgrade-works) section of
[Upgrading from AngularJS](guide/upgrade).
<div class="alert is-helpful">
The [Change Detection](guide/upgrade#change-detection) section of
[Upgrading from AngularJS](guide/upgrade) only applies to apps that use `UpgradeModule`. Though
you handle change detection differently with `downgradeModule()`, which is the focus of this
guide, reading the [Change Detection](guide/upgrade#change-detection) section provides helpful
context for what follows.
</div>
#### Change Detection with `downgradeModule()`
As mentioned before, one of the key differences between `downgradeModule()` and `UpgradeModule` has
to do with change detection and how it is propagated between the two frameworks.
With `UpgradeModule`, the two change detection systems are tied together more tightly. Whenever
something happens in the AngularJS part of the app, change detection is automatically triggered on
the Angular part and vice versa. This is convenient as it ensures that neither framework misses an
important change. Most of the time, though, these extra change detection runs are unnecessary.
`downgradeModule()`, on the other side, avoids explicitly triggering change detection unless it
knows the other part of the app is interested in the changes. For example, if a downgraded component
defines an `@Input()`, chances are that the app needs to be aware when that value changes. Thus,
`downgradeComponent()` automatically triggers change detection on that component.
In most cases, though, the changes made locally in a particular component are of no interest to the
rest of the app. For example, if the user clicks a button that submits a form, the component usually
handles the result of this action. That being said, there _are_ cases where you want to propagate
changes to some other part of the app that may be controlled by the other framework. In such cases,
you are responsible for notifying the interested parties by manually triggering change detection.
If you want a particular piece of code to trigger change detection in the AngularJS part of the app,
you need to wrap it in
[scope.$apply()](https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply). Similarly, for
triggering change detection in Angular you would use {@link NgZone#run ngZone.run()}.
In many cases, a few extra change detection runs may not matter much. However, on larger or
change-detection-heavy apps they can have a noticeable impact. By giving you more fine-grained
control over the change detection propagation, `downgradeModule()` allows you to achieve better
performance for your hybrid apps.
## Using `downgradeModule()`
Both AngularJS and Angular have their own concept of modules to help organize an app into cohesive
blocks of functionality.
Their details are quite different in architecture and implementation. In AngularJS, you create a
module by specifying its name and dependencies with
[angular.module()](https://docs.angularjs.org/api/ng/function/angular.module). Then you can add
assets using its various methods. In Angular, you create a class adorned with an {@link NgModule
NgModule} decorator that describes assets in metadata.
In a hybrid app you run both frameworks at the same time. This means that you need at least one
module each from both AngularJS and Angular.
For the most part, you specify the modules in the same way you would for a regular app. Then, you
use the `upgrade/static` helpers to let the two frameworks know about assets they can use from each
other. This is known as "upgrading" and "downgrading".
<div class="alert is-helpful">
<b>Definitions:</b>
- _Upgrading_: The act of making an AngularJS asset, such as a component or service, available to
the Angular part of the app.
- _Downgrading_: The act of making an Angular asset, such as a component or service, available to
the AngularJS part of the app.
</div>
An important part of inter-linking dependencies is linking the two main modules together. This is
where `downgradeModule()` comes in. Use it to create an AngularJS module&mdash;one that you can use
as a dependency in your main AngularJS module&mdash;that will bootstrap your main Angular module and
kick off the Angular part of the hybrid app. In a sense, it "downgrades" an Angular module to an
AngularJS module.
There are a few things to note, though:
1. You don't pass the Angular module directly to `downgradeModule()`. All `downgradeModule()` needs
is a "recipe", for example, a factory function, to create an instance for your module.
2. The Angular module is not instantiated until the app actually needs it.
The following is an example of how you can use `downgradeModule()` to link the two modules.
```ts
// Import `downgradeModule()`.
import { downgradeModule } from '@angular/upgrade/static';
// Use it to downgrade the Angular module to an AngularJS module.
const downgradedModule = downgradeModule(MainAngularModuleFactory);
// Use the downgraded module as a dependency to the main AngularJS module.
angular.module('mainAngularJsModule', [
downgradedModule
]);
```
#### Specifying a factory for the Angular module
As mentioned earlier, `downgradeModule()` needs to know how to instantiate the Angular module. It
needs a recipe. You define that recipe by providing a factory function that can create an instance
of the Angular module. `downgradeModule()` accepts two types of factory functions:
1. `NgModuleFactory`
2. `(extraProviders: StaticProvider[]) => Promise<NgModuleRef>`
When you pass an `NgModuleFactory`, `downgradeModule()` uses it to instantiate the module using
{@link platformBrowser platformBrowser}'s {@link PlatformRef#bootstrapModuleFactory
bootstrapModuleFactory()}, which is compatible with ahead-of-time (AOT) compilation. AOT compilation
helps make your apps load faster. For more about AOT and how to create an `NgModuleFactory`, see the
[Ahead-of-Time Compilation](guide/aot-compiler) guide.
Alternatively, you can pass a plain function, which is expected to return a promise resolving to an
{@link NgModuleRef NgModuleRef} (i.e. an instance of your Angular module). The function is called
with an array of extra {@link StaticProvider Providers} that are expected to be available on the
returned `NgModuleRef`'s {@link Injector Injector}. For example, if you are using {@link
platformBrowser platformBrowser} or {@link platformBrowserDynamic platformBrowserDynamic}, you can
pass the `extraProviders` array to them:
```ts
const bootstrapFn = (extraProviders: StaticProvider[]) => {
const platformRef = platformBrowserDynamic(extraProviders);
return platformRef.bootstrapModule(MainAngularModule);
};
// or
const bootstrapFn = (extraProviders: StaticProvider[]) => {
const platformRef = platformBrowser(extraProviders);
return platformRef.bootstrapModuleFactory(MainAngularModuleFactory);
};
```
Using an `NgModuleFactory` requires less boilerplate and is a good default option as it supports AOT
out-of-the-box. Using a custom function requires slightly more code, but gives you greater
flexibility.
#### Instantiating the Angular module on-demand
Another key difference between `downgradeModule()` and `UpgradeModule` is that the latter requires
you to instantiate both the AngularJS and Angular modules up-front. This means that you have to pay
the cost of instantiating the Angular part of the app, even if you don't use any Angular assets
until later. `downgradeModule()` is again less aggressive. It will only instantiate the Angular part
when it is required for the first time; that is, as soon as it needs to create a downgraded
component.
You could go a step further and not even download the code for the Angular part of the app to the
user's browser until it is needed. This is especially useful when you use Angular on parts of the
hybrid app that are not necessary for the initial rendering or that the user doesn't reach.
A few examples are:
- You use Angular on specific routes only and you don't need it until/if a user visits such a route.
- You use Angular for features that are only visible to specific types of users; for example,
logged-in users, administrators, or VIP members. You don't need to load Angular until a user is
authenticated.
- You use Angular for a feature that is not critical for the initial rendering of the app and you
can afford a small delay in favor of better initial load performance.
### Bootstrapping with `downgradeModule()`
As you might have guessed, you don't need to change anything in the way you bootstrap your existing
AngularJS app. Unlike `UpgradeModule`&mdash;which requires some extra steps&mdash;
`downgradeModule()` is able to take care of bootstrapping the Angular module, as long as you provide
the recipe.
In order to start using any `upgrade/static` APIs, you still need to load the Angular framework as
you would in a normal Angular app. You can see how this can be done with SystemJS by following the
instructions in the [Setup](guide/setup) guide, selectively copying code from the
[QuickStart github repository](https://github.com/angular/quickstart).
You also need to install the `@angular/upgrade` package via `npm install @angular/upgrade --save`
and add a mapping for the `@angular/upgrade/static` package:
<code-example title="system.config.js">
'@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js',
</code-example>
Next, create an `app.module.ts` file and add the following `NgModule` class:
<code-example title="app.module.ts">
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
imports: [
BrowserModule
]
})
export class MainAngularModule {
// Empty placeholder method to satisfy the `Compiler`.
ngDoBootstrap() {}
}
</code-example>
This bare minimum `NgModule` imports `BrowserModule`, the module every Angular browser-based app
must have. It also defines an empty `ngDoBootstrap()` method, to prevent the {@link Compiler
Compiler} from returning errors. This is necessary because the module will not have a `bootstrap`
declaration on its `NgModule` decorator.
<div class="alert is-important">
You do not add a `bootstrap` declaration to the `NgModule` decorator since AngularJS owns the root
template of the app and `ngUpgrade` bootstraps the necessary components.
</div>
You can now link the AngularJS and Angular modules together using `downgradeModule()`.
<code-example title="app.module.ts">
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { downgradeModule } from '@angular/upgrade/static';
const bootstrapFn = (extraProviders: StaticProvider[]) => {
const platformRef = platformBrowserDynamic(extraProviders);
return platformRef.bootstrapModule(MainAngularModule);
};
const downgradedModule = downgradeModule(bootstrapFn);
angular.module('mainAngularJsModule', [
downgradedModule
]);
</code-example>
The existing AngularJS code works as before _and_ you are ready to start adding Angular code.
### Using Components and Injectables
The differences between `downgradeModule()` and `UpgradeModule` end here. The rest of the
`upgrade/static` APIs and concepts work in the exact same way for both types of hybrid apps.
See [Upgrading from AngularJS](guide/upgrade) to learn about:
- [Using Angular Components from AngularJS Code](guide/upgrade#using-angular-components-from-angularjs-code).
- [Using AngularJS Component Directives from Angular Code](guide/upgrade#using-angularjs-component-directives-from-angular-code).
- [Projecting AngularJS Content into Angular Components](guide/upgrade#projecting-angularjs-content-into-angular-components).
- [Transcluding Angular Content into AngularJS Component Directives](guide/upgrade#transcluding-angular-content-into-angularjs-component-directives).
- [Making AngularJS Dependencies Injectable to Angular](guide/upgrade#making-angularjs-dependencies-injectable-to-angular).
- [Making Angular Dependencies Injectable to AngularJS](guide/upgrade#making-angular-dependencies-injectable-to-angularjs).
<div class="alert is-important">
While it is possible to downgrade injectables, downgraded injectables will not be available until
the Angular module is instantiated. In order to be safe, you need to ensure that the downgraded
injectables are not used anywhere _outside_ the part of the app that is controlled by Angular.
For example, it is _OK_ to use a downgraded service in an upgraded component that is only used
from Angular components, but it is _not OK_ to use it in an AngularJS component that may be used
independently of Angular.
</div>
## Using ahead-of-time compilation with hybrid apps
You can take advantage of ahead-of-time (AOT) compilation in hybrid apps just like in any other
Angular app. The setup for a hybrid app is mostly the same as described in the
[Ahead-of-Time Compilation](guide/aot-compiler) guide save for differences in `index.html` and
`main-aot.ts`.
AOT needs to load any AngularJS files that are in the `<script>` tags in the AngularJS `index.html`.
An easy way to copy them is to add each to the `copy-dist-files.js`file.
You also need to pass the generated `MainAngularModuleFactory` to `downgradeModule()` instead of the
custom bootstrap function:
<code-example title="app/main-aot.ts">
import { downgradeModule } from '@angular/upgrade/static';
import { MainAngularModuleNgFactory } from '../aot/app/app.module.ngfactory';
const downgradedModule = downgradeModule(MainAngularModuleNgFactory);
angular.module('mainAngularJsModule', [
downgradedModule
]);
</code-example>
And that is all you need to do to get the full benefit of AOT for hybrid Angular apps.
## Conclusion
This page covered how to use the {@link upgrade/static upgrade/static} package to incrementally
upgrade existing AngularJS apps at your own pace and without impeding further development of the app
for the duration of the upgrade process.
Specifically, this guide showed how you can achieve better performance and greater flexibility in
your hybrid apps by using {@link downgradeModule downgradeModule()} instead of {@link UpgradeModule
UpgradeModule}.
To summarize, the key differentiating factors of `downgradeModule()` are:
1. It allows instantiating or even loading the Angular part lazily, which improves the initial
loading time. In some cases this may waive the cost of running a second framework altogether.
2. It improves performance by avoiding unnecessary change detection runs while giving the developer
greater ability to customize.
3. It does not require you to change how you bootstrap your AngularJS app.
Using `downgradeModule()` is a good option for hybrid apps when you want to keep the AngularJS and
Angular parts less coupled. You can still mix and match components and services from both
frameworks, but you might need to manually propagate change detection. In return,
`downgradeModule()` offers more control and better performance.

View File

@ -1,7 +1,7 @@
# Upgrading from AngularJS to Angular # Upgrading from AngularJS to Angular
_Angular_ is the name for the Angular of today and tomorrow. _Angular_ is the name for the Angular of today and tomorrow.<br />
_AngularJS_ is the name for all v1.x versions of Angular. _AngularJS_ is the name for all 1.x versions of Angular.
AngularJS apps are great. AngularJS apps are great.
Always consider the business case before moving to Angular. Always consider the business case before moving to Angular.
@ -195,7 +195,7 @@ transition period.
### How ngUpgrade Works ### How ngUpgrade Works
The primary tool provided by ngUpgrade is called the `UpgradeModule`. One of the primary tools provided by ngUpgrade is called the `UpgradeModule`.
This is a module that contains utilities for bootstrapping and managing hybrid This is a module that contains utilities for bootstrapping and managing hybrid
applications that support both Angular and AngularJS code. applications that support both Angular and AngularJS code.
@ -252,7 +252,7 @@ frameworks in how it actually works.
</table> </table>
Even accounting for these differences you can still have dependency injection Even accounting for these differences you can still have dependency injection
interoperability. The `UpgradeModule` resolves the differences and makes interoperability. `upgrade/static` resolves the differences and makes
everything work seamlessly: everything work seamlessly:
* You can make AngularJS services available for injection to Angular code * You can make AngularJS services available for injection to Angular code
@ -569,7 +569,7 @@ So, you can write an Angular component and then use it from AngularJS
code. This is useful when you start to migrate from lower-level code. This is useful when you start to migrate from lower-level
components and work your way up. But in some cases it is more convenient components and work your way up. But in some cases it is more convenient
to do things in the opposite order: To start with higher-level components to do things in the opposite order: To start with higher-level components
and work your way down. This too can be done using the `UpgradeModule`. and work your way down. This too can be done using the `upgrade/static`.
You can *upgrade* AngularJS component directives and then use them from You can *upgrade* AngularJS component directives and then use them from
Angular. Angular.
@ -710,7 +710,7 @@ and then provide the input and output using Angular template syntax:
When you are using a downgraded Angular component from an AngularJS When you are using a downgraded Angular component from an AngularJS
template, the need may arise to *transclude* some content into it. This template, the need may arise to *transclude* some content into it. This
is also possible. While there is no such thing as transclusion in Angular, is also possible. While there is no such thing as transclusion in Angular,
there is a very similar concept called *content projection*. The `UpgradeModule` there is a very similar concept called *content projection*. `upgrade/static`
is able to make these two features interoperate. is able to make these two features interoperate.
Angular components that support content projection make use of an `<ng-content>` Angular components that support content projection make use of an `<ng-content>`

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -360,7 +360,7 @@
"picture": "jorgeucano.jpg", "picture": "jorgeucano.jpg",
"twitter": "jorgeucano", "twitter": "jorgeucano",
"website": "https://medium.com/@jorgeucano", "website": "https://medium.com/@jorgeucano",
"bio": "Jorge is a Fulll Stack Developer in ByteDefault ... Professor in several courses related to javascript , speaker, and writer of technical articles and a book Entendiendo Angular, Google Developer Expert in web technologies nominate by Google, Nativescript Developer Expert nominated by Telerik.", "bio": "Jorge is a Full Stack Developer in ByteDefault, a professor for several courses related to JavaScript, a speaker, and an author of technical articles and the book \"Entendiendo Angular.\" He is a Google Developer Expert in web technologies (nominated by Google) and a NativeScript Developer Expert (nominated by Telerik).",
"group": "GDE" "group": "GDE"
}, },
@ -378,8 +378,8 @@
"picture": "michaelprentice.jpg", "picture": "michaelprentice.jpg",
"twitter": "splaktar", "twitter": "splaktar",
"website": "https://www.DevIntent.com", "website": "https://www.DevIntent.com",
"bio": "Owner and consultant at DevIntent. Active open-source contributor and leader. Passionate advocate, coach, and consultant for LEAN and Agile teams. Google Developer Expert (GDE) in Angular. Founder and organizer for the Google Developers Group (GDG) community on the Space Coast of Florida, USA.", "bio": "Lead for AngularJS Material. Owner and consultant at DevIntent. Ex-Angular GDE. Founder of the Google Developers Group (GDG) community on the Space Coast of Florida, USA.",
"group": "GDE" "group": "Angular"
}, },
"mikebrocchi": { "mikebrocchi": {
@ -645,5 +645,13 @@
"website": "https://kmaida.io/", "website": "https://kmaida.io/",
"bio": "Kim is an an Angular consultant, developer, speaker, writer, and Google Developer Expert. She is passionate about learning from and sharing knowledge with other developers through blogging, speaking, workshops, and open source.", "bio": "Kim is an an Angular consultant, developer, speaker, writer, and Google Developer Expert. She is passionate about learning from and sharing knowledge with other developers through blogging, speaking, workshops, and open source.",
"group": "GDE" "group": "GDE"
},
"elanaolson": {
"name": "Elana Olson",
"picture": "elanaolson.jpg",
"twitter": "elanathellama",
"bio": "Elana is a Developer Relations intern on the Angular team at Google. She is working on migration paths from AngularJS to Angular and would love to chat about your experience with upgrading.",
"group": "Angular"
} }
} }

View File

@ -490,6 +490,13 @@
"rev": true, "rev": true,
"title": "Angular-Buch (German)", "title": "Angular-Buch (German)",
"url": "https://angular-buch.com/" "url": "https://angular-buch.com/"
},
"wishtack-guide-angular": {
"desc": "The free, open-source and up-to-date Angular guide. This pragmatic guide is focused on best practices and will drive you from scratch to cloud.",
"logo": "https://raw.githubusercontent.com/wishtack/gitbook-guide-angular/master/.gitbook/assets/wishtack-logo-with-text.png",
"rev": true,
"title": "The Angular Guide by Wishtack (Français)",
"url": "https://guide-angular.wishtack.io/"
} }
} }
}, },
@ -605,6 +612,12 @@
"title": "Ultimate Angular", "title": "Ultimate Angular",
"url": "https://ultimateangular.com/" "url": "https://ultimateangular.com/"
}, },
"willh-angular-zero": {
"desc": "Online video course in Chinese for newbies who need to learning from the scratch in Chinese. It's covering Angular, Angular CLI, TypeScript, VSCode, and some must known knowledge of Angular development.",
"rev": true,
"title": "Angular in Action: Start From Scratch (正體中文)",
"url": "https://www.udemy.com/angular-zero/?couponCode=ANGULAR.IO"
},
"angular-firebase": { "angular-firebase": {
"desc": "Video lessons covering progressive web apps with Angular, Firebase, RxJS, and related APIs.", "desc": "Video lessons covering progressive web apps with Angular, Firebase, RxJS, and related APIs.",
"rev": true, "rev": true,
@ -659,8 +672,8 @@
"url": "http://ninja-squad.com/formations/formation-angular2" "url": "http://ninja-squad.com/formations/formation-angular2"
}, },
"a2b": { "a2b": {
"desc": "Angular Boot Camp covers introductory and intermediate content. It includes extensive workshop session, with hands-on help from our experienced developer-trainers. At the end of this class, student are usually able to use AngularJS to make an end-to-end, working application.", "desc": "Angular Boot Camp covers introductory through advanced Angular topics. It includes extensive workshop sessions, with hands-on help from our experienced developer-trainers. We take developers or teams from the beginnings of Angular understanding through a working knowledge of all essential Angular features.",
"logo": "", "logo": "https://angularbootcamp.com/images/angular-boot-camp-logo.svg",
"rev": true, "rev": true,
"title": "Angular Boot Camp", "title": "Angular Boot Camp",
"url": "https://angularbootcamp.com" "url": "https://angularbootcamp.com"

View File

@ -514,6 +514,11 @@
"title": "Upgrading Instructions", "title": "Upgrading Instructions",
"tooltip": "Incrementally upgrade an AngularJS application to Angular." "tooltip": "Incrementally upgrade an AngularJS application to Angular."
}, },
{
"url": "guide/upgrade-performance",
"title": "Upgrading for Performance",
"tooltip": "Upgrade from AngularJS to Angular in a more flexible way."
},
{ {
"url": "guide/ajs-quick-reference", "url": "guide/ajs-quick-reference",
"title": "AngularJS-Angular Concepts", "title": "AngularJS-Angular Concepts",

View File

@ -142,6 +142,9 @@ Here are the code files discussed on this page and your app should look like thi
<code-pane title="src/app/heroes/heroes.component.html" path="toh-pt3/src/app/heroes/heroes.component.html"> <code-pane title="src/app/heroes/heroes.component.html" path="toh-pt3/src/app/heroes/heroes.component.html">
</code-pane> </code-pane>
<code-pane title="src/app/app.module.ts" path="toh-pt3/src/app/app.module.ts">
</code-pane>
</code-tabs> </code-tabs>
## Summary ## Summary

View File

@ -15,7 +15,7 @@
// A random bad indexed page that used `api/api` // A random bad indexed page that used `api/api`
{"type": 301, "source": "/api/api/:rest*", "destination": "/api/:rest*"}, {"type": 301, "source": "/api/api/:rest*", "destination": "/api/:rest*"},
// Guide renames // Guide renames/removals
{"type": 301, "source": "/docs/*/latest/cli-quickstart.html", "destination": "/guide/quickstart"}, {"type": 301, "source": "/docs/*/latest/cli-quickstart.html", "destination": "/guide/quickstart"},
{"type": 301, "source": "/docs/*/latest/glossary.html", "destination": "/guide/glossary"}, {"type": 301, "source": "/docs/*/latest/glossary.html", "destination": "/guide/glossary"},
{"type": 301, "source": "/docs/*/latest/quickstart.html", "destination": "/guide/quickstart"}, {"type": 301, "source": "/docs/*/latest/quickstart.html", "destination": "/guide/quickstart"},
@ -25,6 +25,7 @@
{"type": 301, "source": "/guide/service-worker-getstart", "destination": "/guide/service-worker-getting-started"}, {"type": 301, "source": "/guide/service-worker-getstart", "destination": "/guide/service-worker-getting-started"},
{"type": 301, "source": "/guide/service-worker-comm", "destination": "/guide/service-worker-communications"}, {"type": 301, "source": "/guide/service-worker-comm", "destination": "/guide/service-worker-communications"},
{"type": 301, "source": "/guide/service-worker-configref", "destination": "/guide/service-worker-config"}, {"type": 301, "source": "/guide/service-worker-configref", "destination": "/guide/service-worker-config"},
{"type": 301, "source": "/guide/webpack", "destination": "https://v5.angular.io/guide/webpack"},
// some top level guide pages on old site were moved below the guide folder // some top level guide pages on old site were moved below the guide folder
{"type": 301, "source": "/styleguide", "destination": "/guide/styleguide"}, {"type": 301, "source": "/styleguide", "destination": "/guide/styleguide"},

View File

@ -18,7 +18,7 @@
"routing": { "routing": {
"index": "/index.html", "index": "/index.html",
"routes": { "routes": {
"^(?!/styleguide|/docs/.|(?:/guide/(?:cli-quickstart|metadata|ngmodule|service-worker-(?:getstart|comm|configref)|learning-angular)|/news)(?:\\.html|/)?$|/testing|/api/(?:.+/[^/]+-|platform-browser/AnimationDriver|testing/|api/|animate/|(?:common/(?:NgModel|Control|MaxLengthValidator))|(?:[^/]+/)?(?:NgFor(?:$|-)|AnimationStateDeclarationMetadata|CORE_DIRECTIVES|PLATFORM_PIPES|DirectiveMetadata|HTTP_PROVIDERS))|.*/stackblitz(?:\\.html)?(?:\\?.*)?$|.*\\.[^\/.]+$)": { "^(?!/styleguide|/docs/.|(?:/guide/(?:cli-quickstart|metadata|ngmodule|service-worker-(?:getstart|comm|configref)|learning-angular|webpack)|/news)(?:\\.html|/)?$|/testing|/api/(?:.+/[^/]+-|platform-browser/AnimationDriver|testing/|api/|animate/|(?:common/(?:NgModel|Control|MaxLengthValidator))|(?:[^/]+/)?(?:NgFor(?:$|-)|AnimationStateDeclarationMetadata|CORE_DIRECTIVES|PLATFORM_PIPES|DirectiveMetadata|HTTP_PROVIDERS))|.*/stackblitz(?:\\.html)?(?:\\?.*)?$|.*\\.[^\/.]+$)": {
"match": "regex" "match": "regex"
} }
} }

View File

@ -32,7 +32,7 @@
"test-pwa-score": "node scripts/test-pwa-score", "test-pwa-score": "node scripts/test-pwa-score",
"example-e2e": "yarn example-check-local && node ./tools/examples/run-example-e2e", "example-e2e": "yarn example-check-local && node ./tools/examples/run-example-e2e",
"example-lint": "tslint -c \"content/examples/tslint.json\" \"content/examples/**/*.ts\" -e \"content/examples/styleguide/**/*.avoid.ts\"", "example-lint": "tslint -c \"content/examples/tslint.json\" \"content/examples/**/*.ts\" -e \"content/examples/styleguide/**/*.avoid.ts\"",
"example-use-local": "node tools/ng-packages-installer overwrite ./tools/examples/shared", "example-use-local": "node tools/ng-packages-installer overwrite ./tools/examples/shared --debug",
"example-use-npm": "node tools/ng-packages-installer restore ./tools/examples/shared", "example-use-npm": "node tools/ng-packages-installer restore ./tools/examples/shared",
"example-check-local": "node tools/ng-packages-installer check ./tools/examples/shared", "example-check-local": "node tools/ng-packages-installer check ./tools/examples/shared",
"deploy-preview": "scripts/deploy-preview.sh", "deploy-preview": "scripts/deploy-preview.sh",

View File

@ -27,6 +27,12 @@
</a> </a>
<aio-top-menu *ngIf="isSideBySide" [nodes]="topMenuNodes"></aio-top-menu> <aio-top-menu *ngIf="isSideBySide" [nodes]="topMenuNodes"></aio-top-menu>
<aio-search-box class="search-container" #searchBox (onSearch)="doSearch($event)" (onFocus)="doSearch($event)"></aio-search-box> <aio-search-box class="search-container" #searchBox (onSearch)="doSearch($event)" (onFocus)="doSearch($event)"></aio-search-box>
<div class="toolbar-external-icons-container">
<a href="https://twitter.com/angular" title="Twitter">
<img src="assets/images/logos/twitter-icon.svg"></a>
<a href="https://github.com/angular/angular" title="GitHub">
<img src="assets/images/logos/github-icon.svg"></a>
</div>
</mat-toolbar-row> </mat-toolbar-row>
</mat-toolbar> </mat-toolbar>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 51.8 50.4" style="enable-background:new 0 0 51.8 50.4;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M25.9,0.2C11.8,0.2,0.3,11.7,0.3,25.8c0,11.3,7.3,20.9,17.5,24.3c1.3,0.2,1.7-0.6,1.7-1.2c0-0.6,0-2.6,0-4.8
c-7.1,1.5-8.6-3-8.6-3c-1.2-3-2.8-3.7-2.8-3.7c-2.3-1.6,0.2-1.6,0.2-1.6c2.6,0.2,3.9,2.6,3.9,2.6c2.3,3.9,6,2.8,7.5,2.1
c0.2-1.7,0.9-2.8,1.6-3.4c-5.7-0.6-11.7-2.8-11.7-12.7c0-2.8,1-5.1,2.6-6.9c-0.3-0.7-1.1-3.3,0.3-6.8c0,0,2.1-0.7,7,2.6
c2-0.6,4.2-0.9,6.4-0.9c2.2,0,4.4,0.3,6.4,0.9c4.9-3.3,7-2.6,7-2.6c1.4,3.5,0.5,6.1,0.3,6.8c1.6,1.8,2.6,4.1,2.6,6.9
c0,9.8-6,12-11.7,12.6c0.9,0.8,1.7,2.4,1.7,4.7c0,3.4,0,6.2,0,7c0,0.7,0.5,1.5,1.8,1.2c10.2-3.4,17.5-13,17.5-24.3
C51.5,11.7,40.1,0.2,25.9,0.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 49.7" style="enable-background:new 0 0 50 49.7;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M50,9.3c-1.8,0.8-3.8,1.4-5.9,1.6c2.1-1.3,3.7-3.3,4.5-5.7c-2,1.2-4.2,2-6.5,2.5c-1.9-2-4.5-3.2-7.5-3.2
c-5.7,0-10.3,4.6-10.3,10.3c0,0.8,0.1,1.6,0.3,2.3C16.1,16.7,8.5,12.6,3.5,6.4c-0.9,1.5-1.4,3.3-1.4,5.2c0,3.6,1.8,6.7,4.6,8.5
C5,20,3.4,19.6,2,18.8c0,0,0,0.1,0,0.1c0,5,3.5,9.1,8.2,10.1c-0.9,0.2-1.8,0.4-2.7,0.4c-0.7,0-1.3-0.1-1.9-0.2
c1.3,4.1,5.1,7,9.6,7.1c-3.5,2.8-7.9,4.4-12.7,4.4c-0.8,0-1.6,0-2.4-0.1c4.5,2.9,9.9,4.6,15.7,4.6c18.9,0,29.2-15.6,29.2-29.2
c0-0.4,0-0.9,0-1.3C46.9,13.2,48.6,11.4,50,9.3z"/>
</svg>

After

Width:  |  Height:  |  Size: 937 B

View File

@ -37,5 +37,9 @@
padding: 16px 24px; padding: 16px 24px;
} }
} }
.short-description {
margin-left: 0;
}
} }
} }

View File

@ -2,7 +2,6 @@
$hamburgerShownMargin: 0 8px 0 0; $hamburgerShownMargin: 0 8px 0 0;
$hamburgerHiddenMargin: 0 16px 0 -88px; $hamburgerHiddenMargin: 0 16px 0 -88px;
// DOCS PAGE / STANDARD: TOPNAV TOOLBAR FIXED // DOCS PAGE / STANDARD: TOPNAV TOOLBAR FIXED
mat-toolbar.mat-toolbar { mat-toolbar.mat-toolbar {
position: fixed; position: fixed;
@ -10,7 +9,7 @@ mat-toolbar.mat-toolbar {
right: 0; right: 0;
left: 0; left: 0;
z-index: 10; z-index: 10;
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.30); box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.3);
mat-toolbar-row { mat-toolbar-row {
padding: 0 16px 0 0; padding: 0 16px 0 0;
@ -21,7 +20,6 @@ mat-toolbar.mat-toolbar {
} }
} }
// HOME PAGE OVERRIDE: TOPNAV TOOLBAR // HOME PAGE OVERRIDE: TOPNAV TOOLBAR
aio-shell.page-home mat-toolbar.mat-toolbar { aio-shell.page-home mat-toolbar.mat-toolbar {
background-color: $blue; background-color: $blue;
@ -29,12 +27,11 @@ aio-shell.page-home mat-toolbar.mat-toolbar {
@media (min-width: 481px) { @media (min-width: 481px) {
&:not(.transitioning) { &:not(.transitioning) {
background-color: transparent; background-color: transparent;
transition: background-color .2s linear; transition: background-color 0.2s linear;
} }
} }
} }
// MARKETING PAGES OVERRIDE: TOPNAV TOOLBAR AND HAMBURGER // MARKETING PAGES OVERRIDE: TOPNAV TOOLBAR AND HAMBURGER
aio-shell.page-home mat-toolbar.mat-toolbar, aio-shell.page-home mat-toolbar.mat-toolbar,
aio-shell.page-features mat-toolbar.mat-toolbar, aio-shell.page-features mat-toolbar.mat-toolbar,
@ -48,7 +45,6 @@ aio-shell.page-resources mat-toolbar.mat-toolbar {
} }
} }
// DOCS PAGES OVERRIDE: HAMBURGER // DOCS PAGES OVERRIDE: HAMBURGER
aio-shell.folder-api mat-toolbar.mat-toolbar, aio-shell.folder-api mat-toolbar.mat-toolbar,
aio-shell.folder-docs mat-toolbar.mat-toolbar, aio-shell.folder-docs mat-toolbar.mat-toolbar,
@ -62,7 +58,6 @@ aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
} }
} }
// HAMBURGER BUTTON // HAMBURGER BUTTON
.hamburger.mat-button { .hamburger.mat-button {
height: 100%; height: 100%;
@ -70,9 +65,9 @@ aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
padding: 0; padding: 0;
&:not(.starting) { &:not(.starting) {
transition-duration: .4s; transition-duration: 0.4s;
transition-property: color, margin; transition-property: color, margin;
transition-timing-function: cubic-bezier(.25, .8, .25, 1); transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1);
} }
@media (min-width: 992px) { @media (min-width: 992px) {
@ -91,7 +86,6 @@ aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
} }
} }
// HOME NAV-LINK // HOME NAV-LINK
.nav-link.home { .nav-link.home {
cursor: pointer; cursor: pointer;
@ -112,7 +106,6 @@ aio-shell.folder-tutorial mat-toolbar.mat-toolbar {
} }
} }
// TOP MENU // TOP MENU
aio-top-menu { aio-top-menu {
display: flex; display: flex;
@ -158,7 +151,6 @@ aio-top-menu {
} }
} }
// SEARCH BOX // SEARCH BOX
aio-search-box.search-container { aio-search-box.search-container {
display: flex; display: flex;
@ -196,3 +188,25 @@ aio-search-box.search-container {
} }
} }
} }
// EXTERNAL LINK ICONS
.app-toolbar {
.toolbar-external-icons-container {
display: flex;
flex-direction: row;
a {
display: flex;
align-items: center;
margin-left: 16px;
&:hover {
opacity: 0.8;
}
img {
height: 24px;
}
}
}
}

View File

@ -178,6 +178,7 @@
/guide/service-worker-getstart /guide/service-worker-getting-started /guide/service-worker-getstart /guide/service-worker-getting-started
/guide/service-worker-comm /guide/service-worker-communications /guide/service-worker-comm /guide/service-worker-communications
/guide/service-worker-configref /guide/service-worker-config /guide/service-worker-configref /guide/service-worker-config
/guide/webpack https://v5.angular.io/guide/webpack
/news https://blog.angular.io/ /news https://blog.angular.io/
/news.html https://blog.angular.io/ /news.html https://blog.angular.io/
/testing /guide/testing /testing /guide/testing

View File

@ -9,8 +9,8 @@
], ],
"dependencies": [], "dependencies": [],
"devDependencies": [ "devDependencies": [
"@angular/cli",
"@angular-devkit/build-angular", "@angular-devkit/build-angular",
"@angular/cli",
"@types/jasminewd2", "@types/jasminewd2",
"jasmine-spec-reporter", "jasmine-spec-reporter",
"karma-coverage-istanbul-reporter", "karma-coverage-istanbul-reporter",

View File

@ -11,8 +11,8 @@
], ],
"dependencies": [], "dependencies": [],
"devDependencies": [ "devDependencies": [
"@angular/cli",
"@angular-devkit/build-angular", "@angular-devkit/build-angular",
"@angular/cli",
"@types/jasminewd2", "@types/jasminewd2",
"jasmine-spec-reporter", "jasmine-spec-reporter",
"karma-coverage-istanbul-reporter", "karma-coverage-istanbul-reporter",

View File

@ -1,19 +1,21 @@
'use strict'; 'use strict';
const path = require('canonical-path'); const path = require('canonical-path');
const fs = require('fs');
const examplesPath = path.resolve(__dirname, '../../../examples'); const examplesPath = path.resolve(__dirname, '../../../examples');
const packageFolder = path.resolve(__dirname); const packageFolder = path.resolve(__dirname);
class PackageJsonCustomizer { class PackageJsonCustomizer {
constructor() { constructor() {
this.dependenciesPackageJson = require(path.join(examplesPath, '/shared/package.json')); this.dependenciesPackageJson = this.readJson(path.join(examplesPath, '/shared/package.json'));
this.scriptsPackageJson = require(path.join(examplesPath, '/shared/boilerplate/systemjs/package.json')); this.scriptsPackageJson = this.readJson(path.join(examplesPath, '/shared/boilerplate/systemjs/package.json'));
this.basePackageJson = require(`${packageFolder}/base.json`); this.basePackageJson = this.readJson(`${packageFolder}/base.json`);
this.templatePackageJson = this.readJson(`${packageFolder}/package.json`, false);
} }
generate(type = 'systemjs') { generate(type = 'systemjs') {
let packageJson = require(`${packageFolder}/package.json`); let packageJson = JSON.parse(this.templatePackageJson);
let rules = require(`${packageFolder}/${type}.json`); let rules = require(`${packageFolder}/${type}.json`);
this._mergeJSON(rules, this.basePackageJson); this._mergeJSON(rules, this.basePackageJson);
@ -50,6 +52,12 @@ class PackageJsonCustomizer {
} }
return result; return result;
} }
readJson(jsonFile, parse = true) {
const contents = fs.readFileSync(jsonFile, 'utf8');
return parse ? JSON.parse(contents) : contents;
}
} }
module.exports = PackageJsonCustomizer; module.exports = PackageJsonCustomizer;

View File

@ -9,6 +9,7 @@
], ],
"dependencies": [], "dependencies": [],
"devDependencies": [ "devDependencies": [
"@angular-devkit/build-angular",
"@angular/cli", "@angular/cli",
"@types/jasminewd2", "@types/jasminewd2",
"jasmine-marbles", "jasmine-marbles",

View File

@ -15,6 +15,7 @@
"@nguniversal/module-map-ngfactory-loader" "@nguniversal/module-map-ngfactory-loader"
], ],
"devDependencies": [ "devDependencies": [
"@angular-devkit/build-angular",
"@angular/cli", "@angular/cli",
"@types/jasminewd2", "@types/jasminewd2",
"jasmine-spec-reporter", "jasmine-spec-reporter",

View File

@ -18,7 +18,6 @@ const EXAMPLE_CONFIG_FILENAME = 'example-config.json';
const IGNORED_EXAMPLES = [ // temporary ignores const IGNORED_EXAMPLES = [ // temporary ignores
'quickstart', 'quickstart',
'setup', 'setup',
'upgrade-p'
]; ];
/** /**

View File

@ -8,10 +8,8 @@
"experimentalDecorators": true, "experimentalDecorators": true,
"lib": [ "es2015", "dom" ], "lib": [ "es2015", "dom" ],
"noImplicitAny": true, "noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true, "skipLibCheck": true,
"typeRoots": [ "suppressImplicitAnyIndexErrors": true
"../node_modules/@types/"
]
}, },
"compileOnSave": true, "compileOnSave": true,
"exclude": [ "exclude": [

View File

@ -45,16 +45,15 @@
"@angular/compiler-cli": "^6.0.0", "@angular/compiler-cli": "^6.0.0",
"@angular/language-service": "^6.0.0", "@angular/language-service": "^6.0.0",
"@angular/platform-server": "^6.0.0", "@angular/platform-server": "^6.0.0",
"@types/angular": "^1.5.16", "@types/angular": "^1.6.47",
"@types/angular-animate": "^1.5.5", "@types/angular-animate": "^1.5.10",
"@types/angular-cookies": "^1.4.2", "@types/angular-mocks": "^1.6.0",
"@types/angular-mocks": "^1.5.5", "@types/angular-resource": "^1.5.14",
"@types/angular-resource": "^1.5.6", "@types/angular-route": "^1.3.5",
"@types/angular-route": "^1.3.2",
"@types/angular-sanitize": "^1.3.3",
"@types/express": "^4.0.35", "@types/express": "^4.0.35",
"@types/jasmine": "~2.8.0", "@types/jasmine": "~2.8.0",
"@types/jasminewd2": "^2.0.3", "@types/jasminewd2": "^2.0.3",
"@types/jquery": "^3.3.4",
"@types/node": "^6.0.45", "@types/node": "^6.0.45",
"canonical-path": "0.0.2", "canonical-path": "0.0.2",
"concurrently": "^3.0.0", "concurrently": "^3.0.0",
@ -78,6 +77,7 @@
"rollup-plugin-node-resolve": "2.0.0", "rollup-plugin-node-resolve": "2.0.0",
"rollup-plugin-uglify": "^1.0.1", "rollup-plugin-uglify": "^1.0.1",
"source-map-explorer": "^1.3.2", "source-map-explorer": "^1.3.2",
"ts-loader": "^4.2.0",
"ts-node": "^5.0.1", "ts-node": "^5.0.1",
"tslint": "^5.9.1", "tslint": "^5.9.1",
"typescript": "2.7.2", "typescript": "2.7.2",

View File

@ -240,45 +240,33 @@
version "0.7.0" version "0.7.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
"@types/angular-animate@^1.5.5": "@types/angular-animate@^1.5.10":
version "1.5.8" version "1.5.10"
resolved "https://registry.yarnpkg.com/@types/angular-animate/-/angular-animate-1.5.8.tgz#578e058ee0ca5539e1795421a91ae2f52581dc8f" resolved "https://registry.yarnpkg.com/@types/angular-animate/-/angular-animate-1.5.10.tgz#b94b45358c61163f1478768e8b081c76439c515f"
dependencies: dependencies:
"@types/angular" "*" "@types/angular" "*"
"@types/angular-cookies@^1.4.2": "@types/angular-mocks@^1.6.0":
version "1.4.5" version "1.6.0"
resolved "https://registry.yarnpkg.com/@types/angular-cookies/-/angular-cookies-1.4.5.tgz#f5ccf5f42a7b9f4d13e77afb8722034ea9f40bd3" resolved "https://registry.yarnpkg.com/@types/angular-mocks/-/angular-mocks-1.6.0.tgz#bd32f55b678c239880d2d0d9a3a79b5cad45547e"
dependencies: dependencies:
"@types/angular" "*" "@types/angular" "*"
"@types/angular-mocks@^1.5.5": "@types/angular-resource@^1.5.14":
version "1.5.11"
resolved "https://registry.yarnpkg.com/@types/angular-mocks/-/angular-mocks-1.5.11.tgz#d5bbefbf742f2196071bda0fe051878b6f4fd72c"
dependencies:
"@types/angular" "*"
"@types/angular-resource@^1.5.6":
version "1.5.14" version "1.5.14"
resolved "https://registry.yarnpkg.com/@types/angular-resource/-/angular-resource-1.5.14.tgz#902f34e8c98f708ae99493c6d416b39b4a22d9fe" resolved "https://registry.yarnpkg.com/@types/angular-resource/-/angular-resource-1.5.14.tgz#902f34e8c98f708ae99493c6d416b39b4a22d9fe"
dependencies: dependencies:
"@types/angular" "*" "@types/angular" "*"
"@types/angular-route@^1.3.2": "@types/angular-route@^1.3.5":
version "1.3.4" version "1.3.5"
resolved "https://registry.yarnpkg.com/@types/angular-route/-/angular-route-1.3.4.tgz#10d3f7eb313fb8a4b832041f9401869803dcd4df" resolved "https://registry.yarnpkg.com/@types/angular-route/-/angular-route-1.3.5.tgz#78b8e0b069d5efe55ec7072461f4e2f6ae20767b"
dependencies: dependencies:
"@types/angular" "*" "@types/angular" "*"
"@types/angular-sanitize@^1.3.3": "@types/angular@*", "@types/angular@^1.6.47":
version "1.3.6" version "1.6.47"
resolved "https://registry.yarnpkg.com/@types/angular-sanitize/-/angular-sanitize-1.3.6.tgz#fec2bd040d38708e46f02e66fba5199e8a64b22e" resolved "https://registry.yarnpkg.com/@types/angular/-/angular-1.6.47.tgz#f7a31279a02c0892ed9aa76aae2da1b17791bacd"
dependencies:
"@types/angular" "*"
"@types/angular@*", "@types/angular@^1.5.16":
version "1.6.36"
resolved "https://registry.yarnpkg.com/@types/angular/-/angular-1.6.36.tgz#15e73d632274b5655a391470844863548c7755f4"
"@types/body-parser@*": "@types/body-parser@*":
version "1.16.7" version "1.16.7"
@ -315,6 +303,10 @@
dependencies: dependencies:
"@types/jasmine" "*" "@types/jasmine" "*"
"@types/jquery@^3.3.4":
version "3.3.4"
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.4.tgz#f1850fb9a70041a14ace4f81a7ed782db8548317"
"@types/mime@*": "@types/mime@*":
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.0.tgz#5a7306e367c539b9f6543499de8dd519fac37a8b" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.0.tgz#5a7306e367c539b9f6543499de8dd519fac37a8b"
@ -7369,7 +7361,7 @@ semver-intersect@^1.1.2:
dependencies: dependencies:
semver "^5.0.0" semver "^5.0.0"
"semver@2 >=2.2.1 || 3.x || 4 || 5", semver@^5.0.0, semver@^5.5.0: "semver@2 >=2.2.1 || 3.x || 4 || 5", semver@^5.0.0, semver@^5.0.1, semver@^5.5.0:
version "5.5.0" version "5.5.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
@ -8265,6 +8257,16 @@ trim-right@^1.0.1:
dependencies: dependencies:
glob "^6.0.4" glob "^6.0.4"
ts-loader@^4.2.0:
version "4.4.1"
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-4.4.1.tgz#c93a46eea430ebce1f790dfe438caefb8670d365"
dependencies:
chalk "^2.3.0"
enhanced-resolve "^4.0.0"
loader-utils "^1.0.2"
micromatch "^3.1.4"
semver "^5.0.1"
ts-node@^5.0.1: ts-node@^5.0.1:
version "5.0.1" version "5.0.1"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-5.0.1.tgz#78e5d1cb3f704de1b641e43b76be2d4094f06f81" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-5.0.1.tgz#78e5d1cb3f704de1b641e43b76be2d4094f06f81"

View File

@ -32,6 +32,7 @@ module.exports = new Package('angular-api', [basePackage, typeScriptPackage])
.processor(require('./processors/computeSearchTitle')) .processor(require('./processors/computeSearchTitle'))
.processor(require('./processors/simplifyMemberAnchors')) .processor(require('./processors/simplifyMemberAnchors'))
.processor(require('./processors/computeStability')) .processor(require('./processors/computeStability'))
.processor(require('./processors/removeInjectableConstructors'))
/** /**
* These are the API doc types that will be rendered to actual files. * These are the API doc types that will be rendered to actual files.

View File

@ -0,0 +1,18 @@
module.exports = function removeInjectableConstructors() {
return {
$runAfter: ['processing-docs', 'splitDescription'],
$runBefore: ['docs-processed'],
injectableDecorators: ['Injectable', 'Directive', 'Component', 'Pipe', 'NgModule'],
$process(docs) {
docs.forEach(doc => {
if (
doc.constructorDoc &&
!doc.constructorDoc.shortDescription &&
doc.decorators &&
doc.decorators.some(decorator => this.injectableDecorators.indexOf(decorator.name) !== -1)) {
delete doc.constructorDoc;
}
});
}
};
};

View File

@ -0,0 +1,58 @@
const processorFactory = require('./removeInjectableConstructors');
const testPackage = require('../../helpers/test-package');
const Dgeni = require('dgeni');
describe('removeInjectableConstructors processor', () => {
it('should be available on the injector', () => {
const dgeni = new Dgeni([testPackage('angular-api-package')]);
const injector = dgeni.configureInjector();
const processor = injector.get('removeInjectableConstructors');
expect(processor.$process).toBeDefined();
expect(processor.$runAfter).toEqual(['processing-docs', 'splitDescription']);
expect(processor.$runBefore).toEqual(['docs-processed']);
});
it('should remove undocumented constructors from docs that have an "Injectable" decorator on them', () => {
const processor = processorFactory();
const docs = [
{ constructorDoc: {} },
{ constructorDoc: {}, decorators: [] },
{ constructorDoc: {}, decorators: [{ name: 'Injectable' }] },
{ constructorDoc: {}, decorators: [{ name: 'Component' }] },
{ constructorDoc: {}, decorators: [{ name: 'Directive' }] },
{ constructorDoc: {}, decorators: [{ name: 'Pipe' }] },
{ constructorDoc: {}, decorators: [{ name: 'Other' }, { name: 'Injectable' }] },
{ constructorDoc: {}, decorators: [{ name: 'Other' }] },
{ constructorDoc: { shortDescription: 'Blah' } },
{ constructorDoc: { shortDescription: 'Blah' }, decorators: [] },
{ constructorDoc: { shortDescription: 'Blah' }, decorators: [{ name: 'Injectable' }] },
{ constructorDoc: { shortDescription: 'Blah' }, decorators: [{ name: 'Component' }] },
{ constructorDoc: { shortDescription: 'Blah' }, decorators: [{ name: 'Directive' }] },
{ constructorDoc: { shortDescription: 'Blah' }, decorators: [{ name: 'Pipe' }] },
{ constructorDoc: { shortDescription: 'Blah' }, decorators: [{ name: 'Other' }, { name: 'Injectable' }] },
{ constructorDoc: { shortDescription: 'Blah' }, decorators: [{ name: 'Other' }] },
];
processor.$process(docs);
expect(docs[0].constructorDoc).toBeDefined();
expect(docs[1].constructorDoc).toBeDefined();
expect(docs[2].constructorDoc).toBeUndefined();
expect(docs[3].constructorDoc).toBeUndefined();
expect(docs[4].constructorDoc).toBeUndefined();
expect(docs[5].constructorDoc).toBeUndefined();
expect(docs[6].constructorDoc).toBeUndefined();
expect(docs[7].constructorDoc).toBeDefined();
expect(docs[8].constructorDoc).toBeDefined();
expect(docs[9].constructorDoc).toBeDefined();
expect(docs[10].constructorDoc).toBeDefined();
expect(docs[11].constructorDoc).toBeDefined();
expect(docs[12].constructorDoc).toBeDefined();
expect(docs[13].constructorDoc).toBeDefined();
expect(docs[14].constructorDoc).toBeDefined();
expect(docs[15].constructorDoc).toBeDefined();
});
});

View File

@ -1,7 +1,7 @@
{% import "lib/memberHelpers.html" as memberHelper -%} {% import "lib/memberHelpers.html" as memberHelper -%}
<section class="{$ doc.docType $}-overview"> <section class="{$ doc.docType $}-overview">
<code-example language="ts" hideCopy="true"> <code-example language="ts" hideCopy="true" linenums="false">
{% if doc.isAbstract %}abstract {% endif%}{$ doc.docType $} {$ doc.name $}{$ doc.typeParams | escape $}{$ memberHelper.renderHeritage(doc) $} {{$ memberHelper.renderMembers(doc) $} {% if doc.isAbstract %}abstract {% endif%}{$ doc.docType $} {$ doc.name $}{$ doc.typeParams | escape $}{$ memberHelper.renderHeritage(doc) $} {{$ memberHelper.renderMembers(doc) $}
} }
</code-example> </code-example>

View File

@ -40,13 +40,13 @@
{%- macro renderOverloadInfo(overload, cssClass, method) -%} {%- macro renderOverloadInfo(overload, cssClass, method) -%}
<code-example language="ts" hideCopy="true" class="no-box api-heading">{$ renderMemberSyntax(overload) $}</code-example>
{% if overload.shortDescription and (overload.shortDescription != method.shortDescription) %} {% if overload.shortDescription and (overload.shortDescription != method.shortDescription) %}
<div class="short-description"> <div class="short-description">
{$ overload.shortDescription | marked $} {$ overload.shortDescription | marked $}
</div>{% endif %} </div>{% endif %}
<code-example language="ts" hideCopy="true" linenums="false" class="no-box api-heading">{$ renderMemberSyntax(overload) $}</code-example>
<h4 class="no-anchor">Parameters</h4> <h4 class="no-anchor">Parameters</h4>
{$ params.renderParameters(overload.parameterDocs, cssClass + '-parameters', cssClass + '-parameter') $} {$ params.renderParameters(overload.parameterDocs, cssClass + '-parameters', cssClass + '-parameter') $}

View File

@ -21,8 +21,7 @@
{% if showType %}<td class="param-type"><code>{$ parameter.type $}</code></td>{% endif %} {% if showType %}<td class="param-type"><code>{$ parameter.type $}</code></td>{% endif %}
<td class="param-description"> <td class="param-description">
{% marked %} {% marked %}
{% if parameter.description | trim %}{$ parameter.description $} {% if (parameter.shortDescription | trim) or (parameter.description | trim) %}{$ parameter.shortDescription + '\n\n' + parameter.description $}
{% elseif not showType and parameter.type %}<p>Type: <code>{$ parameter.type $}</code>.</p> {% elseif not showType and parameter.type %}<p>Type: <code>{$ parameter.type $}</code>.</p>
{% endif %} {% endif %}

View File

@ -6,9 +6,9 @@ workspace(name = "bazel_integration_test")
http_archive( http_archive(
name = "build_bazel_rules_nodejs", name = "build_bazel_rules_nodejs",
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.9.1.zip", url = "https://github.com/bazelbuild/rules_nodejs/archive/0.10.1.zip",
strip_prefix = "rules_nodejs-0.9.1", strip_prefix = "rules_nodejs-0.10.1",
sha256 = "6139762b62b37c1fd171d7f22aa39566cb7dc2916f0f801d505a9aaf118c117f", sha256 = "634206524d90dc03c52392fa3f19a16637d2bcf154910436fe1d669a0d9d7b9c",
) )
http_archive( http_archive(

View File

@ -20,7 +20,7 @@
"skipDefaultLibCheck": true, "skipDefaultLibCheck": true,
"skipLibCheck": true, "skipLibCheck": true,
"target": "es5", "target": "es5",
"types": ["angularjs"] "types": ["angular"]
}, },
"exclude": [ "exclude": [
"angular1_router", "angular1_router",

View File

@ -1,6 +1,6 @@
{ {
"name": "angular-srcs", "name": "angular-srcs",
"version": "6.0.6", "version": "6.0.8",
"private": true, "private": true,
"branchPattern": "2.0.*", "branchPattern": "2.0.*",
"description": "Angular - a web framework for modern web apps", "description": "Angular - a web framework for modern web apps",
@ -40,7 +40,7 @@
}, },
"devDependencies": { "devDependencies": {
"@bazel/ibazel": "^0.1.1", "@bazel/ibazel": "^0.1.1",
"@types/angularjs": "1.5.14-alpha", "@types/angular": "^1.6.47",
"@types/base64-js": "1.2.5", "@types/base64-js": "1.2.5",
"@types/chai": "^4.1.2", "@types/chai": "^4.1.2",
"@types/chokidar": "1.7.3", "@types/chokidar": "1.7.3",
@ -65,7 +65,7 @@
"canonical-path": "0.0.2", "canonical-path": "0.0.2",
"chokidar": "1.7.0", "chokidar": "1.7.0",
"clang-format": "1.0.41", "clang-format": "1.0.41",
"cldr": "4.8.0", "cldr": "4.10.0",
"cldr-data-downloader": "0.3.2", "cldr-data-downloader": "0.3.2",
"cldrjs": "0.5.0", "cldrjs": "0.5.0",
"conventional-changelog": "1.1.0", "conventional-changelog": "1.1.0",

View File

@ -14,6 +14,10 @@ export function isBrowser() {
return (typeof window !== 'undefined' && typeof window.document !== 'undefined'); return (typeof window !== 'undefined' && typeof window.document !== 'undefined');
} }
export function isNode() {
return (typeof process !== 'undefined');
}
export function optimizeGroupPlayer(players: AnimationPlayer[]): AnimationPlayer { export function optimizeGroupPlayer(players: AnimationPlayer[]): AnimationPlayer {
switch (players.length) { switch (players.length) {
case 0: case 0:
@ -142,11 +146,14 @@ let _query: (element: any, selector: string, multi: boolean) => any[] =
return []; return [];
}; };
if (isBrowser()) { // Define utility methods for browsers and platform-server(domino) where Element
// and utility methods exist.
const _isNode = isNode();
if (_isNode || typeof Element !== 'undefined') {
// this is well supported in all browsers // this is well supported in all browsers
_contains = (elm1: any, elm2: any) => { return elm1.contains(elm2) as boolean; }; _contains = (elm1: any, elm2: any) => { return elm1.contains(elm2) as boolean; };
if (Element.prototype.matches) { if (_isNode || Element.prototype.matches) {
_matches = (element: any, selector: string) => element.matches(selector); _matches = (element: any, selector: string) => element.matches(selector);
} else { } else {
const proto = Element.prototype as any; const proto = Element.prototype as any;

View File

@ -8,6 +8,7 @@
import {AnimateTimings, AnimationMetadata, AnimationMetadataType, AnimationOptions, sequence, ɵStyleData} from '@angular/animations'; import {AnimateTimings, AnimationMetadata, AnimationMetadataType, AnimationOptions, sequence, ɵStyleData} from '@angular/animations';
import {Ast as AnimationAst, AstVisitor as AnimationAstVisitor} from './dsl/animation_ast'; import {Ast as AnimationAst, AstVisitor as AnimationAstVisitor} from './dsl/animation_ast';
import {AnimationDslVisitor} from './dsl/animation_dsl_visitor'; import {AnimationDslVisitor} from './dsl/animation_dsl_visitor';
import {isNode} from './render/shared';
export const ONE_SECOND = 1000; export const ONE_SECOND = 1000;
@ -125,12 +126,47 @@ export function copyStyles(
return destination; return destination;
} }
function getStyleAttributeString(element: any, key: string, value: string) {
// Return the key-value pair string to be added to the style attribute for the
// given CSS style key.
if (value) {
return key + ':' + value + ';';
} else {
return '';
}
}
function writeStyleAttribute(element: any) {
// Read the style property of the element and manually reflect it to the
// style attribute. This is needed because Domino on platform-server doesn't
// understand the full set of allowed CSS properties and doesn't reflect some
// of them automatically.
let styleAttrValue = '';
for (let i = 0; i < element.style.length; i++) {
const key = element.style.item(i);
styleAttrValue += getStyleAttributeString(element, key, element.style.getPropertyValue(key));
}
for (const key in element.style) {
// Skip internal Domino properties that don't need to be reflected.
if (!element.style.hasOwnProperty(key) || key.startsWith('_')) {
continue;
}
const dashKey = camelCaseToDashCase(key);
styleAttrValue += getStyleAttributeString(element, dashKey, element.style[key]);
}
element.setAttribute('style', styleAttrValue);
}
export function setStyles(element: any, styles: ɵStyleData) { export function setStyles(element: any, styles: ɵStyleData) {
if (element['style']) { if (element['style']) {
Object.keys(styles).forEach(prop => { Object.keys(styles).forEach(prop => {
const camelProp = dashCaseToCamelCase(prop); const camelProp = dashCaseToCamelCase(prop);
element.style[camelProp] = styles[prop]; element.style[camelProp] = styles[prop];
}); });
// On the server set the 'style' attribute since it's not automatically reflected.
if (isNode()) {
writeStyleAttribute(element);
}
} }
} }
@ -140,6 +176,10 @@ export function eraseStyles(element: any, styles: ɵStyleData) {
const camelProp = dashCaseToCamelCase(prop); const camelProp = dashCaseToCamelCase(prop);
element.style[camelProp] = ''; element.style[camelProp] = '';
}); });
// On the server set the 'style' attribute since it's not automatically reflected.
if (isNode()) {
writeStyleAttribute(element);
}
} }
} }
@ -231,6 +271,10 @@ export function dashCaseToCamelCase(input: string): string {
return input.replace(DASH_CASE_REGEXP, (...m: any[]) => m[1].toUpperCase()); return input.replace(DASH_CASE_REGEXP, (...m: any[]) => m[1].toUpperCase());
} }
function camelCaseToDashCase(input: string): string {
return input.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
}
export function allowPreviousPlayerStylesMerge(duration: number, delay: number) { export function allowPreviousPlayerStylesMerge(duration: number, delay: number) {
return duration === 0 || delay === 0; return duration === 0 || delay === 0;
} }

View File

@ -13,6 +13,7 @@
"files": [ "files": [
"public_api.ts", "public_api.ts",
"../../../node_modules/@types/node/index.d.ts",
"../../../node_modules/zone.js/dist/zone.js.d.ts", "../../../node_modules/zone.js/dist/zone.js.d.ts",
"../../system.d.ts" "../../system.d.ts"
], ],

View File

@ -40,11 +40,12 @@ export declare type AnimateTimings = {
/** /**
* @description Options that control animation styling and timing. * @description Options that control animation styling and timing.
*
* The following animation functions accept `AnimationOptions` data: * The following animation functions accept `AnimationOptions` data:
* *
* - `transition()` * - `transition()`
* - `sequence()` * - `sequence()`
* - `group()` * - `{@link animations/group group()}`
* - `query()` * - `query()`
* - `animation()` * - `animation()`
* - `useAnimation()` * - `useAnimation()`
@ -100,7 +101,7 @@ export const enum AnimationMetadataType {
Sequence = 2, Sequence = 2,
/** /**
* Contains a set of animation steps. * Contains a set of animation steps.
* See `group()` * See `{@link animations/group group()}`
*/ */
Group = 3, Group = 3,
/** /**
@ -352,7 +353,7 @@ export interface AnimationSequenceMetadata extends AnimationMetadata {
/** /**
* Encapsulates an animation group. * Encapsulates an animation group.
* Instantiated and returned by the `group()` function. * Instantiated and returned by the `{@link animations/group group()}` function.
*/ */
export interface AnimationGroupMetadata extends AnimationMetadata { export interface AnimationGroupMetadata extends AnimationMetadata {
/** /**
@ -579,7 +580,7 @@ export function trigger(name: string, definitions: AnimationMetadata[]): Animati
* @returns An object that encapsulates the animation step. * @returns An object that encapsulates the animation step.
* *
* @usageNotes * @usageNotes
* Call within an animation `sequence()`, `group()`, or * Call within an animation `sequence()`, `{@link animations/group group()}`, or
* `transition()` call to specify an animation step * `transition()` call to specify an animation step
* that applies given style data to the parent animation for a given amount of time. * that applies given style data to the parent animation for a given amount of time.
* *
@ -676,9 +677,9 @@ export function group(
* @usageNotes * @usageNotes
* When you pass an array of steps to a * When you pass an array of steps to a
* `transition()` call, the steps run sequentially by default. * `transition()` call, the steps run sequentially by default.
* Compare this to the `group()` call, which runs animation steps in parallel. * Compare this to the `{@link animations/group group()}` call, which runs animation steps in parallel.
* *
* When a sequence is used within a `group()` or a `transition()` call, * When a sequence is used within a `{@link animations/group group()}` or a `transition()` call,
* execution continues to the next instruction only after each of the inner animation * execution continues to the next instruction only after each of the inner animation
* steps have completed. * steps have completed.
* *
@ -863,7 +864,7 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe
* ...] * ...]
* ``` * ```
* *
* Note that when you call the `sequence()` function within a `group()` * Note that when you call the `sequence()` function within a `{@link animations/group group()}`
* or a `transition()` call, execution does not continue to the next instruction * or a `transition()` call, execution does not continue to the next instruction
* until each of the inner animation steps have completed. * until each of the inner animation steps have completed.
* *

View File

@ -917,7 +917,7 @@ export class HttpClient {
}): Observable<string>; }): Observable<string>;
/** /**
* Construct a GET request which interprets the body as an `ArrayBuffer` and returns the full event stream. * Construct a HEAD request which interprets the body as an `ArrayBuffer` and returns the full event stream.
* *
* @return an `Observable` of all `HttpEvent`s for the request, with a body type of `ArrayBuffer`. * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `ArrayBuffer`.
*/ */
@ -1151,7 +1151,7 @@ export class HttpClient {
}): Observable<Blob>; }): Observable<Blob>;
/** /**
* Construct a OPTIONS request which interprets the body as text and returns it. * Construct an OPTIONS request which interprets the body as text and returns it.
* *
* @return an `Observable` of the body as a `string`. * @return an `Observable` of the body as a `string`.
*/ */
@ -1598,7 +1598,7 @@ export class HttpClient {
}): Observable<string>; }): Observable<string>;
/** /**
* Construct a PATCH request which interprets the body as an `ArrayBuffer` and returns the full event stream. * Construct a POST request which interprets the body as an `ArrayBuffer` and returns the full event stream.
* *
* @return an `Observable` of all `HttpEvent`s for the request, with a body type of `ArrayBuffer`. * @return an `Observable` of all `HttpEvent`s for the request, with a body type of `ArrayBuffer`.
*/ */
@ -1974,8 +1974,8 @@ export class HttpClient {
/** /**
* Constructs an `Observable` which, when subscribed, will cause the configured * Constructs an `Observable` which, when subscribed, will cause the configured
* POST request to be executed on the server. See the individual overloads for * PUT request to be executed on the server. See the individual overloads for
* details of `post()`'s return type based on the provided options. * details of `put()`'s return type based on the provided options.
*/ */
put(url: string, body: any|null, options: { put(url: string, body: any|null, options: {
headers?: HttpHeaders | {[header: string]: string | string[]}, headers?: HttpHeaders | {[header: string]: string | string[]},

View File

@ -19,12 +19,13 @@ import {BrowserXhr, HttpXhrBackend, XhrFactory} from './xhr';
import {HttpXsrfCookieExtractor, HttpXsrfInterceptor, HttpXsrfTokenExtractor, XSRF_COOKIE_NAME, XSRF_HEADER_NAME} from './xsrf'; import {HttpXsrfCookieExtractor, HttpXsrfInterceptor, HttpXsrfTokenExtractor, XSRF_COOKIE_NAME, XSRF_HEADER_NAME} from './xsrf';
/** /**
* An `HttpHandler` that applies a bunch of `HttpInterceptor`s * An injectable `HttpHandler` that applies multiple interceptors
* to a request before passing it to the given `HttpBackend`. * to a request before passing it to the given `HttpBackend`.
* *
* The interceptors are loaded lazily from the injector, to allow * The interceptors are loaded lazily from the injector, to allow
* interceptors to themselves inject classes depending indirectly * interceptors to themselves inject classes depending indirectly
* on `HttpInterceptingHandler` itself. * on `HttpInterceptingHandler` itself.
* @see `HttpInterceptor`
*/ */
@Injectable() @Injectable()
export class HttpInterceptingHandler implements HttpHandler { export class HttpInterceptingHandler implements HttpHandler {
@ -42,6 +43,23 @@ export class HttpInterceptingHandler implements HttpHandler {
} }
} }
/**
* Constructs an `HttpHandler` that applies interceptors
* to a request before passing it to the given `HttpBackend`.
*
* Use as a factory function within `HttpClientModule`.
*
*
*/
export function interceptingHandler(
backend: HttpBackend, interceptors: HttpInterceptor[] | null = []): HttpHandler {
if (!interceptors) {
return backend;
}
return interceptors.reduceRight(
(next, interceptor) => new HttpInterceptorHandler(next, interceptor), backend);
}
/** /**
* Factory function that determines where to store JSONP callbacks. * Factory function that determines where to store JSONP callbacks.
* *
@ -58,14 +76,14 @@ export function jsonpCallbackContext(): Object {
} }
/** /**
* `NgModule` which adds XSRF protection support to outgoing requests. * An NgModule that adds XSRF protection support to outgoing requests.
* *
* Provided the server supports a cookie-based XSRF protection system, this * For a server that supports a cookie-based XSRF protection system,
* module can be used directly to configure XSRF protection with the correct * use directly to configure XSRF protection with the correct
* cookie and header names. * cookie and header names.
* *
* If no such names are provided, the default is to use `X-XSRF-TOKEN` for * If no names are supplied, the default cookie name is `XSRF-TOKEN`
* the header name and `XSRF-TOKEN` for the cookie name. * and the default header name is `X-XSRF-TOKEN`.
* *
* *
*/ */
@ -92,8 +110,12 @@ export class HttpClientXsrfModule {
} }
/** /**
* Configure XSRF protection to use the given cookie name or header name, * Configure XSRF protection.
* or the default names (as described above) if not provided. * @param options An object that can specify either or both
* cookie name or header name.
* - Cookie name default is `XSRF-TOKEN`.
* - Header name default is `X-XSRF-TOKEN`.
*
*/ */
static withOptions(options: { static withOptions(options: {
cookieName?: string, cookieName?: string,
@ -110,7 +132,7 @@ export class HttpClientXsrfModule {
} }
/** /**
* `NgModule` which provides the `HttpClient` and associated services. * An NgModule that provides the `HttpClient` and associated services.
* *
* Interceptors can be added to the chain behind `HttpClient` by binding them * Interceptors can be added to the chain behind `HttpClient` by binding them
* to the multiprovider for `HTTP_INTERCEPTORS`. * to the multiprovider for `HTTP_INTERCEPTORS`.
@ -118,12 +140,18 @@ export class HttpClientXsrfModule {
* *
*/ */
@NgModule({ @NgModule({
/**
* Optional configuration for XSRF protection.
*/
imports: [ imports: [
HttpClientXsrfModule.withOptions({ HttpClientXsrfModule.withOptions({
cookieName: 'XSRF-TOKEN', cookieName: 'XSRF-TOKEN',
headerName: 'X-XSRF-TOKEN', headerName: 'X-XSRF-TOKEN',
}), }),
], ],
/**
* The module provides `HttpClient` itself, and supporting services.
*/
providers: [ providers: [
HttpClient, HttpClient,
{provide: HttpHandler, useClass: HttpInterceptingHandler}, {provide: HttpHandler, useClass: HttpInterceptingHandler},
@ -137,7 +165,7 @@ export class HttpClientModule {
} }
/** /**
* `NgModule` which enables JSONP support in `HttpClient`. * An NgModule that enables JSONP support in `HttpClient`.
* *
* Without this module, Jsonp requests will reach the backend * Without this module, Jsonp requests will reach the backend
* with method JSONP, where they'll be rejected. * with method JSONP, where they'll be rejected.

View File

@ -949,7 +949,7 @@ export const locale_de_AT = [
[['v. Chr.', 'n. Chr.'], u, u], 1, [6, 0], [['v. Chr.', 'n. Chr.'], u, u], 1, [6, 0],
['dd.MM.yy', 'dd.MM.y', 'd. MMMM y', 'EEEE, d. MMMM y'], ['dd.MM.yy', 'dd.MM.y', 'd. MMMM y', 'EEEE, d. MMMM y'],
['HH:mm', 'HH:mm:ss', 'HH:mm:ss z', 'HH:mm:ss zzzz'], ['{1}, {0}', u, '{1} \'um\' {0}', u], ['HH:mm', 'HH:mm:ss', 'HH:mm:ss z', 'HH:mm:ss zzzz'], ['{1}, {0}', u, '{1} \'um\' {0}', u],
[',', ' ', ';', '%', '+', '-', 'E', '·', '‰', '∞', 'NaN', ':', '.'], [',', ' ', ';', '%', '+', '-', 'E', '·', '‰', '∞', 'NaN', ':', u, '.'],
['#,##0.###', '#,##0 %', '¤ #,##0.00', '#E0'], '€', 'Euro', { ['#,##0.###', '#,##0 %', '¤ #,##0.00', '#E0'], '€', 'Euro', {
'ATS': ['öS'], 'ATS': ['öS'],
'AUD': ['AU$', '$'], 'AUD': ['AU$', '$'],
@ -2947,7 +2947,7 @@ export const locale_lv = [
function plural_mk(n: number): number { function plural_mk(n: number): number {
let i = Math.floor(Math.abs(n)), v = n.toString().replace(/^[^.]*\.?/, '').length, let i = Math.floor(Math.abs(n)), v = n.toString().replace(/^[^.]*\.?/, '').length,
f = parseInt(n.toString().replace(/^[^.]*\.?/, ''), 10) || 0; f = parseInt(n.toString().replace(/^[^.]*\.?/, ''), 10) || 0;
if (v === 0 && i % 10 === 1 || f % 10 === 1) return 1; if (v === 0 && i % 10 === 1 && !(i % 100 === 11) || f % 10 === 1 && !(f % 100 === 11)) return 1;
return 5; return 5;
} }

View File

@ -51,7 +51,7 @@ export default [
[['v. Chr.', 'n. Chr.'], u, u], 1, [6, 0], [['v. Chr.', 'n. Chr.'], u, u], 1, [6, 0],
['dd.MM.yy', 'dd.MM.y', 'd. MMMM y', 'EEEE, d. MMMM y'], ['dd.MM.yy', 'dd.MM.y', 'd. MMMM y', 'EEEE, d. MMMM y'],
['HH:mm', 'HH:mm:ss', 'HH:mm:ss z', 'HH:mm:ss zzzz'], ['{1}, {0}', u, '{1} \'um\' {0}', u], ['HH:mm', 'HH:mm:ss', 'HH:mm:ss z', 'HH:mm:ss zzzz'], ['{1}, {0}', u, '{1} \'um\' {0}', u],
[',', ' ', ';', '%', '+', '-', 'E', '·', '‰', '∞', 'NaN', ':', '.'], [',', ' ', ';', '%', '+', '-', 'E', '·', '‰', '∞', 'NaN', ':', u, '.'],
['#,##0.###', '#,##0 %', '¤ #,##0.00', '#E0'], '€', 'Euro', { ['#,##0.###', '#,##0 %', '¤ #,##0.00', '#E0'], '€', 'Euro', {
'ATS': ['öS'], 'ATS': ['öS'],
'AUD': ['AU$', '$'], 'AUD': ['AU$', '$'],

View File

@ -14,7 +14,7 @@ const u = undefined;
function plural(n: number): number { function plural(n: number): number {
let i = Math.floor(Math.abs(n)), v = n.toString().replace(/^[^.]*\.?/, '').length, let i = Math.floor(Math.abs(n)), v = n.toString().replace(/^[^.]*\.?/, '').length,
f = parseInt(n.toString().replace(/^[^.]*\.?/, ''), 10) || 0; f = parseInt(n.toString().replace(/^[^.]*\.?/, ''), 10) || 0;
if (v === 0 && i % 10 === 1 || f % 10 === 1) return 1; if (v === 0 && i % 10 === 1 && !(i % 100 === 11) || f % 10 === 1 && !(f % 100 === 11)) return 1;
return 5; return 5;
} }

View File

@ -176,6 +176,7 @@ export class NgForOf<T> implements DoCheck, OnChanges {
const viewRef = <EmbeddedViewRef<NgForOfContext<T>>>this._viewContainer.get(i); const viewRef = <EmbeddedViewRef<NgForOfContext<T>>>this._viewContainer.get(i);
viewRef.context.index = i; viewRef.context.index = i;
viewRef.context.count = ilen; viewRef.context.count = ilen;
viewRef.context.ngForOf = this.ngForOf;
} }
changes.forEachIdentityChange((record: any) => { changes.forEachIdentityChange((record: any) => {

View File

@ -29,7 +29,7 @@ enum DateType {
Hours, Hours,
Minutes, Minutes,
Seconds, Seconds,
Milliseconds, FractionalSeconds,
Day Day
} }
@ -57,8 +57,6 @@ enum TranslationType {
* If not specified, host system settings are used. * If not specified, host system settings are used.
* *
* See {@link DatePipe} for more details. * See {@link DatePipe} for more details.
*
*
*/ */
export function formatDate( export function formatDate(
value: string | number | Date, format: string, locale: string, timezone?: string): string { value: string | number | Date, format: string, locale: string, timezone?: string): string {
@ -195,6 +193,22 @@ function padNumber(
return neg + strNum; return neg + strNum;
} }
/**
* Trim a fractional part to `digits` number of digits.
* Right pads with "0" to fit the requested number of digits if needed.
*
* @param num The fractional part value
* @param digits The width of the output
*/
function trimRPadFractional(num: number, digits: number): string {
let strNum = String(num);
// Add padding at the end
while (strNum.length < digits) {
strNum = strNum + 0;
}
return strNum.substr(0, digits);
}
/** /**
* Returns a date formatter that transforms a date into its locale digit representation * Returns a date formatter that transforms a date into its locale digit representation
*/ */
@ -202,20 +216,26 @@ function dateGetter(
name: DateType, size: number, offset: number = 0, trim = false, name: DateType, size: number, offset: number = 0, trim = false,
negWrap = false): DateFormatter { negWrap = false): DateFormatter {
return function(date: Date, locale: string): string { return function(date: Date, locale: string): string {
let part = getDatePart(name, date, size); let part = getDatePart(name, date);
if (offset > 0 || part > -offset) { if (offset > 0 || part > -offset) {
part += offset; part += offset;
} }
if (name === DateType.Hours && part === 0 && offset === -12) {
if (name === DateType.Hours) {
if (part === 0 && offset === -12) {
part = 12; part = 12;
} }
return padNumber( } else if (name === DateType.FractionalSeconds) {
part, size, getLocaleNumberSymbol(locale, NumberSymbol.MinusSign), trim, negWrap); return trimRPadFractional(part, size);
}
const localeMinus = getLocaleNumberSymbol(locale, NumberSymbol.MinusSign);
return padNumber(part, size, localeMinus, trim, negWrap);
}; };
} }
function getDatePart(name: DateType, date: Date, size: number): number { function getDatePart(part: DateType, date: Date): number {
switch (name) { switch (part) {
case DateType.FullYear: case DateType.FullYear:
return date.getFullYear(); return date.getFullYear();
case DateType.Month: case DateType.Month:
@ -228,13 +248,12 @@ function getDatePart(name: DateType, date: Date, size: number): number {
return date.getMinutes(); return date.getMinutes();
case DateType.Seconds: case DateType.Seconds:
return date.getSeconds(); return date.getSeconds();
case DateType.Milliseconds: case DateType.FractionalSeconds:
const div = size === 1 ? 100 : (size === 2 ? 10 : 1); return date.getMilliseconds();
return Math.round(date.getMilliseconds() / div);
case DateType.Day: case DateType.Day:
return date.getDay(); return date.getDay();
default: default:
throw new Error(`Unknown DateType value "${name}".`); throw new Error(`Unknown DateType value "${part}".`);
} }
} }
@ -561,16 +580,15 @@ function getDateFormatter(format: string): DateFormatter|null {
formatter = dateGetter(DateType.Seconds, 2); formatter = dateGetter(DateType.Seconds, 2);
break; break;
// Fractional second padded (0-9) // Fractional second
case 'S': case 'S':
formatter = dateGetter(DateType.Milliseconds, 1); formatter = dateGetter(DateType.FractionalSeconds, 1);
break; break;
case 'SS': case 'SS':
formatter = dateGetter(DateType.Milliseconds, 2); formatter = dateGetter(DateType.FractionalSeconds, 2);
break; break;
// = millisecond
case 'SSS': case 'SSS':
formatter = dateGetter(DateType.Milliseconds, 3); formatter = dateGetter(DateType.FractionalSeconds, 3);
break; break;

View File

@ -10,14 +10,24 @@ import {Pipe, PipeTransform} from '@angular/core';
import {invalidPipeArgumentError} from './invalid_pipe_argument_error'; import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
/** /**
* Transforms text to lowercase. * Transforms text to all lower case.
* *
* {@example common/pipes/ts/lowerupper_pipe.ts region='LowerUpperPipe' } * @see `UpperCasePipe`
* @see `TitleCasePipe`
* @usageNotes
*
* The following example defines a view that allows the user to enter
* text, and then uses the pipe to convert the input text to all lower case.
*
* <code-example path="common/pipes/ts/lowerupper_pipe.ts" region='LowerUpperPipe'></code-example>
* *
* *
*/ */
@Pipe({name: 'lowercase'}) @Pipe({name: 'lowercase'})
export class LowerCasePipe implements PipeTransform { export class LowerCasePipe implements PipeTransform {
/**
* @param value The string to transform to lower case.
*/
transform(value: string): string { transform(value: string): string {
if (!value) return value; if (!value) return value;
if (typeof value !== 'string') { if (typeof value !== 'string') {
@ -41,18 +51,25 @@ const unicodeWordMatch =
/** /**
* Transforms text to title case. * Transforms text to title case.
* Capitalizes the first letter of each word, and transforms the
* rest of the word to lower case.
* Words are delimited by any whitespace character, such as a space, tab, or line-feed character.
* *
* The pipe splits up a text into words, capitalizes the first letter of each word and transforms * @see `LowerCasePipe`
* the rest of the word into lowercase. In this case, whitespace characters (such as "space", "\t", * @see `UpperCasePipe`
* "\n", etc) are used as word separators.
* *
* ## Example * @usageNotes
* {@example common/pipes/ts/titlecase_pipe.ts region='TitleCasePipe'} * The following example shows the result of transforming various strings into title case.
*
* <code-example path="common/pipes/ts/titlecase_pipe.ts" region='TitleCasePipe'></code-example>
* *
* *
*/ */
@Pipe({name: 'titlecase'}) @Pipe({name: 'titlecase'})
export class TitleCasePipe implements PipeTransform { export class TitleCasePipe implements PipeTransform {
/**
* @param value The string to transform to title case.
*/
transform(value: string): string { transform(value: string): string {
if (!value) return value; if (!value) return value;
if (typeof value !== 'string') { if (typeof value !== 'string') {
@ -65,12 +82,15 @@ export class TitleCasePipe implements PipeTransform {
} }
/** /**
* Transforms text to uppercase. * Transforms text to all upper case.
* * @see `LowerCasePipe`
* * @see `TitleCasePipe`
*/ */
@Pipe({name: 'uppercase'}) @Pipe({name: 'uppercase'})
export class UpperCasePipe implements PipeTransform { export class UpperCasePipe implements PipeTransform {
/**
* @param value The string to transform to upper case.
*/
transform(value: string): string { transform(value: string): string {
if (!value) return value; if (!value) return value;
if (typeof value !== 'string') { if (typeof value !== 'string') {

View File

@ -15,11 +15,48 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
* @ngModule CommonModule * @ngModule CommonModule
* @description * @description
* *
* Uses the function {@link formatDate} to format a date according to locale rules. * Formats a date value according to locale rules.
* *
* The following tabled describes the formatting options. * Only the `en-US` locale data comes with Angular. To localize dates
* in another language, you must import the corresponding locale data.
* See the [I18n guide](guide/i18n#i18n-pipes) for more information.
* *
* | Field Type | Format | Description | Example Value | * @see `formatDate()`
*
*
* @usageNotes
*
* The result of this pipe is not reevaluated when the input is mutated. To avoid the need to
* reformat the date on every change-detection cycle, treat the date as an immutable object
* and change the reference when the pipe needs to run again.
*
* ### Pre-defined format options
*
* Examples are given in `en-US` locale.
*
* - `'short'`: equivalent to `'M/d/yy, h:mm a'` (`6/15/15, 9:03 AM`).
* - `'medium'`: equivalent to `'MMM d, y, h:mm:ss a'` (`Jun 15, 2015, 9:03:01 AM`).
* - `'long'`: equivalent to `'MMMM d, y, h:mm:ss a z'` (`June 15, 2015 at 9:03:01 AM
* GMT+1`).
* - `'full'`: equivalent to `'EEEE, MMMM d, y, h:mm:ss a zzzz'` (`Monday, June 15, 2015 at
* 9:03:01 AM GMT+01:00`).
* - `'shortDate'`: equivalent to `'M/d/yy'` (`6/15/15`).
* - `'mediumDate'`: equivalent to `'MMM d, y'` (`Jun 15, 2015`).
* - `'longDate'`: equivalent to `'MMMM d, y'` (`June 15, 2015`).
* - `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` (`Monday, June 15, 2015`).
* - `'shortTime'`: equivalent to `'h:mm a'` (`9:03 AM`).
* - `'mediumTime'`: equivalent to `'h:mm:ss a'` (`9:03:01 AM`).
* - `'longTime'`: equivalent to `'h:mm:ss a z'` (`9:03:01 AM GMT+1`).
* - `'fullTime'`: equivalent to `'h:mm:ss a zzzz'` (`9:03:01 AM GMT+01:00`).
*
* ### Custom format options
*
* You can construct a format string using symbols to specify the components
* of a date-time value, as described in the following table.
* Format details depend on the locale.
* Fields marked with (*) are only available in the extra data set for the given locale.
*
* | Field type | Format | Description | Example Value |
* |--------------------|-------------|---------------------------------------------------------------|------------------------------------------------------------| * |--------------------|-------------|---------------------------------------------------------------|------------------------------------------------------------|
* | Era | G, GG & GGG | Abbreviated | AD | * | Era | G, GG & GGG | Abbreviated | AD |
* | | GGGG | Wide | Anno Domini | * | | GGGG | Wide | Anno Domini |
@ -75,30 +112,40 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
* | | O, OO & OOO | Short localized GMT format | GMT-8 | * | | O, OO & OOO | Short localized GMT format | GMT-8 |
* | | OOOO | Long localized GMT format | GMT-08:00 | * | | OOOO | Long localized GMT format | GMT-08:00 |
* *
* Note that timezone correction is not applied to an ISO string that has no time component, such as "2016-09-19"
* *
* When the expression is a ISO string without time (e.g. 2016-09-19) the time zone offset is not * ### Format examples
* applied and the formatted text will have the same day, month and year of the expression.
* *
* WARNINGS: * These examples transform a date into various formats,
* - this pipe has only access to en-US locale data by default. If you want to localize the dates * assuming that `dateObj` is a JavaScript `Date` object for
* in another language, you will have to import data for other locales. * year: 2015, month: 6, day: 15, hour: 21, minute: 43, second: 11,
* See the ["I18n guide"](guide/i18n#i18n-pipes) to know how to import additional locale * given in the local time for the `en-US` locale.
* data.
* - Fields suffixed with * are only available in the extra dataset.
* See the ["I18n guide"](guide/i18n#i18n-pipes) to know how to import extra locale
* data.
* - this pipe is marked as pure hence it will not be re-evaluated when the input is mutated.
* Instead users should treat the date as an immutable object and change the reference when the
* pipe needs to re-run (this is to avoid reformatting the date on every change detection run
* which would be an expensive operation).
* *
* ### Examples * ```
* {{ dateObj | date }} // output is 'Jun 15, 2015'
* {{ dateObj | date:'medium' }} // output is 'Jun 15, 2015, 9:43:11 PM'
* {{ dateObj | date:'shortTime' }} // output is '9:43 PM'
* {{ dateObj | date:'mmss' }} // output is '43:11'
* ```
* *
* Assuming `dateObj` is (year: 2015, month: 6, day: 15, hour: 21, minute: 43, second: 11) * ### Usage example
* in the _local_ time and locale is 'en-US':
* *
* {@example common/pipes/ts/date_pipe.ts region='DatePipe'} * The following component uses a date pipe to display the current date in different formats.
* *
* ```
* @Component({
* selector: 'date-pipe',
* template: `<div>
* <p>Today is {{today | date}}</p>
* <p>Or if you prefer, {{today | date:'fullDate'}}</p>
* <p>The time is {{today | date:'h:mm a z'}}</p>
* </div>`
* })
* // Get the current date and time as a date-time value.
* export class DatePipeComponent {
* today: number = Date.now();
* }
* ```
* *
*/ */
// clang-format on // clang-format on
@ -107,29 +154,17 @@ export class DatePipe implements PipeTransform {
constructor(@Inject(LOCALE_ID) private locale: string) {} constructor(@Inject(LOCALE_ID) private locale: string) {}
/** /**
* @param value a date object or a number (milliseconds since UTC epoch) or an ISO string * @param value The date expression: a `Date` object, a number
* (https://www.w3.org/TR/NOTE-datetime). * (milliseconds since UTC epoch), or an ISO string (https://www.w3.org/TR/NOTE-datetime).
* @param format indicates which date/time components to include. The format can be predefined as * @param format The date/time components to include, using predefined options or a
* shown below (all examples are given for `en-US`) or custom as shown in the table. * custom format string.
* - `'short'`: equivalent to `'M/d/yy, h:mm a'` (e.g. `6/15/15, 9:03 AM`). * @param timezone A timezone offset (such as `'+0430'`), or a standard
* - `'medium'`: equivalent to `'MMM d, y, h:mm:ss a'` (e.g. `Jun 15, 2015, 9:03:01 AM`). * UTC/GMT or continental US timezone abbreviation. Default is
* - `'long'`: equivalent to `'MMMM d, y, h:mm:ss a z'` (e.g. `June 15, 2015 at 9:03:01 AM * the local system timezone of the end-user's machine.
* GMT+1`). * @param locale A locale code for the locale format rules to use.
* - `'full'`: equivalent to `'EEEE, MMMM d, y, h:mm:ss a zzzz'` (e.g. `Monday, June 15, 2015 at * When not supplied, uses the value of `LOCALE_ID`, which is `en-US` by default.
* 9:03:01 AM GMT+01:00`). * See [Setting your app locale](guide/i18n#setting-up-the-locale-of-your-app).
* - `'shortDate'`: equivalent to `'M/d/yy'` (e.g. `6/15/15`). * @returns A date string in the desired format.
* - `'mediumDate'`: equivalent to `'MMM d, y'` (e.g. `Jun 15, 2015`).
* - `'longDate'`: equivalent to `'MMMM d, y'` (e.g. `June 15, 2015`).
* - `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` (e.g. `Monday, June 15, 2015`).
* - `'shortTime'`: equivalent to `'h:mm a'` (e.g. `9:03 AM`).
* - `'mediumTime'`: equivalent to `'h:mm:ss a'` (e.g. `9:03:01 AM`).
* - `'longTime'`: equivalent to `'h:mm:ss a z'` (e.g. `9:03:01 AM GMT+1`).
* - `'fullTime'`: equivalent to `'h:mm:ss a zzzz'` (e.g. `9:03:01 AM GMT+01:00`).
* @param timezone to be used for formatting the time. It understands UTC/GMT and the continental
* US time zone
* abbreviations, but for general use, use a time zone offset (e.g. `'+0430'`).
* @param locale a `string` defining the locale to use (uses the current {@link LOCALE_ID} by
* default).
*/ */
transform(value: any, format = 'mediumDate', timezone?: string, locale?: string): string|null { transform(value: any, format = 'mediumDate', timezone?: string, locale?: string): string|null {
if (value == null || value === '' || value !== value) return null; if (value == null || value === '' || value !== value) return null;

View File

@ -12,14 +12,21 @@ import {Pipe, PipeTransform} from '@angular/core';
* @ngModule CommonModule * @ngModule CommonModule
* @description * @description
* *
* Converts value into string using `JSON.stringify`. Useful for debugging. * Converts a value into its JSON-format representation. Useful for debugging.
* *
* ### Example * @usageNotes
*
* 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}) @Pipe({name: 'json', pure: false})
export class JsonPipe implements PipeTransform { export class JsonPipe implements PipeTransform {
/**
* @param value A value of any type to convert into a JSON-format string.
*/
transform(value: any): string { return JSON.stringify(value, null, 2); } transform(value: any): string { return JSON.stringify(value, null, 2); }
} }

View File

@ -15,14 +15,34 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
* @ngModule CommonModule * @ngModule CommonModule
* @description * @description
* *
* Uses the function {@link formatNumber} to format a number according to locale rules. * Transforms a number into a string,
* formatted according to locale rules that determine group sizing and
* separator, decimal-point character, and other locale-specific
* configurations.
* *
* Formats a number as text. Group sizing and separator and other locale-specific * If no parameters are specified, the function rounds off to the nearest value using this
* configurations are based on the locale. * [rounding method](https://en.wikibooks.org/wiki/Arithmetic/Rounding).
* The behavior differs from that of the JavaScript ```Math.round()``` function.
* In the following case for example, the pipe rounds down where
* ```Math.round()``` rounds up:
*
* ```html
* -2.5 | number:'1.0-0'
* > -3
* Math.round(-2.5)
* > -2
* ```
*
* @see `formatNumber()`
*
* @usageNotes
* The following code shows how the pipe transforms numbers
* into text strings, according to various format specifications,
* where the caller's default locale is `en-US`.
* *
* ### Example * ### Example
* *
* {@example common/pipes/ts/number_pipe.ts region='NumberPipe'} * <code-example path="common/pipes/ts/number_pipe.ts" region='NumberPipe'></code-example>
* *
* *
*/ */
@ -31,16 +51,19 @@ export class DecimalPipe implements PipeTransform {
constructor(@Inject(LOCALE_ID) private _locale: string) {} constructor(@Inject(LOCALE_ID) private _locale: string) {}
/** /**
* @param value a number to be formatted. * @param value The number to be formatted.
* @param digitsInfo a `string` which has a following format: <br> * @param digitsInfo Decimal representation options, specified by a string
* in the following format:<br>
* <code>{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}</code>. * <code>{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}</code>.
* - `minIntegerDigits` is the minimum number of integer digits to use. Defaults to `1`. * - `minIntegerDigits`: The minimum number of integer digits before the decimal point.
* - `minFractionDigits` is the minimum number of digits after the decimal point. Defaults to * Default is `1`.
* `0`. * - `minFractionDigits`: The minimum number of digits after the decimal point.
* - `maxFractionDigits` is the maximum number of digits after the decimal point. Defaults to * Default is `0`.
* `3`. * - `maxFractionDigits`: The maximum number of digits after the decimal point.
* @param locale a `string` defining the locale to use (uses the current {@link LOCALE_ID} by * Default is `3`.
* default). * @param locale A locale code for the locale format rules to use.
* When not supplied, uses the value of `LOCALE_ID`, which is `en-US` by default.
* See [Setting your app locale](guide/i18n#setting-up-the-locale-of-your-app).
*/ */
transform(value: any, digitsInfo?: string, locale?: string): string|null { transform(value: any, digitsInfo?: string, locale?: string): string|null {
if (isEmpty(value)) return null; if (isEmpty(value)) return null;
@ -60,12 +83,19 @@ export class DecimalPipe implements PipeTransform {
* @ngModule CommonModule * @ngModule CommonModule
* @description * @description
* *
* Uses the function {@link formatPercent} to format a number as a percentage according * Transforms a number to a percentage
* to locale rules. * string, formatted according to locale rules that determine group sizing and
* separator, decimal-point character, and other locale-specific
* configurations.
* *
* ### Example * @see `formatPercent()`
* *
* {@example common/pipes/ts/percent_pipe.ts region='PercentPipe'} * @usageNotes
* The following code shows how the pipe transforms numbers
* into text strings, according to various format specifications,
* where the caller's default locale is `en-US`.
*
* <code-example path="common/pipes/ts/percent_pipe.ts" region='PercentPipe'></code-example>
* *
* *
*/ */
@ -75,10 +105,19 @@ export class PercentPipe implements PipeTransform {
/** /**
* *
* @param value a number to be formatted as a percentage. * @param value The number to be formatted as a percentage.
* @param digitsInfo see {@link DecimalPipe} for more details. * @param digitsInfo Decimal representation options, specified by a string
* @param locale a `string` defining the locale to use (uses the current {@link LOCALE_ID} by * in the following format:<br>
* default). * <code>{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}</code>.
* - `minIntegerDigits`: The minimum number of integer digits before the decimal point.
* Default is `1`.
* - `minFractionDigits`: The minimum number of digits after the decimal point.
* Default is `0`.
* - `maxFractionDigits`: The maximum number of digits after the decimal point.
* Default is `3`.
* @param locale A locale code for the locale format rules to use.
* When not supplied, uses the value of `LOCALE_ID`, which is `en-US` by default.
* See [Setting your app locale](guide/i18n#setting-up-the-locale-of-your-app).
*/ */
transform(value: any, digitsInfo?: string, locale?: string): string|null { transform(value: any, digitsInfo?: string, locale?: string): string|null {
if (isEmpty(value)) return null; if (isEmpty(value)) return null;
@ -98,12 +137,19 @@ export class PercentPipe implements PipeTransform {
* @ngModule CommonModule * @ngModule CommonModule
* @description * @description
* *
* Uses the functions {@link getCurrencySymbol} and {@link formatCurrency} to format a * Transforms a number to a currency string, formatted according to locale rules
* number as currency using locale rules. * that determine group sizing and separator, decimal-point character,
* and other locale-specific configurations.
* *
* ### Example * @see `getCurrencySymbol()`
* @see `formatCurrency()`
* *
* {@example common/pipes/ts/currency_pipe.ts region='CurrencyPipe'} * @usageNotes
* The following code shows how the pipe transforms numbers
* into text strings, according to various format specifications,
* where the caller's default locale is `en-US`.
*
* <code-example path="common/pipes/ts/currency_pipe.ts" region='CurrencyPipe'></code-example>
* *
* *
*/ */
@ -113,20 +159,31 @@ export class CurrencyPipe implements PipeTransform {
/** /**
* *
* @param value a number to be formatted as currency. * @param value The number to be formatted as currency.
* @param currencyCodeis the [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currency code, * @param currencyCode The [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currency code,
* such as `USD` for the US dollar and `EUR` for the euro. * such as `USD` for the US dollar and `EUR` for the euro.
* @param display indicates whether to show the currency symbol, the code or a custom value: * @param display The format for the currency indicator. One of the following:
* - `code`: use code (e.g. `USD`). * - `code`: Show the code (such as `USD`).
* - `symbol`(default): use symbol (e.g. `$`). * - `symbol`(default): Show the symbol (such as `$`).
* - `symbol-narrow`: some countries have two symbols for their currency, one regular and one * - `symbol-narrow`: Use the narrow symbol for locales that have two symbols for their
* narrow (e.g. the canadian dollar CAD has the symbol `CA$` and the symbol-narrow `$`). * currency.
* - `string`: use this value instead of a code or a symbol. * For example, the Canadian dollar CAD has the symbol `CA$` and the symbol-narrow `$`. If the
* - boolean (deprecated from v5): `true` for symbol and false for `code`. * locale has no narrow symbol, uses the standard symbol for the locale.
* If there is no narrow symbol for the chosen currency, the regular symbol will be used. * - String: Use the given string value instead of a code or a symbol.
* @param digitsInfo see {@link DecimalPipe} for more details. * - Boolean (marked deprecated in v5): `true` for symbol and false for `code`.
* @param locale a `string` defining the locale to use (uses the current {@link LOCALE_ID} by *
* default). * @param digitsInfo Decimal representation options, specified by a string
* in the following format:<br>
* <code>{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}</code>.
* - `minIntegerDigits`: The minimum number of integer digits before the decimal point.
* Default is `1`.
* - `minFractionDigits`: The minimum number of digits after the decimal point.
* Default is `0`.
* - `maxFractionDigits`: The maximum number of digits after the decimal point.
* Default is `3`.
* @param locale A locale code for the locale format rules to use.
* When not supplied, uses the value of `LOCALE_ID`, which is `en-US` by default.
* See [Setting your app locale](guide/i18n#setting-up-the-locale-of-your-app).
*/ */
transform( transform(
value: any, currencyCode?: string, value: any, currencyCode?: string,
@ -167,7 +224,7 @@ function isEmpty(value: any): boolean {
} }
/** /**
* Transforms a string into a number (if needed) * Transforms a string into a number (if needed).
*/ */
function strToNumber(value: number | string): number { function strToNumber(value: number | string): number {
// Convert strings to numbers // Convert strings to numbers

View File

@ -183,9 +183,12 @@ let thisArg: any;
it('should allow of saving the collection', async(() => { it('should allow of saving the collection', async(() => {
const template = const template =
'<ul><li *ngFor="let item of [1,2,3] as items; index as i">{{i}}/{{items.length}} - {{item}};</li></ul>'; '<ul><li *ngFor="let item of items as collection; index as i">{{i}}/{{collection.length}} - {{item}};</li></ul>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
detectChangesAndExpectText('0/2 - 1;1/2 - 2;');
getComponent().items = [1, 2, 3];
detectChangesAndExpectText('0/3 - 1;1/3 - 2;2/3 - 3;'); detectChangesAndExpectText('0/3 - 1;1/3 - 2;2/3 - 3;');
})); }));

View File

@ -52,7 +52,7 @@ describe('Format date', () => {
// Check the transformation of a date into a pattern // Check the transformation of a date into a pattern
function expectDateFormatAs(date: Date | string, pattern: any, output: string): void { function expectDateFormatAs(date: Date | string, pattern: any, output: string): void {
expect(formatDate(date, pattern, defaultLocale)).toEqual(output); expect(formatDate(date, pattern, defaultLocale)).toEqual(output, `pattern: "${pattern}"`);
} }
beforeAll(() => { beforeAll(() => {
@ -105,7 +105,7 @@ describe('Format date', () => {
mm: '03', mm: '03',
s: '1', s: '1',
ss: '01', ss: '01',
S: '6', S: '5',
SS: '55', SS: '55',
SSS: '550', SSS: '550',
a: 'AM', a: 'AM',
@ -233,7 +233,6 @@ describe('Format date', () => {
Object.keys(dateFixtures).forEach((pattern: string) => { Object.keys(dateFixtures).forEach((pattern: string) => {
expectDateFormatAs(date, pattern, dateFixtures[pattern]); expectDateFormatAs(date, pattern, dateFixtures[pattern]);
}); });
}); });
it('should format with pattern aliases', () => { it('should format with pattern aliases', () => {
@ -266,14 +265,13 @@ describe('Format date', () => {
() => expect(formatDate('2017-01-20T12:00:00+0000', defaultFormat, defaultLocale)) () => expect(formatDate('2017-01-20T12:00:00+0000', defaultFormat, defaultLocale))
.toEqual('Jan 20, 2017')); .toEqual('Jan 20, 2017'));
// test for the following bugs:
// https://github.com/angular/angular/issues/9524 // https://github.com/angular/angular/issues/9524
// https://github.com/angular/angular/issues/9524 // https://github.com/angular/angular/issues/9524
it('should format correctly with iso strings that contain time', it('should format correctly with iso strings that contain time',
() => expect(formatDate('2017-05-07T22:14:39', 'dd-MM-yyyy HH:mm', defaultLocale)) () => expect(formatDate('2017-05-07T22:14:39', 'dd-MM-yyyy HH:mm', defaultLocale))
.toMatch(/07-05-2017 \d{2}:\d{2}/)); .toMatch(/07-05-2017 \d{2}:\d{2}/));
// test for issue https://github.com/angular/angular/issues/21491 // https://github.com/angular/angular/issues/21491
it('should not assume UTC for iso strings in Safari if the timezone is not defined', () => { it('should not assume UTC for iso strings in Safari if the timezone is not defined', () => {
// this test only works if the timezone is not in UTC // this test only works if the timezone is not in UTC
// which is the case for BrowserStack when we test Safari // which is the case for BrowserStack when we test Safari
@ -283,7 +281,6 @@ describe('Format date', () => {
} }
}); });
// test for the following bugs:
// https://github.com/angular/angular/issues/16624 // https://github.com/angular/angular/issues/16624
// https://github.com/angular/angular/issues/17478 // https://github.com/angular/angular/issues/17478
it('should show the correct time when the timezone is fixed', () => { it('should show the correct time when the timezone is fixed', () => {
@ -311,5 +308,17 @@ describe('Format date', () => {
expect(() => formatDate(date, 'b', 'de')) expect(() => formatDate(date, 'b', 'de'))
.toThrowError(/Missing extra locale data for the locale "de"/); .toThrowError(/Missing extra locale data for the locale "de"/);
}); });
// https://github.com/angular/angular/issues/24384
it('should not round fractional seconds', () => {
expect(formatDate(3999, 'm:ss', 'en')).toEqual('0:03');
expect(formatDate(3999, 'm:ss.S', 'en')).toEqual('0:03.9');
expect(formatDate(3999, 'm:ss.SS', 'en')).toEqual('0:03.99');
expect(formatDate(3999, 'm:ss.SSS', 'en')).toEqual('0:03.999');
expect(formatDate(3000, 'm:ss', 'en')).toEqual('0:03');
expect(formatDate(3000, 'm:ss.S', 'en')).toEqual('0:03.0');
expect(formatDate(3000, 'm:ss.SS', 'en')).toEqual('0:03.00');
expect(formatDate(3000, 'm:ss.SSS', 'en')).toEqual('0:03.000');
});
}); });
}); });

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