Compare commits

..

50 Commits
6.0.7 ... 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
85 changed files with 1169 additions and 290 deletions

View File

@ -1,3 +1,25 @@
<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)

View File

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

View File

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

View File

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

View File

@ -132,7 +132,7 @@
<!-- #docregion select-span -->
<select [(ngModel)]="hero">
<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>
</span>
</span>
@ -147,7 +147,7 @@
<!-- #docregion select-ngcontainer -->
<select [(ngModel)]="hero">
<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>
</ng-container>
</ng-container>

View File

@ -6,14 +6,15 @@ import { heroes } from './hero';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
styleUrls: ['./app.component.css']
})
export class AppComponent {
heroes = heroes;
hero = this.heroes[0];
heroTraits = [ 'honest', 'brave', 'considerate' ];
heroTraits = ['honest', 'brave', 'considerate'];
// flags for the table
attrDirs = true;
strucDirs = true;
divNgIf = false;

View File

@ -1,9 +1,9 @@
// #docregion
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { AppComponent } from './app.component';
import { ContentComponent } from './content.component';
import { heroComponents } from './hero.components';

View File

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

View File

@ -6,8 +6,8 @@ export class Hero {
}
export const heroes: Hero[] = [
{ id: 1, name: 'Mr. Nice', emotion: 'happy'},
{ id: 2, name: 'Narco', emotion: 'sad' },
{ id: 1, name: 'Mr. Nice', emotion: 'happy' },
{ id: 2, name: 'Narco', emotion: 'sad' },
{ id: 3, name: 'Windstorm', emotion: 'confused' },
{ id: 4, name: 'Magneta'}
{ id: 4, name: 'Magneta' }
];

View File

@ -3,15 +3,15 @@
<!-- #enddocregion show-hero-1 -->
<!-- #docregion show-hero-2 -->
<h2>{{ hero.name }} Details</h2>
<h2>{{hero.name}} Details</h2>
<div><span>id: </span>{{hero.id}}</div>
<div><span>name: </span>{{hero.name}}</div>
<!-- #enddocregion show-hero-2 -->
<!-- #docregion name-input -->
<div>
<label>name:
<input [(ngModel)]="hero.name" placeholder="name">
</label>
<label>name:
<input [(ngModel)]="hero.name" placeholder="name">
</label>
</div>
<!-- #enddocregion name-input -->

View File

@ -1,10 +1,10 @@
<!-- #docregion -->
<!-- #docregion pipe -->
<h2>{{ hero.name | uppercase }} Details</h2>
<h2>{{hero.name | uppercase}} Details</h2>
<!-- #enddocregion pipe -->
<div><span>id: </span>{{hero.id}}</div>
<div>
<label>name:
<input [(ngModel)]="hero.name" placeholder="name">
</label>
<label>name:
<input [(ngModel)]="hero.name" placeholder="name">
</label>
</div>

View File

@ -14,7 +14,7 @@
<div *ngIf="selectedHero">
<!-- #docregion selectedHero-details -->
<h2>{{ selectedHero.name | uppercase }} Details</h2>
<h2>{{selectedHero.name | uppercase}} Details</h2>
<div><span>id: </span>{{selectedHero.id}}</div>
<div>
<label>name:

View File

@ -1,6 +1,6 @@
<div *ngIf="hero">
<h2>{{ hero.name | uppercase }} Details</h2>
<h2>{{hero.name | uppercase}} Details</h2>
<div><span>id: </span>{{hero.id}}</div>
<div>
<label>name:

View File

@ -1,5 +1,5 @@
<div *ngIf="hero">
<h2>{{ hero.name | uppercase }} Details</h2>
<h2>{{hero.name | uppercase}} Details</h2>
<div><span>id: </span>{{hero.id}}</div>
<div>
<label>name:

View File

@ -1,5 +1,5 @@
<div *ngIf="hero">
<h2>{{ hero.name | uppercase }} Details</h2>
<h2>{{hero.name | uppercase}} Details</h2>
<div><span>id: </span>{{hero.id}}</div>
<div>
<label>name:

View File

@ -1,5 +1,5 @@
<div *ngIf="hero">
<h2>{{ hero.name | uppercase }} Details</h2>
<h2>{{hero.name | uppercase}} Details</h2>
<div><span>id: </span>{{hero.id}}</div>
<div>
<label>name:

View File

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

View File

@ -20,7 +20,7 @@
</a>
<!-- #docregion delete -->
<button class="delete" title="delete hero"
(click)="delete(hero)">x</button>
(click)="delete(hero)">x</button>
<!-- #enddocregion delete -->
</li>
</ul>

View File

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

View File

@ -8,9 +8,10 @@ A _component_ controls a patch of screen called a *view*. For example, individua
* The list of heroes.
* 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>
@ -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_.
* `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
in order to get the list of heroes to display.
* `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.
<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>
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>

View File

@ -60,11 +60,29 @@ The process of `HeroService` injection looks something like this:
### Providing services
You must register at least one *provider* of any service you are going to use. 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
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

@ -96,7 +96,7 @@ Now edit the generated `src/app/highlight.directive.ts` to look as follows:
The `import` statement specifies an additional `ElementRef` symbol from the Angular `core` library:
You use the `ElementRef`in the directive's constructor
You use the `ElementRef` in the directive's constructor
to [inject](guide/dependency-injection) a reference to the host DOM element,
the element to which you applied `appHighlight`.

View File

@ -164,7 +164,7 @@ They are _not inherited_ by any components nested within the template nor by any
</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">
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">
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>
@ -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.
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)
on the [MDN](https://developer.mozilla.org) site)
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.
* `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
(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).
@ -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>
`Native` view encapsulation only works on browsers that have native support
for shadow DOM (see [Shadow DOM v0](http://caniuse.com/#feat=shadowdom) on the
`ShadowDom` view encapsulation only works on browsers that have native support
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,
which is why `Emulated` view encapsulation is the default mode and recommended
in most cases.

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.
* 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.
* 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 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}
## 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).
@ -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.
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.
@ -280,7 +285,7 @@ Compare [Custom element](guide/glossary#custom-element).
## 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`.
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)
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
@ -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.
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
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.
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).
@ -470,8 +496,13 @@ To learn more, see the [pipes](guide/pipes) page.
## 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
@ -531,9 +562,24 @@ For more information, see the [Routing & Navigation](guide/router) page.
{@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
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`.
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)
{@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}

View File

@ -1034,7 +1034,7 @@ Call `request.flush()` with an error message, as seen in the following example.
<code-example
path="http/src/testing/http-client.spec.ts"
region="network-error"
region="404"
linenums="false">
</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.
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/>
@ -233,9 +233,8 @@ When you import an NgModule,
Angular adds the module's service providers (the contents of its `providers` list)
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.
Merging NgModule providers into the application injector
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,
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/>
{@a q-lazy-loaded-module-provider-visibility}
@ -288,6 +289,7 @@ The `AppModule` always wins.
<hr/>
{@a service-scope}
## 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>`.
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/>
{@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.
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.
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

View File

@ -212,7 +212,7 @@ You can get runtime information about the current platform and the `appId` by in
### Build Destination
A Universal app is distributed in two parts: the server-side code that serves up the initial application, and the client-side code that's loaded in dynamically.
A Universal app is distributed in two parts: the server-side code that serves up the initial application, and the client-side code that's loaded in dynamically.
The Angular CLI outputs the client-side code in the `dist` directory by default, so you modify the `outputPath` for the __build__ target in the `angular.json` to keep the client-side build outputs separate from the server-side code. The client-side build output will be served by the Express server.
@ -223,7 +223,7 @@ The Angular CLI outputs the client-side code in the `dist` directory by default,
"options": {
"outputPath": "dist/browser",
...
}
}
}
...
```
@ -235,13 +235,13 @@ The Angular CLI outputs the client-side code in the `dist` directory by default,
The tutorial's `HeroService` and `HeroSearchService` delegate to the Angular `HttpClient` module to fetch application data.
These services send requests to _relative_ URLs such as `api/heroes`.
In a Universal app, HTTP URLs must be _absolute_, for example, `https://my-server.com/api/heroes`
In a Universal app, HTTP URLs must be _absolute_, for example, `https://my-server.com/api/heroes`
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
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.
Start by changing the `HeroService` constructor to take a second `origin` parameter that is optionally injected via the `APP_BASE_HREF` token.
@ -288,7 +288,7 @@ This is also the place to register providers that are specific to running your a
### App server entry point
The `Angular CLI` uses the `AppServerModule` to build the server-side bundle.
The `Angular CLI` uses the `AppServerModule` to build the server-side bundle.
Create a `main.server.ts` file in the `src/` directory that exports the `AppServerModule`:

View File

