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>
|
<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.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(
|
http_archive(
|
||||||
name = "build_bazel_rules_nodejs",
|
name = "build_bazel_rules_nodejs",
|
||||||
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.9.1.zip",
|
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.10.1.zip",
|
||||||
strip_prefix = "rules_nodejs-0.9.1",
|
strip_prefix = "rules_nodejs-0.10.1",
|
||||||
sha256 = "6139762b62b37c1fd171d7f22aa39566cb7dc2916f0f801d505a9aaf118c117f",
|
sha256 = "634206524d90dc03c52392fa3f19a16637d2bcf154910436fe1d669a0d9d7b9c",
|
||||||
)
|
)
|
||||||
|
|
||||||
http_archive(
|
http_archive(
|
||||||
|
@ -40,5 +40,7 @@ export class HighlightDirective {
|
|||||||
// #docregion color-2
|
// #docregion color-2
|
||||||
@Input() appHighlight: string;
|
@Input() appHighlight: string;
|
||||||
// #enddocregion color-2
|
// #enddocregion color-2
|
||||||
}
|
|
||||||
|
|
||||||
|
// #docregion
|
||||||
|
}
|
||||||
|
// #enddocregion
|
||||||
|
@ -4,7 +4,8 @@ button {
|
|||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
code, .code {
|
code,
|
||||||
|
.code {
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
color: black;
|
color: black;
|
||||||
font-family: Courier, sans-serif;
|
font-family: Courier, sans-serif;
|
||||||
@ -21,14 +22,18 @@ div.code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
margin: 40px 0
|
margin: 40px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
td, th {
|
td,
|
||||||
|
th {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* #docregion p-span */
|
/* #docregion p-span */
|
||||||
p span { color: red; font-size: 70%; }
|
p span {
|
||||||
|
color: red;
|
||||||
|
font-size: 70%;
|
||||||
|
}
|
||||||
/* #enddocregion p-span */
|
/* #enddocregion p-span */
|
||||||
|
@ -132,7 +132,7 @@
|
|||||||
<!-- #docregion select-span -->
|
<!-- #docregion select-span -->
|
||||||
<select [(ngModel)]="hero">
|
<select [(ngModel)]="hero">
|
||||||
<span *ngFor="let h of heroes">
|
<span *ngFor="let h of heroes">
|
||||||
<span *ngIf="showSad || h?.emotion != 'sad'">
|
<span *ngIf="showSad || h?.emotion !== 'sad'">
|
||||||
<option [ngValue]="h">{{h.name}} ({{h?.emotion}})</option>
|
<option [ngValue]="h">{{h.name}} ({{h?.emotion}})</option>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@ -147,7 +147,7 @@
|
|||||||
<!-- #docregion select-ngcontainer -->
|
<!-- #docregion select-ngcontainer -->
|
||||||
<select [(ngModel)]="hero">
|
<select [(ngModel)]="hero">
|
||||||
<ng-container *ngFor="let h of heroes">
|
<ng-container *ngFor="let h of heroes">
|
||||||
<ng-container *ngIf="showSad || h?.emotion != 'sad'">
|
<ng-container *ngIf="showSad || h?.emotion !== 'sad'">
|
||||||
<option [ngValue]="h">{{h.name}} ({{h?.emotion}})</option>
|
<option [ngValue]="h">{{h.name}} ({{h?.emotion}})</option>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -14,6 +14,7 @@ export class AppComponent {
|
|||||||
heroTraits = ['honest', 'brave', 'considerate'];
|
heroTraits = ['honest', 'brave', 'considerate'];
|
||||||
|
|
||||||
// flags for the table
|
// flags for the table
|
||||||
|
|
||||||
attrDirs = true;
|
attrDirs = true;
|
||||||
strucDirs = true;
|
strucDirs = true;
|
||||||
divNgIf = false;
|
divNgIf = false;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// #docregion
|
// #docregion
|
||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
|
|
||||||
import { Hero } from './hero';
|
import { Hero } from './hero';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -33,11 +34,15 @@ export class ConfusedHeroComponent {
|
|||||||
export class UnknownHeroComponent {
|
export class UnknownHeroComponent {
|
||||||
@Input() hero: Hero;
|
@Input() hero: Hero;
|
||||||
get message() {
|
get message() {
|
||||||
return this.hero && this.hero.name ?
|
return this.hero && this.hero.name
|
||||||
`${this.hero.name} is strange and mysterious.` :
|
? `${this.hero.name} is strange and mysterious.`
|
||||||
'Are you feeling indecisive?';
|
: 'Are you feeling indecisive?';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const heroComponents =
|
export const heroComponents = [
|
||||||
[ HappyHeroComponent, SadHeroComponent, ConfusedHeroComponent, UnknownHeroComponent ];
|
HappyHeroComponent,
|
||||||
|
SadHeroComponent,
|
||||||
|
ConfusedHeroComponent,
|
||||||
|
UnknownHeroComponent
|
||||||
|
];
|
||||||
|
@ -40,7 +40,7 @@ export class HeroService {
|
|||||||
// #enddocregion getHeroes-1
|
// #enddocregion getHeroes-1
|
||||||
.pipe(
|
.pipe(
|
||||||
// #enddocregion getHeroes-2
|
// #enddocregion getHeroes-2
|
||||||
tap(heroes => this.log(`fetched heroes`)),
|
tap(heroes => this.log('fetched heroes')),
|
||||||
// #docregion getHeroes-2
|
// #docregion getHeroes-2
|
||||||
catchError(this.handleError('getHeroes', []))
|
catchError(this.handleError('getHeroes', []))
|
||||||
);
|
);
|
||||||
@ -151,7 +151,7 @@ export class HeroService {
|
|||||||
// #docregion log
|
// #docregion log
|
||||||
/** Log a HeroService message with the MessageService */
|
/** Log a HeroService message with the MessageService */
|
||||||
private log(message: string) {
|
private log(message: string) {
|
||||||
this.messageService.add('HeroService: ' + message);
|
this.messageService.add(`HeroService: ${message}`);
|
||||||
}
|
}
|
||||||
// #enddocregion log
|
// #enddocregion log
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ export class HeroService {
|
|||||||
getHeroes (): Observable<Hero[]> {
|
getHeroes (): Observable<Hero[]> {
|
||||||
return this.http.get<Hero[]>(this.heroesUrl)
|
return this.http.get<Hero[]>(this.heroesUrl)
|
||||||
.pipe(
|
.pipe(
|
||||||
tap(heroes => this.log(`fetched heroes`)),
|
tap(heroes => this.log('fetched heroes')),
|
||||||
catchError(this.handleError('getHeroes', []))
|
catchError(this.handleError('getHeroes', []))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -123,6 +123,6 @@ export class HeroService {
|
|||||||
|
|
||||||
/** Log a HeroService message with the MessageService */
|
/** Log a HeroService message with the MessageService */
|
||||||
private log(message: string) {
|
private log(message: string) {
|
||||||
this.messageService.add('HeroService: ' + message);
|
this.messageService.add(`HeroService: ${message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,10 @@ A _component_ controls a patch of screen called a *view*. For example, individua
|
|||||||
* The list of heroes.
|
* The list of heroes.
|
||||||
* The hero editor.
|
* The hero editor.
|
||||||
|
|
||||||
You define a component's application logic—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>
|
<code-example path="architecture/src/app/hero-list.component.ts" linenums="false" title="src/app/hero-list.component.ts (class)" region="class"></code-example>
|
||||||
|
|
||||||
@ -39,8 +40,7 @@ Angular inserts an instance of the `HeroListComponent` view between those tags.
|
|||||||
|
|
||||||
* `templateUrl`: The module-relative address of this component's HTML template. Alternatively, you can provide the HTML template inline, as the value of the `template` property. This template defines the component's _host view_.
|
* `templateUrl`: The module-relative address of this component's HTML template. Alternatively, you can provide the HTML template inline, as the value of the `template` property. This template defines the component's _host view_.
|
||||||
|
|
||||||
* `providers`: An array of **dependency injection providers** for services that the component requires. In the example, this tells Angular that the component's constructor requires a `HeroService` instance
|
* `providers`: An array of **dependency injection providers** for services that the component requires. In the example, this tells Angular how provide the `HeroService` instance that the component's constructor uses to get the list of heroes to display.
|
||||||
in order to get the list of heroes to display.
|
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
|
@ -88,7 +88,8 @@ For example, import Angular's `Component` decorator from the `@angular/core` lib
|
|||||||
|
|
||||||
<code-example path="architecture/src/app/app.component.ts" region="import" linenums="false"></code-example>
|
<code-example path="architecture/src/app/app.component.ts" region="import" linenums="false"></code-example>
|
||||||
|
|
||||||
You also import NgModules from Angular _libraries_ using JavaScript import statements:
|
You also import NgModules from Angular _libraries_ using JavaScript import statements.
|
||||||
|
For example, the following code imports the `BrowserModule` NgModule from the `platform-browser` library:
|
||||||
|
|
||||||
<code-example path="architecture/src/app/mini-app.ts" region="import-browser-module" linenums="false"></code-example>
|
<code-example path="architecture/src/app/mini-app.ts" region="import-browser-module" linenums="false"></code-example>
|
||||||
|
|
||||||
|
@ -60,11 +60,29 @@ The process of `HeroService` injection looks something like this:
|
|||||||
|
|
||||||
### Providing services
|
### Providing services
|
||||||
|
|
||||||
You must register at least one *provider* of any service you are going to use. You can register providers in modules or in components.
|
You must register at least one *provider* of any service you are going to use. A service can register providers itself, making it available everywhere, or you can register providers with specific modules or components. You register providers in the metadata of the service (in the `@Injectable` decorator), or in the `@NgModule` or `@Component` metadata
|
||||||
|
|
||||||
* When you add providers to the [root module](guide/architecture-modules), the same instance of a service is available to all components in your app.
|
* By default, the Angular CLI command `ng generate service` registers a provider with the root injector for your service by including provider metadata in the `@Injectable` decorator. The tutorial uses this method to register the provider of HeroService class definition:
|
||||||
|
|
||||||
<code-example path="architecture/src/app/app.module.ts" linenums="false" title="src/app/app.module.ts (module providers)" region="providers"></code-example>
|
```
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
When you provide the service at the root level, Angular creates a single, shared instance of HeroService and injects into any class that asks for it. Registering the provider in the `@Injectable` metadata also allows Angular to optimize an app by removing the service if it turns out not to be used after all.
|
||||||
|
|
||||||
|
* When you register a provider with a [specific NgModule](guide/architecture-modules), the same instance of a service is available to all components in that NgModule. To register at this level, use the `providers` property of the `@NgModule` decorator:
|
||||||
|
|
||||||
|
```
|
||||||
|
@NgModule({
|
||||||
|
providers: [
|
||||||
|
BackendService,
|
||||||
|
Logger
|
||||||
|
],
|
||||||
|
...
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
* When you register a provider at the component level, you get a new instance of the
|
* When you register a provider at the component level, you get a new instance of the
|
||||||
service with each new instance of that component. At the component level, register a service provider in the `providers` property of the `@Component` metadata:
|
service with each new instance of that component. At the component level, register a service provider in the `providers` property of the `@Component` metadata:
|
||||||
|
@ -164,7 +164,7 @@ They are _not inherited_ by any components nested within the template nor by any
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
The CLI defines an empty `styles` array when you create the component with the `--inline-styles` flag.
|
The CLI defines an empty `styles` array when you create the component with the `--inline-style` flag.
|
||||||
|
|
||||||
<code-example language="sh" class="code-shell">
|
<code-example language="sh" class="code-shell">
|
||||||
ng generate component hero-app --inline-style
|
ng generate component hero-app --inline-style
|
||||||
@ -189,7 +189,7 @@ They are _not inherited_ by any components nested within the template nor by any
|
|||||||
|
|
||||||
<div class="l-sub-section">
|
<div class="l-sub-section">
|
||||||
|
|
||||||
You can specify more than one styles file or even a combination of `style` and `styleUrls`.
|
You can specify more than one styles file or even a combination of `styles` and `styleUrls`.
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -280,12 +280,14 @@ To control how this encapsulation happens on a *per
|
|||||||
component* basis, you can set the *view encapsulation mode* in the component metadata.
|
component* basis, you can set the *view encapsulation mode* in the component metadata.
|
||||||
Choose from the following modes:
|
Choose from the following modes:
|
||||||
|
|
||||||
* `Native` view encapsulation uses the browser's native shadow DOM implementation (see
|
* `ShadowDom` view encapsulation uses the browser's native shadow DOM implementation (see
|
||||||
[Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM)
|
[Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM)
|
||||||
on the [MDN](https://developer.mozilla.org) site)
|
on the [MDN](https://developer.mozilla.org) site)
|
||||||
to attach a shadow DOM to the component's host element, and then puts the component
|
to attach a shadow DOM to the component's host element, and then puts the component
|
||||||
view inside that shadow DOM. The component's styles are included within the shadow DOM.
|
view inside that shadow DOM. The component's styles are included within the shadow DOM.
|
||||||
|
|
||||||
|
* `Native` view encapsulation uses a now deprecated version of the browser's native shadow DOM implementation - [learn about the changes](https://hayato.io/2016/shadowdomv1/).
|
||||||
|
|
||||||
* `Emulated` view encapsulation (the default) emulates the behavior of shadow DOM by preprocessing
|
* `Emulated` view encapsulation (the default) emulates the behavior of shadow DOM by preprocessing
|
||||||
(and renaming) the CSS code to effectively scope the CSS to the component's view.
|
(and renaming) the CSS code to effectively scope the CSS to the component's view.
|
||||||
For details, see [Appendix 1](guide/component-styles#inspect-generated-css).
|
For details, see [Appendix 1](guide/component-styles#inspect-generated-css).
|
||||||
@ -300,8 +302,8 @@ To set the components encapsulation mode, use the `encapsulation` property in th
|
|||||||
<code-example path="component-styles/src/app/quest-summary.component.ts" region="encapsulation.native" title="src/app/quest-summary.component.ts" linenums="false">
|
<code-example path="component-styles/src/app/quest-summary.component.ts" region="encapsulation.native" title="src/app/quest-summary.component.ts" linenums="false">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
`Native` view encapsulation only works on browsers that have native support
|
`ShadowDom` view encapsulation only works on browsers that have native support
|
||||||
for shadow DOM (see [Shadow DOM v0](http://caniuse.com/#feat=shadowdom) on the
|
for shadow DOM (see [Shadow DOM v1](https://caniuse.com/#feat=shadowdomv1) on the
|
||||||
[Can I use](http://caniuse.com) site). The support is still limited,
|
[Can I use](http://caniuse.com) site). The support is still limited,
|
||||||
which is why `Emulated` view encapsulation is the default mode and recommended
|
which is why `Emulated` view encapsulation is the default mode and recommended
|
||||||
in most cases.
|
in most cases.
|
||||||
|
@ -152,7 +152,8 @@ nothing to distinguish it from any component you've written before.
|
|||||||
Understanding this component requires only the Angular concepts covered in previous pages.
|
Understanding this component requires only the Angular concepts covered in previous pages.
|
||||||
|
|
||||||
* The code imports the Angular core library and the `Hero` model you just created.
|
* The code imports the Angular core library and the `Hero` model you just created.
|
||||||
* The `@Component` selector value of "hero-form" means you can drop this form in a parent template with a `<hero-form>` tag.
|
* The `@Component` selector value of "app-hero-form" means you can drop this form in a parent
|
||||||
|
template with a `<app-hero-form>` tag.
|
||||||
* The `templateUrl` property points to a separate file for the template HTML.
|
* The `templateUrl` property points to a separate file for the template HTML.
|
||||||
* You defined dummy data for `model` and `powers`, as befits a demo.
|
* You defined dummy data for `model` and `powers`, as befits a demo.
|
||||||
|
|
||||||
|
@ -114,19 +114,23 @@ The following class types can be declared:
|
|||||||
|
|
||||||
A [decorator](guide/glossary#decorator) statement immediately before a field in a class definition that declares the type of that field. Some examples are `@Input` and `@Output`.
|
A [decorator](guide/glossary#decorator) statement immediately before a field in a class definition that declares the type of that field. Some examples are `@Input` and `@Output`.
|
||||||
|
|
||||||
|
{@a cli}
|
||||||
|
|
||||||
## CLI
|
## CLI
|
||||||
|
|
||||||
The [Angular CLI](https://cli.angular.io/) is a command-line tool that can create a project, add files, and perform a variety of ongoing development tasks such as testing, bundling, and deployment.
|
The [Angular CLI](https://cli.angular.io/) is a command-line tool for managing the Angular development cycle. Use it to create the initial filesystem scaffolding for a [workspace](guide/glossary#workspace) or [project](guide/glossary#project), and to run [schematics](guide/glossary#schematic) that add and modify code for initial generic versions of various elements. The tool supports all stages of the development cycle, including building, testing, bundling, and deployment.
|
||||||
|
|
||||||
Learn more in the [Getting Started](guide/quickstart) guide.
|
* To begin using the CLI for a new project, see [Getting Started](guide/quickstart) guide.
|
||||||
|
* To learn more about the full capabilities of the CLI, see the [Angular CLI documentation].(https://github.com/angular/angular-cli/wiki).
|
||||||
|
|
||||||
{@a component}
|
{@a component}
|
||||||
|
|
||||||
## Component
|
## Component
|
||||||
|
|
||||||
A class with the `@Component` [decorator](guide/glossary#decorator) that associates it with a companion [template](guide/glossary#template).
|
A class with the `@Component` [decorator](guide/glossary#decorator) that associates it with a companion [template](guide/glossary#template). Together, the component and template define a [view](guide/glossary#view).
|
||||||
|
|
||||||
A component is a special type of [directive](guide/glossary#directive) that represents a [view](guide/glossary#view).The `@Component` decorator extends the `@Directive` decorator with template-oriented features.
|
A component is a special type of [directive](guide/glossary#directive).
|
||||||
|
The `@Component` decorator extends the `@Directive` decorator with template-oriented features.
|
||||||
|
|
||||||
An Angular component class is responsible for exposing data and handling most of the view's display and user-interaction logic through [data binding](guide/glossary#data-binding).
|
An Angular component class is responsible for exposing data and handling most of the view's display and user-interaction logic through [data binding](guide/glossary#data-binding).
|
||||||
|
|
||||||
@ -208,7 +212,8 @@ See [Class decorator](guide/glossary#class-decorator), [Class field decorator](g
|
|||||||
|
|
||||||
A design pattern and mechanism for creating and delivering parts of an application (dependencies) to other parts of an application that require them.
|
A design pattern and mechanism for creating and delivering parts of an application (dependencies) to other parts of an application that require them.
|
||||||
|
|
||||||
In Angular, dependencies are typically services, but can also be values, such as strings or functions. An [injector](guide/glossary#injector) for an app (created automatically during bootstrap) creates dependencies when needed, using a registered [provider](guide/glossary#provider) of the service or value. Different providers can provide different implementations of the same service.
|
In Angular, dependencies are typically services, but can also be values, such as strings or functions.
|
||||||
|
An [injector](guide/glossary#injector) for an app (created automatically during bootstrap) instantiates dependencies when needed, using a configured [provider](guide/glossary#provider) of the service or value.
|
||||||
|
|
||||||
Learn more in the [Dependency Injection](guide/dependency-injection) guide.
|
Learn more in the [Dependency Injection](guide/dependency-injection) guide.
|
||||||
|
|
||||||
@ -280,7 +285,7 @@ Compare [Custom element](guide/glossary#custom-element).
|
|||||||
|
|
||||||
## Entry point
|
## Entry point
|
||||||
|
|
||||||
A JavaScript ID that makes parts of an NPM package available for import by other code.
|
A JavaScript symbol that makes parts of an npm package available for import by other code.
|
||||||
The Angular [scoped packages](guide/glossary#scoped-package) each have an entry point named `index`.
|
The Angular [scoped packages](guide/glossary#scoped-package) each have an entry point named `index`.
|
||||||
|
|
||||||
Within Angular, use [NgModules](guide/glossary#ngmodule) to achieve the same result.
|
Within Angular, use [NgModules](guide/glossary#ngmodule) to achieve the same result.
|
||||||
@ -310,7 +315,17 @@ Both a [service](guide/glossary#service) and a [component](guide/glossary#compon
|
|||||||
|
|
||||||
An object in the Angular [dependency-injection system](guide/glossary#dependency-injection)
|
An object in the Angular [dependency-injection system](guide/glossary#dependency-injection)
|
||||||
that can find a named dependency in its cache or create a dependency
|
that can find a named dependency in its cache or create a dependency
|
||||||
with a registered [provider](guide/glossary#provider). Injectors are created for NgModules automatically as part of the bootstrap process, and inherited through the component hierarchy.
|
using a configured [provider](guide/glossary#provider).
|
||||||
|
Injectors are created for NgModules automatically as part of the bootstrap process
|
||||||
|
and are inherited through the component hierarchy.
|
||||||
|
|
||||||
|
* An injector provides a singleton instance of a dependency, and can inject this same instance in multiple components.
|
||||||
|
|
||||||
|
* A hierarchy of injectors at the NgModule and component level can provide different instances of a dependency to their own components and child components.
|
||||||
|
|
||||||
|
* You can configure injectors with different providers that can provide different implementations of the same dependency.
|
||||||
|
|
||||||
|
Learn more about the injector hierarchy in the [Dependency Injection guide](guide/hierarchical-dependency-injection).
|
||||||
|
|
||||||
|
|
||||||
## Input
|
## Input
|
||||||
@ -373,6 +388,17 @@ Lazy loading speeds up application load time by splitting the application into m
|
|||||||
For example, dependencies can be lazy-loaded as needed&emdash;as opposed to "eager-loaded" modules that are required by the root module, and are thus loaded on launch.
|
For example, dependencies can be lazy-loaded as needed&emdash;as opposed to "eager-loaded" modules that are required by the root module, and are thus loaded on launch.
|
||||||
Similarly, the [router](guide/glossary#router) can load child views only when the parent view is activated, and you can build custom elements that can be loaded into an Angular app when needed.
|
Similarly, the [router](guide/glossary#router) can load child views only when the parent view is activated, and you can build custom elements that can be loaded into an Angular app when needed.
|
||||||
|
|
||||||
|
{@a library}
|
||||||
|
|
||||||
|
## Library
|
||||||
|
|
||||||
|
In Angular, a [project](guide/glossary#project) that provides functionality that can be included in other Angular apps. A library is not a complete Angular app, and it cannot run independently.
|
||||||
|
|
||||||
|
* Library developers can use the [CLI](guide/glossary#cli) to `generate` scaffolding for a new library in an existing [workspace](guide/glossary#workspace), and can publish a library as an `npm` package.
|
||||||
|
|
||||||
|
* App developers can use the [CLI](guide/glossary#cli) to `add` a published library for use with an app in the same [workspace](guide/glossary#workspace).
|
||||||
|
|
||||||
|
|
||||||
## Lifecycle hook
|
## Lifecycle hook
|
||||||
|
|
||||||
An interface that allows you to tap into the lifecycle of [directives](guide/glossary#directive) and [components](guide/glossary#component) as they are created, updated, and destroyed.
|
An interface that allows you to tap into the lifecycle of [directives](guide/glossary#directive) and [components](guide/glossary#component) as they are created, updated, and destroyed.
|
||||||
@ -402,7 +428,7 @@ In general, a module collects a block of code dedicated to a single purpose. Ang
|
|||||||
|
|
||||||
In JavaScript (ECMAScript), each file is a module and all objects defined in the file belong to that module. Objects can exported, making them public, and public objects can be imported for use by other modules.
|
In JavaScript (ECMAScript), each file is a module and all objects defined in the file belong to that module. Objects can exported, making them public, and public objects can be imported for use by other modules.
|
||||||
|
|
||||||
Angular ships as a collection of JavaScript modules, or libraries. Each Angular library name begins with the `@angular` prefix. Install them with the NPM package manager and import parts of them with JavaScript `import` declarations.
|
Angular ships as a collection of JavaScript modules, or libraries. Each Angular library name begins with the `@angular` prefix. Install them with the npm package manager and import parts of them with JavaScript `import` declarations.
|
||||||
|
|
||||||
Compare the Angular [NgModule](guide/glossary#ngmodule).
|
Compare the Angular [NgModule](guide/glossary#ngmodule).
|
||||||
|
|
||||||
@ -470,8 +496,13 @@ To learn more, see the [pipes](guide/pipes) page.
|
|||||||
|
|
||||||
## Polyfill
|
## Polyfill
|
||||||
|
|
||||||
An [NPM package](guide/npm-packages) that plugs gaps in a browser's JavaScript implementation. See the [Browser Support](guide/browser-support) guide for polyfills that support particular functionality for particular platforms.
|
An [npm package](guide/npm-packages) that plugs gaps in a browser's JavaScript implementation. See the [Browser Support](guide/browser-support) guide for polyfills that support particular functionality for particular platforms.
|
||||||
|
|
||||||
|
{@a project}
|
||||||
|
|
||||||
|
## Project
|
||||||
|
|
||||||
|
In Angular, a folder within a [workspace](guide/glossary#workspace) that contains an Angular app or [library](guide/glossary#library). A workspace can contain multiple projects. All apps in a workspace can use libraries in the same workspace.
|
||||||
|
|
||||||
## Provider
|
## Provider
|
||||||
|
|
||||||
@ -531,9 +562,24 @@ For more information, see the [Routing & Navigation](guide/router) page.
|
|||||||
|
|
||||||
{@a S}
|
{@a S}
|
||||||
|
|
||||||
|
{@a schematic}
|
||||||
|
|
||||||
|
## Schematic
|
||||||
|
|
||||||
|
A scaffolding library that defines how to generate or transform a programming project by creating, modifying, refactoring, or moving files and code.
|
||||||
|
|
||||||
|
The Angular [CLI](guide/glossary#cli) uses schematics to generate and modify [Angular projects](guide/glossary#project) and parts of projects.
|
||||||
|
|
||||||
|
* Angular provides a set of schematics for use with the CLI.
|
||||||
|
For details, see [Angular CLI documentation].(https://github.com/angular/angular-cli/wiki).
|
||||||
|
|
||||||
|
* Library developers can create schematics that enable the CLI to generate their published libraries.
|
||||||
|
For more information, see https://www.npmjs.com/package/@angular-devkit/schematics.
|
||||||
|
|
||||||
|
|
||||||
## Scoped package
|
## Scoped package
|
||||||
|
|
||||||
A way to group related NPM packages.
|
A way to group related npm packages.
|
||||||
NgModules are delivered within *scoped packages* whose names begin with the Angular *scope name* `@angular`. For example, `@angular/core`, `@angular/common`, `@angular/http`, and `@angular/router`.
|
NgModules are delivered within *scoped packages* whose names begin with the Angular *scope name* `@angular`. For example, `@angular/core`, `@angular/common`, `@angular/http`, and `@angular/router`.
|
||||||
|
|
||||||
Import a scoped package in the same way that you import a normal package.
|
Import a scoped package in the same way that you import a normal package.
|
||||||
@ -677,6 +723,12 @@ The view hierarchy does not imply a component hierarchy. Views that are embedded
|
|||||||
|
|
||||||
See [Custom element](guide/glossary#custom-element)
|
See [Custom element](guide/glossary#custom-element)
|
||||||
|
|
||||||
|
{@a workspace}
|
||||||
|
|
||||||
|
## Workspace
|
||||||
|
|
||||||
|
In Angular, a folder that contains [projects](guide/glossary#project) (that is, apps and libraries).
|
||||||
|
The [CLI](guide/glossary#cli) `new` command creates a workspace to contain projects. Commands such as `add` and `generate`, that create or operate on apps and libraries, must be executed from within a workspace folder.
|
||||||
|
|
||||||
{@a X}
|
{@a X}
|
||||||
|
|
||||||
|
@ -1034,7 +1034,7 @@ Call `request.flush()` with an error message, as seen in the following example.
|
|||||||
|
|
||||||
<code-example
|
<code-example
|
||||||
path="http/src/testing/http-client.spec.ts"
|
path="http/src/testing/http-client.spec.ts"
|
||||||
region="network-error"
|
region="404"
|
||||||
linenums="false">
|
linenums="false">
|
||||||
</code-example>
|
</code-example>
|
||||||
|
|
||||||
|
@ -219,7 +219,7 @@ configure services in root and feature modules respectively.
|
|||||||
|
|
||||||
Angular doesn't recognize these names but Angular developers do.
|
Angular doesn't recognize these names but Angular developers do.
|
||||||
Follow this convention when you write similar modules with configurable service providers.
|
Follow this convention when you write similar modules with configurable service providers.
|
||||||
<!--KW--I don't understand how Angular doesn't understand these methods...-->
|
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
@ -233,9 +233,8 @@ When you import an NgModule,
|
|||||||
Angular adds the module's service providers (the contents of its `providers` list)
|
Angular adds the module's service providers (the contents of its `providers` list)
|
||||||
to the application root injector.
|
to the application root injector.
|
||||||
|
|
||||||
This makes the provider visible to every class in the application that knows the provider's lookup token, or knows its name.
|
This makes the provider visible to every class in the application that knows the provider's lookup token, or name.
|
||||||
|
|
||||||
This is by design.
|
|
||||||
Extensibility through NgModule imports is a primary goal of the NgModule system.
|
Extensibility through NgModule imports is a primary goal of the NgModule system.
|
||||||
Merging NgModule providers into the application injector
|
Merging NgModule providers into the application injector
|
||||||
makes it easy for a module library to enrich the entire application with new services.
|
makes it easy for a module library to enrich the entire application with new services.
|
||||||
@ -247,6 +246,8 @@ If the `HeroModule` provides the `HeroService` and the root `AppModule` imports
|
|||||||
any class that knows the `HeroService` _type_ can inject that service,
|
any class that knows the `HeroService` _type_ can inject that service,
|
||||||
not just the classes declared in the `HeroModule`.
|
not just the classes declared in the `HeroModule`.
|
||||||
|
|
||||||
|
To limit access to a service, consider lazy loading the NgModule that provides that service. See [How do I restrict service scope to a module?](guide/ngmodule-faq#service-scope) for more information.
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
{@a q-lazy-loaded-module-provider-visibility}
|
{@a q-lazy-loaded-module-provider-visibility}
|
||||||
@ -288,6 +289,7 @@ The `AppModule` always wins.
|
|||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
|
{@a service-scope}
|
||||||
|
|
||||||
## How do I restrict service scope to a module?
|
## How do I restrict service scope to a module?
|
||||||
|
|
||||||
@ -335,6 +337,8 @@ You can embed the child components in the top component's template.
|
|||||||
Alternatively, make the top component a routing host by giving it a `<router-outlet>`.
|
Alternatively, make the top component a routing host by giving it a `<router-outlet>`.
|
||||||
Define child routes and let the router load module components into that outlet.
|
Define child routes and let the router load module components into that outlet.
|
||||||
|
|
||||||
|
Though you can limit access to a service by providing it in a lazy loaded module or providing it in a component, providing services in a component can lead to multiple instances of those services. Thus, the lazy loading is preferable.
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
{@a q-root-component-or-module}
|
{@a q-root-component-or-module}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
Every application starts out with what seems like a simple task: get data, transform them, and show them to users.
|
Every application starts out with what seems like a simple task: get data, transform them, and show them to users.
|
||||||
Getting data could be as simple as creating a local variable or as complex as streaming data over a WebSocket.
|
Getting data could be as simple as creating a local variable or as complex as streaming data over a WebSocket.
|
||||||
|
|
||||||
Once data arrive, you could push their raw `toString` values directly to the view,
|
Once data arrives, you could push their raw `toString` values directly to the view,
|
||||||
but that rarely makes for a good user experience.
|
but that rarely makes for a good user experience.
|
||||||
For example, in most use cases, users prefer to see a date in a simple format like
|
For example, in most use cases, users prefer to see a date in a simple format like
|
||||||
<samp>April 15, 1988</samp> rather than the raw string format
|
<samp>April 15, 1988</samp> rather than the raw string format
|
||||||
|
@ -241,7 +241,7 @@ even when the Universal web server is capable of handling those requests.
|
|||||||
You'll have to change the services to make requests with absolute URLs when running on the server
|
You'll have to change the services to make requests with absolute URLs when running on the server
|
||||||
and with relative URLs when running in the browser.
|
and with relative URLs when running in the browser.
|
||||||
|
|
||||||
One solution is to provide the server's runtime origin under the Angular [`APP_BASE_REF` token](api/common/APP_BASE_HREF),
|
One solution is to provide the server's runtime origin under the Angular [`APP_BASE_HREF` token](api/common/APP_BASE_HREF),
|
||||||
inject it into the service, and prepend the origin to the request URL.
|
inject it into the service, and prepend the origin to the request URL.
|
||||||
|
|
||||||
Start by changing the `HeroService` constructor to take a second `origin` parameter that is optionally injected via the `APP_BASE_HREF` token.
|
Start by changing the `HeroService` constructor to take a second `origin` parameter that is optionally injected via the `APP_BASE_HREF` token.
|
||||||
|
@ -360,7 +360,7 @@
|
|||||||
"picture": "jorgeucano.jpg",
|
"picture": "jorgeucano.jpg",
|
||||||
"twitter": "jorgeucano",
|
"twitter": "jorgeucano",
|
||||||
"website": "https://medium.com/@jorgeucano",
|
"website": "https://medium.com/@jorgeucano",
|
||||||
"bio": "Jorge is a Fulll Stack Developer in ByteDefault ... Professor in several courses related to javascript , speaker, and writer of technical articles and a book ‘Entendiendo Angular’, Google Developer Expert in web technologies nominate by Google, Nativescript Developer Expert nominated by Telerik.",
|
"bio": "Jorge is a Full Stack Developer in ByteDefault, a professor for several courses related to JavaScript, a speaker, and an author of technical articles and the book \"Entendiendo Angular.\" He is a Google Developer Expert in web technologies (nominated by Google) and a NativeScript Developer Expert (nominated by Telerik).",
|
||||||
"group": "GDE"
|
"group": "GDE"
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -378,8 +378,8 @@
|
|||||||
"picture": "michaelprentice.jpg",
|
"picture": "michaelprentice.jpg",
|
||||||
"twitter": "splaktar",
|
"twitter": "splaktar",
|
||||||
"website": "https://www.DevIntent.com",
|
"website": "https://www.DevIntent.com",
|
||||||
"bio": "Owner and consultant at DevIntent. Active open-source contributor and leader. Passionate advocate, coach, and consultant for LEAN and Agile teams. Google Developer Expert (GDE) in Angular. Founder and organizer for the Google Developers Group (GDG) community on the Space Coast of Florida, USA.",
|
"bio": "Lead for AngularJS Material. Owner and consultant at DevIntent. Ex-Angular GDE. Founder of the Google Developers Group (GDG) community on the Space Coast of Florida, USA.",
|
||||||
"group": "GDE"
|
"group": "Angular"
|
||||||
},
|
},
|
||||||
|
|
||||||
"mikebrocchi": {
|
"mikebrocchi": {
|
||||||
|
@ -612,6 +612,12 @@
|
|||||||
"title": "Ultimate Angular",
|
"title": "Ultimate Angular",
|
||||||
"url": "https://ultimateangular.com/"
|
"url": "https://ultimateangular.com/"
|
||||||
},
|
},
|
||||||
|
"willh-angular-zero": {
|
||||||
|
"desc": "Online video course in Chinese for newbies who need to learning from the scratch in Chinese. It's covering Angular, Angular CLI, TypeScript, VSCode, and some must known knowledge of Angular development.",
|
||||||
|
"rev": true,
|
||||||
|
"title": "Angular in Action: Start From Scratch (正體中文)",
|
||||||
|
"url": "https://www.udemy.com/angular-zero/?couponCode=ANGULAR.IO"
|
||||||
|
},
|
||||||
"angular-firebase": {
|
"angular-firebase": {
|
||||||
"desc": "Video lessons covering progressive web apps with Angular, Firebase, RxJS, and related APIs.",
|
"desc": "Video lessons covering progressive web apps with Angular, Firebase, RxJS, and related APIs.",
|
||||||
"rev": true,
|
"rev": true,
|
||||||
@ -666,8 +672,8 @@
|
|||||||
"url": "http://ninja-squad.com/formations/formation-angular2"
|
"url": "http://ninja-squad.com/formations/formation-angular2"
|
||||||
},
|
},
|
||||||
"a2b": {
|
"a2b": {
|
||||||
"desc": "Angular Boot Camp covers introductory and intermediate content. It includes extensive workshop session, with hands-on help from our experienced developer-trainers. At the end of this class, student are usually able to use AngularJS to make an end-to-end, working application.",
|
"desc": "Angular Boot Camp covers introductory through advanced Angular topics. It includes extensive workshop sessions, with hands-on help from our experienced developer-trainers. We take developers or teams from the beginnings of Angular understanding through a working knowledge of all essential Angular features.",
|
||||||
"logo": "",
|
"logo": "https://angularbootcamp.com/images/angular-boot-camp-logo.svg",
|
||||||
"rev": true,
|
"rev": true,
|
||||||
"title": "Angular Boot Camp",
|
"title": "Angular Boot Camp",
|
||||||
"url": "https://angularbootcamp.com"
|
"url": "https://angularbootcamp.com"
|
||||||
|
@ -142,6 +142,9 @@ Here are the code files discussed on this page and your app should look like thi
|
|||||||
<code-pane title="src/app/heroes/heroes.component.html" path="toh-pt3/src/app/heroes/heroes.component.html">
|
<code-pane title="src/app/heroes/heroes.component.html" path="toh-pt3/src/app/heroes/heroes.component.html">
|
||||||
</code-pane>
|
</code-pane>
|
||||||
|
|
||||||
|
<code-pane title="src/app/app.module.ts" path="toh-pt3/src/app/app.module.ts">
|
||||||
|
</code-pane>
|
||||||
|
|
||||||
</code-tabs>
|
</code-tabs>
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
@ -9,8 +9,8 @@
|
|||||||
],
|
],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"devDependencies": [
|
"devDependencies": [
|
||||||
"@angular/cli",
|
|
||||||
"@angular-devkit/build-angular",
|
"@angular-devkit/build-angular",
|
||||||
|
"@angular/cli",
|
||||||
"@types/jasminewd2",
|
"@types/jasminewd2",
|
||||||
"jasmine-spec-reporter",
|
"jasmine-spec-reporter",
|
||||||
"karma-coverage-istanbul-reporter",
|
"karma-coverage-istanbul-reporter",
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
],
|
],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"devDependencies": [
|
"devDependencies": [
|
||||||
"@angular/cli",
|
|
||||||
"@angular-devkit/build-angular",
|
"@angular-devkit/build-angular",
|
||||||
|
"@angular/cli",
|
||||||
"@types/jasminewd2",
|
"@types/jasminewd2",
|
||||||
"jasmine-spec-reporter",
|
"jasmine-spec-reporter",
|
||||||
"karma-coverage-istanbul-reporter",
|
"karma-coverage-istanbul-reporter",
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const path = require('canonical-path');
|
const path = require('canonical-path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
const examplesPath = path.resolve(__dirname, '../../../examples');
|
const examplesPath = path.resolve(__dirname, '../../../examples');
|
||||||
const packageFolder = path.resolve(__dirname);
|
const packageFolder = path.resolve(__dirname);
|
||||||
|
|
||||||
class PackageJsonCustomizer {
|
class PackageJsonCustomizer {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.dependenciesPackageJson = require(path.join(examplesPath, '/shared/package.json'));
|
this.dependenciesPackageJson = this.readJson(path.join(examplesPath, '/shared/package.json'));
|
||||||
this.scriptsPackageJson = require(path.join(examplesPath, '/shared/boilerplate/systemjs/package.json'));
|
this.scriptsPackageJson = this.readJson(path.join(examplesPath, '/shared/boilerplate/systemjs/package.json'));
|
||||||
this.basePackageJson = require(`${packageFolder}/base.json`);
|
this.basePackageJson = this.readJson(`${packageFolder}/base.json`);
|
||||||
|
this.templatePackageJson = this.readJson(`${packageFolder}/package.json`, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
generate(type = 'systemjs') {
|
generate(type = 'systemjs') {
|
||||||
let packageJson = require(`${packageFolder}/package.json`);
|
let packageJson = JSON.parse(this.templatePackageJson);
|
||||||
let rules = require(`${packageFolder}/${type}.json`);
|
let rules = require(`${packageFolder}/${type}.json`);
|
||||||
|
|
||||||
this._mergeJSON(rules, this.basePackageJson);
|
this._mergeJSON(rules, this.basePackageJson);
|
||||||
@ -50,6 +52,12 @@ class PackageJsonCustomizer {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readJson(jsonFile, parse = true) {
|
||||||
|
const contents = fs.readFileSync(jsonFile, 'utf8');
|
||||||
|
|
||||||
|
return parse ? JSON.parse(contents) : contents;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = PackageJsonCustomizer;
|
module.exports = PackageJsonCustomizer;
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
],
|
],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"devDependencies": [
|
"devDependencies": [
|
||||||
|
"@angular-devkit/build-angular",
|
||||||
"@angular/cli",
|
"@angular/cli",
|
||||||
"@types/jasminewd2",
|
"@types/jasminewd2",
|
||||||
"jasmine-marbles",
|
"jasmine-marbles",
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"@nguniversal/module-map-ngfactory-loader"
|
"@nguniversal/module-map-ngfactory-loader"
|
||||||
],
|
],
|
||||||
"devDependencies": [
|
"devDependencies": [
|
||||||
|
"@angular-devkit/build-angular",
|
||||||
"@angular/cli",
|
"@angular/cli",
|
||||||
"@types/jasminewd2",
|
"@types/jasminewd2",
|
||||||
"jasmine-spec-reporter",
|
"jasmine-spec-reporter",
|
||||||
|
@ -77,6 +77,7 @@
|
|||||||
"rollup-plugin-node-resolve": "2.0.0",
|
"rollup-plugin-node-resolve": "2.0.0",
|
||||||
"rollup-plugin-uglify": "^1.0.1",
|
"rollup-plugin-uglify": "^1.0.1",
|
||||||
"source-map-explorer": "^1.3.2",
|
"source-map-explorer": "^1.3.2",
|
||||||
|
"ts-loader": "^4.2.0",
|
||||||
"ts-node": "^5.0.1",
|
"ts-node": "^5.0.1",
|
||||||
"tslint": "^5.9.1",
|
"tslint": "^5.9.1",
|
||||||
"typescript": "2.7.2",
|
"typescript": "2.7.2",
|
||||||
|
@ -7361,7 +7361,7 @@ semver-intersect@^1.1.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
semver "^5.0.0"
|
semver "^5.0.0"
|
||||||
|
|
||||||
"semver@2 >=2.2.1 || 3.x || 4 || 5", semver@^5.0.0, semver@^5.5.0:
|
"semver@2 >=2.2.1 || 3.x || 4 || 5", semver@^5.0.0, semver@^5.0.1, semver@^5.5.0:
|
||||||
version "5.5.0"
|
version "5.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
|
||||||
|
|
||||||
@ -8257,6 +8257,16 @@ trim-right@^1.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
glob "^6.0.4"
|
glob "^6.0.4"
|
||||||
|
|
||||||
|
ts-loader@^4.2.0:
|
||||||
|
version "4.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-4.4.1.tgz#c93a46eea430ebce1f790dfe438caefb8670d365"
|
||||||
|
dependencies:
|
||||||
|
chalk "^2.3.0"
|
||||||
|
enhanced-resolve "^4.0.0"
|
||||||
|
loader-utils "^1.0.2"
|
||||||
|
micromatch "^3.1.4"
|
||||||
|
semver "^5.0.1"
|
||||||
|
|
||||||
ts-node@^5.0.1:
|
ts-node@^5.0.1:
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-5.0.1.tgz#78e5d1cb3f704de1b641e43b76be2d4094f06f81"
|
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-5.0.1.tgz#78e5d1cb3f704de1b641e43b76be2d4094f06f81"
|
||||||
|
@ -21,8 +21,7 @@
|
|||||||
{% if showType %}<td class="param-type"><code>{$ parameter.type $}</code></td>{% endif %}
|
{% if showType %}<td class="param-type"><code>{$ parameter.type $}</code></td>{% endif %}
|
||||||
<td class="param-description">
|
<td class="param-description">
|
||||||
{% marked %}
|
{% marked %}
|
||||||
{% if parameter.description | trim %}{$ parameter.description $}
|
{% if (parameter.shortDescription | trim) or (parameter.description | trim) %}{$ parameter.shortDescription + '\n\n' + parameter.description $}
|
||||||
|
|
||||||
{% elseif not showType and parameter.type %}<p>Type: <code>{$ parameter.type $}</code>.</p>
|
{% elseif not showType and parameter.type %}<p>Type: <code>{$ parameter.type $}</code>.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -6,9 +6,9 @@ workspace(name = "bazel_integration_test")
|
|||||||
|
|
||||||
http_archive(
|
http_archive(
|
||||||
name = "build_bazel_rules_nodejs",
|
name = "build_bazel_rules_nodejs",
|
||||||
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.9.1.zip",
|
url = "https://github.com/bazelbuild/rules_nodejs/archive/0.10.1.zip",
|
||||||
strip_prefix = "rules_nodejs-0.9.1",
|
strip_prefix = "rules_nodejs-0.10.1",
|
||||||
sha256 = "6139762b62b37c1fd171d7f22aa39566cb7dc2916f0f801d505a9aaf118c117f",
|
sha256 = "634206524d90dc03c52392fa3f19a16637d2bcf154910436fe1d669a0d9d7b9c",
|
||||||
)
|
)
|
||||||
|
|
||||||
http_archive(
|
http_archive(
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "angular-srcs",
|
"name": "angular-srcs",
|
||||||
"version": "6.0.7",
|
"version": "6.0.8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"branchPattern": "2.0.*",
|
"branchPattern": "2.0.*",
|
||||||
"description": "Angular - a web framework for modern web apps",
|
"description": "Angular - a web framework for modern web apps",
|
||||||
|
@ -917,7 +917,7 @@ export class HttpClient {
|
|||||||
}): Observable<string>;
|
}): Observable<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a GET request which interprets the body as an `ArrayBuffer` and returns the full event stream.
|
* Construct a HEAD request which interprets the body as an `ArrayBuffer` and returns the full event stream.
|
||||||
*
|
*
|
||||||
* @return an `Observable` of all `HttpEvent`s for the request, with a body type of `ArrayBuffer`.
|
* @return an `Observable` of all `HttpEvent`s for the request, with a body type of `ArrayBuffer`.
|
||||||
*/
|
*/
|
||||||
@ -1151,7 +1151,7 @@ export class HttpClient {
|
|||||||
}): Observable<Blob>;
|
}): Observable<Blob>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a OPTIONS request which interprets the body as text and returns it.
|
* Construct an OPTIONS request which interprets the body as text and returns it.
|
||||||
*
|
*
|
||||||
* @return an `Observable` of the body as a `string`.
|
* @return an `Observable` of the body as a `string`.
|
||||||
*/
|
*/
|
||||||
@ -1598,7 +1598,7 @@ export class HttpClient {
|
|||||||
}): Observable<string>;
|
}): Observable<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a PATCH request which interprets the body as an `ArrayBuffer` and returns the full event stream.
|
* Construct a POST request which interprets the body as an `ArrayBuffer` and returns the full event stream.
|
||||||
*
|
*
|
||||||
* @return an `Observable` of all `HttpEvent`s for the request, with a body type of `ArrayBuffer`.
|
* @return an `Observable` of all `HttpEvent`s for the request, with a body type of `ArrayBuffer`.
|
||||||
*/
|
*/
|
||||||
@ -1974,8 +1974,8 @@ export class HttpClient {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an `Observable` which, when subscribed, will cause the configured
|
* Constructs an `Observable` which, when subscribed, will cause the configured
|
||||||
* POST request to be executed on the server. See the individual overloads for
|
* PUT request to be executed on the server. See the individual overloads for
|
||||||
* details of `post()`'s return type based on the provided options.
|
* details of `put()`'s return type based on the provided options.
|
||||||
*/
|
*/
|
||||||
put(url: string, body: any|null, options: {
|
put(url: string, body: any|null, options: {
|
||||||
headers?: HttpHeaders | {[header: string]: string | string[]},
|
headers?: HttpHeaders | {[header: string]: string | string[]},
|
||||||
|
@ -949,7 +949,7 @@ export const locale_de_AT = [
|
|||||||
[['v. Chr.', 'n. Chr.'], u, u], 1, [6, 0],
|
[['v. Chr.', 'n. Chr.'], u, u], 1, [6, 0],
|
||||||
['dd.MM.yy', 'dd.MM.y', 'd. MMMM y', 'EEEE, d. MMMM y'],
|
['dd.MM.yy', 'dd.MM.y', 'd. MMMM y', 'EEEE, d. MMMM y'],
|
||||||
['HH:mm', 'HH:mm:ss', 'HH:mm:ss z', 'HH:mm:ss zzzz'], ['{1}, {0}', u, '{1} \'um\' {0}', u],
|
['HH:mm', 'HH:mm:ss', 'HH:mm:ss z', 'HH:mm:ss zzzz'], ['{1}, {0}', u, '{1} \'um\' {0}', u],
|
||||||
[',', ' ', ';', '%', '+', '-', 'E', '·', '‰', '∞', 'NaN', ':', '.'],
|
[',', ' ', ';', '%', '+', '-', 'E', '·', '‰', '∞', 'NaN', ':', u, '.'],
|
||||||
['#,##0.###', '#,##0 %', '¤ #,##0.00', '#E0'], '€', 'Euro', {
|
['#,##0.###', '#,##0 %', '¤ #,##0.00', '#E0'], '€', 'Euro', {
|
||||||
'ATS': ['öS'],
|
'ATS': ['öS'],
|
||||||
'AUD': ['AU$', '$'],
|
'AUD': ['AU$', '$'],
|
||||||
|
@ -51,7 +51,7 @@ export default [
|
|||||||
[['v. Chr.', 'n. Chr.'], u, u], 1, [6, 0],
|
[['v. Chr.', 'n. Chr.'], u, u], 1, [6, 0],
|
||||||
['dd.MM.yy', 'dd.MM.y', 'd. MMMM y', 'EEEE, d. MMMM y'],
|
['dd.MM.yy', 'dd.MM.y', 'd. MMMM y', 'EEEE, d. MMMM y'],
|
||||||
['HH:mm', 'HH:mm:ss', 'HH:mm:ss z', 'HH:mm:ss zzzz'], ['{1}, {0}', u, '{1} \'um\' {0}', u],
|
['HH:mm', 'HH:mm:ss', 'HH:mm:ss z', 'HH:mm:ss zzzz'], ['{1}, {0}', u, '{1} \'um\' {0}', u],
|
||||||
[',', ' ', ';', '%', '+', '-', 'E', '·', '‰', '∞', 'NaN', ':', '.'],
|
[',', ' ', ';', '%', '+', '-', 'E', '·', '‰', '∞', 'NaN', ':', u, '.'],
|
||||||
['#,##0.###', '#,##0 %', '¤ #,##0.00', '#E0'], '€', 'Euro', {
|
['#,##0.###', '#,##0 %', '¤ #,##0.00', '#E0'], '€', 'Euro', {
|
||||||
'ATS': ['öS'],
|
'ATS': ['öS'],
|
||||||
'AUD': ['AU$', '$'],
|
'AUD': ['AU$', '$'],
|
||||||
|
@ -176,6 +176,7 @@ export class NgForOf<T> implements DoCheck, OnChanges {
|
|||||||
const viewRef = <EmbeddedViewRef<NgForOfContext<T>>>this._viewContainer.get(i);
|
const viewRef = <EmbeddedViewRef<NgForOfContext<T>>>this._viewContainer.get(i);
|
||||||
viewRef.context.index = i;
|
viewRef.context.index = i;
|
||||||
viewRef.context.count = ilen;
|
viewRef.context.count = ilen;
|
||||||
|
viewRef.context.ngForOf = this.ngForOf;
|
||||||
}
|
}
|
||||||
|
|
||||||
changes.forEachIdentityChange((record: any) => {
|
changes.forEachIdentityChange((record: any) => {
|
||||||
|
@ -29,7 +29,7 @@ enum DateType {
|
|||||||
Hours,
|
Hours,
|
||||||
Minutes,
|
Minutes,
|
||||||
Seconds,
|
Seconds,
|
||||||
Milliseconds,
|
FractionalSeconds,
|
||||||
Day
|
Day
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,8 +57,6 @@ enum TranslationType {
|
|||||||
* If not specified, host system settings are used.
|
* If not specified, host system settings are used.
|
||||||
*
|
*
|
||||||
* See {@link DatePipe} for more details.
|
* See {@link DatePipe} for more details.
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
export function formatDate(
|
export function formatDate(
|
||||||
value: string | number | Date, format: string, locale: string, timezone?: string): string {
|
value: string | number | Date, format: string, locale: string, timezone?: string): string {
|
||||||
@ -195,6 +193,22 @@ function padNumber(
|
|||||||
return neg + strNum;
|
return neg + strNum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trim a fractional part to `digits` number of digits.
|
||||||
|
* Right pads with "0" to fit the requested number of digits if needed.
|
||||||
|
*
|
||||||
|
* @param num The fractional part value
|
||||||
|
* @param digits The width of the output
|
||||||
|
*/
|
||||||
|
function trimRPadFractional(num: number, digits: number): string {
|
||||||
|
let strNum = String(num);
|
||||||
|
// Add padding at the end
|
||||||
|
while (strNum.length < digits) {
|
||||||
|
strNum = strNum + 0;
|
||||||
|
}
|
||||||
|
return strNum.substr(0, digits);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a date formatter that transforms a date into its locale digit representation
|
* Returns a date formatter that transforms a date into its locale digit representation
|
||||||
*/
|
*/
|
||||||
@ -202,20 +216,26 @@ function dateGetter(
|
|||||||
name: DateType, size: number, offset: number = 0, trim = false,
|
name: DateType, size: number, offset: number = 0, trim = false,
|
||||||
negWrap = false): DateFormatter {
|
negWrap = false): DateFormatter {
|
||||||
return function(date: Date, locale: string): string {
|
return function(date: Date, locale: string): string {
|
||||||
let part = getDatePart(name, date, size);
|
let part = getDatePart(name, date);
|
||||||
if (offset > 0 || part > -offset) {
|
if (offset > 0 || part > -offset) {
|
||||||
part += offset;
|
part += offset;
|
||||||
}
|
}
|
||||||
if (name === DateType.Hours && part === 0 && offset === -12) {
|
|
||||||
|
if (name === DateType.Hours) {
|
||||||
|
if (part === 0 && offset === -12) {
|
||||||
part = 12;
|
part = 12;
|
||||||
}
|
}
|
||||||
return padNumber(
|
} else if (name === DateType.FractionalSeconds) {
|
||||||
part, size, getLocaleNumberSymbol(locale, NumberSymbol.MinusSign), trim, negWrap);
|
return trimRPadFractional(part, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
const localeMinus = getLocaleNumberSymbol(locale, NumberSymbol.MinusSign);
|
||||||
|
return padNumber(part, size, localeMinus, trim, negWrap);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDatePart(name: DateType, date: Date, size: number): number {
|
function getDatePart(part: DateType, date: Date): number {
|
||||||
switch (name) {
|
switch (part) {
|
||||||
case DateType.FullYear:
|
case DateType.FullYear:
|
||||||
return date.getFullYear();
|
return date.getFullYear();
|
||||||
case DateType.Month:
|
case DateType.Month:
|
||||||
@ -228,13 +248,12 @@ function getDatePart(name: DateType, date: Date, size: number): number {
|
|||||||
return date.getMinutes();
|
return date.getMinutes();
|
||||||
case DateType.Seconds:
|
case DateType.Seconds:
|
||||||
return date.getSeconds();
|
return date.getSeconds();
|
||||||
case DateType.Milliseconds:
|
case DateType.FractionalSeconds:
|
||||||
const div = size === 1 ? 100 : (size === 2 ? 10 : 1);
|
return date.getMilliseconds();
|
||||||
return Math.round(date.getMilliseconds() / div);
|
|
||||||
case DateType.Day:
|
case DateType.Day:
|
||||||
return date.getDay();
|
return date.getDay();
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown DateType value "${name}".`);
|
throw new Error(`Unknown DateType value "${part}".`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -561,16 +580,15 @@ function getDateFormatter(format: string): DateFormatter|null {
|
|||||||
formatter = dateGetter(DateType.Seconds, 2);
|
formatter = dateGetter(DateType.Seconds, 2);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Fractional second padded (0-9)
|
// Fractional second
|
||||||
case 'S':
|
case 'S':
|
||||||
formatter = dateGetter(DateType.Milliseconds, 1);
|
formatter = dateGetter(DateType.FractionalSeconds, 1);
|
||||||
break;
|
break;
|
||||||
case 'SS':
|
case 'SS':
|
||||||
formatter = dateGetter(DateType.Milliseconds, 2);
|
formatter = dateGetter(DateType.FractionalSeconds, 2);
|
||||||
break;
|
break;
|
||||||
// = millisecond
|
|
||||||
case 'SSS':
|
case 'SSS':
|
||||||
formatter = dateGetter(DateType.Milliseconds, 3);
|
formatter = dateGetter(DateType.FractionalSeconds, 3);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,6 +20,19 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
|||||||
* separator, decimal-point character, and other locale-specific
|
* separator, decimal-point character, and other locale-specific
|
||||||
* configurations.
|
* 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()`
|
* @see `formatNumber()`
|
||||||
*
|
*
|
||||||
* @usageNotes
|
* @usageNotes
|
||||||
@ -27,6 +40,8 @@ import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
|
|||||||
* into text strings, according to various format specifications,
|
* into text strings, according to various format specifications,
|
||||||
* where the caller's default locale is `en-US`.
|
* where the caller's default locale is `en-US`.
|
||||||
*
|
*
|
||||||
|
* ### Example
|
||||||
|
*
|
||||||
* <code-example path="common/pipes/ts/number_pipe.ts" region='NumberPipe'></code-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(() => {
|
it('should allow of saving the collection', async(() => {
|
||||||
const template =
|
const template =
|
||||||
'<ul><li *ngFor="let item of [1,2,3] as items; index as i">{{i}}/{{items.length}} - {{item}};</li></ul>';
|
'<ul><li *ngFor="let item of items as collection; index as i">{{i}}/{{collection.length}} - {{item}};</li></ul>';
|
||||||
fixture = createTestComponent(template);
|
fixture = createTestComponent(template);
|
||||||
|
|
||||||
|
detectChangesAndExpectText('0/2 - 1;1/2 - 2;');
|
||||||
|
|
||||||
|
getComponent().items = [1, 2, 3];
|
||||||
detectChangesAndExpectText('0/3 - 1;1/3 - 2;2/3 - 3;');
|
detectChangesAndExpectText('0/3 - 1;1/3 - 2;2/3 - 3;');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ describe('Format date', () => {
|
|||||||
|
|
||||||
// Check the transformation of a date into a pattern
|
// Check the transformation of a date into a pattern
|
||||||
function expectDateFormatAs(date: Date | string, pattern: any, output: string): void {
|
function expectDateFormatAs(date: Date | string, pattern: any, output: string): void {
|
||||||
expect(formatDate(date, pattern, defaultLocale)).toEqual(output);
|
expect(formatDate(date, pattern, defaultLocale)).toEqual(output, `pattern: "${pattern}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
@ -105,7 +105,7 @@ describe('Format date', () => {
|
|||||||
mm: '03',
|
mm: '03',
|
||||||
s: '1',
|
s: '1',
|
||||||
ss: '01',
|
ss: '01',
|
||||||
S: '6',
|
S: '5',
|
||||||
SS: '55',
|
SS: '55',
|
||||||
SSS: '550',
|
SSS: '550',
|
||||||
a: 'AM',
|
a: 'AM',
|
||||||
@ -233,7 +233,6 @@ describe('Format date', () => {
|
|||||||
Object.keys(dateFixtures).forEach((pattern: string) => {
|
Object.keys(dateFixtures).forEach((pattern: string) => {
|
||||||
expectDateFormatAs(date, pattern, dateFixtures[pattern]);
|
expectDateFormatAs(date, pattern, dateFixtures[pattern]);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should format with pattern aliases', () => {
|
it('should format with pattern aliases', () => {
|
||||||
@ -266,14 +265,13 @@ describe('Format date', () => {
|
|||||||
() => expect(formatDate('2017-01-20T12:00:00+0000', defaultFormat, defaultLocale))
|
() => expect(formatDate('2017-01-20T12:00:00+0000', defaultFormat, defaultLocale))
|
||||||
.toEqual('Jan 20, 2017'));
|
.toEqual('Jan 20, 2017'));
|
||||||
|
|
||||||
// test for the following bugs:
|
|
||||||
// https://github.com/angular/angular/issues/9524
|
// https://github.com/angular/angular/issues/9524
|
||||||
// https://github.com/angular/angular/issues/9524
|
// https://github.com/angular/angular/issues/9524
|
||||||
it('should format correctly with iso strings that contain time',
|
it('should format correctly with iso strings that contain time',
|
||||||
() => expect(formatDate('2017-05-07T22:14:39', 'dd-MM-yyyy HH:mm', defaultLocale))
|
() => expect(formatDate('2017-05-07T22:14:39', 'dd-MM-yyyy HH:mm', defaultLocale))
|
||||||
.toMatch(/07-05-2017 \d{2}:\d{2}/));
|
.toMatch(/07-05-2017 \d{2}:\d{2}/));
|
||||||
|
|
||||||
// test for issue https://github.com/angular/angular/issues/21491
|
// https://github.com/angular/angular/issues/21491
|
||||||
it('should not assume UTC for iso strings in Safari if the timezone is not defined', () => {
|
it('should not assume UTC for iso strings in Safari if the timezone is not defined', () => {
|
||||||
// this test only works if the timezone is not in UTC
|
// this test only works if the timezone is not in UTC
|
||||||
// which is the case for BrowserStack when we test Safari
|
// which is the case for BrowserStack when we test Safari
|
||||||
@ -283,7 +281,6 @@ describe('Format date', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// test for the following bugs:
|
|
||||||
// https://github.com/angular/angular/issues/16624
|
// https://github.com/angular/angular/issues/16624
|
||||||
// https://github.com/angular/angular/issues/17478
|
// https://github.com/angular/angular/issues/17478
|
||||||
it('should show the correct time when the timezone is fixed', () => {
|
it('should show the correct time when the timezone is fixed', () => {
|
||||||
@ -311,5 +308,17 @@ describe('Format date', () => {
|
|||||||
expect(() => formatDate(date, 'b', 'de'))
|
expect(() => formatDate(date, 'b', 'de'))
|
||||||
.toThrowError(/Missing extra locale data for the locale "de"/);
|
.toThrowError(/Missing extra locale data for the locale "de"/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// https://github.com/angular/angular/issues/24384
|
||||||
|
it('should not round fractional seconds', () => {
|
||||||
|
expect(formatDate(3999, 'm:ss', 'en')).toEqual('0:03');
|
||||||
|
expect(formatDate(3999, 'm:ss.S', 'en')).toEqual('0:03.9');
|
||||||
|
expect(formatDate(3999, 'm:ss.SS', 'en')).toEqual('0:03.99');
|
||||||
|
expect(formatDate(3999, 'm:ss.SSS', 'en')).toEqual('0:03.999');
|
||||||
|
expect(formatDate(3000, 'm:ss', 'en')).toEqual('0:03');
|
||||||
|
expect(formatDate(3000, 'm:ss.S', 'en')).toEqual('0:03.0');
|
||||||
|
expect(formatDate(3000, 'm:ss.SS', 'en')).toEqual('0:03.00');
|
||||||
|
expect(formatDate(3000, 'm:ss.SSS', 'en')).toEqual('0:03.000');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -10,6 +10,7 @@ import localeEn from '@angular/common/locales/en';
|
|||||||
import localeEsUS from '@angular/common/locales/es-US';
|
import localeEsUS from '@angular/common/locales/es-US';
|
||||||
import localeFr from '@angular/common/locales/fr';
|
import localeFr from '@angular/common/locales/fr';
|
||||||
import localeAr from '@angular/common/locales/ar';
|
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 {registerLocaleData, CurrencyPipe, DecimalPipe, PercentPipe, formatNumber} from '@angular/common';
|
||||||
import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testing_internal';
|
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(localeEsUS);
|
||||||
registerLocaleData(localeFr);
|
registerLocaleData(localeFr);
|
||||||
registerLocaleData(localeAr);
|
registerLocaleData(localeAr);
|
||||||
|
registerLocaleData(localeDeAt);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('DecimalPipe', () => {
|
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'))
|
expect(pipe.transform(5.1234, 'CAD', 'symbol-narrow', '5.2-2', 'fr'))
|
||||||
.toEqual('00 005,12 $');
|
.toEqual('00 005,12 $');
|
||||||
expect(pipe.transform(5, 'USD', 'symbol', '', 'fr')).toEqual('5,00 $US');
|
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', () => {
|
it('should support any currency code name', () => {
|
||||||
|
@ -103,7 +103,7 @@ export function createBundleIndexHost<H extends ts.CompilerHost>(
|
|||||||
// etc.
|
// etc.
|
||||||
const getMetadataBundle = (cache: MetadataCache | null) => {
|
const getMetadataBundle = (cache: MetadataCache | null) => {
|
||||||
const bundler = new MetadataBundler(
|
const bundler = new MetadataBundler(
|
||||||
indexModule, ngOptions.flatModuleId, new CompilerHostAdapter(host, cache),
|
indexModule, ngOptions.flatModuleId, new CompilerHostAdapter(host, cache, ngOptions),
|
||||||
ngOptions.flatModulePrivateSymbolPrefix);
|
ngOptions.flatModulePrivateSymbolPrefix);
|
||||||
return bundler.getMetadataBundle();
|
return bundler.getMetadataBundle();
|
||||||
};
|
};
|
||||||
|
@ -72,7 +72,7 @@ export interface BundledModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface MetadataBundlerHost {
|
export interface MetadataBundlerHost {
|
||||||
getMetadataFor(moduleName: string): ModuleMetadata|undefined;
|
getMetadataFor(moduleName: string, containingFile: string): ModuleMetadata|undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
type StaticsMetadata = {
|
type StaticsMetadata = {
|
||||||
@ -135,7 +135,7 @@ export class MetadataBundler {
|
|||||||
if (!result) {
|
if (!result) {
|
||||||
if (moduleName.startsWith('.')) {
|
if (moduleName.startsWith('.')) {
|
||||||
const fullModuleName = resolveModule(moduleName, this.root);
|
const fullModuleName = resolveModule(moduleName, this.root);
|
||||||
result = this.host.getMetadataFor(fullModuleName);
|
result = this.host.getMetadataFor(fullModuleName, this.root);
|
||||||
}
|
}
|
||||||
this.metadataCache.set(moduleName, result);
|
this.metadataCache.set(moduleName, result);
|
||||||
}
|
}
|
||||||
@ -597,11 +597,27 @@ export class MetadataBundler {
|
|||||||
export class CompilerHostAdapter implements MetadataBundlerHost {
|
export class CompilerHostAdapter implements MetadataBundlerHost {
|
||||||
private collector = new MetadataCollector();
|
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): ModuleMetadata|undefined {
|
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;
|
if (!this.host.fileExists(fileName + '.ts')) return undefined;
|
||||||
const sourceFile = this.host.getSourceFile(fileName + '.ts', ts.ScriptTarget.Latest);
|
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,
|
// If there is a metadata cache, use it to get the metadata for this source file. Otherwise,
|
||||||
// fall back on the locally created MetadataCollector.
|
// fall back on the locally created MetadataCollector.
|
||||||
if (!sourceFile) {
|
if (!sourceFile) {
|
||||||
|
@ -9,6 +9,7 @@ ts_library(
|
|||||||
"//packages:types",
|
"//packages:types",
|
||||||
"//packages/compiler",
|
"//packages/compiler",
|
||||||
"//packages/compiler-cli",
|
"//packages/compiler-cli",
|
||||||
|
"//packages/compiler-cli/test:test_utils",
|
||||||
"//packages/core",
|
"//packages/core",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -9,11 +9,187 @@
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
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 {MetadataCollector} from '../../src/metadata/collector';
|
||||||
import {ClassMetadata, MetadataGlobalReferenceExpression, ModuleMetadata} from '../../src/metadata/schema';
|
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', () => {
|
describe('metadata bundler', () => {
|
||||||
|
|
||||||
@ -231,25 +407,23 @@ describe('metadata bundler', () => {
|
|||||||
|
|
||||||
export class MockStringBundlerHost implements MetadataBundlerHost {
|
export class MockStringBundlerHost implements MetadataBundlerHost {
|
||||||
collector = new MetadataCollector();
|
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 {
|
getMetadataFor(moduleName: string): ModuleMetadata|undefined {
|
||||||
const fileName = path.join(this.dirName, moduleName) + '.ts';
|
return this.adapter.getMetadataFor(moduleName, this.dirName);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export const SIMPLE_LIBRARY = {
|
export const SIMPLE_LIBRARY = {
|
||||||
'lib': {
|
'lib': {
|
||||||
@ -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'; }
|
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 {
|
readFile(fileName: string): string {
|
||||||
const data = this.getEntry(fileName);
|
const data = this.getEntry(fileName);
|
||||||
|
@ -73,7 +73,8 @@ export interface Component extends Directive {
|
|||||||
export enum ViewEncapsulation {
|
export enum ViewEncapsulation {
|
||||||
Emulated = 0,
|
Emulated = 0,
|
||||||
Native = 1,
|
Native = 1,
|
||||||
None = 2
|
None = 2,
|
||||||
|
ShadowDom = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ChangeDetectionStrategy {
|
export enum ChangeDetectionStrategy {
|
||||||
|
@ -60,10 +60,8 @@ export abstract class Injector {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves an instance from the injector based on the provided token.
|
* Retrieves an instance from the injector based on the provided token.
|
||||||
* If not found:
|
* @returns The instance from the injector if defined, otherwise the `notFoundValue`.
|
||||||
* - Throws an error if no `notFoundValue` that is not equal to
|
* @throws When the `notFoundValue` is `undefined` or `Injector.THROW_IF_NOT_FOUND`.
|
||||||
* Injector.THROW_IF_NOT_FOUND is given
|
|
||||||
* - Returns the `notFoundValue` otherwise
|
|
||||||
*/
|
*/
|
||||||
abstract get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
|
abstract get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): T;
|
||||||
/**
|
/**
|
||||||
|
@ -23,14 +23,28 @@ export enum ViewEncapsulation {
|
|||||||
*/
|
*/
|
||||||
Emulated = 0,
|
Emulated = 0,
|
||||||
/**
|
/**
|
||||||
|
* @deprecated v6.1.0 - use {ViewEncapsulation.ShadowDom} instead.
|
||||||
* Use the native encapsulation mechanism of the renderer.
|
* 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.
|
* creating a ShadowRoot for Component's Host Element.
|
||||||
*/
|
*/
|
||||||
Native = 1,
|
Native = 1,
|
||||||
/**
|
/**
|
||||||
* Don't provide any template or style encapsulation.
|
* 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": "EMPTY_ARRAY$1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "GET_PROPERTY_NAME$1"
|
"name": "GET_PROPERTY_NAME"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "INJECTOR$1"
|
"name": "INJECTOR"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Inject"
|
"name": "Inject"
|
||||||
@ -51,7 +51,7 @@
|
|||||||
"name": "THROW_IF_NOT_FOUND"
|
"name": "THROW_IF_NOT_FOUND"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "USE_VALUE$1"
|
"name": "USE_VALUE"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "_THROW_IF_NOT_FOUND"
|
"name": "_THROW_IF_NOT_FOUND"
|
||||||
|
@ -21,6 +21,18 @@ class SpyTestObj extends SpyObject {
|
|||||||
|
|
||||||
{
|
{
|
||||||
describe('testing', () => {
|
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', () => {
|
describe('equality', () => {
|
||||||
it('should structurally compare objects', () => {
|
it('should structurally compare objects', () => {
|
||||||
const expected = new TestObj(new TestObj({'one': [1, 2]}));
|
const expected = new TestObj(new TestObj({'one': [1, 2]}));
|
||||||
|
@ -38,6 +38,9 @@ registerLocaleData(localeFr);
|
|||||||
|
|
||||||
<!--output '003.14000'-->
|
<!--output '003.14000'-->
|
||||||
<p>pi (3.5-5): {{pi | number:'3.5-5'}}</p>
|
<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>`
|
</div>`
|
||||||
})
|
})
|
||||||
export class NumberPipeComponent {
|
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,
|
* A directive which installs the `MaxLengthValidator` for any `formControlName`,
|
||||||
* `formControl`,
|
* `formControl`, or control with `ngModel` that also has a `maxlength` attribute.
|
||||||
* or control with `ngModel` that also has a `maxlength` attribute.
|
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@ -18,36 +18,38 @@ 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.
|
* @see `NG_ASYNC_VALIDATORS`
|
||||||
*
|
*
|
||||||
* ### Example
|
* @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
|
* ```typescript
|
||||||
* @Directive({
|
* @Directive({
|
||||||
* selector: '[custom-validator]',
|
* selector: '[customValidator]',
|
||||||
* providers: [{provide: NG_VALIDATORS, useExisting: CustomValidatorDirective, multi: true}]
|
* providers: [{provide: NG_VALIDATORS, useExisting: CustomValidatorDirective, multi: true}]
|
||||||
* })
|
* })
|
||||||
* class CustomValidatorDirective implements Validator {
|
* class CustomValidatorDirective implements Validator {
|
||||||
* validate(control: AbstractControl): ValidationErrors | null {
|
* validate(control: AbstractControl): ValidationErrors | null {
|
||||||
* return {"custom": true};
|
* return { 'custom': true };
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
export const NG_VALIDATORS = new InjectionToken<Array<Validator|Function>>('NgValidators');
|
export const NG_VALIDATORS = new InjectionToken<Array<Validator|Function>>('NgValidators');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Providers for asynchronous validators to be used for `FormControl`s
|
* @description
|
||||||
* in a form.
|
* An `InjectionToken` for registering additional asynchronous validators used with `AbstractControl`s.
|
||||||
*
|
|
||||||
* Provide this using `multi: true` to add validators.
|
|
||||||
*
|
|
||||||
* See `NG_VALIDATORS` for more details.
|
|
||||||
*
|
*
|
||||||
|
* @see `NG_VALIDATORS`
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const NG_ASYNC_VALIDATORS =
|
export const NG_ASYNC_VALIDATORS =
|
||||||
@ -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])?)*$/;
|
/^(?=.{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
|
* 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.
|
* controls and returns an error map or null. A null map means that validation has passed.
|
||||||
*
|
|
||||||
* ### Example
|
|
||||||
*
|
|
||||||
* ```typescript
|
|
||||||
* var loginControl = new FormControl("", Validators.required)
|
|
||||||
* ```
|
|
||||||
*
|
*
|
||||||
|
* @see [Form Validation](/guide/form-validation)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export class Validators {
|
export class Validators {
|
||||||
/**
|
/**
|
||||||
* Validator that requires controls to have a value greater than a number.
|
* @description
|
||||||
*`min()` exists only as a function, not as a directive. For example,
|
* Validator that requires the control's value to be greater than or equal to the provided number.
|
||||||
* `control = new FormControl('', Validators.min(3));`.
|
* 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 {
|
static min(min: number): ValidatorFn {
|
||||||
return (control: AbstractControl): ValidationErrors | null => {
|
return (control: AbstractControl): ValidationErrors | null => {
|
||||||
@ -89,9 +101,23 @@ export class Validators {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validator that requires controls to have a value less than a number.
|
* @description
|
||||||
* `max()` exists only as a function, not as a directive. For example,
|
* Validator that requires the control's value to be less than or equal to the provided number.
|
||||||
* `control = new FormControl('', Validators.max(15));`.
|
* 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 {
|
static max(max: number): ValidatorFn {
|
||||||
return (control: AbstractControl): ValidationErrors | null => {
|
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 {
|
static required(control: AbstractControl): ValidationErrors|null {
|
||||||
return isEmptyInputValue(control.value) ? {'required': true} : 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 {
|
static requiredTrue(control: AbstractControl): ValidationErrors|null {
|
||||||
return control.value === true ? null : {'required': true};
|
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 {
|
static email(control: AbstractControl): ValidationErrors|null {
|
||||||
if (isEmptyInputValue(control.value)) {
|
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 {
|
static minLength(minLength: number): ValidatorFn {
|
||||||
return (control: AbstractControl): ValidationErrors | null => {
|
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 {
|
static maxLength(maxLength: number): ValidatorFn {
|
||||||
return (control: AbstractControl): ValidationErrors | null => {
|
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 {
|
static pattern(pattern: string|RegExp): ValidatorFn {
|
||||||
if (!pattern) return Validators.nullValidator;
|
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; }
|
static nullValidator(c: AbstractControl): ValidationErrors|null { return null; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @description
|
||||||
* Compose multiple validators into a single function that returns the union
|
* 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: null): null;
|
||||||
static compose(validators: (ValidatorFn|null|undefined)[]): ValidatorFn|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 {
|
static composeAsync(validators: (AsyncValidatorFn|null)[]): AsyncValidatorFn|null {
|
||||||
if (!validators) return null;
|
if (!validators) return null;
|
||||||
const presentValidators: AsyncValidatorFn[] = validators.filter(isPresent) as any;
|
const presentValidators: AsyncValidatorFn[] = validators.filter(isPresent) as any;
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
var $reflect = {defineMetadata: function() {}, getOwnMetadata: function() {}};
|
var $reflect = {defineMetadata: function() {}, getOwnMetadata: function() {}};
|
||||||
((typeof global !== 'undefined' && global)||{})['Reflect'] = $reflect;
|
var Reflect = (typeof global !== 'undefined' ? global : {})['Reflect'] || {};
|
||||||
|
Object.keys($reflect).forEach(function(key) { Reflect[key] = Reflect[key] || $reflect[key]; });
|
||||||
var $deferred, $resolved, $provided;
|
var $deferred, $resolved, $provided;
|
||||||
function $getModule(name) { return $provided[name] || require(name); }
|
function $getModule(name) { return $provided[name] || require(name); }
|
||||||
function define(modules, cb) { $deferred = { modules: modules, cb: cb }; }
|
function define(modules, cb) { $deferred = { modules: modules, cb: cb }; }
|
||||||
|
@ -9,12 +9,12 @@
|
|||||||
const commonjs = require('rollup-plugin-commonjs');
|
const commonjs = require('rollup-plugin-commonjs');
|
||||||
const sourcemaps = require('rollup-plugin-sourcemaps');
|
const sourcemaps = require('rollup-plugin-sourcemaps');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
var m = /^\@angular\/((\w|\-)+)(\/(\w|\d|\/|\-)+)?$/;
|
var m = /^\@angular\/((\w|\-)+)(\/(\w|\d|\/|\-)+)?$/;
|
||||||
var location = normalize('../../dist/packages-dist') + '/';
|
var location = normalize('../../dist/packages-dist') + '/';
|
||||||
var rxjsLocation = normalize('../../node_modules/rxjs');
|
var rxjsLocation = normalize('../../node_modules/rxjs');
|
||||||
var tslibLocation = normalize('../../node_modules/tslib');
|
var tslibLocation = normalize('../../node_modules/tslib');
|
||||||
var esm = 'esm/';
|
|
||||||
|
|
||||||
var locations = {'compiler-cli': normalize('../../dist/packages') + '/'};
|
var locations = {'compiler-cli': normalize('../../dist/packages') + '/'};
|
||||||
|
|
||||||
@ -43,21 +43,7 @@ function resolve(id, from) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var banner = `
|
var banner = fs.readFileSync('bundles/banner.js.txt', 'utf8');
|
||||||
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;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: '../../dist/packages-dist/language-service/fesm5/language-service.js',
|
entry: '../../dist/packages-dist/language-service/fesm5/language-service.js',
|
||||||
|
@ -83,6 +83,7 @@ export class DomRendererFactory2 implements RendererFactory2 {
|
|||||||
return renderer;
|
return renderer;
|
||||||
}
|
}
|
||||||
case ViewEncapsulation.Native:
|
case ViewEncapsulation.Native:
|
||||||
|
case ViewEncapsulation.ShadowDom:
|
||||||
return new ShadowDomRenderer(this.eventManager, this.sharedStylesHost, element, type);
|
return new ShadowDomRenderer(this.eventManager, this.sharedStylesHost, element, type);
|
||||||
default: {
|
default: {
|
||||||
if (!this.rendererByCompId.has(type.id)) {
|
if (!this.rendererByCompId.has(type.id)) {
|
||||||
@ -256,7 +257,11 @@ class ShadowDomRenderer extends DefaultDomRenderer2 {
|
|||||||
eventManager: EventManager, private sharedStylesHost: DomSharedStylesHost,
|
eventManager: EventManager, private sharedStylesHost: DomSharedStylesHost,
|
||||||
private hostEl: any, private component: RendererType2) {
|
private hostEl: any, private component: RendererType2) {
|
||||||
super(eventManager);
|
super(eventManager);
|
||||||
|
if (component.encapsulation === ViewEncapsulation.ShadowDom) {
|
||||||
|
this.shadowRoot = (hostEl as any).attachShadow({mode: 'open'});
|
||||||
|
} else {
|
||||||
this.shadowRoot = (hostEl as any).createShadowRoot();
|
this.shadowRoot = (hostEl as any).createShadowRoot();
|
||||||
|
}
|
||||||
this.sharedStylesHost.addHost(this.shadowRoot);
|
this.sharedStylesHost.addHost(this.shadowRoot);
|
||||||
const styles = flattenStyles(component.id, component.styles, []);
|
const styles = flattenStyles(component.id, component.styles, []);
|
||||||
for (let i = 0; i < styles.length; i++) {
|
for (let i = 0; i < styles.length; i++) {
|
||||||
|
@ -20,7 +20,8 @@ import {NAMESPACE_URIS} from '../../src/dom/dom_renderer';
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
TestCmp, SomeApp, CmpEncapsulationEmulated, CmpEncapsulationNative, CmpEncapsulationNone
|
TestCmp, SomeApp, CmpEncapsulationEmulated, CmpEncapsulationNative, CmpEncapsulationNone,
|
||||||
|
CmpEncapsulationNative
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
renderer = TestBed.createComponent(TestCmp).componentInstance.renderer;
|
renderer = TestBed.createComponent(TestCmp).componentInstance.renderer;
|
||||||
@ -135,6 +136,15 @@ class CmpEncapsulationEmulated {
|
|||||||
class CmpEncapsulationNone {
|
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({
|
@Component({
|
||||||
selector: 'some-app',
|
selector: 'some-app',
|
||||||
template: `
|
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() {
|
_global.beforeEach(function() {
|
||||||
jasmine.addMatchers({
|
// Custom handler for Map as we use Jasmine 2.4, and support for maps is not
|
||||||
// Custom handler for Map as Jasmine does not support it yet
|
// added until Jasmine 2.6.
|
||||||
toEqual: function(util) {
|
jasmine.addCustomEqualityTester(function compareMap(actual: any, expected: any): boolean {
|
||||||
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) {
|
if (actual instanceof Map) {
|
||||||
let pass = actual.size === expected.size;
|
let pass = actual.size === expected.size;
|
||||||
if (pass) {
|
if (pass) {
|
||||||
actual.forEach((v: any, k: any) => { pass = pass && util.equals(v, expected.get(k)); });
|
actual.forEach((v: any, k: any) => {
|
||||||
|
pass = pass && jasmine.matchersUtil.equals(v, expected.get(k));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return pass;
|
return pass;
|
||||||
} else {
|
} else {
|
||||||
// TODO(misko): we should change the return, but jasmine.d.ts is not null safe
|
// TODO(misko): we should change the return, but jasmine.d.ts is not null safe
|
||||||
return undefined !;
|
return undefined !;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
},
|
jasmine.addMatchers({
|
||||||
|
|
||||||
toBePromise: function() {
|
toBePromise: function() {
|
||||||
return {
|
return {
|
||||||
compare: function(actual: any) {
|
compare: function(actual: any) {
|
||||||
|
@ -160,6 +160,11 @@ function defaultErrorHandler(error: any): any {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function defaultMalformedUriErrorHandler(
|
||||||
|
error: URIError, urlSerializer: UrlSerializer, url: string): UrlTree {
|
||||||
|
return urlSerializer.parse('/');
|
||||||
|
}
|
||||||
|
|
||||||
type NavStreamValue =
|
type NavStreamValue =
|
||||||
boolean | {appliedUrl: UrlTree, snapshot: RouterStateSnapshot, shouldActivate?: boolean};
|
boolean | {appliedUrl: UrlTree, snapshot: RouterStateSnapshot, shouldActivate?: boolean};
|
||||||
|
|
||||||
@ -217,7 +222,14 @@ export class Router {
|
|||||||
*/
|
*/
|
||||||
errorHandler: ErrorHandler = defaultErrorHandler;
|
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.
|
* Indicates if at least one navigation happened.
|
||||||
@ -312,7 +324,7 @@ export class Router {
|
|||||||
// run into ngZone
|
// run into ngZone
|
||||||
if (!this.locationSubscription) {
|
if (!this.locationSubscription) {
|
||||||
this.locationSubscription = <any>this.location.subscribe((change: any) => {
|
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 source: NavigationTrigger = change['type'] === 'popstate' ? 'popstate' : 'hashchange';
|
||||||
const state = change.state && change.state.navigationId ?
|
const state = change.state && change.state.navigationId ?
|
||||||
{navigationId: change.state.navigationId} :
|
{navigationId: change.state.navigationId} :
|
||||||
@ -490,7 +502,15 @@ export class Router {
|
|||||||
serializeUrl(url: UrlTree): string { return this.urlSerializer.serialize(url); }
|
serializeUrl(url: UrlTree): string { return this.urlSerializer.serialize(url); }
|
||||||
|
|
||||||
/** Parses a string into a `UrlTree` */
|
/** 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 */
|
/** Returns whether the url is activated */
|
||||||
isActive(url: string|UrlTree, exact: boolean): boolean {
|
isActive(url: string|UrlTree, exact: boolean): boolean {
|
||||||
@ -498,7 +518,7 @@ export class Router {
|
|||||||
return containsTree(this.currentUrlTree, url, exact);
|
return containsTree(this.currentUrlTree, url, exact);
|
||||||
}
|
}
|
||||||
|
|
||||||
const urlTree = this.urlSerializer.parse(url);
|
const urlTree = this.parseUrl(url);
|
||||||
return containsTree(this.currentUrlTree, urlTree, exact);
|
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 {NoPreloading, PreloadAllModules, PreloadingStrategy, RouterPreloader} from './router_preloader';
|
||||||
import {ActivatedRoute} from './router_state';
|
import {ActivatedRoute} from './router_state';
|
||||||
import {UrlHandlingStrategy} from './url_handling_strategy';
|
import {UrlHandlingStrategy} from './url_handling_strategy';
|
||||||
import {DefaultUrlSerializer, UrlSerializer} from './url_tree';
|
import {DefaultUrlSerializer, UrlSerializer, UrlTree} from './url_tree';
|
||||||
import {flatten} from './utils/collection';
|
import {flatten} from './utils/collection';
|
||||||
|
|
||||||
|
|
||||||
@ -151,6 +151,8 @@ export class RouterModule {
|
|||||||
* * `preloadingStrategy` configures a preloading strategy (see `PreloadAllModules`).
|
* * `preloadingStrategy` configures a preloading strategy (see `PreloadAllModules`).
|
||||||
* * `onSameUrlNavigation` configures how the router handles navigation to the current URL. See
|
* * `onSameUrlNavigation` configures how the router handles navigation to the current URL. See
|
||||||
* `ExtraOptions` for more details.
|
* `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 {
|
static forRoot(routes: Routes, config?: ExtraOptions): ModuleWithProviders {
|
||||||
return {
|
return {
|
||||||
@ -306,6 +308,18 @@ export interface ExtraOptions {
|
|||||||
* - `'always'`, enables unconditional inheritance of parent params.
|
* - `'always'`, enables unconditional inheritance of parent params.
|
||||||
*/
|
*/
|
||||||
paramsInheritanceStrategy?: 'emptyOnly'|'always';
|
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(
|
export function setupRouter(
|
||||||
@ -328,6 +342,10 @@ export function setupRouter(
|
|||||||
router.errorHandler = opts.errorHandler;
|
router.errorHandler = opts.errorHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts.malformedUriErrorHandler) {
|
||||||
|
router.malformedUriErrorHandler = opts.malformedUriErrorHandler;
|
||||||
|
}
|
||||||
|
|
||||||
if (opts.enableTracing) {
|
if (opts.enableTracing) {
|
||||||
const dom = getDOM();
|
const dom = getDOM();
|
||||||
router.events.subscribe((e: RouterEvent) => {
|
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 {ComponentFixture, TestBed, fakeAsync, inject, tick} from '@angular/core/testing';
|
||||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
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 {Observable, Observer, of } from 'rxjs';
|
||||||
import {map} from 'rxjs/operators';
|
import {map} from 'rxjs/operators';
|
||||||
|
|
||||||
@ -1010,6 +1010,30 @@ describe('Integration', () => {
|
|||||||
expectEvents(recordedEvents, [[NavigationStart, '/invalid'], [NavigationError, '/invalid']]);
|
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) => {
|
it('should not swallow errors', fakeAsync(inject([Router], (router: Router) => {
|
||||||
const fixture = createRoot(router, RootCmp);
|
const fixture = createRoot(router, RootCmp);
|
||||||
|
|
||||||
@ -3938,6 +3962,22 @@ describe('Testing router options', () => {
|
|||||||
expect(router.paramsInheritanceStrategy).toEqual('always');
|
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[]) {
|
function expectEvents(events: Event[], pairs: any[]) {
|
||||||
|
@ -115,14 +115,22 @@ export function setupTestingRouter(
|
|||||||
opts?: ExtraOptions | UrlHandlingStrategy, urlHandlingStrategy?: UrlHandlingStrategy) {
|
opts?: ExtraOptions | UrlHandlingStrategy, urlHandlingStrategy?: UrlHandlingStrategy) {
|
||||||
const router = new Router(
|
const router = new Router(
|
||||||
null !, urlSerializer, contexts, location, injector, loader, compiler, flatten(routes));
|
null !, urlSerializer, contexts, location, injector, loader, compiler, flatten(routes));
|
||||||
// Handle deprecated argument ordering.
|
|
||||||
if (opts) {
|
if (opts) {
|
||||||
|
// Handle deprecated argument ordering.
|
||||||
if (isUrlHandlingStrategy(opts)) {
|
if (isUrlHandlingStrategy(opts)) {
|
||||||
router.urlHandlingStrategy = opts;
|
router.urlHandlingStrategy = opts;
|
||||||
} else if (opts.paramsInheritanceStrategy) {
|
} else {
|
||||||
|
// Handle ExtraOptions
|
||||||
|
|
||||||
|
if (opts.malformedUriErrorHandler) {
|
||||||
|
router.malformedUriErrorHandler = opts.malformedUriErrorHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.paramsInheritanceStrategy) {
|
||||||
router.paramsInheritanceStrategy = opts.paramsInheritanceStrategy;
|
router.paramsInheritanceStrategy = opts.paramsInheritanceStrategy;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (urlHandlingStrategy) {
|
if (urlHandlingStrategy) {
|
||||||
router.urlHandlingStrategy = urlHandlingStrategy;
|
router.urlHandlingStrategy = urlHandlingStrategy;
|
||||||
|
@ -198,8 +198,6 @@ export class AppVersion implements UpdateSource {
|
|||||||
* Check this version for a given resource with a particular hash.
|
* Check this version for a given resource with a particular hash.
|
||||||
*/
|
*/
|
||||||
async lookupResourceWithHash(url: string, hash: string): Promise<Response|null> {
|
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,
|
// Verify that this version has the requested resource cached. If not,
|
||||||
// there's no point in trying.
|
// there's no point in trying.
|
||||||
if (!this.hashTable.has(url)) {
|
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
|
// Next, check whether the resource has the correct hash. If not, any cached
|
||||||
// response isn't usable.
|
// response isn't usable.
|
||||||
if (this.hashTable.get(url) ! !== hash) {
|
if (this.hashTable.get(url) !== hash) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: no-op context and appropriate contract. Currently this is a violation
|
const cacheState = await this.lookupResourceWithoutHash(url);
|
||||||
// of the typings and could cause issues if handleFetch() has side effects. A
|
return cacheState && cacheState.response;
|
||||||
// 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 !);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -225,7 +225,9 @@ export class Driver implements Debuggable, UpdateSource {
|
|||||||
// Initialization is the only event which is sent directly from the SW to itself,
|
// 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
|
// and thus `event.source` is not a Client. Handle it here, before the check
|
||||||
// for Client sources.
|
// for Client sources.
|
||||||
if (data.action === 'INITIALIZE' && this.initialized === null) {
|
if (data.action === 'INITIALIZE') {
|
||||||
|
// Only initialize if not already initialized (or initializing).
|
||||||
|
if (this.initialized === null) {
|
||||||
// Initialize the SW.
|
// Initialize the SW.
|
||||||
this.initialized = this.initialize();
|
this.initialized = this.initialize();
|
||||||
|
|
||||||
@ -237,6 +239,9 @@ export class Driver implements Debuggable, UpdateSource {
|
|||||||
})());
|
})());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Only messages from true clients are accepted past this point (this is essentially
|
// Only messages from true clients are accepted past this point (this is essentially
|
||||||
// a typecast).
|
// a typecast).
|
||||||
if (!this.adapter.isClient(event.source)) {
|
if (!this.adapter.isClient(event.source)) {
|
||||||
@ -725,16 +730,6 @@ export class Driver implements Debuggable, UpdateSource {
|
|||||||
private async setupUpdate(manifest: Manifest, hash: string): Promise<void> {
|
private async setupUpdate(manifest: Manifest, hash: string): Promise<void> {
|
||||||
const newVersion = new AppVersion(this.scope, this.adapter, this.db, this.idle, manifest, hash);
|
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.
|
// Firstly, check if the manifest version is correct.
|
||||||
if (manifest.configVersion !== SUPPORTED_CONFIG_VERSION) {
|
if (manifest.configVersion !== SUPPORTED_CONFIG_VERSION) {
|
||||||
await this.deleteAllCaches();
|
await this.deleteAllCaches();
|
||||||
|
@ -24,6 +24,9 @@ const dist =
|
|||||||
.addFile('/baz.txt', 'this is baz')
|
.addFile('/baz.txt', 'this is baz')
|
||||||
.addFile('/qux.txt', 'this is qux')
|
.addFile('/qux.txt', 'this is qux')
|
||||||
.addFile('/quux.txt', 'this is quux')
|
.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'})
|
.addUnhashedFile('/unhashed/a.txt', 'this is unhashed', {'Cache-Control': 'max-age=10'})
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@ -34,6 +37,9 @@ const distUpdate =
|
|||||||
.addFile('/baz.txt', 'this is baz v2')
|
.addFile('/baz.txt', 'this is baz v2')
|
||||||
.addFile('/qux.txt', 'this is qux v2')
|
.addFile('/qux.txt', 'this is qux v2')
|
||||||
.addFile('/quux.txt', 'this is quux 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('/unhashed/a.txt', 'this is unhashed v2', {'Cache-Control': 'max-age=10'})
|
||||||
.addUnhashedFile('/ignored/file1', 'this is not handled by the SW')
|
.addUnhashedFile('/ignored/file1', 'this is not handled by the SW')
|
||||||
.addUnhashedFile('/ignored/dir/file2', 'this is not handled by the SW either')
|
.addUnhashedFile('/ignored/dir/file2', 'this is not handled by the SW either')
|
||||||
@ -92,7 +98,12 @@ const manifest: Manifest = {
|
|||||||
name: 'lazy_prefetch',
|
name: 'lazy_prefetch',
|
||||||
installMode: 'lazy',
|
installMode: 'lazy',
|
||||||
updateMode: 'prefetch',
|
updateMode: 'prefetch',
|
||||||
urls: ['/quux.txt'],
|
urls: [
|
||||||
|
'/quux.txt',
|
||||||
|
'/quuux.txt',
|
||||||
|
'/lazy/unchanged1.txt',
|
||||||
|
'/lazy/unchanged2.txt',
|
||||||
|
],
|
||||||
patterns: [],
|
patterns: [],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -134,7 +145,12 @@ const manifestUpdate: Manifest = {
|
|||||||
name: 'lazy_prefetch',
|
name: 'lazy_prefetch',
|
||||||
installMode: 'lazy',
|
installMode: 'lazy',
|
||||||
updateMode: 'prefetch',
|
updateMode: 'prefetch',
|
||||||
urls: ['/quux.txt'],
|
urls: [
|
||||||
|
'/quux.txt',
|
||||||
|
'/quuux.txt',
|
||||||
|
'/lazy/unchanged1.txt',
|
||||||
|
'/lazy/unchanged2.txt',
|
||||||
|
],
|
||||||
patterns: [],
|
patterns: [],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -528,14 +544,45 @@ const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate));
|
|||||||
async_it('prefetches updates to lazy cache when set', async() => {
|
async_it('prefetches updates to lazy cache when set', async() => {
|
||||||
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo');
|
||||||
await driver.initialized;
|
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);
|
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');
|
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();
|
serverUpdate.clearRequests();
|
||||||
|
|
||||||
|
// Update client.
|
||||||
await driver.updateClient(await scope.clients.get('default'));
|
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();
|
serverUpdate.assertNoOtherRequests();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
15
packages/types.d.ts
vendored
15
packages/types.d.ts
vendored
@ -19,3 +19,18 @@
|
|||||||
|
|
||||||
declare let isNode: boolean;
|
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,
|
symbols.timeSeparator,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (symbols.currencyDecimal) {
|
if (symbols.currencyDecimal || symbols.currencyGroup) {
|
||||||
symbolValues.push(symbols.currencyDecimal);
|
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,
|
Emulated = 0,
|
||||||
Native = 1,
|
Native = 1,
|
||||||
None = 2,
|
None = 2,
|
||||||
|
ShadowDom = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare abstract class ViewRef extends ChangeDetectorRef {
|
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;
|
enableTracing?: boolean;
|
||||||
errorHandler?: ErrorHandler;
|
errorHandler?: ErrorHandler;
|
||||||
initialNavigation?: InitialNavigation;
|
initialNavigation?: InitialNavigation;
|
||||||
|
malformedUriErrorHandler?: (error: URIError, urlSerializer: UrlSerializer, url: string) => UrlTree;
|
||||||
onSameUrlNavigation?: 'reload' | 'ignore';
|
onSameUrlNavigation?: 'reload' | 'ignore';
|
||||||
paramsInheritanceStrategy?: 'emptyOnly' | 'always';
|
paramsInheritanceStrategy?: 'emptyOnly' | 'always';
|
||||||
preloadingStrategy?: any;
|
preloadingStrategy?: any;
|
||||||
@ -311,6 +312,7 @@ export declare class Router {
|
|||||||
config: Routes;
|
config: Routes;
|
||||||
errorHandler: ErrorHandler;
|
errorHandler: ErrorHandler;
|
||||||
readonly events: Observable<Event>;
|
readonly events: Observable<Event>;
|
||||||
|
malformedUriErrorHandler: (error: URIError, urlSerializer: UrlSerializer, url: string) => UrlTree;
|
||||||
navigated: boolean;
|
navigated: boolean;
|
||||||
onSameUrlNavigation: 'reload' | 'ignore';
|
onSameUrlNavigation: 'reload' | 'ignore';
|
||||||
paramsInheritanceStrategy: 'emptyOnly' | 'always';
|
paramsInheritanceStrategy: 'emptyOnly' | 'always';
|
||||||
|
@ -15,6 +15,7 @@ def js_expected_symbol_test(name, src, golden, **kwargs):
|
|||||||
"""
|
"""
|
||||||
all_data = [src, golden]
|
all_data = [src, golden]
|
||||||
all_data += [Label("//tools/symbol-extractor:lib")]
|
all_data += [Label("//tools/symbol-extractor:lib")]
|
||||||
|
all_data += [Label("@bazel_tools//tools/bash/runfiles")]
|
||||||
entry_point = "angular/tools/symbol-extractor/cli.js"
|
entry_point = "angular/tools/symbol-extractor/cli.js"
|
||||||
|
|
||||||
nodejs_test(
|
nodejs_test(
|
||||||
|
@ -24,6 +24,7 @@ def ts_api_guardian_test(name, golden, actual, data = [], **kwargs):
|
|||||||
data += [
|
data += [
|
||||||
"//tools/ts-api-guardian:lib",
|
"//tools/ts-api-guardian:lib",
|
||||||
"//tools/ts-api-guardian:bin/ts-api-guardian",
|
"//tools/ts-api-guardian:bin/ts-api-guardian",
|
||||||
|
"@bazel_tools//tools/bash/runfiles",
|
||||||
]
|
]
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
|
Reference in New Issue
Block a user