Compare commits

..

30 Commits
5.0.2 ... 5.0.3

Author SHA1 Message Date
65a40e659b docs: add changelog for 5.0.3 2017-11-22 13:09:12 -08:00
215832d8c6 release: cut the 5.0.3 release 2017-11-22 13:08:11 -08:00
686c4efe6d docs(aio): add ngATL header (#20590)
Fix #20568

PR Close #20590
2017-11-22 15:04:09 -06:00
efce39605b docs(aio): add a French onsite training ressource (#19889)
PR Close #19889
2017-11-22 10:49:12 -06:00
b567f9cc21 docs(aio): add a French onsite training ressource (#19889)
PR Close #19889
2017-11-22 10:49:12 -06:00
4ff60c3562 docs(aio): fix filename (#20569)
PR Close #20569
2017-11-22 10:48:57 -06:00
bc904b19a4 fix(common): return ISubscription from Location.subscribe() (#20429)
Fix #20406

PR Close #20429
2017-11-22 10:48:38 -06:00
135cf226bf ci: Update 1% payload size test (#20524)
PR Close #20524
2017-11-22 10:48:26 -06:00
0550383fc9 build(aio): filter out ambiguous directives from auto code linking (#20512)
Closes #20466

PR Close #20512
2017-11-22 10:48:19 -06:00
07699cbaec build(aio): do not store duplicate metadata aliases (#20512)
Having duplicates was causing the code auto-linking
to ignore `ngForm` directives.

PR Close #20512
2017-11-22 10:48:18 -06:00
e4bb077d27 build(aio): better parsing of selectors as aliases for directives/components (#20512)
PR Close #20512
2017-11-22 10:48:18 -06:00
27e7439c3b docs(aio): add angularfirebase.com to education resources (#20302)
PR Close #20302
2017-11-22 10:48:09 -06:00
66d160215e ci: update pullapprove (#20540)
PR Close #20540
2017-11-22 10:48:02 -06:00
0feba49c4b fix(core): fix #20532, should be able to cancel listener from mixed zone (#20538)
PR Close #20538
2017-11-22 10:47:53 -06:00
9ca6ee9eed fix(benchpress): Allow ignoring navigationStart events in perflog metric. (#20312)
PR Close #20312
2017-11-22 10:46:34 -06:00
a5a82296b7 docs(aio): fix typo in Attribute Directives documentation (#20143)
changed "appHightlight" to "appHighlight"

PR Close #20143
2017-11-22 10:45:41 -06:00
f9f2c20a57 fix(forms): updateOn should check if change occurred (#20358)
Fixes #20259.

PR Close #20358
2017-11-22 10:45:22 -06:00
662422a67e docs: NgModule guide prose for CLI (partial) (#19776)
Also replaces “Angular Module” with “NgModule” wherever that is clarifying.
Continue using “module” when qualified as in “feature module”, “root module”, “routing module”, etc.

PR Close #19776
2017-11-22 10:44:58 -06:00
b53ead4c45 fix(compiler): support event bindings in fullTemplateTypeCheck (#20490)
The type-check block now disables type checking event access instead
of generating a reference to an undefined variable.

PR Close #20490
2017-11-21 10:47:08 -06:00
d0abfa3acc docs: update the triaging doc with the latest process (#20128)
PR Close #20128
2017-11-21 10:43:28 -06:00
3bf36e9492 docs: add description for target labels + add LTS target label (#20128)
PR Close #20128
2017-11-21 10:43:28 -06:00
82aace63ea fix(core): should support event.stopImmediatePropagation (#20469)
PR Close #20469
2017-11-17 18:19:34 -06:00
15795d09cf fix(animations): validate against trigger() names that use @ symbols (#20326)
PR Close #20326
2017-11-17 18:19:28 -06:00
8ddbed8f7b build(aio): tighten up code autolinking (#20468)
Do not match code "words" that are part of a hyphenated
string of characters: e.g. `platform-browser-dynamic` should
not auto-link `browser`.

Do not match code "words" that correspond to pipe names
but are not preceded by a pipe `|` character. E.g. `package.json` should
not auto link `json` to the `JsonPipe`.

Closes #20187

PR Close #20468
2017-11-17 18:19:22 -06:00
81f1d42328 fix(compiler): emit correct type-check-blocks with TemplateRef's (#20463)
The type-check block generated with `"fullTemplateTypeCheck"` was
invalid if the it contained a template ref as would be generated
using the `else` micro-syntax of `NgIf`.

Fixes: #19485

PR Close #20463
2017-11-17 18:19:13 -06:00
3df1542c87 docs(aio): fix a typo to improve readability (#20435)
Remove a comma and space

PR Close #20435
2017-11-17 18:19:06 -06:00
8e1e7faef4 docs(aio): Clearing array with [] (#20369) (#20395)
Convert remaining references to directly use LoggerServices logs.

PR Close #20395
2017-11-17 18:18:59 -06:00
27f8c69d8a docs(aio): Removing reference to LoggerService property (#20369) (#20395)
The reference removed so that calling LoggerService clear() method
behaves as intended in SpyComponent.

PR Close #20395
2017-11-17 18:18:59 -06:00
5d0dd97402 docs(aio): Clearing array with [] (#20369) (#20395)
Clearing array with setting length to 0 replaced with [] for being short
and marginally efficient. For reference: [] is turned into a sequence of
around 15 machine instructions on x64 (if bump pointer allocation succeeds),
whereas a.length=0 is a C++ runtime function call, which requires 10-100x as
many instructions.
Benedikt Meurer

PR Close #20395
2017-11-17 18:18:59 -06:00
814f06289b fix(animations): always fire inner trigger callbacks even if blocked by parent animations (#19753)
Closes #19100

PR Close #19753
2017-11-17 18:17:46 -06:00
113 changed files with 2275 additions and 1671 deletions

View File

@ -22,7 +22,6 @@
# petebacondarwin - Pete Bacon Darwin
# pkozlowski-opensource - Pawel Kozlowski
# robwormald - Rob Wormald
# tbosch - Tobias Bosch
# tinayuangao - Tina Gao
# vicb - Victor Berchet
# vikerman - Vikram Subramanian
@ -100,7 +99,6 @@ groups:
users:
- alexeagle
- mhevery
- tbosch
- vicb
- IgorMinar #fallback
@ -109,8 +107,7 @@ groups:
files:
- "packages/core/*"
users:
- tbosch #primary
- chuckjaz
- chuckjaz #primary
- mhevery
- vicb
- IgorMinar #fallback
@ -132,7 +129,7 @@ groups:
- "packages/compiler/src/i18n/*"
users:
- vicb #primary
- tbosch
- chuckjaz
- IgorMinar #fallback
- mhevery #fallback
@ -141,9 +138,8 @@ groups:
files:
- "packages/compiler/*"
users:
- tbosch #primary
- chuckjaz #primary
- vicb
- chuckjaz
- mhevery
- IgorMinar #fallback
@ -163,12 +159,11 @@ groups:
- "packages/compiler-cli/*"
- "packages/bazel/*"
exclude:
- "packages/compiler-cli/src/ngtools*"
- "packages/compiler-cli/src/ngtools*"
users:
- alexeagle
- chuckjaz
- vicb
- tbosch
- IgorMinar #fallback
- mhevery #fallback
@ -212,7 +207,7 @@ groups:
- "packages/language-service/*"
users:
- chuckjaz #primary
- tbosch #secondary
# needs secondary
- vicb
- IgorMinar #fallback
- mhevery #fallback
@ -242,8 +237,8 @@ groups:
files:
- "packages/platform-browser/*"
users:
- tbosch #primary
- vicb #secondary
- vicb #primary
# needs secondary
- IgorMinar #fallback
- mhevery #fallback
@ -253,9 +248,9 @@ groups:
- "packages/platform-server/*"
users:
- vikerman #primary
# needs secondary
- alxhub
- vicb
- tbosch
- IgorMinar #fallback
- mhevery #fallback
@ -265,7 +260,7 @@ groups:
- "packages/platform-webworker/*"
users:
- vicb #primary
- tbosch #secondary
# needs secondary
- IgorMinar #fallback
- mhevery #fallback
@ -284,7 +279,7 @@ groups:
files:
- "packages/benchpress/*"
users:
- tbosch #primary
- alxhub #primary
# needs secondary
- IgorMinar #fallback
- mhevery #fallback

View File

@ -1,3 +1,21 @@
<a name="5.0.3"></a>
## [5.0.3](https://github.com/angular/angular/compare/5.0.2...5.0.3) (2017-11-22)
### Bug Fixes
* **animations:** always fire inner trigger callbacks even if blocked by parent animations ([#19753](https://github.com/angular/angular/issues/19753)) ([814f062](https://github.com/angular/angular/commit/814f062)), closes [#19100](https://github.com/angular/angular/issues/19100)
* **animations:** validate against trigger() names that use @ symbols ([#20326](https://github.com/angular/angular/issues/20326)) ([15795d0](https://github.com/angular/angular/commit/15795d0))
* **benchpress:** Allow ignoring navigationStart events in perflog metric. ([#20312](https://github.com/angular/angular/issues/20312)) ([9ca6ee9](https://github.com/angular/angular/commit/9ca6ee9))
* **common:** return ISubscription from Location.subscribe() ([#20429](https://github.com/angular/angular/issues/20429)) ([bc904b1](https://github.com/angular/angular/commit/bc904b1)), closes [#20406](https://github.com/angular/angular/issues/20406)
* **compiler:** emit correct type-check-blocks with TemplateRef's ([#20463](https://github.com/angular/angular/issues/20463)) ([81f1d42](https://github.com/angular/angular/commit/81f1d42))
* **compiler:** support event bindings in `fullTemplateTypeCheck` ([#20490](https://github.com/angular/angular/issues/20490)) ([b53ead4](https://github.com/angular/angular/commit/b53ead4))
* **core:** fix [#20532](https://github.com/angular/angular/issues/20532), should be able to cancel listener from mixed zone ([#20538](https://github.com/angular/angular/issues/20538)) ([0feba49](https://github.com/angular/angular/commit/0feba49))
* **core:** should support event.stopImmediatePropagation ([#20469](https://github.com/angular/angular/issues/20469)) ([82aace6](https://github.com/angular/angular/commit/82aace6))
* **forms:** updateOn should check if change occurred ([#20358](https://github.com/angular/angular/issues/20358)) ([f9f2c20](https://github.com/angular/angular/commit/f9f2c20)), closes [#20259](https://github.com/angular/angular/issues/20259)
<a name="5.0.2"></a>
## [5.0.2](https://github.com/angular/angular/compare/5.0.1...5.0.2) (2017-11-16)

View File

@ -1,14 +1,14 @@
<!-- #docregion -->
<h1>My First Attribute Directive</h1>
<!-- #docregion applied -->
<p appHightlight>Highlight me!</p>
<p appHighlight>Highlight me!</p>
<!-- #enddocregion applied, -->
<!-- #docregion color-1 -->
<p appHightlight highlightColor="yellow">Highlighted in yellow</p>
<p appHightlight [highlightColor]="'orange'">Highlighted in orange</p>
<p appHighlight highlightColor="yellow">Highlighted in yellow</p>
<p appHighlight [highlightColor]="'orange'">Highlighted in orange</p>
<!-- #enddocregion color-1 -->
<!-- #docregion color-2 -->
<p appHightlight [highlightColor]="color">Highlighted with parent component's color</p>
<p appHighlight [highlightColor]="color">Highlighted with parent component's color</p>
<!-- #enddocregion color-2 -->

View File

@ -93,22 +93,20 @@ export class AfterContentComponent implements AfterContentChecked, AfterContentI
<h4>-- AfterContent Logs --</h4>
<p><button (click)="reset()">Reset</button></p>
<div *ngFor="let msg of logs">{{msg}}</div>
<div *ngFor="let msg of logger.logs">{{msg}}</div>
</div>
`,
styles: ['.parent {background: burlywood}'],
providers: [LoggerService]
})
export class AfterContentParentComponent {
logs: string[];
show = true;
constructor(private logger: LoggerService) {
this.logs = logger.logs;
constructor(public logger: LoggerService) {
}
reset() {
this.logs.length = 0;
this.logger.clear();
// quickly remove and reload AfterContentComponent which recreates it
this.show = false;
this.logger.tick_then(() => this.show = true);

View File

@ -95,22 +95,20 @@ export class AfterViewComponent implements AfterViewChecked, AfterViewInit {
<h4>-- AfterView Logs --</h4>
<p><button (click)="reset()">Reset</button></p>
<div *ngFor="let msg of logs">{{msg}}</div>
<div *ngFor="let msg of logger.logs">{{msg}}</div>
</div>
`,
styles: ['.parent {background: burlywood}'],
providers: [LoggerService]
})
export class AfterViewParentComponent {
logs: string[];
show = true;
constructor(private logger: LoggerService) {
this.logs = logger.logs;
constructor(public logger: LoggerService) {
}
reset() {
this.logs.length = 0;
this.logger.clear();
// quickly remove and reload AfterViewComponent which recreates it
this.show = false;
this.logger.tick_then(() => this.show = true);

View File

@ -27,7 +27,7 @@ export class MyCounterComponent implements OnChanges {
// Empty the changeLog whenever counter goes to zero
// hint: this is a way to respond programmatically to external value changes.
if (this.counter === 0) {
this.changeLog.length = 0;
this.changeLog = [];
}
// A change to `counter` is the only change we care about

View File

@ -68,7 +68,7 @@ export class DoCheckComponent implements DoCheck {
reset() {
this.changeDetected = true;
this.changeLog.length = 0;
this.changeLog = [];
}
}

View File

@ -18,7 +18,7 @@ export class LoggerService {
}
}
clear() { this.logs.length = 0; }
clear() { this.logs = []; }
// schedules a view refresh to ensure display catches up
tick() { this.tick_then(() => { }); }

View File

@ -43,7 +43,7 @@ export class OnChangesComponent implements OnChanges {
}
// #enddocregion ng-on-changes
reset() { this.changeLog.length = 0; }
reset() { this.changeLog = []; }
}
/***************************************/

View File

@ -12,5 +12,5 @@
</div>
<!-- #enddocregion template -->
<h4>-- Spy Lifecycle Hook Log --</h4>
<div *ngFor="let msg of spyLog">{{msg}}</div>
<div *ngFor="let msg of logger.logs">{{msg}}</div>
</div>

View File

@ -15,10 +15,8 @@ import { LoggerService } from './logger.service';
export class SpyParentComponent {
newName = 'Herbie';
heroes: string[] = ['Windstorm', 'Magneta'];
spyLog: string[];
constructor(private logger: LoggerService) {
this.spyLog = logger.logs;
constructor(public logger: LoggerService) {
}
addHero() {
@ -34,7 +32,7 @@ export class SpyParentComponent {
}
reset() {
this.logger.log('-- reset --');
this.heroes.length = 0;
this.heroes = [];
this.logger.tick();
}
}

View File

@ -14,7 +14,7 @@
"app/contact/contact.component.html",
"app/contact/contact.component.3.ts",
"app/contact/contact.service.ts",
"app/contact/highlight.directive.ts",
"app/contact/contact-highlight.directive.ts",
"main.1b.ts",
"styles.css",

View File

@ -16,7 +16,7 @@
"app/contact/awesome.pipe.ts",
"app/contact/contact.component.3.ts",
"app/contact/contact.module.2.ts",
"app/contact/highlight.directive.ts",
"app/contact/contact-highlight.directive.ts",
"main.2.ts",
"styles.css",

View File

@ -15,7 +15,7 @@ describe('NgModule', function () {
return {
title: element.all(by.tagName('h1')).get(0),
subtitle: element.all(by.css('app-title p i')).get(0),
welcome: element.all(by.css('app-title p i')).get(0),
contactButton: buttons.get(0),
crisisButton: buttons.get(1),
heroesButton: buttons.get(2)
@ -67,7 +67,7 @@ describe('NgModule', function () {
it('should welcome us', function () {
const commons = getCommonsSectionStruct();
expect(commons.subtitle.getText()).toBe('Welcome, ' + (name || 'Sherlock Holmes'));
expect(commons.welcome.getText()).toBe('Welcome, ' + (name || 'Sherlock Holmes'));
});
};
}

View File

@ -19,7 +19,7 @@
"app/contact/contact.component.3.ts",
"app/contact/contact.module.3.ts",
"app/contact/contact-routing.module.3.ts",
"app/contact/highlight.directive.ts",
"app/contact/contact-highlight.directive.ts",
"app/crisis/*.ts",

View File

@ -1,14 +1,19 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
export const routes: Routes = [
import { ContactModule } from './contact/contact.module.3';
const routes: Routes = [
{ path: '', redirectTo: 'contact', pathMatch: 'full'},
{ path: 'crisis', loadChildren: 'app/crisis/crisis.module#CrisisModule' },
{ path: 'heroes', loadChildren: 'app/hero/hero.module.3#HeroModule' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
imports: [
ContactModule,
RouterModule.forRoot(routes)
],
exports: [RouterModule]
})
export class AppRoutingModule {}

View File

@ -2,18 +2,29 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
export const routes: Routes = [
import { ContactModule } from './contact/contact.module';
// #docregion routes
const routes: Routes = [
{ path: '', redirectTo: 'contact', pathMatch: 'full'},
// #docregion lazy-routes
// #docregion lazy-routes
{ path: 'crisis', loadChildren: 'app/crisis/crisis.module#CrisisModule' },
{ path: 'heroes', loadChildren: 'app/hero/hero.module#HeroModule' }
// #enddocregion lazy-routes
// #enddocregion lazy-routes
];
// #enddocregion routes
// #docregion forRoot
@NgModule({
imports: [RouterModule.forRoot(routes)],
// #docregion imports
imports: [
ContactModule,
// #docregion forRoot
RouterModule.forRoot(routes),
// #enddocregion forRoot
],
// #enddocregion imports
// #docregion exports
exports: [RouterModule]
// #enddocregion exports
})
export class AppRoutingModule {}
// #enddocregion forRoot

View File

@ -6,5 +6,5 @@ import { Component } from '@angular/core';
template: '<h1>{{title}}</h1>',
})
export class AppComponent {
title = 'Minimal NgModule';
title = 'Angular Modules';
}

View File

@ -11,9 +11,7 @@ import { Component } from '@angular/core';
// #enddocregion template
*/
// #docregion
template: '<app-title [subtitle]="subtitle"></app-title>'
template: '<app-title></app-title>'
})
export class AppComponent {
subtitle = '(v1)';
}
export class AppComponent {}
// #enddocregion

View File

@ -5,11 +5,9 @@ import { Component } from '@angular/core';
selector: 'app-root',
// #docregion template
template: `
<app-title [subtitle]="subtitle"></app-title>
<app-title></app-title>
<app-contact></app-contact>
`
// #enddocregion template
})
export class AppComponent {
subtitle = '(v1)';
}
export class AppComponent {}

View File

@ -3,10 +3,8 @@ import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<app-title [subtitle]="subtitle"></app-title>
<app-title></app-title>
<app-contact></app-contact>
`
})
export class AppComponent {
subtitle = '(v2)';
}
export class AppComponent {}

View File

@ -4,7 +4,7 @@ import { Component } from '@angular/core';
selector: 'app-root',
// #docregion template
template: `
<app-title [subtitle]="subtitle"></app-title>
<app-title></app-title>
<nav>
<a routerLink="contact" routerLinkActive="active">Contact</a>
<a routerLink="crisis" routerLinkActive="active">Crisis Center</a>
@ -14,6 +14,4 @@ import { Component } from '@angular/core';
`
// #enddocregion template
})
export class AppComponent {
subtitle = '(v3)';
}
export class AppComponent {}

View File

@ -5,7 +5,7 @@ import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<app-title [subtitle]="subtitle"></app-title>
<app-title></app-title>
<nav>
<a routerLink="contact" routerLinkActive="active">Contact</a>
<a routerLink="crisis" routerLinkActive="active">Crisis Center</a>
@ -14,6 +14,4 @@ import { Component } from '@angular/core';
<router-outlet></router-outlet>
`
})
export class AppComponent {
subtitle = '(Final)';
}
export class AppComponent {}

View File

@ -1,17 +1,7 @@
// #docplaster
// #docregion
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import
// #enddocregion
{ AppComponent } from './app.component.0';
/*
// #docregion
{ AppComponent } from './app.component';
// #enddocregion
*/
// #docregion
import { AppComponent } from './app.component.0';
@NgModule({
// #docregion imports

View File

@ -1,14 +1,15 @@
// #docplaster
// #docregion
/* Angular Imports */
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import
/* App Imports */
// #enddocregion
{ AppComponent } from './app.component.1';
import { AppComponent } from './app.component.1';
/*
// #docregion
{ AppComponent } from './app.component';
import { AppComponent } from './app.component';
// #enddocregion
*/
// #docregion
@ -21,12 +22,9 @@ import { FormsModule } from '@angular/forms';
import { AwesomePipe } from './contact/awesome.pipe';
import { ContactComponent } from './contact/contact.component.3';
// #docregion import-contact-directive
import {
HighlightDirective as ContactHighlightDirective
} from './contact/highlight.directive';
// #enddocregion import-contact-directive
ContactHighlightDirective as ContactHighlightDirective
} from './contact/contact-highlight.directive';
@NgModule({
// #docregion imports

View File

@ -1,15 +1,16 @@
// #docplaster
// #docregion
/* Angular Imports */
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
/* App Root */
import
/* App Imports */
// #enddocregion
{ AppComponent } from './app.component.1b';
import { AppComponent } from './app.component.1b';
/*
// #docregion
{ AppComponent } from './app.component';
import { AppComponent } from './app.component';
// #enddocregion
*/
// #docregion
@ -18,25 +19,17 @@ import { TitleComponent } from './title.component';
import { UserService } from './user.service';
/* Contact Imports */
import
// #enddocregion
{ ContactComponent } from './contact/contact.component.3';
import { ContactComponent } from './contact/contact.component.3';
/*
// #docregion
{ ContactComponent } from './contact/contact.component';
import { ContactComponent } from './contact/contact.component';
// #enddocregion
*/
// #docregion
import { ContactService } from './contact/contact.service';
import { AwesomePipe } from './contact/awesome.pipe';
// #docregion import-alias
import {
HighlightDirective as ContactHighlightDirective
} from './contact/highlight.directive';
// #enddocregion import-alias
import { FormsModule } from '@angular/forms';
import { AwesomePipe } from './contact/awesome.pipe';
import { ContactService } from './contact/contact.service';
import { ContactHighlightDirective } from './contact/contact-highlight.directive';
@NgModule({
imports: [ BrowserModule, FormsModule ],

View File

@ -1,15 +1,15 @@
// #docplaster
// #docregion
/* Angular Imports */
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
/* App Root */
import
/* App Imports */
// #enddocregion
{ AppComponent } from './app.component.2';
import { AppComponent } from './app.component.2';
/*
// #docregion
{ AppComponent } from './app.component';
import { AppComponent } from './app.component';
// #enddocregion
*/
// #docregion
@ -18,12 +18,11 @@ import { TitleComponent } from './title.component';
import { UserService } from './user.service';
/* Contact Imports */
import
// #enddocregion
{ ContactModule } from './contact/contact.module.2';
import { ContactModule } from './contact/contact.module.2';
/*
// #docregion
{ ContactModule } from './contact/contact.module';
import { ContactModule } from './contact/contact.module';
// #enddocregion
*/
// #docregion

View File

@ -1,25 +1,36 @@
// #docplaster
// #docregion
/* Angular Imports */
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
/* App Root */
/* App Imports */
// #enddocregion
import { AppComponent } from './app.component.3';
/*
// #docregion
import { AppComponent } from './app.component';
// #enddocregion
*/
// #docregion
import { HighlightDirective } from './highlight.directive';
import { TitleComponent } from './title.component';
import { UserService } from './user.service';
/* Feature Modules */
import { ContactModule } from './contact/contact.module.3';
/* Routing Module */
// #enddocregion
import { AppRoutingModule } from './app-routing.module.3';
/*
// #docregion
import { AppRoutingModule } from './app-routing.module';
// #enddocregion
*/
// #docregion
@NgModule({
// #docregion imports
imports: [
BrowserModule,
ContactModule,
AppRoutingModule
],
// #enddocregion imports

View File

@ -1,14 +1,14 @@
// #docplaster
// #docregion
// #docregion v4
/* Angular Imports */
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
/* App Root */
/* App Imports */
import { AppComponent } from './app.component';
/* Feature Modules */
import { ContactModule } from './contact/contact.module';
/* Core Modules */
import { CoreModule } from './core/core.module';
/* Routing Module */
@ -18,7 +18,6 @@ import { AppRoutingModule } from './app-routing.module';
// #docregion import-for-root
imports: [
BrowserModule,
ContactModule,
// #enddocregion v4
// #enddocregion import-for-root
/*

View File

@ -1,4 +1,4 @@
/* tslint:disable */
// #docplaster
// Same directive name and selector as
// HighlightDirective in parent AppModule
// It selects for both input boxes and 'highlight' attr
@ -7,12 +7,14 @@
// #docregion
import { Directive, ElementRef } from '@angular/core';
// Highlight the host element or any InputElement in blue
@Directive({ selector: '[highlight], input' })
/** Highlight the attached element or an InputElement in blue */
export class HighlightDirective {
export class ContactHighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = 'powderblue';
console.log(
`* Contact highlight called for ${el.nativeElement.tagName}`);
// #enddocregion
console.log(`* Contact highlight called for ${el.nativeElement.tagName}`);
// #docregion
}
}
// #enddocregion

View File

@ -3,10 +3,12 @@ import { RouterModule } from '@angular/router';
import { ContactComponent } from './contact.component.3';
const routes = [
{ path: 'contact', component: ContactComponent}
];
@NgModule({
imports: [RouterModule.forChild([
{ path: 'contact', component: ContactComponent}
])],
exports: [RouterModule]
imports: [ RouterModule.forChild(routes) ],
exports: [ RouterModule ]
})
export class ContactRoutingModule {}

View File

@ -4,11 +4,13 @@ import { RouterModule } from '@angular/router';
import { ContactComponent } from './contact.component';
// #docregion routing
const routes = [
{ path: 'contact', component: ContactComponent}
];
@NgModule({
imports: [RouterModule.forChild([
{ path: 'contact', component: ContactComponent }
])],
exports: [RouterModule]
imports: [ RouterModule.forChild(routes) ],
exports: [ RouterModule ]
})
export class ContactRoutingModule {}
// #enddocregion

View File

@ -21,7 +21,7 @@ export class ContactComponent implements OnInit {
}
ngOnInit() {
this.contactService.getContacts().then(contacts => {
this.contactService.getContacts().subscribe(contacts => {
this.msg = '';
this.contacts = contacts;
this.contact = contacts[0];

View File

@ -27,3 +27,6 @@
margin-bottom: 20px;
}
.button-group {
padding-top: 12px;
}

View File

@ -6,18 +6,32 @@
<!-- #docregion awesome -->
<h3 highlight>{{ contact.name | awesome }}</h3>
<!-- #enddocregion awesome -->
<div class="form-group">
<label for="name">Name</label>
<!-- #docregion ngModel -->
<input type="text" class="form-control" required
[(ngModel)]="contact.name"
name="name" #name="ngModel" >
name="name" #name="ngModel" >
<!-- #enddocregion ngModel -->
<div [hidden]="name.valid" class="alert alert-danger">
Name is required
</div>
</div>
<br>
<button type="submit" class="btn btn-default" [disabled]="!contactForm.form.valid">Save</button>
<button type="button" class="btn" (click)="next()" [disabled]="!contactForm.form.valid">Next Contact</button>
<button type="button" class="btn" (click)="newContact()">New Contact</button>
<div class="button-group">
<button type="submit" class="btn btn-default"
[disabled]="!contactForm.form.valid">
Save</button>
<button type="button" class="btn" (click)="next()"
[disabled]="!contactForm.form.valid">
Next Contact</button>
<button type="button" class="btn" (click)="newContact()">
New Contact</button>
</div>
</form>
<!-- #enddocregion -->

View File

@ -22,7 +22,7 @@ export class ContactComponent implements OnInit {
}
ngOnInit() {
this.contactService.getContacts().then(contacts => {
this.contactService.getContacts().subscribe(contacts => {
this.msg = '';
this.contacts = contacts;
this.contact = contacts[0];

View File

@ -0,0 +1,11 @@
// #docregion
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
imports: [
CommonModule
],
declarations: []
})
export class ContactModule { }

View File

@ -5,25 +5,32 @@ import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { AwesomePipe } from './awesome.pipe';
import
// #enddocregion
{ ContactComponent } from './contact.component.3';
import { ContactComponent } from './contact.component.3';
/*
// #docregion
{ ContactComponent } from './contact.component';
import { ContactComponent } from './contact.component';
// #enddocregion
*/
// #docregion
import { ContactHighlightDirective } from './contact-highlight.directive';
import { ContactService } from './contact.service';
import { HighlightDirective } from './highlight.directive';
// #docregion class
@NgModule({
imports: [ CommonModule, FormsModule ],
declarations: [ ContactComponent, HighlightDirective, AwesomePipe ],
exports: [ ContactComponent ],
providers: [ ContactService ]
imports: [
CommonModule,
FormsModule
],
declarations: [
AwesomePipe,
ContactComponent,
ContactHighlightDirective
],
// #docregion exports
exports: [ ContactComponent ],
// #enddocregion exports
providers: [ ContactService ]
})
export class ContactModule { }
// #enddocregion class

View File

@ -1,21 +1,43 @@
// #docplaster
// #docregion
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { AwesomePipe } from './awesome.pipe';
// #enddocregion
import { ContactComponent } from './contact.component.3';
/*
// #docregion
import { ContactComponent } from './contact.component';
// #enddocregion
*/
// #docregion
import { ContactHighlightDirective } from './contact-highlight.directive';
import { ContactService } from './contact.service';
import { HighlightDirective } from './highlight.directive';
// #enddocregion
import { ContactRoutingModule } from './contact-routing.module.3';
/*
// #docregion
import { ContactRoutingModule } from './contact-routing.module';
// #enddocregion
*/
// #docregion
// #docregion class
@NgModule({
imports: [ CommonModule, FormsModule, ContactRoutingModule ],
declarations: [ ContactComponent, HighlightDirective, AwesomePipe ],
providers: [ ContactService ]
imports: [
CommonModule,
FormsModule,
ContactRoutingModule
],
declarations: [
AwesomePipe,
ContactComponent,
ContactHighlightDirective
],
providers: [ ContactService ]
})
export class ContactModule { }
// #enddocregion class

View File

@ -8,7 +8,10 @@ import { ContactRoutingModule } from './contact-routing.module';
// #docregion class
@NgModule({
imports: [ SharedModule, ContactRoutingModule ],
imports: [
SharedModule,
ContactRoutingModule
],
declarations: [ ContactComponent ],
providers: [ ContactService ]
})

View File

@ -1,5 +1,10 @@
// #docplaster
// #docregion
import { Injectable } from '@angular/core';
import { Injectable, OnDestroy } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { delay } from 'rxjs/operators';
export class Contact {
constructor(public id: number, public name: string) { }
@ -13,17 +18,21 @@ const CONTACTS: Contact[] = [
const FETCH_LATENCY = 500;
/** Simulate a data service that retrieves contacts from a server */
@Injectable()
export class ContactService {
export class ContactService implements OnDestroy {
// #enddocregion
constructor() { console.log('ContactService instance created.'); }
ngOnDestroy() { console.log('ContactService instance destroyed.'); }
getContacts() {
return new Promise<Contact[]>(resolve => {
setTimeout(() => { resolve(CONTACTS); }, FETCH_LATENCY);
});
// #docregion
getContacts(): Observable<Contact[]> {
return of(CONTACTS).pipe(delay(FETCH_LATENCY));
}
getContact(id: number | string) {
return this.getContacts()
.then(heroes => heroes.find(hero => hero.id === +id));
getContact(id: number | string): Observable<Contact> {
return of(CONTACTS.find(contact => contact.id === +id))
.pipe(delay(FETCH_LATENCY));
}
}
// #enddocregion

View File

@ -1,5 +1,5 @@
<!-- Exact copy from earlier app.component.html -->
<h1 highlight>{{title}} {{subtitle}}</h1>
<h1 highlight>{{title}}</h1>
<p *ngIf="user">
<i>Welcome, {{user}}</i>
<p>

View File

@ -7,7 +7,6 @@ import { UserService } from '../core/user.service';
templateUrl: './title.component.html',
})
export class TitleComponent {
@Input() subtitle = '';
title = 'Angular Modules';
user = '';

View File

@ -1,22 +1,21 @@
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Crisis,
CrisisService } from './crisis.service';
CrisisService } from './crisis.service';
@Component({
template: `
<h3 highlight>Crisis List</h3>
<div *ngFor='let crisis of crisises | async'>
<div *ngFor='let crisis of crises | async'>
<a routerLink="{{'../' + crisis.id}}">{{crisis.id}} - {{crisis.name}}</a>
</div>
`
})
export class CrisisListComponent implements OnInit {
crisises: Promise<Crisis[]>;
export class CrisisListComponent {
crises: Observable<Crisis[]>;
constructor(private crisisService: CrisisService) { }
ngOnInit() {
this.crisises = this.crisisService.getCrises();
constructor(private crisisService: CrisisService) {
this.crises = this.crisisService.getCrises();
}
}

View File

@ -1,4 +1,8 @@
import { Injectable } from '@angular/core';
import { Injectable, OnDestroy } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { delay } from 'rxjs/operators';
export class Crisis {
constructor(public id: number, public name: string) { }
@ -13,18 +17,18 @@ const CRISES: Crisis[] = [
const FETCH_LATENCY = 500;
/** Simulate a data service that retrieves crises from a server */
@Injectable()
export class CrisisService {
export class CrisisService implements OnDestroy {
constructor() { console.log('CrisisService instance created.'); }
ngOnDestroy() { console.log('CrisisService instance destroyed.'); }
getCrises() {
return new Promise<Crisis[]>(resolve => {
setTimeout(() => { resolve(CRISES); }, FETCH_LATENCY);
});
getCrises(): Observable<Crisis[]> {
return of(CRISES).pipe(delay(FETCH_LATENCY));
}
getCrisis(id: number | string) {
return this.getCrises()
.then(heroes => heroes.find(hero => hero.id === +id));
getCrisis(id: number | string): Observable<Crisis> {
return of(CRISES.find(crisis => crisis.id === +id))
.pipe(delay(FETCH_LATENCY));
}
}

View File

@ -26,6 +26,6 @@ export class HeroDetailComponent implements OnInit {
ngOnInit() {
let id = parseInt(this.route.snapshot.paramMap.get('id'), 10);
this.heroService.getHero(id).then(hero => this.hero = hero);
this.heroService.getHero(id).subscribe(hero => this.hero = hero);
}
}

View File

@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Hero,
HeroService } from './hero.service';
@ -11,11 +12,9 @@ import { Hero,
</div>
`
})
export class HeroListComponent implements OnInit {
heroes: Promise<Hero[]>;
constructor(private heroService: HeroService) { }
ngOnInit() {
export class HeroListComponent {
heroes: Observable<Hero[]>;
constructor(private heroService: HeroService) {
this.heroes = this.heroService.getHeroes();
}
}

View File

@ -5,9 +5,10 @@ import { FormsModule } from '@angular/forms';
import { HeroComponent } from './hero.component.3';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroListComponent } from './hero-list.component';
import { HighlightDirective } from './highlight.directive';
import { HeroRoutingModule } from './hero-routing.module.3';
import { HighlightDirective } from './highlight.directive';
// #docregion class
@NgModule({
imports: [ CommonModule, FormsModule, HeroRoutingModule ],

View File

@ -1,4 +1,8 @@
import { Injectable } from '@angular/core';
import { Injectable, OnDestroy } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { delay } from 'rxjs/operators';
export class Hero {
constructor(public id: number, public name: string) { }
@ -15,18 +19,19 @@ const HEROES: Hero[] = [
const FETCH_LATENCY = 500;
/** Simulate a data service that retrieves heroes from a server */
@Injectable()
export class HeroService {
export class HeroService implements OnDestroy {
getHeroes() {
return new Promise<Hero[]>(resolve => {
setTimeout(() => { resolve(HEROES); }, FETCH_LATENCY);
});
constructor() { console.log('HeroService instance created.'); }
ngOnDestroy() { console.log('HeroService instance destroyed.'); }
getHeroes(): Observable<Hero[]> {
return of(HEROES).pipe(delay(FETCH_LATENCY));
}
getHero(id: number | string) {
return this.getHeroes()
.then(heroes => heroes.find(hero => hero.id === +id));
getHero(id: number | string): Observable<Hero> {
return of(HEROES.find(hero => hero.id === +id))
.pipe(delay(FETCH_LATENCY));
}
}

View File

@ -1,12 +1,15 @@
// #docplaster
// #docregion
import { Directive, ElementRef } from '@angular/core';
// Highlight the host element in gold
@Directive({ selector: '[highlight]' })
/** Highlight the attached element in gold */
export class HighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = 'gold';
console.log(
`* AppRoot highlight called for ${el.nativeElement.tagName}`);
// #enddocregion
console.log(`* AppRoot highlight called for ${el.nativeElement.tagName}`);
// #docregion
}
}
// #enddocregion

View File

@ -1,9 +1,8 @@
/* tslint:disable */
// Exact copy of contact/highlight.directive except for color and message
import { Directive, ElementRef } from '@angular/core';
@Directive({ selector: '[highlight], input' })
/** Highlight the attached element or an InputElement in gray */
// Highlight the host element or any InputElement in gray
export class HighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = 'lightgray';

View File

@ -1,6 +1,6 @@
<!-- #docregion -->
<!-- #docregion v1 -->
<h1 highlight>{{title}} {{subtitle}}</h1>
<h1 highlight>{{title}}</h1>
<!-- #enddocregion v1 -->
<!-- #docregion ngIf -->
<p *ngIf="user">

View File

@ -1,18 +1,17 @@
// #docplaster
// #docregion
// #docregion v1
import { Component, Input } from '@angular/core';
import { Component } from '@angular/core';
// #enddocregion v1
import { UserService } from './user.service';
// #docregion v1
@Component({
selector: 'app-title',
templateUrl: './title.component.html',
templateUrl: './title.component.html'
})
export class TitleComponent {
@Input() subtitle = '';
title = 'NgModules';
title = 'Angular Modules';
// #enddocregion v1
user = '';

View File

@ -9,6 +9,6 @@ export class MessageService {
}
clear() {
this.messages.length = 0;
this.messages = [];
}
}

View File

@ -9,6 +9,6 @@ export class MessageService {
}
clear() {
this.messages.length = 0;
this.messages = [];
}
}

View File

@ -9,6 +9,6 @@ export class MessageService {
}
clear() {
this.messages.length = 0;
this.messages = [];
}
}

View File

@ -9,6 +9,6 @@ export class MessageService {
}
clear() {
this.messages.length = 0;
this.messages = [];
}
}

View File

@ -409,7 +409,7 @@ function; it can only contain a single `return` statement.
The Angular [`RouterModule`](api/router/RouterModule) exports two macro static methods, `forRoot` and `forChild`, to help declare root and child routes.
Review the [source code](https://github.com/angular/angular/blob/master/packages/router/src/router_module.ts#L139 "RouterModule.forRoot source code")
for these methods to see how macros can simplify configuration of complex Angular modules.
for these methods to see how macros can simplify configuration of complex [NgModules](guide/ngmodule).
### Metadata rewriting

View File

@ -160,7 +160,7 @@ Providing the `UserService` with an Angular module is a good choice.
<div class="l-sub-section">
To be precise, Angular module providers are registered with the root injector
_unless the module is_ [lazy loaded](guide/ngmodule#lazy-load).
_unless the module is_ [lazy loaded](guide/ngmodule#lazy-load-DI).
In this sample, all modules are _eagerly loaded_ when the application starts,
so all module providers are registered with the app's root injector.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -32,9 +32,9 @@
<!-- Announcement Bar -->
<div class="homepage-container">
<div class="announcement-bar">
<img src="generated/images/marketing/home/angular-connect.png">
<p>Join us in London for AngularConnect<br>November 7-8, 2017</p>
<a class="button" href="https://angularconnect.com/">Learn More</a>
<img src="generated/images/marketing/home/ng-atl.png">
<p>Join us in Atlanta for ngATL<br>Jan 30 - Feb 2, 2018</p>
<a class="button" href="http://ng-atl.org/">Learn More</a>
</div>
</div>

View File

@ -534,6 +534,12 @@
"rev": true,
"title": "Ultimate Angular",
"url": "https://ultimateangular.com/"
},
"angular-firebase": {
"desc": "Video lessons covering progressive web apps with Angular, Firebase, RxJS, and related APIs.",
"rev": true,
"title": "AngularFirebase.com",
"url": "https://angularfirebase.com/"
}
}
},
@ -621,6 +627,12 @@
"rev": true,
"title": "SFEIR School (French)",
"url": "https://school.sfeir.com/project/sa200/"
},
"formationjs": {
"desc": "Angular onsite training in Paris (France). Monthly Angular workshops and custom onsite classes. We are focused on Angular, so we are always up to date.",
"rev": true,
"title": "Formation JavaScript (French)",
"url": "https://formationjavascript.com/formation-angular/"
}
}
}

View File

@ -225,17 +225,17 @@
{
"title": "NgModules",
"tooltip": "Learn how to use NgModules to make your apps efficient.",
"tooltip": "Modularize your app with NgModules.",
"children": [
{
"url": "guide/ngmodule",
"title": "NgModules",
"tooltip": "Define application modules with @NgModule."
"tooltip": "Define application modules with the NgModule."
},
{
"url": "guide/ngmodule-faq",
"title": "NgModule FAQs",
"tooltip": "Answers to frequently asked questions about @NgModule."
"tooltip": "Answers to frequently asked questions about NgModules."
}
]},

View File

@ -77,7 +77,7 @@ Then define an array of routes with a single `route` to that component.
</code-example>
Once you've finished setting up, the router will match that URL to `path: 'heroes'`
and display the `HeroesComponent`, .
and display the `HeroesComponent`.
### _RouterModule.forRoot()_

View File

@ -98,6 +98,7 @@
"codelyzer": "~2.0.0",
"concurrently": "^3.4.0",
"cross-spawn": "^5.1.0",
"css-selector-parser": "^1.3.0",
"dgeni": "^0.4.7",
"dgeni-packages": "0.22.0",
"entities": "^1.1.1",

View File

@ -0,0 +1,3 @@
{
"aio":{"master":{"change":"application","gzip7":{"inline":925,"main":119519,"polyfills":11863},"gzip9":{"inline":925,"main":119301,"polyfills":11861},"uncompressed":{"inline":1533,"main":486493,"polyfills":37068}}}
}

View File

@ -1,14 +0,0 @@
#!/bin/bash
set -u -e -o pipefail
declare -A payloadLimits
payloadLimits["aio", "uncompressed", "inline"]=1600
payloadLimits["aio", "uncompressed", "main"]=487000
payloadLimits["aio", "uncompressed", "polyfills"]=38000
payloadLimits["aio", "gzip7", "inline"]=1000
payloadLimits["aio", "gzip7", "main"]=120000
payloadLimits["aio", "gzip7", "polyfills"]=11900
payloadLimits["aio", "gzip9", "inline"]=1000
payloadLimits["aio", "gzip9", "main"]=120000
payloadLimits["aio", "gzip9", "polyfills"]=11900

View File

@ -7,7 +7,6 @@ readonly parentDir=$(dirname $thisDir)
# Track payload size functions
source ../scripts/ci/payload-size.sh
source ${thisDir}/_payload-limits.sh
trackPayloadSize "aio" "dist/*.bundle.js" true true
trackPayloadSize "aio" "dist/*.bundle.js" true true "${thisDir}/_payload-limits.json"

View File

@ -84,7 +84,7 @@ This tool expects all the examples to be build with `npm run build`. If an examp
with that script, the author would need to specify the new build command in the `example-config.json`
as shown earlier.
### add-example-boilerplate.js
### example-boilerplate.js
This script installs all the dependencies that are shared among all the examples, creates the
`node_modules` symlinks and copy all the boilerplate files where needed. It won't do anything
@ -102,7 +102,7 @@ This script will find all the `e2e-spec.ts` files and run them.
To not run all tests, you can use the `--filter=name` flag to run the example's e2e that contains
that name.
It also has an optional `--setup` flag to run the `add-example-boilerplate.js` script and install
It also has an optional `--setup` flag to run the `example-boilerplate.js` script and install
the latest `webdriver`.
It will create a `/aio/protractor-results-txt` file when it finishes running tests.

View File

@ -1,3 +1,5 @@
const CssSelectorParser = require('css-selector-parser').CssSelectorParser;
const cssParser = new CssSelectorParser();
/**
* @dgProcessor addMetadataAliases
*
@ -28,11 +30,18 @@ module.exports = function addMetadataAliasesProcessor() {
};
function extractSelectors(selectors) {
if (selectors) {
return stripQuotes(selectors).split(',').map(selector => selector.replace(/^\W*([\w-]+)\W*$/, '$1'));
} else {
return [];
}
const selectorAST = cssParser.parse(stripQuotes(selectors));
const rules = selectorAST.selectors ? selectorAST.selectors.map(ruleSet => ruleSet.rule) : [selectorAST.rule];
const aliases = {};
rules.forEach(rule => {
if (rule.tagName) {
aliases[rule.tagName] = true;
}
if (rule.attrs) {
rule.attrs.forEach(attr => aliases[attr.name] = true);
}
});
return Object.keys(aliases);
}
function stripQuotes(value) {

View File

@ -45,8 +45,8 @@ describe('addSelectorsAsAliases processor', () => {
expect(docs[2].aliases).toEqual([docs[2].name]);
expect(docs[3].aliases).toEqual([docs[3].name]);
expect(docs[4].aliases).toEqual([docs[4].name, 'myPipe']);
expect(docs[5].aliases).toEqual([docs[5].name, 'my-directive', 'myDirective', 'my-directive']);
expect(docs[6].aliases).toEqual([docs[6].name, '[ngModel]:not([formControlName]):not([formControl])']);
expect(docs[5].aliases).toEqual([docs[5].name, 'my-directive', 'myDirective']);
expect(docs[6].aliases).toEqual([docs[6].name, 'ngModel']);
expect(docs[7].aliases).toEqual([docs[7].name, 'my-component']);
expect(docs[8].aliases).toEqual([docs[8].name]);
expect(docs[9].aliases).toEqual([docs[9].name]);

View File

@ -35,6 +35,8 @@ module.exports = new Package('angular-base', [
.factory('packageInfo', function() { return require(path.resolve(PROJECT_ROOT, 'package.json')); })
.factory(require('./readers/json'))
.factory(require('./services/copyFolder'))
.factory(require('./services/filterPipes'))
.factory(require('./services/filterAmbiguousDirectiveAliases'))
.factory(require('./services/getImageDimensions'))
.factory(require('./post-processors/add-image-dimensions'))
@ -126,8 +128,9 @@ module.exports = new Package('angular-base', [
})
.config(function(postProcessHtml, addImageDimensions, autoLinkCode) {
.config(function(postProcessHtml, addImageDimensions, autoLinkCode, filterPipes, filterAmbiguousDirectiveAliases) {
addImageDimensions.basePath = path.resolve(AIO_PATH, 'src');
autoLinkCode.customFilters = [filterPipes, filterAmbiguousDirectiveAliases];
postProcessHtml.plugins = [
require('./post-processors/autolink-headings'),
addImageDimensions,

View File

@ -10,12 +10,19 @@ const textContent = require('hast-util-to-string');
* Only docs that have one of these docTypes will be linked to.
* Usually set to the API exported docTypes, e.g. "class", "function", "directive", etc.
*
* @property customFilters array of functions `(docs, words, wordIndex) => docs` that will filter
* out docs where a word should not link to a doc.
* - `docs` is the array of docs that match the link `word`
* - `words` is the collection of words parsed from the code text
* - `wordIndex` is the index of the current `word` for which we are finding a link
*
* @property codeElements an array of strings.
* Only text contained in these elements will be linked to.
* Usually set to "code" but also "code-example" for angular.io.
*/
module.exports = function autoLinkCode(getDocFromAlias) {
autoLinkCodeImpl.docTypes = [];
autoLinkCodeImpl.customFilters = [];
autoLinkCodeImpl.codeElements = ['code'];
return autoLinkCodeImpl;
@ -38,12 +45,13 @@ module.exports = function autoLinkCode(getDocFromAlias) {
parent.children.splice(index, 1, createLinkNode(docs[0], node.value));
} else {
// Parse the text for words that we can convert to links
const nodes = textContent(node).split(/([A-Za-z0-9_]+)/)
const nodes = textContent(node).split(/([A-Za-z0-9_-]+)/)
.filter(word => word.length)
.map(word => {
const docs = getDocFromAlias(word);
return foundValidDoc(docs) ?
createLinkNode(docs[0], word) : // Create a link wrapping the text node.
.map((word, index, words) => {
// remove docs that fail the custom filter tests
const filteredDocs = autoLinkCodeImpl.customFilters.reduce((docs, filter) => filter(docs, words, index), getDocFromAlias(word));
return foundValidDoc(filteredDocs) ?
createLinkNode(filteredDocs[0], word) : // Create a link wrapping the text node.
{ type: 'text', value: word }; // this is just text so push a new text node
});

View File

@ -2,7 +2,7 @@ var createTestPackage = require('../../helpers/test-package');
var Dgeni = require('dgeni');
describe('autoLinkCode post-processor', () => {
let processor, autoLinkCode, aliasMap;
let processor, autoLinkCode, aliasMap, filterPipes;
beforeEach(() => {
const testPackage = createTestPackage('angular-base-package');
@ -14,6 +14,7 @@ describe('autoLinkCode post-processor', () => {
processor = injector.get('postProcessHtml');
processor.docTypes = ['test-doc'];
processor.plugins = [autoLinkCode];
filterPipes = injector.get('filterPipes');
});
it('should insert an anchor into every code item that matches the id of an API doc', () => {
@ -51,6 +52,26 @@ describe('autoLinkCode post-processor', () => {
expect(doc.renderedContent).toEqual('<code>MyClass</code>');
});
it('should ignore code items that match an API doc but are attached to other text via a dash', () => {
aliasMap.addDoc({ docType: 'class', id: 'MyClass', aliases: ['MyClass'], path: 'a/b/myclass' });
const doc = { docType: 'test-doc', renderedContent: '<code>xyz-MyClass</code>' };
processor.$process([doc]);
expect(doc.renderedContent).toEqual('<code>xyz-MyClass</code>');
});
it('should ignore code items that are filtered out by custom filters', () => {
autoLinkCode.customFilters = [filterPipes];
aliasMap.addDoc({ docType: 'pipe', id: 'MyClass', aliases: ['MyClass', 'myClass'], path: 'a/b/myclass', pipeOptions: { name: '\'myClass\'' } });
const doc = { docType: 'test-doc', renderedContent: '<code>{ xyz | myClass } { xyz|myClass } MyClass myClass OtherClass|MyClass</code>' };
processor.$process([doc]);
expect(doc.renderedContent).toEqual('<code>' +
'{ xyz | <a href="a/b/myclass" class="code-anchor">myClass</a> } ' +
'{ xyz|<a href="a/b/myclass" class="code-anchor">myClass</a> } ' +
'<a href="a/b/myclass" class="code-anchor">MyClass</a> ' +
'myClass OtherClass|<a href="a/b/myclass" class="code-anchor">MyClass</a>' +
'</code>');
});
it('should insert anchors for individual text nodes within a code block', () => {
aliasMap.addDoc({ docType: 'class', id: 'MyClass', aliases: ['MyClass'], path: 'a/b/myclass' });
const doc = { docType: 'test-doc', renderedContent: '<code><span>MyClass</span><span>MyClass</span></code>' };

View File

@ -0,0 +1,26 @@
/**
* This service is used by the autoLinkCode post-processor to filter out ambiguous directive
* docs where the matching word is a directive selector.
* E.g. `ngModel`, which is a selector for a number of directives, where we are only really
* interested in the `NgModel` class.
*/
module.exports = function filterAmbiguousDirectiveAliases() {
return (docs, words, index) => {
const word = words[index];
// we are only interested if there are multiple matching docs
if (docs.length > 1) {
if (docs.every(doc =>
// We are only interested if they are all either directives or components
(doc.docType === 'directive' || doc.docType === 'component') &&
// and the matching word is in the selector for all of them
doc[doc.docType + 'Options'].selector.indexOf(word) != -1
)) {
// find the directive whose class name matches the word (case-insensitive)
return docs.filter(doc => doc.name.toLowerCase() === word.toLowerCase());
}
}
return docs;
};
};

View File

@ -0,0 +1,50 @@
const filterAmbiguousDirectiveAliases = require('./filterAmbiguousDirectiveAliases')();
const words = ['Http', 'ngModel', 'NgModel', 'NgControlStatus'];
describe('filterAmbiguousDirectiveAliases(docs, words, index)', () => {
it('should not try to filter the docs, if the docs are not all directives or components', () => {
const docs = [
{ docType: 'class', name: 'Http' },
{ docType: 'directive', name: 'NgModel', directiveOptions: { selector: '[ngModel]' } },
{ docType: 'component', name: 'NgModel', componentOptions: { selector: '[ngModel]' } }
];
// take a copy to prove `docs` was not modified
const filteredDocs = docs.slice(0);
expect(filterAmbiguousDirectiveAliases(docs, words, 1)).toEqual(filteredDocs);
expect(filterAmbiguousDirectiveAliases(docs, words, 2)).toEqual(filteredDocs);
});
describe('(where all the docs are components or directives', () => {
describe('and do not all contain the matching word in their selector)', () => {
it('should not try to filter the docs', () => {
const docs = [
{ docType: 'directive', name: 'NgModel', ['directiveOptions']: { selector: '[ngModel]' } },
{ docType: 'component', name: 'NgControlStatus', ['componentOptions']: { selector: '[ngControlStatus]' } }
];
// take a copy to prove `docs` was not modified
const filteredDocs = docs.slice(0);
expect(filterAmbiguousDirectiveAliases(docs, words, 1)).toEqual(filteredDocs);
expect(filterAmbiguousDirectiveAliases(docs, words, 2)).toEqual(filteredDocs);
// Also test that the check is case-sensitive
docs[1].componentOptions.selector = '[ngModel]';
filteredDocs[1].componentOptions.selector = '[ngModel]';
expect(filterAmbiguousDirectiveAliases(docs, words, 2)).toEqual(filteredDocs);
});
});
describe('and do all contain the matching word in there selector)', () => {
it('should filter out docs whose class name is not (case-insensitively) equal to the matching word', () => {
const docs = [
{ docType: 'directive', name: 'NgModel', ['directiveOptions']: { selector: '[ngModel],[ngControlStatus]' } },
{ docType: 'component', name: 'NgControlStatus', ['componentOptions']: { selector: '[ngModel],[ngControlStatus]' } }
];
const filteredDocs = [
{ docType: 'directive', name: 'NgModel', ['directiveOptions']: { selector: '[ngModel],[ngControlStatus]' } }
];
expect(filterAmbiguousDirectiveAliases(docs, words, 1)).toEqual(filteredDocs);
});
});
});
});

View File

@ -0,0 +1,12 @@
/**
* This service is used by the autoLinkCode post-processors to filter out pipe docs
* where the matching word is the pipe name and is not preceded by a pipe
*/
module.exports = function filterPipes() {
return (docs, words, index) =>
docs.filter(doc =>
doc.docType !== 'pipe' ||
doc.pipeOptions.name !== '\'' + words[index] + '\'' ||
index > 0 && words[index - 1].trim() === '|');
};

View File

@ -0,0 +1,36 @@
const filterPipes = require('./filterPipes')();
describe('filterPipes', () => {
it('should ignore docs that are not pipes', () => {
const docs = [{ docType: 'class', name: 'B', pipeOptions: { name: '\'b\'' } }];
const words = ['A', 'b', 'B', 'C'];
const filteredDocs = [{ docType: 'class', name: 'B', pipeOptions: { name: '\'b\'' } }];
expect(filterPipes(docs, words, 1)).toEqual(filteredDocs);
expect(filterPipes(docs, words, 2)).toEqual(filteredDocs);
});
it('should ignore docs that are pipes but do not match the pipe name', () => {
const docs = [{ docType: 'pipe', name: 'B', pipeOptions: { name: '\'b\'' } }];
const words = ['A', 'B', 'C'];
const filteredDocs = [{ docType: 'pipe', name: 'B', pipeOptions: { name: '\'b\'' } }];
expect(filterPipes(docs, words, 1)).toEqual(filteredDocs);
});
it('should ignore docs that are pipes, match the pipe name and are preceded by a pipe character', () => {
const docs = [{ docType: 'pipe', name: 'B', pipeOptions: { name: '\'b\'' } }];
const words = ['A', '|', 'b', 'C'];
const filteredDocs = [{ docType: 'pipe', name: 'B', pipeOptions: { name: '\'b\'' } }];
expect(filterPipes(docs, words, 2)).toEqual(filteredDocs);
});
it('should filter out docs that are pipes, match the pipe name but are not preceded by a pipe character', () => {
const docs = [
{ docType: 'pipe', name: 'B', pipeOptions: { name: '\'b\'' } },
{ docType: 'class', name: 'B' }
];
const words = ['A', 'b', 'C'];
const index = 1;
const filteredDocs = [{ docType: 'class', name: 'B' }];
expect(filterPipes(docs, words, index)).toEqual(filteredDocs);
});
});

View File

@ -2007,6 +2007,10 @@ css-select@^1.1.0:
domutils "1.5.1"
nth-check "~1.0.1"
css-selector-parser@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/css-selector-parser/-/css-selector-parser-1.3.0.tgz#5f1ad43e2d8eefbfdc304fcd39a521664943e3eb"
css-selector-tokenizer@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86"

View File

@ -1,49 +1,52 @@
# Triage Process and GitHub Labels for Angular
This document describes how the Angular team uses labels and milestones
to triage issues on github. The basic idea of the process is that
caretaker only assigns a component and type (bug, feature) label. The
owner of the component than is in full control of how the issues should
be triaged further.
This document describes how the Angular team uses labels and milestones to triage issues on github.
The basic idea of the process is that caretaker only assigns a component (`comp: *`) label.
The owner of the component is then responsible for the secondary / component-level triage.
Once this process is implemented and in use, we will revisit it to see
if further labeling is needed.
## Label Types
### Components
A caretaker should be able to determine which component the issue
belongs to. The components have a clear piece of source code associated
with it within the `/packages/` folder of this repo.
The caretaker should be able to determine which component the issue belongs to.
The components have a clear piece of source code associated with it within the `/packages/` folder of this repo.
* `comp: aio` - the angular.io application
* `comp: animations`
* `comp: bazel`
* `comp: bazel` - @angular/bazel rules
* `comp: benchpress`
* `comp: common` - this includes core components / pipes
* `comp: core, compiler` - because core, compiler, compiler-cli and
* `comp: common/http` - this includes core components / pipes
* `comp: core & compiler` - because core, compiler, compiler-cli and
browser-platforms are very intertwined, we will be treating them as one
* `comp: forms`
* `comp: http`
* `comp: i18n`
* `comp: language service`
* `comp: language-service`
* `comp: metadata-extractor`
* `comp: router`
* `comp: server`
* `comp: service-worker`
* `comp: testing`
* `comp: upgrade`
* `comp: upgrade/dynamic`
* `comp: upgrade/static`
* `comp: web-worker`
* `comp: zones`
There are few components which are cross-cutting. They don't have
a clear location in the source tree. We will treat them as a component
even thought no specific source tree is associated with them.
There are few components which are cross-cutting.
They don't have a clear location in the source tree.
We will treat them as a component even thought no specific source tree is associated with them.
* `comp: build & ci` - all build and CI scripts
* `comp: build & ci` - build and CI infrastructure for the angular/angular repo
* `comp: docs` - documentation, including API docs, guides, tutorial
* `comp: packaging`
* `comp: packaging` - packaging format of @angular/* npm packages
* `comp: performance`
* `comp: security`
Sometimes, especially in the case of cross-cutting issues or PRs, these PRs or issues belong to multiple components.
In these cases, all applicable component labels should be used to triage the issue or PR.
### Type
@ -56,27 +59,27 @@ What kind of problem is this?
* `type: performance`
* `type: refactor`
## Caretaker Triage Process
It is the caretaker's responsibility to assign `comp: *` to each new
issue as they come in. The reason why we limit the responsibility of the
caretaker to this one label is that it is likely that without domain
knowledge the caretaker could mislabel issues or lack knowledge of
duplicate issues.
## Caretaker Triage Process (Primary Triage)
It is the caretaker's responsibility to assign `comp: *` to each new issue as they come in.
If it's obvious that the issue or PR is related to a release regression, the caretaker is also responsible for assigning the `severity(5): regression` label to make the issue or PR highly visible.
The primary triage should be done on a daily basis so that the issues become available for secondary triage without much of delay.
The reason why we limit the responsibility of the caretaker to this one label is that it is likely that without domain knowledge the caretaker could mislabel issues or lack knowledge of duplicate issues.
## Component's owner Triage Process
At this point we are leaving each component owner to determine their own
process for their component.
The component owner is responsible for assigning one of the labels from each of these categories:
It will be up to the component owner to determine the order in which the
issues within the component will be resolved.
- `type: *`
- `frequency: *`
- `severity: *`
Several owners have adopted the issue categorization based on
[user pain](http://www.lostgarden.com/2008/05/improving-bug-triage-with-user-pain.html)
used by AngularJS. In this system every issue is assigned frequency and
severity based on which the total user pain score is calculated.
We've adopted the issue categorization based on [user pain](http://www.lostgarden.com/2008/05/improving-bug-triage-with-user-pain.html) used by AngularJS. In this system every issue is assigned frequency and severity based on which the total user pain score is calculated.
Following is the definition of various frequency and severity levels:
@ -98,42 +101,42 @@ These criteria are then used to calculate a "user pain" score as follows:
`pain = severity × frequency`
This score can then be used to estimate the impact of the issue which helps with prioritization.
## Triaged vs Untrained PRs
PRs should also be label with a `comp: *` so that it is clear which
primary area the PR effects.
## Triaging PRs
Because of the cumulative pain associated with rebasing PRs, we triage PRs daily, and
closing or reviewing PRs is a top priority ahead of other ongoing work.
Triaging PRs is the same as triaging issues, except that PRs have additional label categories that should be used to signal their state.
Every triaged PR must have a `pr_action` label assigned to it and an assignee:
Every triaged PR must have a `pr_action` label assigned to it:
* `PR action: review` - work is complete and comment is needed from the assignee.
* `PR action: cleanup` - more work is needed from the current assignee.
* `PR action: discuss` - discussion is needed, to be led by the current assignee.
* `PR action: review` - work is complete and comment is needed from the reviewers.
* `PR action: cleanup` - more work is needed from the author.
* `PR action: discuss` - discussion is needed, to be led by the author.
* `PR action: merge` - the PR is ready to be merged by the caretaker.
In addition, PRs can have the following states:
* `PR state: WIP` - PR is experimental or rapidly changing. Not ready for review or triage.
* `PR state: blocked` - PR is blocked on an issue or other PR. Not ready for review or triage.
* `PR state: blocked` - PR is blocked on an issue or other PR. Not ready for review or triage or merge.
## PR Target
In our git workflow, we merge changes either to the `master` branch, the most recent patch branch (e.g. `4.3.x`), or to both.
In our git workflow, we merge changes either to the `master` branch, the active patch branch (e.g. `5.0.x`), or to both.
The decision about the target must be done by the PR author and/or reviewer. This decision is then honored when the PR is being merged.
The decision about the target must be done by the PR author and/or reviewer.
This decision is then honored when the PR is being merged by the caretaker.
To communicate the target we use the following labels:
* `PR target: master-only`
* `PR target: patch-only`
* `PR target: master & patch`
* `PR target: TBD` - the target is yet to be determined
* `PR target: master & patch`: the PR should me merged into the master branch and cherry-picked into the most recent patch branch. All PRs with fixes, docs and refactorings should use this target.
* `PR target: master-only`: the PR should be merged only into the `master` branch. All PRs with new features, API changes or high-risk changes should use this target.
* `PR target: patch-only`: the PR should be merged only into the most recent patch branch (e.g. 5.0.x). This target is useful if a `master & patch` PR can't be cleanly cherry-picked into the stable branch and a new PR is needed.
* `PR target: LTS-only`: the PR should be merged only into the active LTS branch(es). Only security and critical fixes are allowed in these branches. Always send a new PR targeting just the LTS branch and request review approval from @IgorMinar.
* `PR target: TBD`: the target is yet to be determined.
If a PR is missing the "PR target" label, or if the label is set to "TBD" when the PR is sent to the caretaker, the caretaker should reject the PR and request the appropriate target label to be applied before the PR is merged.
If a PR is missing the "PR target: *" label, or if the label is set to "TBD" when the PR is sent to the caretaker, the caretaker should reject the PR and request the appropriate target label to be applied before the PR is merged.
## PR Approvals
@ -142,23 +145,17 @@ Before a PR can be merged it must be approved by the appropriate reviewer(s).
To ensure that there right people review each change, we configured [PullApprove bot](https://about.pullapprove.com/) via (`.pullapprove.yaml`) to provide aggregate approval state via the GitHub PR Status API.
Note that approved state does not mean a PR is ready to be merged. For example, a reviewer might
approve the PR but request a minor tweak that doesn't need further review, e.g., a rebase or small
uncontroversial change.
Note that approved state does not mean a PR is ready to be merged.
For example, a reviewer might approve the PR but request a minor tweak that doesn't need further review, e.g., a rebase or small uncontroversial change.
Only the `PR action: merge` label means that the PR is ready for merging.
## Special Labels
### action:design
More active discussion is needed before the issue can be worked on further. Typically used for
`type: feature` or `type: RFC/discussion/question`
### `cla: yes`, `cla: no`
Managed by googlebot.
Indicates whether a PR has a CLA on file for its author(s).
Only issues with `cla:yes` should be merged into master.
[See all issues that need discussion](https://github.com/angular/angular/labels/action:%20Design)
### cla: yes, cla: no
Managed by googlebot. Indicates whether a PR has a CLA on file for its author(s). Only issues with
`cla:yes` should be merged into master.
### WORKS_AS_INTENDED
Only used on closed issues, to indicate to the reporter why we closed it.
### `aio: preview`
Applying this label to a PR makes the angular.io preview available regardless of the author. [More info](../aio/aio-builds-setup/docs/overview--security-model.md)

View File

@ -0,0 +1,4 @@
{
"cli-hello-world":{"master":{"gzip7":{"inline":847,"main":42533,"polyfills":20207},"gzip9":{"inline":847,"main":42483,"polyfills":20204},"uncompressed":{"inline":1447,"main":154295,"polyfills":61254}}},
"hello_world__closure":{"master":{"gzip7":{"bundle":32793},"gzip9":{"bundle":32758},"uncompressed":{"bundle":100661}}}
}

View File

@ -1,19 +0,0 @@
#!/bin/bash
set -u -e -o pipefail
declare -A payloadLimits
payloadLimits["hello_world__closure", "uncompressed", "bundle"]=106000
payloadLimits["hello_world__closure", "gzip7", "bundle"]=35000
payloadLimits["hello_world__closure", "gzip9", "bundle"]=35000
payloadLimits["cli-hello-world", "uncompressed", "inline"]=1500
payloadLimits["cli-hello-world", "uncompressed", "main"]=160000
payloadLimits["cli-hello-world", "uncompressed", "polyfills"]=66000
payloadLimits["cli-hello-world", "gzip7", "inline"]=900
payloadLimits["cli-hello-world", "gzip7", "main"]=45000
payloadLimits["cli-hello-world", "gzip7", "polyfills"]=22000
payloadLimits["cli-hello-world", "gzip9", "inline"]=900
payloadLimits["cli-hello-world", "gzip9", "main"]=45000
payloadLimits["cli-hello-world", "gzip9", "polyfills"]=22000

View File

@ -4,9 +4,10 @@ set -e -o pipefail
cd `dirname $0`
readonly thisDir=$(cd $(dirname $0); pwd)
# Track payload size functions
source ../scripts/ci/payload-size.sh
source ./_payload-limits.sh
# Workaround https://github.com/yarnpkg/yarn/issues/2165
# Yarn will cache file://dist URIs and not update Angular code
@ -48,7 +49,7 @@ for testDir in $(ls | grep -v node_modules) ; do
if [[ $testDir == cli-hello-world ]]; then
yarn build
fi
trackPayloadSize "$testDir" "dist/*.js" true false
trackPayloadSize "$testDir" "dist/*.js" true false "${thisDir}/_payload-limits.json"
fi
)
done

View File

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

View File

@ -90,6 +90,11 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
let depCount = context.depCount = 0;
const states: StateAst[] = [];
const transitions: TransitionAst[] = [];
if (metadata.name.charAt(0) == '@') {
context.errors.push(
'animation triggers cannot be prefixed with an `@` sign (e.g. trigger(\'@foo\', [...]))');
}
metadata.definitions.forEach(def => {
this._resetContextStyleTimingState(context);
if (def.type == AnimationMetadataType.State) {

View File

@ -986,11 +986,15 @@ export class TransitionAnimationEngine {
}
const allPreviousPlayersMap = new Map<any, TransitionAnimationPlayer[]>();
let sortedParentElements: any[] = [];
// this map works to tell which element in the DOM tree is contained by
// which animation. Further down below this map will get populated once
// the players are built and in doing so it can efficiently figure out
// if a sub player is skipped due to a parent player having priority.
const animationElementMap = new Map<any, any>();
queuedInstructions.forEach(entry => {
const element = entry.element;
if (subTimelines.has(element)) {
sortedParentElements.unshift(element);
animationElementMap.set(element, element);
this._beforeAnimationBuild(
entry.player.namespaceId, entry.instruction, allPreviousPlayersMap);
}
@ -1041,6 +1045,7 @@ export class TransitionAnimationEngine {
const rootPlayers: TransitionAnimationPlayer[] = [];
const subPlayers: TransitionAnimationPlayer[] = [];
const NO_PARENT_ANIMATION_ELEMENT_DETECTED = {};
queuedInstructions.forEach(entry => {
const {element, player, instruction} = entry;
// this means that it was never consumed by a parent animation which
@ -1052,29 +1057,41 @@ export class TransitionAnimationEngine {
return;
}
// this will flow up the DOM and query the map to figure out
// if a parent animation has priority over it. In the situation
// that a parent is detected then it will cancel the loop. If
// nothing is detected, or it takes a few hops to find a parent,
// then it will fill in the missing nodes and signal them as having
// a detected parent (or a NO_PARENT value via a special constant).
let parentWithAnimation: any = NO_PARENT_ANIMATION_ELEMENT_DETECTED;
if (animationElementMap.size > 1) {
let elm = element;
const parentsToAdd: any[] = [];
while (elm = elm.parentNode) {
const detectedParent = animationElementMap.get(elm);
if (detectedParent) {
parentWithAnimation = detectedParent;
break;
}
parentsToAdd.push(elm);
}
parentsToAdd.forEach(parent => animationElementMap.set(parent, parentWithAnimation));
}
const innerPlayer = this._buildAnimation(
player.namespaceId, instruction, allPreviousPlayersMap, skippedPlayersMap, preStylesMap,
postStylesMap);
player.setRealPlayer(innerPlayer);
let parentHasPriority: any = null;
for (let i = 0; i < sortedParentElements.length; i++) {
const parent = sortedParentElements[i];
if (parent === element) break;
if (this.driver.containsElement(parent, element)) {
parentHasPriority = parent;
break;
}
}
if (parentHasPriority) {
const parentPlayers = this.playersByElement.get(parentHasPriority);
if (parentWithAnimation === NO_PARENT_ANIMATION_ELEMENT_DETECTED) {
rootPlayers.push(player);
} else {
const parentPlayers = this.playersByElement.get(parentWithAnimation);
if (parentPlayers && parentPlayers.length) {
player.parentPlayer = optimizeGroupPlayer(parentPlayers);
}
skippedPlayers.push(player);
} else {
rootPlayers.push(player);
}
} else {
eraseStyles(element, instruction.fromStyles);
@ -1105,7 +1122,7 @@ export class TransitionAnimationEngine {
// fire the start/done transition callback events
skippedPlayers.forEach(player => {
if (player.parentPlayer) {
player.parentPlayer.onDestroy(() => player.destroy());
player.syncPlayerEvents(player.parentPlayer);
} else {
player.destroy();
}
@ -1366,6 +1383,15 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
getRealPlayer() { return this._player; }
syncPlayerEvents(player: AnimationPlayer) {
const p = this._player as any;
if (p.triggerCallback) {
player.onStart(() => p.triggerCallback('start'));
}
player.onDone(() => this.finish());
player.onDestroy(() => this.destroy());
}
private _queueEvent(name: string, callback: (event: any) => any): void {
getOrSetAsInMap(this._queuedCallbacks, name, []).push(callback);
}
@ -1419,6 +1445,14 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
getPosition(): number { return this.queued ? 0 : this._player.getPosition(); }
get totalTime(): number { return this._player.totalTime; }
/* @internal */
triggerCallback(phaseName: string): void {
const p = this._player as any;
if (p.triggerCallback) {
p.triggerCallback(phaseName);
}
}
}
function deleteOrUnsetInMap(map: Map<any, any[]>| {[key: string]: any}, key: any, value: any) {

View File

@ -184,6 +184,13 @@ export class WebAnimationsPlayer implements AnimationPlayer {
}
this.currentSnapshot = styles;
}
/* @internal */
triggerCallback(phaseName: string): void {
const methods = phaseName == 'start' ? this._onStartFns : this._onDoneFns;
methods.forEach(fn => fn());
methods.length = 0;
}
}
function _computeStyle(element: any, prop: string): string {

View File

@ -119,6 +119,14 @@ export function main() {
expect(() => validateAndThrowAnimationSequence(steps)).not.toThrow();
});
it('should not allow triggers to be defined with a prefixed `@` symbol', () => {
const steps = trigger('@foo', []);
expect(() => validateAndThrowAnimationSequence(steps))
.toThrowError(
/animation triggers cannot be prefixed with an `@` sign \(e\.g\. trigger\('@foo', \[...\]\)\)/);
});
it('should throw an error if an animation time is invalid', () => {
const steps = [animate('500xs', style({opacity: 1}))];

View File

@ -45,6 +45,26 @@ export function main() {
const p = innerPlayer !;
expect(p.log).toEqual(['play']);
});
it('should fire start/done callbacks manually when called directly', () => {
const log: string[] = [];
const player = new WebAnimationsPlayer(element, [], {duration: 1000});
player.onStart(() => log.push('started'));
player.onDone(() => log.push('done'));
player.triggerCallback('start');
expect(log).toEqual(['started']);
player.play();
expect(log).toEqual(['started']);
player.triggerCallback('done');
expect(log).toEqual(['started', 'done']);
player.finish();
expect(log).toEqual(['started', 'done']);
});
});
}

View File

@ -140,4 +140,11 @@ export class AnimationGroupPlayer implements AnimationPlayer {
}
});
}
/* @internal */
triggerCallback(phaseName: string): void {
const methods = phaseName == 'start' ? this._onStartFns : this._onDoneFns;
methods.forEach(fn => fn());
methods.length = 0;
}
}

View File

@ -31,6 +31,8 @@ export interface AnimationPlayer {
parentPlayer: AnimationPlayer|null;
readonly totalTime: number;
beforeDestroy?: () => any;
/* @internal */
triggerCallback?: (phaseName: string) => void;
}
/**
@ -91,4 +93,11 @@ export class NoopAnimationPlayer implements AnimationPlayer {
reset(): void {}
setPosition(p: number): void {}
getPosition(): number { return 0; }
}
/* @internal */
triggerCallback(phaseName: string): void {
const methods = phaseName == 'start' ? this._onStartFns : this._onDoneFns;
methods.forEach(fn => fn());
methods.length = 0;
}
}

View File

@ -40,5 +40,29 @@ export function main() {
player.destroy();
expect(log).toEqual(['started', 'done', 'destroy']);
});
it('should fire start/done callbacks manually when called directly', fakeAsync(() => {
const log: string[] = [];
const player = new NoopAnimationPlayer();
player.onStart(() => log.push('started'));
player.onDone(() => log.push('done'));
flushMicrotasks();
player.triggerCallback('start');
expect(log).toEqual(['started']);
player.play();
expect(log).toEqual(['started']);
player.triggerCallback('done');
expect(log).toEqual(['started', 'done']);
player.finish();
expect(log).toEqual(['started', 'done']);
flushMicrotasks();
expect(log).toEqual(['started', 'done']);
}));
});
}

View File

@ -19,18 +19,21 @@ import {PerfLogEvent, PerfLogFeatures, WebDriverExtension} from '../web_driver_e
@Injectable()
export class PerflogMetric extends Metric {
static SET_TIMEOUT = new InjectionToken('PerflogMetric.setTimeout');
static IGNORE_NAVIGATION = new InjectionToken('PerflogMetric.ignoreNavigation');
static PROVIDERS = [
{
provide: PerflogMetric,
deps: [
WebDriverExtension, PerflogMetric.SET_TIMEOUT, Options.MICRO_METRICS, Options.FORCE_GC,
Options.CAPTURE_FRAMES, Options.RECEIVED_DATA, Options.REQUEST_COUNT
Options.CAPTURE_FRAMES, Options.RECEIVED_DATA, Options.REQUEST_COUNT,
PerflogMetric.IGNORE_NAVIGATION
]
},
{
provide: PerflogMetric.SET_TIMEOUT,
useValue: (fn: Function, millis: number) => <any>setTimeout(fn, millis)
}
},
{provide: PerflogMetric.IGNORE_NAVIGATION, useValue: false}
];
private _remainingEvents: PerfLogEvent[];
@ -41,6 +44,8 @@ export class PerflogMetric extends Metric {
* @param driverExtension
* @param setTimeout
* @param microMetrics Name and description of metrics provided via console.time / console.timeEnd
* @param ignoreNavigation If true, don't measure from navigationStart events. These events are
* usually triggered by a page load, but can also be triggered when adding iframes to the DOM.
**/
constructor(
private _driverExtension: WebDriverExtension,
@ -49,7 +54,8 @@ export class PerflogMetric extends Metric {
@Inject(Options.FORCE_GC) private _forceGc: boolean,
@Inject(Options.CAPTURE_FRAMES) private _captureFrames: boolean,
@Inject(Options.RECEIVED_DATA) private _receivedData: boolean,
@Inject(Options.REQUEST_COUNT) private _requestCount: boolean) {
@Inject(Options.REQUEST_COUNT) private _requestCount: boolean,
@Inject(PerflogMetric.IGNORE_NAVIGATION) private _ignoreNavigation: boolean) {
super();
this._remainingEvents = [];
@ -231,7 +237,7 @@ export class PerflogMetric extends Metric {
const name = event['name'];
if (ph === 'B' && name === markName) {
markStartEvent = event;
} else if (ph === 'I' && name === 'navigationStart') {
} else if (ph === 'I' && name === 'navigationStart' && !this._ignoreNavigation) {
// if a benchmark measures reload of a page, use the last
// navigationStart as begin event
markStartEvent = event;

View File

@ -18,12 +18,13 @@ export function main() {
function createMetric(
perfLogs: PerfLogEvent[], perfLogFeatures: PerfLogFeatures,
{microMetrics, forceGc, captureFrames, receivedData, requestCount}: {
{microMetrics, forceGc, captureFrames, receivedData, requestCount, ignoreNavigation}: {
microMetrics?: {[key: string]: string},
forceGc?: boolean,
captureFrames?: boolean,
receivedData?: boolean,
requestCount?: boolean
requestCount?: boolean,
ignoreNavigation?: boolean
} = {}): Metric {
commandLog = [];
if (!perfLogFeatures) {
@ -59,6 +60,9 @@ export function main() {
if (requestCount != null) {
providers.push({provide: Options.REQUEST_COUNT, useValue: requestCount});
}
if (ignoreNavigation != null) {
providers.push({provide: PerflogMetric.IGNORE_NAVIGATION, useValue: ignoreNavigation});
}
return Injector.create(providers).get(PerflogMetric);
}
@ -185,6 +189,22 @@ export function main() {
});
}));
it('should ignore navigationStart if ignoreNavigation is set',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const events = [[
eventFactory.markStart('benchpress0', 0), eventFactory.start('script', 4),
eventFactory.end('script', 6), eventFactory.instant('navigationStart', 7),
eventFactory.start('script', 8), eventFactory.end('script', 9),
eventFactory.markEnd('benchpress0', 10)
]];
const metric = createMetric(events, null !, {ignoreNavigation: true});
metric.beginMeasure().then((_) => metric.endMeasure(false)).then((data) => {
expect(data['scriptTime']).toBe(3);
async.done();
});
}));
it('should restart timing', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
const events = [
[

View File

@ -7,6 +7,7 @@
*/
import {EventEmitter, Injectable} from '@angular/core';
import {ISubscription} from 'rxjs/Subscription';
import {LocationStrategy} from './location_strategy';
@ -129,7 +130,7 @@ export class Location {
*/
subscribe(
onNext: (value: PopStateEvent) => void, onThrow?: ((exception: any) => void)|null,
onReturn?: (() => void)|null): Object {
onReturn?: (() => void)|null): ISubscription {
return this._subject.subscribe({next: onNext, error: onThrow, complete: onReturn});
}

View File

@ -13,7 +13,8 @@ const globals = {
'@angular/core': 'ng.core',
'@angular/common': 'ng.common',
'rxjs/Observable': 'Rx',
'rxjs/Subject': 'Rx'
'rxjs/Subject': 'Rx',
'rxjs/Subscription': 'Rx'
};
module.exports = {

View File

@ -8,6 +8,7 @@
import {Location, LocationStrategy} from '@angular/common';
import {EventEmitter, Injectable} from '@angular/core';
import {ISubscription} from 'rxjs/Subscription';
/**
@ -107,7 +108,7 @@ export class SpyLocation implements Location {
subscribe(
onNext: (value: any) => void, onThrow?: ((error: any) => void)|null,
onReturn?: (() => void)|null): Object {
onReturn?: (() => void)|null): ISubscription {
return this._subject.subscribe({next: onNext, error: onThrow, complete: onReturn});
}

View File

@ -14,13 +14,16 @@ import * as ts from 'typescript';
import {TestSupport, expectNoDiagnostics, setup} from '../test_support';
type MockFiles = {
[fileName: string]: string
};
describe('ng type checker', () => {
let errorSpy: jasmine.Spy&((s: string) => void);
let testSupport: TestSupport;
function compileAndCheck(
mockDirs: {[fileName: string]: string}[],
overrideOptions: ng.CompilerOptions = {}): ng.Diagnostics {
mockDirs: MockFiles[], overrideOptions: ng.CompilerOptions = {}): ng.Diagnostics {
testSupport.writeFiles(...mockDirs);
const fileNames: string[] = [];
mockDirs.forEach((dir) => {
@ -40,13 +43,12 @@ describe('ng type checker', () => {
testSupport = setup();
});
function accept(
files: {[fileName: string]: string} = {}, overrideOptions: ng.CompilerOptions = {}) {
function accept(files: MockFiles = {}, overrideOptions: ng.CompilerOptions = {}) {
expectNoDiagnostics({}, compileAndCheck([QUICKSTART, files], overrideOptions));
}
function reject(
message: string | RegExp, location: RegExp, files: {[fileName: string]: string},
message: string | RegExp, location: RegExp, files: MockFiles,
overrideOptions: ng.CompilerOptions = {}) {
const diagnostics = compileAndCheck([QUICKSTART, files], overrideOptions);
if (!diagnostics || !diagnostics.length) {
@ -79,6 +81,41 @@ describe('ng type checker', () => {
});
});
describe('regressions ', () => {
const a = (files: MockFiles, options: object = {}) => {
accept(files, {fullTemplateTypeCheck: true, ...options});
};
// #19905
it('should accept an event binding', () => {
a({
'src/app.component.ts': '',
'src/lib.ts': '',
'src/app.module.ts': `
import {NgModule, Component, Directive, HostListener} from '@angular/core';
@Component({
selector: 'comp',
template: '<div someDir></div>'
})
export class MainComp {}
@Directive({
selector: '[someDir]'
})
export class SomeDirective {
@HostListener('click', ['$event'])
onClick(event: any) {}
}
@NgModule({
declarations: [MainComp, SomeDirective],
})
export class MainModule {}`
});
});
});
describe('with modified quickstart (fullTemplateTypeCheck: false)', () => {
addTests({fullTemplateTypeCheck: false});
});
@ -87,6 +124,23 @@ describe('ng type checker', () => {
addTests({fullTemplateTypeCheck: true});
});
describe('regressions', () => {
// #19485
it('should accept if else (TemplateRef)', () => {
accept(
{
'src/app.component.html': `
<div class="text-center" *ngIf="!person; else e">
No person supplied.
</div>
<ng-template #e>
Welcome {{person.name}}!
<ng-template>`
},
{fullTemplateTypeCheck: true});
});
});
function addTests(config: {fullTemplateTypeCheck: boolean}) {
function a(template: string) { accept({'src/app.component.html': template}, config); }
@ -234,6 +288,7 @@ const QUICKSTART = {
`,
'src/app.module.ts': `
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AppComponent, APipe, ADirective } from './app.component';
import { LibDirective, LibPipe } from './lib';
@ -246,7 +301,7 @@ const QUICKSTART = {
@NgModule({
declarations: [ AppComponent, APipe, ADirective ],
bootstrap: [ AppComponent ],
imports: [ LibModule ]
imports: [ LibModule, CommonModule ]
})
export class AppModule { }
`

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