@ -360,7 +360,7 @@
"picture": "jorgeucano.jpg",
"twitter": "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"
},
@ -378,8 +378,8 @@
"picture": "michaelprentice.jpg",
"twitter": "splaktar",
"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.",
"group": "GDE"
"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": "Angular"
},
"mikebrocchi": {

View File

@ -612,6 +612,12 @@
"title": "Ultimate Angular",
"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": {
"desc": "Video lessons covering progressive web apps with Angular, Firebase, RxJS, and related APIs.",
"rev": true,
@ -666,8 +672,8 @@
"url": "http://ninja-squad.com/formations/formation-angular2"
},
"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.",
"logo": "",
"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": "https://angularbootcamp.com/images/angular-boot-camp-logo.svg",
"rev": true,
"title": "Angular Boot Camp",
"url": "https://angularbootcamp.com"

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>
<code-pane title="src/app/app.module.ts" path="toh-pt3/src/app/app.module.ts">
</code-pane>
</code-tabs>
## Summary

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -77,6 +77,7 @@
"rollup-plugin-node-resolve": "2.0.0",
"rollup-plugin-uglify": "^1.0.1",
"source-map-explorer": "^1.3.2",
"ts-loader": "^4.2.0",
"ts-node": "^5.0.1",
"tslint": "^5.9.1",
"typescript": "2.7.2",

View File

@ -7361,7 +7361,7 @@ semver-intersect@^1.1.2:
dependencies:
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"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
@ -8257,6 +8257,16 @@ trim-right@^1.0.1:
dependencies:
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:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-5.0.1.tgz#78e5d1cb3f704de1b641e43b76be2d4094f06f81"

View File

@ -21,8 +21,7 @@
{% if showType %}<td class="param-type"><code>{$ parameter.type $}</code></td>{% endif %}
<td class="param-description">
{% 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>
{% endif %}

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "6.0.7",
"version": "6.0.8",
"private": true,
"branchPattern": "2.0.*",
"description": "Angular - a web framework for modern web apps",

View File

@ -917,7 +917,7 @@ export class HttpClient {
}): 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`.
*/
@ -1151,7 +1151,7 @@ export class HttpClient {
}): 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`.
*/
@ -1598,7 +1598,7 @@ export class HttpClient {
}): 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`.
*/
@ -1974,8 +1974,8 @@ export class HttpClient {
/**
* Constructs an `Observable` which, when subscribed, will cause the configured
* POST request to be executed on the server. See the individual overloads for
* details of `post()`'s return type based on the provided options.
* PUT request to be executed on the server. See the individual overloads for
* details of `put()`'s return type based on the provided options.
*/
put(url: string, body: any|null, options: {
headers?: HttpHeaders | {[header: string]: string | string[]},

View File

@ -949,7 +949,7 @@ export const locale_de_AT = [
[['v. Chr.', 'n. Chr.'], u, u], 1, [6, 0],
['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],
[',', ' ', ';', '%', '+', '-', 'E', '·', '‰', '∞', 'NaN', ':', '.'],
[',', ' ', ';', '%', '+', '-', 'E', '·', '‰', '∞', 'NaN', ':', u, '.'],
['#,##0.###', '#,##0 %', '¤ #,##0.00', '#E0'], '€', 'Euro', {
'ATS': ['öS'],
'AUD': ['AU$', '$'],

View File

@ -51,7 +51,7 @@ export default [
[['v. Chr.', 'n. Chr.'], u, u], 1, [6, 0],
['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],
[',', ' ', ';', '%', '+', '-', 'E', '·', '‰', '∞', 'NaN', ':', '.'],
[',', ' ', ';', '%', '+', '-', 'E', '·', '‰', '∞', 'NaN', ':', u, '.'],
['#,##0.###', '#,##0 %', '¤ #,##0.00', '#E0'], '€', 'Euro', {
'ATS': ['öS'],
'AUD': ['AU$', '$'],

View File

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

View File

@ -29,7 +29,7 @@ enum DateType {
Hours,
Minutes,
Seconds,
Milliseconds,
FractionalSeconds,
Day
}
@ -57,8 +57,6 @@ enum TranslationType {
* If not specified, host system settings are used.
*
* See {@link DatePipe} for more details.
*
*
*/
export function formatDate(
value: string | number | Date, format: string, locale: string, timezone?: string): string {
@ -195,6 +193,22 @@ function padNumber(
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
*/
@ -202,20 +216,26 @@ function dateGetter(
name: DateType, size: number, offset: number = 0, trim = false,
negWrap = false): DateFormatter {
return function(date: Date, locale: string): string {
let part = getDatePart(name, date, size);
let part = getDatePart(name, date);
if (offset > 0 || part > -offset) {
part += offset;
}
if (name === DateType.Hours && part === 0 && offset === -12) {
part = 12;
if (name === DateType.Hours) {
if (part === 0 && offset === -12) {
part = 12;
}
} else if (name === DateType.FractionalSeconds) {
return trimRPadFractional(part, size);
}
return padNumber(
part, size, getLocaleNumberSymbol(locale, NumberSymbol.MinusSign), trim, negWrap);
const localeMinus = getLocaleNumberSymbol(locale, NumberSymbol.MinusSign);
return padNumber(part, size, localeMinus, trim, negWrap);
};
}
function getDatePart(name: DateType, date: Date, size: number): number {
switch (name) {
function getDatePart(part: DateType, date: Date): number {
switch (part) {
case DateType.FullYear:
return date.getFullYear();
case DateType.Month:
@ -228,13 +248,12 @@ function getDatePart(name: DateType, date: Date, size: number): number {
return date.getMinutes();
case DateType.Seconds:
return date.getSeconds();
case DateType.Milliseconds:
const div = size === 1 ? 100 : (size === 2 ? 10 : 1);
return Math.round(date.getMilliseconds() / div);
case DateType.FractionalSeconds:
return date.getMilliseconds();
case DateType.Day:
return date.getDay();
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);
break;
// Fractional second padded (0-9)
// Fractional second
case 'S':
formatter = dateGetter(DateType.Milliseconds, 1);
formatter = dateGetter(DateType.FractionalSeconds, 1);
break;
case 'SS':
formatter = dateGetter(DateType.Milliseconds, 2);
formatter = dateGetter(DateType.FractionalSeconds, 2);
break;
// = millisecond
case 'SSS':
formatter = dateGetter(DateType.Milliseconds, 3);
formatter = dateGetter(DateType.FractionalSeconds, 3);
break;

View File

@ -19,7 +19,20 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
* formatted according to locale rules that determine group sizing and
* separator, decimal-point character, and other locale-specific
* configurations.
*
*
* If no parameters are specified, the function rounds off to the nearest value using this
* [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
@ -27,6 +40,8 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
* into text strings, according to various format specifications,
* where the caller's default locale is `en-US`.
*
* ### Example
*
* <code-example path="common/pipes/ts/number_pipe.ts" region='NumberPipe'></code-example>
*
*

View File

@ -183,9 +183,12 @@ let thisArg: any;
it('should allow of saving the collection', async(() => {
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);
detectChangesAndExpectText('0/2 - 1;1/2 - 2;');
getComponent().items = [1, 2, 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
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(() => {
@ -105,7 +105,7 @@ describe('Format date', () => {
mm: '03',
s: '1',
ss: '01',
S: '6',
S: '5',
SS: '55',
SSS: '550',
a: 'AM',
@ -233,7 +233,6 @@ describe('Format date', () => {
Object.keys(dateFixtures).forEach((pattern: string) => {
expectDateFormatAs(date, pattern, dateFixtures[pattern]);
});
});
it('should format with pattern aliases', () => {
@ -266,14 +265,13 @@ describe('Format date', () => {
() => expect(formatDate('2017-01-20T12:00:00+0000', defaultFormat, defaultLocale))
.toEqual('Jan 20, 2017'));
// test for the following bugs:
// https://github.com/angular/angular/issues/9524
// https://github.com/angular/angular/issues/9524
it('should format correctly with iso strings that contain time',
() => expect(formatDate('2017-05-07T22:14:39', 'dd-MM-yyyy HH:mm', defaultLocale))
.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', () => {
// this test only works if the timezone is not in UTC
// 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/17478
it('should show the correct time when the timezone is fixed', () => {
@ -311,5 +308,17 @@ describe('Format date', () => {
expect(() => formatDate(date, 'b', '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');
});
});
});

View File

@ -10,6 +10,7 @@ import localeEn from '@angular/common/locales/en';
import localeEsUS from '@angular/common/locales/es-US';
import localeFr from '@angular/common/locales/fr';
import localeAr from '@angular/common/locales/ar';
import localeDeAt from '@angular/common/locales/de-AT';
import {registerLocaleData, CurrencyPipe, DecimalPipe, PercentPipe, formatNumber} from '@angular/common';
import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testing_internal';
@ -20,6 +21,7 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testin
registerLocaleData(localeEsUS);
registerLocaleData(localeFr);
registerLocaleData(localeAr);
registerLocaleData(localeDeAt);
});
describe('DecimalPipe', () => {
@ -95,6 +97,8 @@ import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testin
expect(pipe.transform(5.1234, 'CAD', 'symbol-narrow', '5.2-2', 'fr'))
.toEqual('00 005,12 $');
expect(pipe.transform(5, 'USD', 'symbol', '', 'fr')).toEqual('5,00 $US');
expect(pipe.transform(123456789, 'EUR', 'symbol', '', 'de-at'))
.toEqual('€ 123.456.789,00');
});
it('should support any currency code name', () => {

View File

@ -103,7 +103,7 @@ export function createBundleIndexHost<H extends ts.CompilerHost>(
// etc.
const getMetadataBundle = (cache: MetadataCache | null) => {
const bundler = new MetadataBundler(
indexModule, ngOptions.flatModuleId, new CompilerHostAdapter(host, cache),
indexModule, ngOptions.flatModuleId, new CompilerHostAdapter(host, cache, ngOptions),
ngOptions.flatModulePrivateSymbolPrefix);
return bundler.getMetadataBundle();
};

View File

@ -72,7 +72,7 @@ export interface BundledModule {
}
export interface MetadataBundlerHost {
getMetadataFor(moduleName: string): ModuleMetadata|undefined;
getMetadataFor(moduleName: string, containingFile: string): ModuleMetadata|undefined;
}
type StaticsMetadata = {
@ -135,7 +135,7 @@ export class MetadataBundler {
if (!result) {
if (moduleName.startsWith('.')) {
const fullModuleName = resolveModule(moduleName, this.root);
result = this.host.getMetadataFor(fullModuleName);
result = this.host.getMetadataFor(fullModuleName, this.root);
}
this.metadataCache.set(moduleName, result);
}
@ -597,11 +597,27 @@ export class MetadataBundler {
export class CompilerHostAdapter implements MetadataBundlerHost {
private collector = new MetadataCollector();
constructor(private host: ts.CompilerHost, private cache: MetadataCache|null) {}
constructor(
private host: ts.CompilerHost, private cache: MetadataCache|null,
private options: ts.CompilerOptions) {}
getMetadataFor(fileName: string, containingFile: string): ModuleMetadata|undefined {
const {resolvedModule} =
ts.resolveModuleName(fileName, containingFile, this.options, this.host);
let sourceFile: ts.SourceFile|undefined;
if (resolvedModule) {
let {resolvedFileName} = resolvedModule;
if (resolvedModule.extension !== '.ts') {
resolvedFileName = resolvedFileName.replace(/(\.d\.ts|\.js)$/, '.ts');
}
sourceFile = this.host.getSourceFile(resolvedFileName, ts.ScriptTarget.Latest);
} else {
// If typescript is unable to resolve the file, fallback on old behavior
if (!this.host.fileExists(fileName + '.ts')) return undefined;
sourceFile = this.host.getSourceFile(fileName + '.ts', ts.ScriptTarget.Latest);
}
getMetadataFor(fileName: string): ModuleMetadata|undefined {
if (!this.host.fileExists(fileName + '.ts')) return undefined;
const sourceFile = this.host.getSourceFile(fileName + '.ts', ts.ScriptTarget.Latest);
// If there is a metadata cache, use it to get the metadata for this source file. Otherwise,
// fall back on the locally created MetadataCollector.
if (!sourceFile) {

View File

@ -9,6 +9,7 @@ ts_library(
"//packages:types",
"//packages/compiler",
"//packages/compiler-cli",
"//packages/compiler-cli/test:test_utils",
"//packages/core",
],
)

View File

@ -9,11 +9,187 @@
import * as path from 'path';
import * as ts from 'typescript';
import {MetadataBundler, MetadataBundlerHost} from '../../src/metadata/bundler';
import {CompilerHostAdapter, MetadataBundler, MetadataBundlerHost} from '../../src/metadata/bundler';
import {MetadataCollector} from '../../src/metadata/collector';
import {ClassMetadata, MetadataGlobalReferenceExpression, ModuleMetadata} from '../../src/metadata/schema';
import {Directory, MockAotContext, MockCompilerHost} from '../mocks';
import {Directory, open} from './typescript.mocks';
describe('compiler host adapter', () => {
it('should retrieve metadata for an explicit index relative path reference', () => {
const context = new MockAotContext('.', SIMPLE_LIBRARY);
const host = new MockCompilerHost(context);
const options: ts.CompilerOptions = {
moduleResolution: ts.ModuleResolutionKind.NodeJs,
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES5,
};
const adapter = new CompilerHostAdapter(host, null, options);
const metadata = adapter.getMetadataFor('./lib/src/two/index', '.');
expect(metadata).toBeDefined();
expect(Object.keys(metadata !.metadata).sort()).toEqual([
'PrivateTwo',
'TWO_CLASSES',
'Two',
'TwoMore',
]);
});
it('should retrieve metadata for an implied index relative path reference', () => {
const context = new MockAotContext('.', SIMPLE_LIBRARY_WITH_IMPLIED_INDEX);
const host = new MockCompilerHost(context);
const options: ts.CompilerOptions = {
moduleResolution: ts.ModuleResolutionKind.NodeJs,
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES5,
};
const adapter = new CompilerHostAdapter(host, null, options);
const metadata = adapter.getMetadataFor('./lib/src/two', '.');
expect(metadata).toBeDefined();
expect(Object.keys(metadata !.metadata).sort()).toEqual([
'PrivateTwo',
'TWO_CLASSES',
'Two',
'TwoMore',
]);
});
it('should fail to retrieve metadata for an implied index with classic module resolution', () => {
const context = new MockAotContext('.', SIMPLE_LIBRARY_WITH_IMPLIED_INDEX);
const host = new MockCompilerHost(context);
const options: ts.CompilerOptions = {
moduleResolution: ts.ModuleResolutionKind.Classic,
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES5,
};
const adapter = new CompilerHostAdapter(host, null, options);
const metadata = adapter.getMetadataFor('./lib/src/two', '.');
expect(metadata).toBeUndefined();
});
it('should retrieve exports for an explicit index relative path reference', () => {
const context = new MockAotContext('.', SIMPLE_LIBRARY);
const host = new MockCompilerHost(context);
const options: ts.CompilerOptions = {
moduleResolution: ts.ModuleResolutionKind.NodeJs,
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES5,
};
const adapter = new CompilerHostAdapter(host, null, options);
const metadata = adapter.getMetadataFor('./lib/src/index', '.');
expect(metadata).toBeDefined();
expect(metadata !.exports !.map(e => e.export !)
.reduce((prev, next) => prev.concat(next), [])
.sort())
.toEqual([
'ONE_CLASSES',
'One',
'OneMore',
'TWO_CLASSES',
'Two',
'TwoMore',
]);
});
it('should look for .ts file when resolving metadata via a package.json "main" entry', () => {
const files = {
'lib': {
'one.ts': `
class One {}
class OneMore extends One {}
class PrivateOne {}
const ONE_CLASSES = [One, OneMore, PrivateOne];
export {One, OneMore, PrivateOne, ONE_CLASSES};
`,
'one.js': `
// This will throw an error if the metadata collector tries to load one.js
`,
'package.json': `
{
"main": "one"
}
`
}
};
const context = new MockAotContext('.', files);
const host = new MockCompilerHost(context);
const options: ts.CompilerOptions = {
moduleResolution: ts.ModuleResolutionKind.NodeJs,
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES5,
};
const adapter = new CompilerHostAdapter(host, null, options);
const metadata = adapter.getMetadataFor('./lib', '.');
expect(metadata).toBeDefined();
expect(Object.keys(metadata !.metadata).sort()).toEqual([
'ONE_CLASSES',
'One',
'OneMore',
'PrivateOne',
]);
expect(Array.isArray(metadata !.metadata !['ONE_CLASSES'])).toBeTruthy();
});
it('should look for non-declaration file when resolving metadata via a package.json "types" entry',
() => {
const files = {
'lib': {
'one.ts': `
class One {}
class OneMore extends One {}
class PrivateOne {}
const ONE_CLASSES = [One, OneMore, PrivateOne];
export {One, OneMore, PrivateOne, ONE_CLASSES};
`,
'one.d.ts': `
declare class One {
}
declare class OneMore extends One {
}
declare class PrivateOne {
}
declare const ONE_CLASSES: (typeof One)[];
export { One, OneMore, PrivateOne, ONE_CLASSES };
`,
'one.js': `
// This will throw an error if the metadata collector tries to load one.js
`,
'package.json': `
{
"main": "one",
"types": "one.d.ts"
}
`
}
};
const context = new MockAotContext('.', files);
const host = new MockCompilerHost(context);
const options: ts.CompilerOptions = {
moduleResolution: ts.ModuleResolutionKind.NodeJs,
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES5,
};
const adapter = new CompilerHostAdapter(host, null, options);
const metadata = adapter.getMetadataFor('./lib', '.');
expect(metadata).toBeDefined();
expect(Object.keys(metadata !.metadata).sort()).toEqual([
'ONE_CLASSES',
'One',
'OneMore',
'PrivateOne',
]);
expect(Array.isArray(metadata !.metadata !['ONE_CLASSES'])).toBeTruthy();
});
});
describe('metadata bundler', () => {
@ -231,26 +407,24 @@ describe('metadata bundler', () => {
export class MockStringBundlerHost implements MetadataBundlerHost {
collector = new MetadataCollector();
adapter: CompilerHostAdapter;
constructor(private dirName: string, private directory: Directory) {}
constructor(private dirName: string, directory: Directory) {
const context = new MockAotContext(dirName, directory);
const host = new MockCompilerHost(context);
const options = {
moduleResolution: ts.ModuleResolutionKind.NodeJs,
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES5,
};
this.adapter = new CompilerHostAdapter(host, null, options);
}
getMetadataFor(moduleName: string): ModuleMetadata|undefined {
const fileName = path.join(this.dirName, moduleName) + '.ts';
const text = open(this.directory, fileName);
if (typeof text == 'string') {
const sourceFile = ts.createSourceFile(
fileName, text, ts.ScriptTarget.Latest, /* setParent */ true, ts.ScriptKind.TS);
const diagnostics: ts.Diagnostic[] = (sourceFile as any).parseDiagnostics;
if (diagnostics && diagnostics.length) {
throw Error('Unexpected syntax error in test');
}
const result = this.collector.getMetadata(sourceFile);
return result;
}
return this.adapter.getMetadataFor(moduleName, this.dirName);
}
}
export const SIMPLE_LIBRARY = {
'lib': {
'index.ts': `
@ -278,3 +452,31 @@ export const SIMPLE_LIBRARY = {
}
}
};
export const SIMPLE_LIBRARY_WITH_IMPLIED_INDEX = {
'lib': {
'index.ts': `
export * from './src';
`,
'src': {
'index.ts': `
export {One, OneMore, ONE_CLASSES} from './one';
export {Two, TwoMore, TWO_CLASSES} from './two';
`,
'one.ts': `
export class One {}
export class OneMore extends One {}
export class PrivateOne {}
export const ONE_CLASSES = [One, OneMore, PrivateOne];
`,
'two': {
'index.ts': `
export class Two {}
export class TwoMore extends Two {}
export class PrivateTwo {}
export const TWO_CLASSES = [Two, TwoMore, PrivateTwo];
`
}
}
}
};

