Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
65a40e659b | |||
215832d8c6 | |||
686c4efe6d | |||
efce39605b | |||
b567f9cc21 | |||
4ff60c3562 | |||
bc904b19a4 | |||
135cf226bf | |||
0550383fc9 | |||
07699cbaec | |||
e4bb077d27 | |||
27e7439c3b | |||
66d160215e | |||
0feba49c4b | |||
9ca6ee9eed | |||
a5a82296b7 | |||
f9f2c20a57 | |||
662422a67e | |||
b53ead4c45 | |||
d0abfa3acc | |||
3bf36e9492 | |||
82aace63ea | |||
15795d09cf | |||
8ddbed8f7b | |||
81f1d42328 | |||
3df1542c87 | |||
8e1e7faef4 | |||
27f8c69d8a | |||
5d0dd97402 | |||
814f06289b |
@ -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
|
||||
|
18
CHANGELOG.md
18
CHANGELOG.md
@ -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)
|
||||
|
||||
|
@ -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 -->
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -68,7 +68,7 @@ export class DoCheckComponent implements DoCheck {
|
||||
|
||||
reset() {
|
||||
this.changeDetected = true;
|
||||
this.changeLog.length = 0;
|
||||
this.changeLog = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(() => { }); }
|
||||
|
@ -43,7 +43,7 @@ export class OnChangesComponent implements OnChanges {
|
||||
}
|
||||
// #enddocregion ng-on-changes
|
||||
|
||||
reset() { this.changeLog.length = 0; }
|
||||
reset() { this.changeLog = []; }
|
||||
}
|
||||
|
||||
/***************************************/
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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'));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -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",
|
||||
|
||||
|
@ -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 {}
|
||||
|
@ -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
|
||||
|
@ -6,5 +6,5 @@ import { Component } from '@angular/core';
|
||||
template: '<h1>{{title}}</h1>',
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'Minimal NgModule';
|
||||
title = 'Angular Modules';
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {}
|
||||
|
@ -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 {}
|
||||
|
@ -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 {}
|
||||
|
@ -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 {}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 ],
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
/*
|
||||
|
@ -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
|
@ -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 {}
|
||||
|
@ -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
|
||||
|
@ -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];
|
||||
|
@ -27,3 +27,6 @@
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
@ -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 -->
|
||||
|
@ -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];
|
||||
|
@ -0,0 +1,11 @@
|
||||
// #docregion
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule
|
||||
],
|
||||
declarations: []
|
||||
})
|
||||
export class ContactModule { }
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -8,7 +8,10 @@ import { ContactRoutingModule } from './contact-routing.module';
|
||||
|
||||
// #docregion class
|
||||
@NgModule({
|
||||
imports: [ SharedModule, ContactRoutingModule ],
|
||||
imports: [
|
||||
SharedModule,
|
||||
ContactRoutingModule
|
||||
],
|
||||
declarations: [ ContactComponent ],
|
||||
providers: [ ContactService ]
|
||||
})
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -7,7 +7,6 @@ import { UserService } from '../core/user.service';
|
||||
templateUrl: './title.component.html',
|
||||
})
|
||||
export class TitleComponent {
|
||||
@Input() subtitle = '';
|
||||
title = 'Angular Modules';
|
||||
user = '';
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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 ],
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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';
|
||||
|
@ -1,6 +1,6 @@
|
||||
<!-- #docregion -->
|
||||
<!-- #docregion v1 -->
|
||||
<h1 highlight>{{title}} {{subtitle}}</h1>
|
||||
<h1 highlight>{{title}}</h1>
|
||||
<!-- #enddocregion v1 -->
|
||||
<!-- #docregion ngIf -->
|
||||
<p *ngIf="user">
|
||||
|
@ -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 = '';
|
||||
|
||||
|
@ -9,6 +9,6 @@ export class MessageService {
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.messages.length = 0;
|
||||
this.messages = [];
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,6 @@ export class MessageService {
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.messages.length = 0;
|
||||
this.messages = [];
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,6 @@ export class MessageService {
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.messages.length = 0;
|
||||
this.messages = [];
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,6 @@ export class MessageService {
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.messages.length = 0;
|
||||
this.messages = [];
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
BIN
aio/content/images/marketing/home/ng-atl.png
Normal file
BIN
aio/content/images/marketing/home/ng-atl.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
@ -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>
|
||||
|
||||
|
@ -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/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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."
|
||||
}
|
||||
]},
|
||||
|
||||
|
@ -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()_
|
||||
|
||||
|
@ -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",
|
||||
|
3
aio/scripts/_payload-limits.json
Executable file
3
aio/scripts/_payload-limits.json
Executable 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}}}
|
||||
}
|
@ -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
|
@ -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"
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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]);
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
});
|
||||
|
||||
|
@ -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>' };
|
||||
|
26
aio/tools/transforms/angular-base-package/services/filterAmbiguousDirectiveAliases.js
vendored
Normal file
26
aio/tools/transforms/angular-base-package/services/filterAmbiguousDirectiveAliases.js
vendored
Normal 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;
|
||||
};
|
||||
};
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
12
aio/tools/transforms/angular-base-package/services/filterPipes.js
vendored
Normal file
12
aio/tools/transforms/angular-base-package/services/filterPipes.js
vendored
Normal 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() === '|');
|
||||
};
|
@ -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);
|
||||
});
|
||||
});
|
@ -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"
|
||||
|
@ -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)
|
||||
|
4
integration/_payload-limits.json
Normal file
4
integration/_payload-limits.json
Normal 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}}}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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}))];
|
||||
|
||||
|
@ -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']);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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']);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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 = [
|
||||
[
|
||||
|
@ -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});
|
||||
}
|
||||
|
||||
|
@ -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 = {
|
||||
|
@ -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});
|
||||
}
|
||||
|
||||
|
@ -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
Reference in New Issue
Block a user