Compare commits
50 Commits
Author | SHA1 | Date | |
---|---|---|---|
024aba075c | |||
a86d038875 | |||
bc8a93e842 | |||
4c18f637ae | |||
fc189b2577 | |||
778ddb257a | |||
2d4f4b5d12 | |||
912742f1ca | |||
0746485136 | |||
265489518e | |||
00dd566b47 | |||
59fd91d785 | |||
56712aa771 | |||
529e4b1565 | |||
9ff4617956 | |||
8887d75723 | |||
7717ff187a | |||
98b18cd49f | |||
fe23a6e77e | |||
d7c4898081 | |||
98c509fecb | |||
a92f111b66 | |||
de1c44f6e3 | |||
183b079175 | |||
8f8caa13b7 | |||
195dc0748b | |||
2c6f84b25d | |||
8e726f7d7b | |||
2d1102f5bf | |||
0437598609 | |||
bc2bf184a2 | |||
b51ce62c58 | |||
25b532e819 | |||
69c8226a9f | |||
5326537985 | |||
ada486a1dd | |||
563e8e3e56 | |||
dd931c73ec | |||
b8975a90ca | |||
f44161503a | |||
6c55a130b1 | |||
144a624088 | |||
f561f5a460 | |||
916914be13 | |||
9a98de941d | |||
0f1de35604 | |||
d89f57f9d5 | |||
ac5b69f783 | |||
5ddd6dcedd | |||
ad68332fa0 |
22
CHANGELOG.md
22
CHANGELOG.md
@ -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)
|
||||
|
||||
|
@ -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(
|
||||
|
@ -40,5 +40,7 @@ export class HighlightDirective {
|
||||
// #docregion color-2
|
||||
@Input() appHighlight: string;
|
||||
// #enddocregion color-2
|
||||
}
|
||||
|
||||
// #docregion
|
||||
}
|
||||
// #enddocregion
|
||||
|
@ -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 */
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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
|
||||
];
|
||||
|
@ -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' }
|
||||
];
|
||||
|
@ -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 -->
|
||||
|
@ -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>
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
|
@ -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—what it does to support the view—inside a class. The class interacts with the view through an API of properties and methods.
|
||||
You define a component's application logic—what it does to support the view—inside a class.
|
||||
The class interacts with the view through an API of properties and methods.
|
||||
|
||||
For example, the `HeroListComponent` has a `heroes` property that 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/>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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`.
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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}
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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`:
|
||||
|
||||
|
@ -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": {
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -9,8 +9,8 @@
|
||||
],
|
||||
"dependencies": [],
|
||||
"devDependencies": [
|
||||
"@angular/cli",
|
||||
"@angular-devkit/build-angular",
|
||||
"@angular/cli",
|
||||
"@types/jasminewd2",
|
||||
"jasmine-spec-reporter",
|
||||
"karma-coverage-istanbul-reporter",
|
||||
|
@ -11,8 +11,8 @@
|
||||
],
|
||||
"dependencies": [],
|
||||
"devDependencies": [
|
||||
"@angular/cli",
|
||||
"@angular-devkit/build-angular",
|
||||
"@angular/cli",
|
||||
"@types/jasminewd2",
|
||||
"jasmine-spec-reporter",
|
||||
"karma-coverage-istanbul-reporter",
|
||||
|
@ -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;
|
||||
|
@ -9,6 +9,7 @@
|
||||
],
|
||||
"dependencies": [],
|
||||
"devDependencies": [
|
||||
"@angular-devkit/build-angular",
|
||||
"@angular/cli",
|
||||
"@types/jasminewd2",
|
||||
"jasmine-marbles",
|
||||
|
@ -15,6 +15,7 @@
|
||||
"@nguniversal/module-map-ngfactory-loader"
|
||||
],
|
||||
"devDependencies": [
|
||||
"@angular-devkit/build-angular",
|
||||
"@angular/cli",
|
||||
"@types/jasminewd2",
|
||||
"jasmine-spec-reporter",
|
||||
|
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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 %}
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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",
|
||||
|
@ -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[]},
|
||||
|
@ -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$', '$'],
|
||||
|
@ -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$', '$'],
|
||||
|
@ -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) => {
|
||||
|
@ -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;
|
||||
|
||||
|
||||
|
@ -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>
|
||||
*
|
||||
*
|
||||
|
@ -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;');
|
||||
}));
|
||||
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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', () => {
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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) {
|
||||
|
@ -9,6 +9,7 @@ ts_library(
|
||||
"//packages:types",
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli",
|
||||
"//packages/compiler-cli/test:test_utils",
|
||||
"//packages/core",
|
||||
],
|
||||
)
|
||||
|
@ -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];
|
||||
`
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
/**
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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]}));
|
||||
|
@ -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 {
|
||||
|
35
packages/examples/core/ts/metadata/encapsulation.ts
Normal file
35
packages/examples/core/ts/metadata/encapsulation.ts
Normal 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
|
@ -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.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
@ -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;
|
||||
|
@ -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 }; }
|
||||
|
@ -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',
|
||||
|
@ -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++) {
|
||||
|
@ -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: `
|
||||
|
122
packages/platform-browser/test/dom/shadow_dom_spec.ts
Normal file
122
packages/platform-browser/test/dom/shadow_dom_spec.ts
Normal 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() {}
|
||||
}
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 that’s 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) => {
|
||||
|
@ -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[]) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
|
@ -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
17
packages/types.d.ts
vendored
@ -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;
|
||||
}
|
||||
|
@ -444,7 +444,7 @@ function getNumberSettings(localeData) {
|
||||
symbols.timeSeparator,
|
||||
];
|
||||
|
||||
if (symbols.currencyDecimal) {
|
||||
if (symbols.currencyDecimal || symbols.currencyGroup) {
|
||||
symbolValues.push(symbols.currencyDecimal);
|
||||
}
|
||||
|
||||
|
1
tools/public_api_guard/core/core.d.ts
vendored
1
tools/public_api_guard/core/core.d.ts
vendored
@ -932,6 +932,7 @@ export declare enum ViewEncapsulation {
|
||||
Emulated = 0,
|
||||
Native = 1,
|
||||
None = 2,
|
||||
ShadowDom = 3,
|
||||
}
|
||||
|
||||
export declare abstract class ViewRef extends ChangeDetectorRef {
|
||||
|
2
tools/public_api_guard/router/router.d.ts
vendored
2
tools/public_api_guard/router/router.d.ts
vendored
@ -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';
|
||||
|
@ -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(
|
||||
|
@ -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 = [
|
||||
|
Reference in New Issue
Block a user