View File

@ -19,7 +19,9 @@ export class MockAotContext {
fileExists(fileName: string): boolean { return typeof this.getEntry(fileName) === 'string'; }
directoryExists(path: string): boolean { return typeof this.getEntry(path) === 'object'; }
directoryExists(path: string): boolean {
return path === this.currentDirectory || typeof this.getEntry(path) === 'object';
}
readFile(fileName: string): string {
const data = this.getEntry(fileName);

View File

@ -73,7 +73,8 @@ export interface Component extends Directive {
export enum ViewEncapsulation {
Emulated = 0,
Native = 1,
None = 2
None = 2,
ShadowDom = 3
}
export enum ChangeDetectionStrategy {

View File

@ -60,10 +60,8 @@ export abstract class Injector {
/**
* Retrieves an instance from the injector based on the provided token.
* If not found:
* - Throws an error if no `notFoundValue` that is not equal to
* Injector.THROW_IF_NOT_FOUND is given
* - Returns the `notFoundValue` otherwise
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.
* @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`.
*/
abstract get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
/**

View File

@ -23,14 +23,28 @@ export enum ViewEncapsulation {
*/
Emulated = 0,
/**
* @deprecated v6.1.0 - use {ViewEncapsulation.ShadowDom} instead.
* Use the native encapsulation mechanism of the renderer.
*
* For the DOM this means using [Shadow DOM](https://w3c.github.io/webcomponents/spec/shadow/) and
* For the DOM this means using the deprecated [Shadow DOM
* v0](https://w3c.github.io/webcomponents/spec/shadow/) and
* creating a ShadowRoot for Component's Host Element.
*/
Native = 1,
/**
* Don't provide any template or style encapsulation.
*/
None = 2
None = 2,
/**
* Use Shadow DOM to encapsulate styles.
*
* For the DOM this means using modern [Shadow
* DOM](https://w3c.github.io/webcomponents/spec/shadow/) and
* creating a ShadowRoot for Component's Host Element.
*
* ### Example
* {@example core/ts/metadata/encapsulation.ts region='longform'}
*/
ShadowDom = 3
}

View File

@ -9,10 +9,10 @@
"name": "EMPTY_ARRAY$1"
},
{
"name": "GET_PROPERTY_NAME$1"
"name": "GET_PROPERTY_NAME"
},
{
"name": "INJECTOR$1"
"name": "INJECTOR"
},
{
"name": "Inject"
@ -51,7 +51,7 @@
"name": "THROW_IF_NOT_FOUND"
},
{
"name": "USE_VALUE$1"
"name": "USE_VALUE"
},
{
"name": "_THROW_IF_NOT_FOUND"

View File

@ -21,6 +21,18 @@ class SpyTestObj extends SpyObject {
{
describe('testing', () => {
describe('should respect custom equality tester', () => {
beforeEach(() => {
const equalIfMarried =
(first: any, second: any) => { return first === 'kevin' && second === 'patricia'; };
jasmine.addCustomEqualityTester(equalIfMarried);
});
it('for positive test', () => { expect('kevin').toEqual('patricia'); });
it('for negative test', () => { expect('kevin').not.toEqual('kevin'); });
});
describe('equality', () => {
it('should structurally compare objects', () => {
const expected = new TestObj(new TestObj({'one': [1, 2]}));

View File

@ -38,6 +38,9 @@ registerLocaleData(localeFr);
<!--output '003.14000'-->
<p>pi (3.5-5): {{pi | number:'3.5-5'}}</p>
<!--output '-3' / unlike '-2' by Math.round()-->
<p>-2.5 (1.0-0): {{-2.5 | number:'1.0-0'}}</p>
</div>`
})
export class NumberPipeComponent {

View File

@ -0,0 +1,35 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Component, ViewEncapsulation} from '@angular/core';
// #docregion longform
@Component({
selector: 'my-app',
template: `
<h1>Hello World!</h1>
<span class="red">Shadow DOM Rocks!</span>
`,
styles: [`
:host {
display: block;
border: 1px solid black;
}
h1 {
color: blue;
}
.red {
background-color: red;
}
`],
encapsulation: ViewEncapsulation.ShadowDom
})
class MyApp {
}
// #enddocregion

View File

@ -235,9 +235,8 @@ export const MAX_LENGTH_VALIDATOR: any = {
};
/**
* A directive which installs the `MaxLengthValidator` for any `formControlName,
* `formControl`,
* or control with `ngModel` that also has a `maxlength` attribute.
* A directive which installs the `MaxLengthValidator` for any `formControlName`,
* `formControl`, or control with `ngModel` that also has a `maxlength` attribute.
*
*
*/

View File

@ -18,37 +18,39 @@ function isEmptyInputValue(value: any): boolean {
}
/**
* Providers for validators to be used for `FormControl`s in a form.
* @description
* An `InjectionToken` for registering additional synchronous validators used with `AbstractControl`s.
*
* Provide this using `multi: true` to add validators.
*
* ### Example
* @see `NG_ASYNC_VALIDATORS`
*
* @usageNotes
*
* ### Providing a custom validator
*
* The following example registers a custom validator directive. Adding the validator to the
* existing collection of validators requires the `multi: true` option.
*
* ```typescript
* @Directive({
* selector: '[custom-validator]',
* selector: '[customValidator]',
* providers: [{provide: NG_VALIDATORS, useExisting: CustomValidatorDirective, multi: true}]
* })
* class CustomValidatorDirective implements Validator {
* validate(control: AbstractControl): ValidationErrors | null {
* return {"custom": true};
* return { 'custom': true };
* }
* }
* ```
*
*
*/
export const NG_VALIDATORS = new InjectionToken<Array<Validator|Function>>('NgValidators');
/**
* Providers for asynchronous validators to be used for `FormControl`s
* in a form.
*
* Provide this using `multi: true` to add validators.
*
* See `NG_VALIDATORS` for more details.
*
* @description
* An `InjectionToken` for registering additional asynchronous validators used with `AbstractControl`s.
*
* @see `NG_VALIDATORS`
*
*/
export const NG_ASYNC_VALIDATORS =
new InjectionToken<Array<Validator|Function>>('NgAsyncValidators');
@ -57,24 +59,34 @@ const EMAIL_REGEXP =
/^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/;
/**
* Provides a set of validators used by form controls.
* @description
* Provides a set of built-in validators that can be used by form controls.
*
* A validator is a function that processes a `FormControl` or collection of
* controls and returns a map of errors. A null map means that validation has passed.
*
* ### Example
*
* ```typescript
* var loginControl = new FormControl("", Validators.required)
* ```
*
* controls and returns an error map or null. A null map means that validation has passed.
*
* @see [Form Validation](/guide/form-validation)
*
*/
export class Validators {
/**
* Validator that requires controls to have a value greater than a number.
*`min()` exists only as a function, not as a directive. For example,
* `control = new FormControl('', Validators.min(3));`.
* @description
* Validator that requires the control's value to be greater than or equal to the provided number.
* The validator exists only as a function and not as a directive.
*
* @usageNotes
*
* ### Validate against a minimum of 3
*
* ```typescript
* const control = new FormControl(2, Validators.min(3));
*
* console.log(control.errors); // {min: {min: 3, actual: 2}}
* ```
*
* @returns A validator function that returns an error map with the
* `min` property if the validation check fails, otherwise `null`.
*
*/
static min(min: number): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
@ -89,9 +101,23 @@ export class Validators {
}
/**
* Validator that requires controls to have a value less than a number.
* `max()` exists only as a function, not as a directive. For example,
* `control = new FormControl('', Validators.max(15));`.
* @description
* Validator that requires the control's value to be less than or equal to the provided number.
* The validator exists only as a function and not as a directive.
*
* @usageNotes
*
* ### Validate against a maximum of 15
*
* ```typescript
* const control = new FormControl(16, Validators.max(15));
*
* console.log(control.errors); // {max: {max: 15, actual: 16}}
* ```
*
* @returns A validator function that returns an error map with the
* `max` property if the validation check fails, otherwise `null`.
*
*/
static max(max: number): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
@ -106,21 +132,66 @@ export class Validators {
}
/**
* Validator that requires controls to have a non-empty value.
* @description
* Validator that requires the control have a non-empty value.
*
* @usageNotes
*
* ### Validate that the field is non-empty
*
* ```typescript
* const control = new FormControl('', Validators.required);
*
* console.log(control.errors); // {required: true}
* ```
*
* @returns An error map with the `required` property
* if the validation check fails, otherwise `null`.
*
*/
static required(control: AbstractControl): ValidationErrors|null {
return isEmptyInputValue(control.value) ? {'required': true} : null;
}
/**
* Validator that requires control value to be true.
* @description
* Validator that requires the control's value be true. This validator is commonly
* used for required checkboxes.
*
* @usageNotes
*
* ### Validate that the field value is true
*
* ```typescript
* const control = new FormControl('', Validators.requiredTrue);
*
* console.log(control.errors); // {required: true}
* ```
*
* @returns An error map that contains the `required` property
* set to `true` if the validation check fails, otherwise `null`.
*/
static requiredTrue(control: AbstractControl): ValidationErrors|null {
return control.value === true ? null : {'required': true};
}
/**
* Validator that performs email validation.
* @description
* Validator that requires the control's value pass an email validation test.
*
* @usageNotes
*
* ### Validate that the field matches a valid email pattern
*
* ```typescript
* const control = new FormControl('bad@', Validators.email);
*
* console.log(control.errors); // {email: true}
* ```
*
* @returns An error map with the `email` property
* if the validation check fails, otherwise `null`.
*
*/
static email(control: AbstractControl): ValidationErrors|null {
if (isEmptyInputValue(control.value)) {
@ -130,7 +201,27 @@ export class Validators {
}
/**
* Validator that requires controls to have a value of a minimum length.
* @description
* Validator that requires the length of the control's value to be greater than or equal
* to the provided minimum length. This validator is also provided by default if you use the
* the HTML5 `minlength` attribute.
*
* @usageNotes
*
* ### Validate that the field has a minimum of 3 characters
*
* ```typescript
* const control = new FormControl('ng', Validators.minLength(3));
*
* console.log(control.errors); // {minlength: {requiredLength: 3, actualLength: 2}}
* ```
*
* ```html
* <input minlength="5">
* ```
*
* @returns A validator function that returns an error map with the
* `minlength` if the validation check fails, otherwise `null`.
*/
static minLength(minLength: number): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
@ -145,7 +236,27 @@ export class Validators {
}
/**
* Validator that requires controls to have a value of a maximum length.
* @description
* Validator that requires the length of the control's value to be less than or equal
* to the provided maximum length. This validator is also provided by default if you use the
* the HTML5 `maxlength` attribute.
*
* @usageNotes
*
* ### Validate that the field has maximum of 5 characters
*
* ```typescript
* const control = new FormControl('Angular', Validators.maxLength(5));
*
* console.log(control.errors); // {maxlength: {requiredLength: 5, actualLength: 7}}
* ```
*
* ```html
* <input maxlength="5">
* ```
*
* @returns A validator function that returns an error map with the
* `maxlength` property if the validation check fails, otherwise `null`.
*/
static maxLength(maxLength: number): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
@ -157,7 +268,27 @@ export class Validators {
}
/**
* Validator that requires a control to match a regex to its value.
* @description
* Validator that requires the control's value to match a regex pattern. This validator is also
* provided
* by default if you use the HTML5 `pattern` attribute.
*
* @usageNotes
*
* ### Validate that the field only contains letters or spaces
*
* ```typescript
* const control = new FormControl('1', Validators.pattern('[a-zA-Z ]*'));
*
* console.log(control.errors); // {pattern: {requiredPattern: '^[a-zA-Z ]*$', actualValue: '1'}}
* ```
*
* ```html
* <input pattern="[a-zA-Z ]*">
* ```
*
* @returns A validator function that returns an error map with the
* `pattern` property if the validation check fails, otherwise `null`.
*/
static pattern(pattern: string|RegExp): ValidatorFn {
if (!pattern) return Validators.nullValidator;
@ -188,13 +319,18 @@ export class Validators {
}
/**
* No-op validator.
* @description
* Validator that performs no operation.
*/
static nullValidator(c: AbstractControl): ValidationErrors|null { return null; }
/**
* @description
* Compose multiple validators into a single function that returns the union
* of the individual error maps.
* of the individual error maps for the provided control.
*
* @returns A validator function that returns an error map with the
* merged error maps of the validators if the validation check fails, otherwise `null`.
*/
static compose(validators: null): null;
static compose(validators: (ValidatorFn|null|undefined)[]): ValidatorFn|null;
@ -208,6 +344,14 @@ export class Validators {
};
}
/**
* @description
* Compose multiple async validators into a single function that returns the union
* of the individual error objects for the provided control.
*
* @returns A validator function that returns an error map with the
* merged error objects of the async validators if the validation check fails, otherwise `null`.
*/
static composeAsync(validators: (AsyncValidatorFn|null)[]): AsyncValidatorFn|null {
if (!validators) return null;
const presentValidators: AsyncValidatorFn[] = validators.filter(isPresent) as any;

View File

@ -4,8 +4,9 @@
* License: MIT
*/
var $reflect = {defineMetadata: function() {}, getOwnMetadata: function(){}};
((typeof global !== 'undefined' && global)||{})['Reflect'] = $reflect;
var $reflect = {defineMetadata: function() {}, getOwnMetadata: function() {}};
var Reflect = (typeof global !== 'undefined' ? global : {})['Reflect'] || {};
Object.keys($reflect).forEach(function(key) { Reflect[key] = Reflect[key] || $reflect[key]; });
var $deferred, $resolved, $provided;
function $getModule(name) { return $provided[name] || require(name); }
function define(modules, cb) { $deferred = { modules: modules, cb: cb }; }

View File

@ -9,12 +9,12 @@
const commonjs = require('rollup-plugin-commonjs');
const sourcemaps = require('rollup-plugin-sourcemaps');
const path = require('path');
const fs = require('fs');
var m = /^\@angular\/((\w|\-)+)(\/(\w|\d|\/|\-)+)?$/;
var location = normalize('../../dist/packages-dist') + '/';
var rxjsLocation = normalize('../../node_modules/rxjs');
var tslibLocation = normalize('../../node_modules/tslib');
var esm = 'esm/';
var locations = {'compiler-cli': normalize('../../dist/packages') + '/'};
@ -43,21 +43,7 @@ function resolve(id, from) {
}
}
var banner = `
var $reflect = {defineMetadata: function() {}, getOwnMetadata: function(){}};
((typeof global !== 'undefined' && global)||{})['Reflect'] = $reflect;
var $deferred, $resolved, $provided;
function $getModule(name) { return $provided[name] || require(name); }
function define(modules, cb) { $deferred = { modules: modules, cb: cb }; }
module.exports = function(provided) {
if ($resolved) return $resolved;
var result = {};
$provided = Object.assign({'reflect-metadata': $reflect}, provided || {}, { exports: result });
$deferred.cb.apply(this, $deferred.modules.map($getModule));
$resolved = result;
return result;
}
`;
var banner = fs.readFileSync('bundles/banner.js.txt', 'utf8');
module.exports = {
entry: '../../dist/packages-dist/language-service/fesm5/language-service.js',

View File

@ -83,6 +83,7 @@ export class DomRendererFactory2 implements RendererFactory2 {
return renderer;
}
case ViewEncapsulation.Native:
case ViewEncapsulation.ShadowDom:
return new ShadowDomRenderer(this.eventManager, this.sharedStylesHost, element, type);
default: {
if (!this.rendererByCompId.has(type.id)) {
@ -256,7 +257,11 @@ class ShadowDomRenderer extends DefaultDomRenderer2 {
eventManager: EventManager, private sharedStylesHost: DomSharedStylesHost,
private hostEl: any, private component: RendererType2) {
super(eventManager);
this.shadowRoot = (hostEl as any).createShadowRoot();
if (component.encapsulation === ViewEncapsulation.ShadowDom) {
this.shadowRoot = (hostEl as any).attachShadow({mode: 'open'});
} else {
this.shadowRoot = (hostEl as any).createShadowRoot();
}
this.sharedStylesHost.addHost(this.shadowRoot);
const styles = flattenStyles(component.id, component.styles, []);
for (let i = 0; i < styles.length; i++) {

View File

@ -20,7 +20,8 @@ import {NAMESPACE_URIS} from '../../src/dom/dom_renderer';
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
TestCmp, SomeApp, CmpEncapsulationEmulated, CmpEncapsulationNative, CmpEncapsulationNone
TestCmp, SomeApp, CmpEncapsulationEmulated, CmpEncapsulationNative, CmpEncapsulationNone,
CmpEncapsulationNative
]
});
renderer = TestBed.createComponent(TestCmp).componentInstance.renderer;
@ -135,6 +136,15 @@ class CmpEncapsulationEmulated {
class CmpEncapsulationNone {
}
@Component({
selector: 'cmp-shadow',
template: `<div class="shadow"></div><cmp-emulated></cmp-emulated><cmp-none></cmp-none>`,
styles: [`.native { color: red; }`],
encapsulation: ViewEncapsulation.ShadowDom
})
class CmpEncapsulationShadow {
}
@Component({
selector: 'some-app',
template: `

View File

@ -0,0 +1,122 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Component, EventEmitter, Injector, Input, NgModule, Output, Renderer2, ViewEncapsulation, destroyPlatform} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
function supportsShadowDOMV1() {
const testEl = document.createElement('div');
return (typeof customElements !== 'undefined') && (typeof testEl.attachShadow !== 'undefined');
}
if (supportsShadowDOMV1()) {
describe('ShadowDOM Support', () => {
let testContainer: HTMLDivElement;
beforeEach(() => { TestBed.configureTestingModule({imports: [TestModule]}); });
it('should attach and use a shadowRoot when ViewEncapsulation.Native is set', () => {
const compEl = TestBed.createComponent(ShadowComponent).nativeElement;
expect(compEl.shadowRoot !.textContent).toEqual('Hello World');
});
it('should use the shadow root to encapsulate styles', () => {
const compEl = TestBed.createComponent(StyledShadowComponent).nativeElement;
expect(window.getComputedStyle(compEl).border).toEqual('1px solid rgb(0, 0, 0)');
const redDiv = compEl.shadowRoot.querySelector('div.red');
expect(window.getComputedStyle(redDiv).border).toEqual('1px solid rgb(255, 0, 0)');
});
it('should allow the usage of <slot> elements', () => {
const el = TestBed.createComponent(ShadowSlotComponent).nativeElement;
const projectedContent = document.createTextNode('Hello Slot!');
el.appendChild(projectedContent);
const slot = el.shadowRoot !.querySelector('slot');
expect(slot !.assignedNodes().length).toBe(1);
expect(slot !.assignedNodes()[0].textContent).toBe('Hello Slot!');
});
it('should allow the usage of named <slot> elements', () => {
const el = TestBed.createComponent(ShadowSlotsComponent).nativeElement;
const headerContent = document.createElement('h1');
headerContent.setAttribute('slot', 'header');
headerContent.textContent = 'Header Text!';
const articleContent = document.createElement('span');
articleContent.setAttribute('slot', 'article');
articleContent.textContent = 'Article Text!';
const articleSubcontent = document.createElement('span');
articleSubcontent.setAttribute('slot', 'article');
articleSubcontent.textContent = 'Article Subtext!';
el.appendChild(headerContent);
el.appendChild(articleContent);
el.appendChild(articleSubcontent);
const headerSlot = el.shadowRoot !.querySelector('slot[name=header]') as HTMLSlotElement;
const articleSlot = el.shadowRoot !.querySelector('slot[name=article]') as HTMLSlotElement;
expect(headerSlot !.assignedNodes().length).toBe(1);
expect(headerSlot !.assignedNodes()[0].textContent).toBe('Header Text!');
expect(headerContent.assignedSlot).toBe(headerSlot);
expect(articleSlot !.assignedNodes().length).toBe(2);
expect(articleSlot !.assignedNodes()[0].textContent).toBe('Article Text!');
expect(articleSlot !.assignedNodes()[1].textContent).toBe('Article Subtext!');
expect(articleContent.assignedSlot).toBe(articleSlot);
expect(articleSubcontent.assignedSlot).toBe(articleSlot);
});
});
}
@Component(
{selector: 'shadow-comp', template: 'Hello World', encapsulation: ViewEncapsulation.ShadowDom})
class ShadowComponent {
}
@Component({
selector: 'styled-shadow-comp',
template: '<div class="red"></div>',
encapsulation: ViewEncapsulation.ShadowDom,
styles: [`:host { border: 1px solid black; } .red { border: 1px solid red; }`]
})
class StyledShadowComponent {
}
@Component({
selector: 'shadow-slot-comp',
template: '<slot></slot>',
encapsulation: ViewEncapsulation.ShadowDom
})
class ShadowSlotComponent {
}
@Component({
selector: 'shadow-slots-comp',
template:
'<header><slot name="header"></slot></header><article><slot name="article"></slot></article>',
encapsulation: ViewEncapsulation.ShadowDom
})
class ShadowSlotsComponent {
}
@NgModule({
imports: [BrowserModule],
declarations: [ShadowComponent, ShadowSlotComponent, ShadowSlotsComponent, StyledShadowComponent],
entryComponents:
[ShadowComponent, ShadowSlotComponent, ShadowSlotsComponent, StyledShadowComponent],
})
class TestModule {
ngDoBootstrap() {}
}

View File

@ -112,29 +112,23 @@ export const expect: (actual: any) => NgMatchers = <any>_global.expect;
};
_global.beforeEach(function() {
jasmine.addMatchers({
// Custom handler for Map as Jasmine does not support it yet
toEqual: function(util) {
return {
compare: function(actual: any, expected: any) {
return {pass: util.equals(actual, expected, [compareMap])};
}
};
function compareMap(actual: any, expected: any): boolean {
if (actual instanceof Map) {
let pass = actual.size === expected.size;
if (pass) {
actual.forEach((v: any, k: any) => { pass = pass && util.equals(v, expected.get(k)); });
}
return pass;
} else {
// TODO(misko): we should change the return, but jasmine.d.ts is not null safe
return undefined !;
}
// Custom handler for Map as we use Jasmine 2.4, and support for maps is not
// added until Jasmine 2.6.
jasmine.addCustomEqualityTester(function compareMap(actual: any, expected: any): boolean {
if (actual instanceof Map) {
let pass = actual.size === expected.size;
if (pass) {
actual.forEach((v: any, k: any) => {
pass = pass && jasmine.matchersUtil.equals(v, expected.get(k));
});
}
},
return pass;
} else {
// TODO(misko): we should change the return, but jasmine.d.ts is not null safe
return undefined !;
}
});
jasmine.addMatchers({
toBePromise: function() {
return {
compare: function(actual: any) {

View File

@ -160,6 +160,11 @@ function defaultErrorHandler(error: any): any {
throw error;
}
function defaultMalformedUriErrorHandler(
error: URIError, urlSerializer: UrlSerializer, url: string): UrlTree {
return urlSerializer.parse('/');
}
type NavStreamValue =
boolean | {appliedUrl: UrlTree, snapshot: RouterStateSnapshot, shouldActivate?: boolean};
@ -217,7 +222,14 @@ export class Router {
*/
errorHandler: ErrorHandler = defaultErrorHandler;
/**
* Malformed uri error handler is invoked when `Router.parseUrl(url)` throws an
* error due to containing an invalid character. The most common case would be a `%` sign
* that's not encoded and is not part of a percent encoded sequence.
*/
malformedUriErrorHandler:
(error: URIError, urlSerializer: UrlSerializer,
url: string) => UrlTree = defaultMalformedUriErrorHandler;
/**
* Indicates if at least one navigation happened.
@ -312,7 +324,7 @@ export class Router {
// run into ngZone
if (!this.locationSubscription) {
this.locationSubscription = <any>this.location.subscribe((change: any) => {
const rawUrlTree = this.urlSerializer.parse(change['url']);
let rawUrlTree = this.parseUrl(change['url']);
const source: NavigationTrigger = change['type'] === 'popstate' ? 'popstate' : 'hashchange';
const state = change.state && change.state.navigationId ?
{navigationId: change.state.navigationId} :
@ -490,7 +502,15 @@ export class Router {
serializeUrl(url: UrlTree): string { return this.urlSerializer.serialize(url); }
/** Parses a string into a `UrlTree` */
parseUrl(url: string): UrlTree { return this.urlSerializer.parse(url); }
parseUrl(url: string): UrlTree {
let urlTree: UrlTree;
try {
urlTree = this.urlSerializer.parse(url);
} catch (e) {
urlTree = this.malformedUriErrorHandler(e, this.urlSerializer, url);
}
return urlTree;
}
/** Returns whether the url is activated */
isActive(url: string|UrlTree, exact: boolean): boolean {
@ -498,7 +518,7 @@ export class Router {
return containsTree(this.currentUrlTree, url, exact);
}
const urlTree = this.urlSerializer.parse(url);
const urlTree = this.parseUrl(url);
return containsTree(this.currentUrlTree, urlTree, exact);
}

View File

@ -24,7 +24,7 @@ import {ChildrenOutletContexts} from './router_outlet_context';
import {NoPreloading, PreloadAllModules, PreloadingStrategy, RouterPreloader} from './router_preloader';
import {ActivatedRoute} from './router_state';
import {UrlHandlingStrategy} from './url_handling_strategy';
import {DefaultUrlSerializer, UrlSerializer} from './url_tree';
import {DefaultUrlSerializer, UrlSerializer, UrlTree} from './url_tree';
import {flatten} from './utils/collection';
@ -151,6 +151,8 @@ export class RouterModule {
* * `preloadingStrategy` configures a preloading strategy (see `PreloadAllModules`).
* * `onSameUrlNavigation` configures how the router handles navigation to the current URL. See
* `ExtraOptions` for more details.
* * `paramsInheritanceStrategy` defines how the router merges params, data and resolved data
* from parent to child routes.
*/
static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders {
return {
@ -306,6 +308,18 @@ export interface ExtraOptions {
* - `'always'`, enables unconditional inheritance of parent params.
*/
paramsInheritanceStrategy?: 'emptyOnly'|'always';
/**
* A custom malformed uri error handler function. This handler is invoked when encodedURI contains
* invalid character sequences. The default implementation is to redirect to the root url dropping
* any path or param info. This function passes three parameters:
*
* - `'URIError'` - Error thrown when parsing a bad URL
* - `'UrlSerializer'` - UrlSerializer thats configured with the router.
* - `'url'` - The malformed URL that caused the URIError
* */
malformedUriErrorHandler?:
(error: URIError, urlSerializer: UrlSerializer, url: string) => UrlTree;
}
export function setupRouter(
@ -328,6 +342,10 @@ export function setupRouter(
router.errorHandler = opts.errorHandler;
}
if (opts.malformedUriErrorHandler) {
router.malformedUriErrorHandler = opts.malformedUriErrorHandler;
}
if (opts.enableTracing) {
const dom = getDOM();
router.events.subscribe((e: RouterEvent) => {

View File

@ -12,7 +12,7 @@ import {ChangeDetectionStrategy, Component, Injectable, NgModule, NgModuleFactor
import {ComponentFixture, TestBed, fakeAsync, inject, tick} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {ActivatedRoute, ActivatedRouteSnapshot, ActivationEnd, ActivationStart, CanActivate, CanDeactivate, ChildActivationEnd, ChildActivationStart, DetachedRouteHandle, Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, PRIMARY_OUTLET, ParamMap, Params, PreloadAllModules, PreloadingStrategy, Resolve, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouteReuseStrategy, Router, RouterEvent, RouterModule, RouterPreloader, RouterStateSnapshot, RoutesRecognized, RunGuardsAndResolvers, UrlHandlingStrategy, UrlSegmentGroup, UrlTree} from '@angular/router';
import {ActivatedRoute, ActivatedRouteSnapshot, ActivationEnd, ActivationStart, CanActivate, CanDeactivate, ChildActivationEnd, ChildActivationStart, DetachedRouteHandle, Event, GuardsCheckEnd, GuardsCheckStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, PRIMARY_OUTLET, ParamMap, Params, PreloadAllModules, PreloadingStrategy, Resolve, ResolveEnd, ResolveStart, RouteConfigLoadEnd, RouteConfigLoadStart, RouteReuseStrategy, Router, RouterEvent, RouterModule, RouterPreloader, RouterStateSnapshot, RoutesRecognized, RunGuardsAndResolvers, UrlHandlingStrategy, UrlSegmentGroup, UrlSerializer, UrlTree} from '@angular/router';
import {Observable, Observer, of } from 'rxjs';
import {map} from 'rxjs/operators';
@ -1010,6 +1010,30 @@ describe('Integration', () => {
expectEvents(recordedEvents, [[NavigationStart, '/invalid'], [NavigationError, '/invalid']]);
})));
it('should recover from malformed uri errors',
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
router.resetConfig([{path: 'simple', component: SimpleCmp}]);
const fixture = createRoot(router, RootCmp);
router.navigateByUrl('/invalid/url%with%percent');
advance(fixture);
expect(location.path()).toEqual('/');
})));
it('should support custom malformed uri error handler',
fakeAsync(inject([Router, Location], (router: Router, location: Location) => {
const customMalformedUriErrorHandler =
(e: URIError, urlSerializer: UrlSerializer, url: string):
UrlTree => { return urlSerializer.parse('/?error=The-URL-you-went-to-is-invalid'); };
router.malformedUriErrorHandler = customMalformedUriErrorHandler;
router.resetConfig([{path: 'simple', component: SimpleCmp}]);
const fixture = createRoot(router, RootCmp);
router.navigateByUrl('/invalid/url%with%percent');
advance(fixture);
expect(location.path()).toEqual('/?error=The-URL-you-went-to-is-invalid');
})));
it('should not swallow errors', fakeAsync(inject([Router], (router: Router) => {
const fixture = createRoot(router, RootCmp);
@ -3938,6 +3962,22 @@ describe('Testing router options', () => {
expect(router.paramsInheritanceStrategy).toEqual('always');
})));
});
describe('malformedUriErrorHandler', () => {
function malformedUriErrorHandler(e: URIError, urlSerializer: UrlSerializer, url: string) {
return urlSerializer.parse('/error');
}
beforeEach(() => {
TestBed.configureTestingModule(
{imports: [RouterTestingModule.withRoutes([], {malformedUriErrorHandler})]});
});
it('should configure the router', fakeAsync(inject([Router], (router: Router) => {
expect(router.malformedUriErrorHandler).toBe(malformedUriErrorHandler);
})));
});
});
function expectEvents(events: Event[], pairs: any[]) {

View File

@ -115,12 +115,20 @@ export function setupTestingRouter(
opts?: ExtraOptions | UrlHandlingStrategy, urlHandlingStrategy?: UrlHandlingStrategy) {
const router = new Router(
null !, urlSerializer, contexts, location, injector, loader, compiler, flatten(routes));
// Handle deprecated argument ordering.
if (opts) {
// Handle deprecated argument ordering.
if (isUrlHandlingStrategy(opts)) {
router.urlHandlingStrategy = opts;
} else if (opts.paramsInheritanceStrategy) {
router.paramsInheritanceStrategy = opts.paramsInheritanceStrategy;
} else {
// Handle ExtraOptions
if (opts.malformedUriErrorHandler) {
router.malformedUriErrorHandler = opts.malformedUriErrorHandler;
}
if (opts.paramsInheritanceStrategy) {
router.paramsInheritanceStrategy = opts.paramsInheritanceStrategy;
}
}
}

View File

@ -198,8 +198,6 @@ export class AppVersion implements UpdateSource {
* Check this version for a given resource with a particular hash.
*/
async lookupResourceWithHash(url: string, hash: string): Promise<Response|null> {
const req = this.adapter.newRequest(url);
// Verify that this version has the requested resource cached. If not,
// there's no point in trying.
if (!this.hashTable.has(url)) {
@ -208,16 +206,12 @@ export class AppVersion implements UpdateSource {
// Next, check whether the resource has the correct hash. If not, any cached
// response isn't usable.
if (this.hashTable.get(url) ! !== hash) {
if (this.hashTable.get(url) !== hash) {
return null;
}
// TODO: no-op context and appropriate contract. Currently this is a violation
// of the typings and could cause issues if handleFetch() has side effects. A
// better strategy to deal with side effects is needed.
// TODO: this could result in network fetches if the response is lazy. Refactor
// to avoid them.
return this.handleFetch(req, null !);
const cacheState = await this.lookupResourceWithoutHash(url);
return cacheState && cacheState.response;
}
/**

View File

@ -225,16 +225,21 @@ export class Driver implements Debuggable, UpdateSource {
// Initialization is the only event which is sent directly from the SW to itself,
// and thus `event.source` is not a Client. Handle it here, before the check
// for Client sources.
if (data.action === 'INITIALIZE' && this.initialized === null) {
// Initialize the SW.
this.initialized = this.initialize();
if (data.action === 'INITIALIZE') {
// Only initialize if not already initialized (or initializing).
if (this.initialized === null) {
// Initialize the SW.
this.initialized = this.initialize();
// Wait until initialization is properly scheduled, then trigger idle
// events to allow it to complete (assuming the SW is idle).
event.waitUntil((async() => {
await this.initialized;
await this.idle.trigger();
})());
// Wait until initialization is properly scheduled, then trigger idle
// events to allow it to complete (assuming the SW is idle).
event.waitUntil((async() => {
await this.initialized;
await this.idle.trigger();
})());
}
return;
}
// Only messages from true clients are accepted past this point (this is essentially
@ -725,16 +730,6 @@ export class Driver implements Debuggable, UpdateSource {
private async setupUpdate(manifest: Manifest, hash: string): Promise<void> {
const newVersion = new AppVersion(this.scope, this.adapter, this.db, this.idle, manifest, hash);
// Try to determine a version that's safe to update from.
let updateFrom: AppVersion|undefined = undefined;
// It's always safe to update from a version, even a broken one, as it will still
// only have valid resources cached. If there is no latest version, though, this
// update will have to install as a fresh version.
if (this.latestHash !== null) {
updateFrom = this.versions.get(this.latestHash);
}
// Firstly, check if the manifest version is correct.
if (manifest.configVersion !== SUPPORTED_CONFIG_VERSION) {
await this.deleteAllCaches();

View File

@ -24,6 +24,9 @@ const dist =
.addFile('/baz.txt', 'this is baz')
.addFile('/qux.txt', 'this is qux')
.addFile('/quux.txt', 'this is quux')
.addFile('/quuux.txt', 'this is quuux')
.addFile('/lazy/unchanged1.txt', 'this is unchanged (1)')
.addFile('/lazy/unchanged2.txt', 'this is unchanged (2)')
.addUnhashedFile('/unhashed/a.txt', 'this is unhashed', {'Cache-Control': 'max-age=10'})
.build();
@ -34,6 +37,9 @@ const distUpdate =
.addFile('/baz.txt', 'this is baz v2')
.addFile('/qux.txt', 'this is qux v2')
.addFile('/quux.txt', 'this is quux v2')
.addFile('/quuux.txt', 'this is quuux v2')
.addFile('/lazy/unchanged1.txt', 'this is unchanged (1)')
.addFile('/lazy/unchanged2.txt', 'this is unchanged (2)')
.addUnhashedFile('/unhashed/a.txt', 'this is unhashed v2', {'Cache-Control': 'max-age=10'})
.addUnhashedFile('/ignored/file1', 'this is not handled by the SW')
.addUnhashedFile('/ignored/dir/file2', 'this is not handled by the SW either')
@ -92,7 +98,12 @@ const manifest: Manifest = {
name: 'lazy_prefetch',
installMode: 'lazy',
updateMode: 'prefetch',
urls: ['/quux.txt'],
urls: [
'/quux.txt',
'/quuux.txt',
'/lazy/unchanged1.txt',
'/lazy/unchanged2.txt',
],
patterns: [],
}
],
@ -134,7 +145,12 @@ const manifestUpdate: Manifest = {
name: 'lazy_prefetch',
installMode: 'lazy',
updateMode: 'prefetch',
urls: ['/quux.txt'],
urls: [
'/quux.txt',
'/quuux.txt',
'/lazy/unchanged1.txt',
'/lazy/unchanged2.txt',
],
patterns: [],
}
],
@ -528,14 +544,45 @@ const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate));
async_it('prefetches updates to lazy cache when set', async() => {
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
await driver.initialized;
expect(await makeRequest(scope, '/quux.txt')).toEqual('this is quux');
// Fetch some files from the `lazy_prefetch` asset group.
expect(await makeRequest(scope, '/quux.txt')).toEqual('this is quux');
expect(await makeRequest(scope, '/lazy/unchanged1.txt')).toEqual('this is unchanged (1)');
// Install update.
scope.updateServerState(serverUpdate);
expect(await driver.checkForUpdate()).toEqual(true);
expect(await driver.checkForUpdate()).toBe(true);
// Previously requested and changed: Fetch from network.
serverUpdate.assertSawRequestFor('/quux.txt');
// Never requested and changed: Don't fetch.
serverUpdate.assertNoRequestFor('/quuux.txt');
// Previously requested and unchanged: Fetch from cache.
serverUpdate.assertNoRequestFor('/lazy/unchanged1.txt');
// Never requested and unchanged: Don't fetch.
serverUpdate.assertNoRequestFor('/lazy/unchanged2.txt');
serverUpdate.clearRequests();
// Update client.
await driver.updateClient(await scope.clients.get('default'));
expect(await makeRequest(scope, '/quux.txt')).toEqual('this is quux v2');
// Already cached.
expect(await makeRequest(scope, '/quux.txt')).toBe('this is quux v2');
serverUpdate.assertNoOtherRequests();
// Not cached: Fetch from network.
expect(await makeRequest(scope, '/quuux.txt')).toBe('this is quuux v2');
serverUpdate.assertSawRequestFor('/quuux.txt');
// Already cached (copied from old cache).
expect(await makeRequest(scope, '/lazy/unchanged1.txt')).toBe('this is unchanged (1)');
serverUpdate.assertNoOtherRequests();
// Not cached: Fetch from network.
expect(await makeRequest(scope, '/lazy/unchanged2.txt')).toBe('this is unchanged (2)');
serverUpdate.assertSawRequestFor('/lazy/unchanged2.txt');
serverUpdate.assertNoOtherRequests();
});

17
packages/types.d.ts vendored
View File

@ -18,4 +18,19 @@
/// <reference path="./goog.d.ts" />
declare let isNode: boolean;
declare let isBrowser: boolean;
declare let isBrowser: boolean;
declare namespace jasmine {
interface Matchers {
toHaveProperties(obj: any): boolean;
}
}
/**
*Jasmine matching utilities. These are added in the a more recent version of
*the Jasmine typedefs than what we are using:
*https://github.com/DefinitelyTyped/DefinitelyTyped/pull/20771
*/
declare namespace jasmine {
const matchersUtil: MatchersUtil;
}

View File

@ -444,7 +444,7 @@ function getNumberSettings(localeData) {
symbols.timeSeparator,
];
if (symbols.currencyDecimal) {
if (symbols.currencyDecimal || symbols.currencyGroup) {
symbolValues.push(symbols.currencyDecimal);
}

View File

@ -932,6 +932,7 @@ export declare enum ViewEncapsulation {
Emulated = 0,
Native = 1,
None = 2,
ShadowDom = 3,
}
export declare abstract class ViewRef extends ChangeDetectorRef {

View File

@ -114,6 +114,7 @@ export interface ExtraOptions {
enableTracing?: boolean;
errorHandler?: ErrorHandler;
initialNavigation?: InitialNavigation;
malformedUriErrorHandler?: (error: URIError, urlSerializer: UrlSerializer, url: string) => UrlTree;
onSameUrlNavigation?: 'reload' | 'ignore';
paramsInheritanceStrategy?: 'emptyOnly' | 'always';
preloadingStrategy?: any;
@ -311,6 +312,7 @@ export declare class Router {
config: Routes;
errorHandler: ErrorHandler;
readonly events: Observable<Event>;
malformedUriErrorHandler: (error: URIError, urlSerializer: UrlSerializer, url: string) => UrlTree;
navigated: boolean;
onSameUrlNavigation: 'reload' | 'ignore';
paramsInheritanceStrategy: 'emptyOnly' | 'always';

View File

@ -15,6 +15,7 @@ def js_expected_symbol_test(name, src, golden, **kwargs):
"""
all_data = [src, golden]
all_data += [Label("//tools/symbol-extractor:lib")]
all_data += [Label("@bazel_tools//tools/bash/runfiles")]
entry_point = "angular/tools/symbol-extractor/cli.js"
nodejs_test(

View File

@ -24,6 +24,7 @@ def ts_api_guardian_test(name, golden, actual, data = [], **kwargs):
data += [
"//tools/ts-api-guardian:lib",
"//tools/ts-api-guardian:bin/ts-api-guardian",
"@bazel_tools//tools/bash/runfiles",
]
args = [