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
|
# petebacondarwin - Pete Bacon Darwin
|
||||||
# pkozlowski-opensource - Pawel Kozlowski
|
# pkozlowski-opensource - Pawel Kozlowski
|
||||||
# robwormald - Rob Wormald
|
# robwormald - Rob Wormald
|
||||||
# tbosch - Tobias Bosch
|
|
||||||
# tinayuangao - Tina Gao
|
# tinayuangao - Tina Gao
|
||||||
# vicb - Victor Berchet
|
# vicb - Victor Berchet
|
||||||
# vikerman - Vikram Subramanian
|
# vikerman - Vikram Subramanian
|
||||||
@ -100,7 +99,6 @@ groups:
|
|||||||
users:
|
users:
|
||||||
- alexeagle
|
- alexeagle
|
||||||
- mhevery
|
- mhevery
|
||||||
- tbosch
|
|
||||||
- vicb
|
- vicb
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
|
|
||||||
@ -109,8 +107,7 @@ groups:
|
|||||||
files:
|
files:
|
||||||
- "packages/core/*"
|
- "packages/core/*"
|
||||||
users:
|
users:
|
||||||
- tbosch #primary
|
- chuckjaz #primary
|
||||||
- chuckjaz
|
|
||||||
- mhevery
|
- mhevery
|
||||||
- vicb
|
- vicb
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
@ -132,7 +129,7 @@ groups:
|
|||||||
- "packages/compiler/src/i18n/*"
|
- "packages/compiler/src/i18n/*"
|
||||||
users:
|
users:
|
||||||
- vicb #primary
|
- vicb #primary
|
||||||
- tbosch
|
- chuckjaz
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
|
|
||||||
@ -141,9 +138,8 @@ groups:
|
|||||||
files:
|
files:
|
||||||
- "packages/compiler/*"
|
- "packages/compiler/*"
|
||||||
users:
|
users:
|
||||||
- tbosch #primary
|
- chuckjaz #primary
|
||||||
- vicb
|
- vicb
|
||||||
- chuckjaz
|
|
||||||
- mhevery
|
- mhevery
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
|
|
||||||
@ -163,12 +159,11 @@ groups:
|
|||||||
- "packages/compiler-cli/*"
|
- "packages/compiler-cli/*"
|
||||||
- "packages/bazel/*"
|
- "packages/bazel/*"
|
||||||
exclude:
|
exclude:
|
||||||
- "packages/compiler-cli/src/ngtools*"
|
- "packages/compiler-cli/src/ngtools*"
|
||||||
users:
|
users:
|
||||||
- alexeagle
|
- alexeagle
|
||||||
- chuckjaz
|
- chuckjaz
|
||||||
- vicb
|
- vicb
|
||||||
- tbosch
|
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
|
|
||||||
@ -212,7 +207,7 @@ groups:
|
|||||||
- "packages/language-service/*"
|
- "packages/language-service/*"
|
||||||
users:
|
users:
|
||||||
- chuckjaz #primary
|
- chuckjaz #primary
|
||||||
- tbosch #secondary
|
# needs secondary
|
||||||
- vicb
|
- vicb
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
@ -242,8 +237,8 @@ groups:
|
|||||||
files:
|
files:
|
||||||
- "packages/platform-browser/*"
|
- "packages/platform-browser/*"
|
||||||
users:
|
users:
|
||||||
- tbosch #primary
|
- vicb #primary
|
||||||
- vicb #secondary
|
# needs secondary
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
|
|
||||||
@ -253,9 +248,9 @@ groups:
|
|||||||
- "packages/platform-server/*"
|
- "packages/platform-server/*"
|
||||||
users:
|
users:
|
||||||
- vikerman #primary
|
- vikerman #primary
|
||||||
|
# needs secondary
|
||||||
- alxhub
|
- alxhub
|
||||||
- vicb
|
- vicb
|
||||||
- tbosch
|
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
|
|
||||||
@ -265,7 +260,7 @@ groups:
|
|||||||
- "packages/platform-webworker/*"
|
- "packages/platform-webworker/*"
|
||||||
users:
|
users:
|
||||||
- vicb #primary
|
- vicb #primary
|
||||||
- tbosch #secondary
|
# needs secondary
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #fallback
|
- mhevery #fallback
|
||||||
|
|
||||||
@ -284,7 +279,7 @@ groups:
|
|||||||
files:
|
files:
|
||||||
- "packages/benchpress/*"
|
- "packages/benchpress/*"
|
||||||
users:
|
users:
|
||||||
- tbosch #primary
|
- alxhub #primary
|
||||||
# needs secondary
|
# needs secondary
|
||||||
- IgorMinar #fallback
|
- IgorMinar #fallback
|
||||||
- mhevery #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>
|
<a name="5.0.2"></a>
|
||||||
## [5.0.2](https://github.com/angular/angular/compare/5.0.1...5.0.2) (2017-11-16)
|
## [5.0.2](https://github.com/angular/angular/compare/5.0.1...5.0.2) (2017-11-16)
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
<!-- #docregion -->
|
<!-- #docregion -->
|
||||||
<h1>My First Attribute Directive</h1>
|
<h1>My First Attribute Directive</h1>
|
||||||
<!-- #docregion applied -->
|
<!-- #docregion applied -->
|
||||||
<p appHightlight>Highlight me!</p>
|
<p appHighlight>Highlight me!</p>
|
||||||
<!-- #enddocregion applied, -->
|
<!-- #enddocregion applied, -->
|
||||||
|
|
||||||
<!-- #docregion color-1 -->
|
<!-- #docregion color-1 -->
|
||||||
<p appHightlight highlightColor="yellow">Highlighted in yellow</p>
|
<p appHighlight highlightColor="yellow">Highlighted in yellow</p>
|
||||||
<p appHightlight [highlightColor]="'orange'">Highlighted in orange</p>
|
<p appHighlight [highlightColor]="'orange'">Highlighted in orange</p>
|
||||||
<!-- #enddocregion color-1 -->
|
<!-- #enddocregion color-1 -->
|
||||||
|
|
||||||
<!-- #docregion color-2 -->
|
<!-- #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 -->
|
<!-- #enddocregion color-2 -->
|
||||||
|
@ -93,22 +93,20 @@ export class AfterContentComponent implements AfterContentChecked, AfterContentI
|
|||||||
|
|
||||||
<h4>-- AfterContent Logs --</h4>
|
<h4>-- AfterContent Logs --</h4>
|
||||||
<p><button (click)="reset()">Reset</button></p>
|
<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>
|
</div>
|
||||||
`,
|
`,
|
||||||
styles: ['.parent {background: burlywood}'],
|
styles: ['.parent {background: burlywood}'],
|
||||||
providers: [LoggerService]
|
providers: [LoggerService]
|
||||||
})
|
})
|
||||||
export class AfterContentParentComponent {
|
export class AfterContentParentComponent {
|
||||||
logs: string[];
|
|
||||||
show = true;
|
show = true;
|
||||||
|
|
||||||
constructor(private logger: LoggerService) {
|
constructor(public logger: LoggerService) {
|
||||||
this.logs = logger.logs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.logs.length = 0;
|
this.logger.clear();
|
||||||
// quickly remove and reload AfterContentComponent which recreates it
|
// quickly remove and reload AfterContentComponent which recreates it
|
||||||
this.show = false;
|
this.show = false;
|
||||||
this.logger.tick_then(() => this.show = true);
|
this.logger.tick_then(() => this.show = true);
|
||||||
|
@ -95,22 +95,20 @@ export class AfterViewComponent implements AfterViewChecked, AfterViewInit {
|
|||||||
|
|
||||||
<h4>-- AfterView Logs --</h4>
|
<h4>-- AfterView Logs --</h4>
|
||||||
<p><button (click)="reset()">Reset</button></p>
|
<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>
|
</div>
|
||||||
`,
|
`,
|
||||||
styles: ['.parent {background: burlywood}'],
|
styles: ['.parent {background: burlywood}'],
|
||||||
providers: [LoggerService]
|
providers: [LoggerService]
|
||||||
})
|
})
|
||||||
export class AfterViewParentComponent {
|
export class AfterViewParentComponent {
|
||||||
logs: string[];
|
|
||||||
show = true;
|
show = true;
|
||||||
|
|
||||||
constructor(private logger: LoggerService) {
|
constructor(public logger: LoggerService) {
|
||||||
this.logs = logger.logs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.logs.length = 0;
|
this.logger.clear();
|
||||||
// quickly remove and reload AfterViewComponent which recreates it
|
// quickly remove and reload AfterViewComponent which recreates it
|
||||||
this.show = false;
|
this.show = false;
|
||||||
this.logger.tick_then(() => this.show = true);
|
this.logger.tick_then(() => this.show = true);
|
||||||
|
@ -27,7 +27,7 @@ export class MyCounterComponent implements OnChanges {
|
|||||||
// Empty the changeLog whenever counter goes to zero
|
// Empty the changeLog whenever counter goes to zero
|
||||||
// hint: this is a way to respond programmatically to external value changes.
|
// hint: this is a way to respond programmatically to external value changes.
|
||||||
if (this.counter === 0) {
|
if (this.counter === 0) {
|
||||||
this.changeLog.length = 0;
|
this.changeLog = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// A change to `counter` is the only change we care about
|
// A change to `counter` is the only change we care about
|
||||||
|
@ -68,7 +68,7 @@ export class DoCheckComponent implements DoCheck {
|
|||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.changeDetected = true;
|
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
|
// schedules a view refresh to ensure display catches up
|
||||||
tick() { this.tick_then(() => { }); }
|
tick() { this.tick_then(() => { }); }
|
||||||
|
@ -43,7 +43,7 @@ export class OnChangesComponent implements OnChanges {
|
|||||||
}
|
}
|
||||||
// #enddocregion ng-on-changes
|
// #enddocregion ng-on-changes
|
||||||
|
|
||||||
reset() { this.changeLog.length = 0; }
|
reset() { this.changeLog = []; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/***************************************/
|
/***************************************/
|
||||||
|
@ -12,5 +12,5 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- #enddocregion template -->
|
<!-- #enddocregion template -->
|
||||||
<h4>-- Spy Lifecycle Hook Log --</h4>
|
<h4>-- Spy Lifecycle Hook Log --</h4>
|
||||||
<div *ngFor="let msg of spyLog">{{msg}}</div>
|
<div *ngFor="let msg of logger.logs">{{msg}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,10 +15,8 @@ import { LoggerService } from './logger.service';
|
|||||||
export class SpyParentComponent {
|
export class SpyParentComponent {
|
||||||
newName = 'Herbie';
|
newName = 'Herbie';
|
||||||
heroes: string[] = ['Windstorm', 'Magneta'];
|
heroes: string[] = ['Windstorm', 'Magneta'];
|
||||||
spyLog: string[];
|
|
||||||
|
|
||||||
constructor(private logger: LoggerService) {
|
constructor(public logger: LoggerService) {
|
||||||
this.spyLog = logger.logs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addHero() {
|
addHero() {
|
||||||
@ -34,7 +32,7 @@ export class SpyParentComponent {
|
|||||||
}
|
}
|
||||||
reset() {
|
reset() {
|
||||||
this.logger.log('-- reset --');
|
this.logger.log('-- reset --');
|
||||||
this.heroes.length = 0;
|
this.heroes = [];
|
||||||
this.logger.tick();
|
this.logger.tick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
"app/contact/contact.component.html",
|
"app/contact/contact.component.html",
|
||||||
"app/contact/contact.component.3.ts",
|
"app/contact/contact.component.3.ts",
|
||||||
"app/contact/contact.service.ts",
|
"app/contact/contact.service.ts",
|
||||||
"app/contact/highlight.directive.ts",
|
"app/contact/contact-highlight.directive.ts",
|
||||||
|
|
||||||
"main.1b.ts",
|
"main.1b.ts",
|
||||||
"styles.css",
|
"styles.css",
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
"app/contact/awesome.pipe.ts",
|
"app/contact/awesome.pipe.ts",
|
||||||
"app/contact/contact.component.3.ts",
|
"app/contact/contact.component.3.ts",
|
||||||
"app/contact/contact.module.2.ts",
|
"app/contact/contact.module.2.ts",
|
||||||
"app/contact/highlight.directive.ts",
|
"app/contact/contact-highlight.directive.ts",
|
||||||
|
|
||||||
"main.2.ts",
|
"main.2.ts",
|
||||||
"styles.css",
|
"styles.css",
|
||||||
|
@ -15,7 +15,7 @@ describe('NgModule', function () {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
title: element.all(by.tagName('h1')).get(0),
|
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),
|
contactButton: buttons.get(0),
|
||||||
crisisButton: buttons.get(1),
|
crisisButton: buttons.get(1),
|
||||||
heroesButton: buttons.get(2)
|
heroesButton: buttons.get(2)
|
||||||
@ -67,7 +67,7 @@ describe('NgModule', function () {
|
|||||||
|
|
||||||
it('should welcome us', function () {
|
it('should welcome us', function () {
|
||||||
const commons = getCommonsSectionStruct();
|
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.component.3.ts",
|
||||||
"app/contact/contact.module.3.ts",
|
"app/contact/contact.module.3.ts",
|
||||||
"app/contact/contact-routing.module.3.ts",
|
"app/contact/contact-routing.module.3.ts",
|
||||||
"app/contact/highlight.directive.ts",
|
"app/contact/contact-highlight.directive.ts",
|
||||||
|
|
||||||
"app/crisis/*.ts",
|
"app/crisis/*.ts",
|
||||||
|
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { Routes, RouterModule } from '@angular/router';
|
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: '', redirectTo: 'contact', pathMatch: 'full'},
|
||||||
{ path: 'crisis', loadChildren: 'app/crisis/crisis.module#CrisisModule' },
|
{ path: 'crisis', loadChildren: 'app/crisis/crisis.module#CrisisModule' },
|
||||||
{ path: 'heroes', loadChildren: 'app/hero/hero.module.3#HeroModule' }
|
{ path: 'heroes', loadChildren: 'app/hero/hero.module.3#HeroModule' }
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forRoot(routes)],
|
imports: [
|
||||||
|
ContactModule,
|
||||||
|
RouterModule.forRoot(routes)
|
||||||
|
],
|
||||||
exports: [RouterModule]
|
exports: [RouterModule]
|
||||||
})
|
})
|
||||||
export class AppRoutingModule {}
|
export class AppRoutingModule {}
|
||||||
|
@ -2,18 +2,29 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { Routes, RouterModule } from '@angular/router';
|
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'},
|
{ path: '', redirectTo: 'contact', pathMatch: 'full'},
|
||||||
// #docregion lazy-routes
|
// #docregion lazy-routes
|
||||||
{ path: 'crisis', loadChildren: 'app/crisis/crisis.module#CrisisModule' },
|
{ path: 'crisis', loadChildren: 'app/crisis/crisis.module#CrisisModule' },
|
||||||
{ path: 'heroes', loadChildren: 'app/hero/hero.module#HeroModule' }
|
{ path: 'heroes', loadChildren: 'app/hero/hero.module#HeroModule' }
|
||||||
// #enddocregion lazy-routes
|
// #enddocregion lazy-routes
|
||||||
];
|
];
|
||||||
|
// #enddocregion routes
|
||||||
|
|
||||||
// #docregion forRoot
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forRoot(routes)],
|
// #docregion imports
|
||||||
|
imports: [
|
||||||
|
ContactModule,
|
||||||
|
// #docregion forRoot
|
||||||
|
RouterModule.forRoot(routes),
|
||||||
|
// #enddocregion forRoot
|
||||||
|
],
|
||||||
|
// #enddocregion imports
|
||||||
|
// #docregion exports
|
||||||
exports: [RouterModule]
|
exports: [RouterModule]
|
||||||
|
// #enddocregion exports
|
||||||
})
|
})
|
||||||
export class AppRoutingModule {}
|
export class AppRoutingModule {}
|
||||||
// #enddocregion forRoot
|
|
||||||
|
@ -6,5 +6,5 @@ import { Component } from '@angular/core';
|
|||||||
template: '<h1>{{title}}</h1>',
|
template: '<h1>{{title}}</h1>',
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
title = 'Minimal NgModule';
|
title = 'Angular Modules';
|
||||||
}
|
}
|
||||||
|
@ -11,9 +11,7 @@ import { Component } from '@angular/core';
|
|||||||
// #enddocregion template
|
// #enddocregion template
|
||||||
*/
|
*/
|
||||||
// #docregion
|
// #docregion
|
||||||
template: '<app-title [subtitle]="subtitle"></app-title>'
|
template: '<app-title></app-title>'
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {}
|
||||||
subtitle = '(v1)';
|
|
||||||
}
|
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
|
@ -5,11 +5,9 @@ import { Component } from '@angular/core';
|
|||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
// #docregion template
|
// #docregion template
|
||||||
template: `
|
template: `
|
||||||
<app-title [subtitle]="subtitle"></app-title>
|
<app-title></app-title>
|
||||||
<app-contact></app-contact>
|
<app-contact></app-contact>
|
||||||
`
|
`
|
||||||
// #enddocregion template
|
// #enddocregion template
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {}
|
||||||
subtitle = '(v1)';
|
|
||||||
}
|
|
||||||
|
@ -3,10 +3,8 @@ import { Component } from '@angular/core';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
template: `
|
template: `
|
||||||
<app-title [subtitle]="subtitle"></app-title>
|
<app-title></app-title>
|
||||||
<app-contact></app-contact>
|
<app-contact></app-contact>
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {}
|
||||||
subtitle = '(v2)';
|
|
||||||
}
|
|
||||||
|
@ -4,7 +4,7 @@ import { Component } from '@angular/core';
|
|||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
// #docregion template
|
// #docregion template
|
||||||
template: `
|
template: `
|
||||||
<app-title [subtitle]="subtitle"></app-title>
|
<app-title></app-title>
|
||||||
<nav>
|
<nav>
|
||||||
<a routerLink="contact" routerLinkActive="active">Contact</a>
|
<a routerLink="contact" routerLinkActive="active">Contact</a>
|
||||||
<a routerLink="crisis" routerLinkActive="active">Crisis Center</a>
|
<a routerLink="crisis" routerLinkActive="active">Crisis Center</a>
|
||||||
@ -14,6 +14,4 @@ import { Component } from '@angular/core';
|
|||||||
`
|
`
|
||||||
// #enddocregion template
|
// #enddocregion template
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {}
|
||||||
subtitle = '(v3)';
|
|
||||||
}
|
|
||||||
|
@ -5,7 +5,7 @@ import { Component } from '@angular/core';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
template: `
|
template: `
|
||||||
<app-title [subtitle]="subtitle"></app-title>
|
<app-title></app-title>
|
||||||
<nav>
|
<nav>
|
||||||
<a routerLink="contact" routerLinkActive="active">Contact</a>
|
<a routerLink="contact" routerLinkActive="active">Contact</a>
|
||||||
<a routerLink="crisis" routerLinkActive="active">Crisis Center</a>
|
<a routerLink="crisis" routerLinkActive="active">Crisis Center</a>
|
||||||
@ -14,6 +14,4 @@ import { Component } from '@angular/core';
|
|||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {}
|
||||||
subtitle = '(Final)';
|
|
||||||
}
|
|
||||||
|
@ -1,17 +1,7 @@
|
|||||||
// #docplaster
|
|
||||||
// #docregion
|
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
|
||||||
import
|
import { AppComponent } from './app.component.0';
|
||||||
// #enddocregion
|
|
||||||
{ AppComponent } from './app.component.0';
|
|
||||||
/*
|
|
||||||
// #docregion
|
|
||||||
{ AppComponent } from './app.component';
|
|
||||||
// #enddocregion
|
|
||||||
*/
|
|
||||||
// #docregion
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
// #docregion imports
|
// #docregion imports
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
// #docplaster
|
// #docplaster
|
||||||
// #docregion
|
// #docregion
|
||||||
|
/* Angular Imports */
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
|
||||||
import
|
/* App Imports */
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
{ AppComponent } from './app.component.1';
|
import { AppComponent } from './app.component.1';
|
||||||
/*
|
/*
|
||||||
// #docregion
|
// #docregion
|
||||||
{ AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
*/
|
*/
|
||||||
// #docregion
|
// #docregion
|
||||||
@ -21,12 +22,9 @@ import { FormsModule } from '@angular/forms';
|
|||||||
|
|
||||||
import { AwesomePipe } from './contact/awesome.pipe';
|
import { AwesomePipe } from './contact/awesome.pipe';
|
||||||
import { ContactComponent } from './contact/contact.component.3';
|
import { ContactComponent } from './contact/contact.component.3';
|
||||||
|
|
||||||
// #docregion import-contact-directive
|
|
||||||
import {
|
import {
|
||||||
HighlightDirective as ContactHighlightDirective
|
ContactHighlightDirective as ContactHighlightDirective
|
||||||
} from './contact/highlight.directive';
|
} from './contact/contact-highlight.directive';
|
||||||
// #enddocregion import-contact-directive
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
// #docregion imports
|
// #docregion imports
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
// #docplaster
|
// #docplaster
|
||||||
// #docregion
|
// #docregion
|
||||||
|
/* Angular Imports */
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
/* App Root */
|
/* App Imports */
|
||||||
import
|
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
{ AppComponent } from './app.component.1b';
|
import { AppComponent } from './app.component.1b';
|
||||||
/*
|
/*
|
||||||
// #docregion
|
// #docregion
|
||||||
{ AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
*/
|
*/
|
||||||
// #docregion
|
// #docregion
|
||||||
@ -18,25 +19,17 @@ import { TitleComponent } from './title.component';
|
|||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
|
|
||||||
/* Contact Imports */
|
/* Contact Imports */
|
||||||
import
|
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
{ ContactComponent } from './contact/contact.component.3';
|
import { ContactComponent } from './contact/contact.component.3';
|
||||||
/*
|
/*
|
||||||
// #docregion
|
// #docregion
|
||||||
{ ContactComponent } from './contact/contact.component';
|
import { ContactComponent } from './contact/contact.component';
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
*/
|
*/
|
||||||
// #docregion
|
// #docregion
|
||||||
import { ContactService } from './contact/contact.service';
|
import { AwesomePipe } from './contact/awesome.pipe';
|
||||||
import { AwesomePipe } from './contact/awesome.pipe';
|
import { ContactService } from './contact/contact.service';
|
||||||
|
import { ContactHighlightDirective } from './contact/contact-highlight.directive';
|
||||||
// #docregion import-alias
|
|
||||||
import {
|
|
||||||
HighlightDirective as ContactHighlightDirective
|
|
||||||
} from './contact/highlight.directive';
|
|
||||||
// #enddocregion import-alias
|
|
||||||
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [ BrowserModule, FormsModule ],
|
imports: [ BrowserModule, FormsModule ],
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
// #docplaster
|
// #docplaster
|
||||||
// #docregion
|
// #docregion
|
||||||
|
/* Angular Imports */
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
|
||||||
/* App Root */
|
/* App Imports */
|
||||||
import
|
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
{ AppComponent } from './app.component.2';
|
import { AppComponent } from './app.component.2';
|
||||||
/*
|
/*
|
||||||
// #docregion
|
// #docregion
|
||||||
{ AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
*/
|
*/
|
||||||
// #docregion
|
// #docregion
|
||||||
@ -18,12 +18,11 @@ import { TitleComponent } from './title.component';
|
|||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
|
|
||||||
/* Contact Imports */
|
/* Contact Imports */
|
||||||
import
|
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
{ ContactModule } from './contact/contact.module.2';
|
import { ContactModule } from './contact/contact.module.2';
|
||||||
/*
|
/*
|
||||||
// #docregion
|
// #docregion
|
||||||
{ ContactModule } from './contact/contact.module';
|
import { ContactModule } from './contact/contact.module';
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
*/
|
*/
|
||||||
// #docregion
|
// #docregion
|
||||||
|
@ -1,25 +1,36 @@
|
|||||||
// #docplaster
|
// #docplaster
|
||||||
// #docregion
|
// #docregion
|
||||||
|
/* Angular Imports */
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
|
||||||
/* App Root */
|
/* App Imports */
|
||||||
|
// #enddocregion
|
||||||
import { AppComponent } from './app.component.3';
|
import { AppComponent } from './app.component.3';
|
||||||
|
/*
|
||||||
|
// #docregion
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
// #enddocregion
|
||||||
|
*/
|
||||||
|
// #docregion
|
||||||
import { HighlightDirective } from './highlight.directive';
|
import { HighlightDirective } from './highlight.directive';
|
||||||
import { TitleComponent } from './title.component';
|
import { TitleComponent } from './title.component';
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
|
|
||||||
/* Feature Modules */
|
|
||||||
import { ContactModule } from './contact/contact.module.3';
|
|
||||||
|
|
||||||
/* Routing Module */
|
/* Routing Module */
|
||||||
|
// #enddocregion
|
||||||
import { AppRoutingModule } from './app-routing.module.3';
|
import { AppRoutingModule } from './app-routing.module.3';
|
||||||
|
/*
|
||||||
|
// #docregion
|
||||||
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
|
// #enddocregion
|
||||||
|
*/
|
||||||
|
// #docregion
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
// #docregion imports
|
// #docregion imports
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
ContactModule,
|
|
||||||
AppRoutingModule
|
AppRoutingModule
|
||||||
],
|
],
|
||||||
// #enddocregion imports
|
// #enddocregion imports
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
// #docplaster
|
// #docplaster
|
||||||
// #docregion
|
// #docregion
|
||||||
// #docregion v4
|
// #docregion v4
|
||||||
|
/* Angular Imports */
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
|
||||||
/* App Root */
|
/* App Imports */
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
|
|
||||||
/* Feature Modules */
|
/* Core Modules */
|
||||||
import { ContactModule } from './contact/contact.module';
|
|
||||||
import { CoreModule } from './core/core.module';
|
import { CoreModule } from './core/core.module';
|
||||||
|
|
||||||
/* Routing Module */
|
/* Routing Module */
|
||||||
@ -18,7 +18,6 @@ import { AppRoutingModule } from './app-routing.module';
|
|||||||
// #docregion import-for-root
|
// #docregion import-for-root
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
ContactModule,
|
|
||||||
// #enddocregion v4
|
// #enddocregion v4
|
||||||
// #enddocregion import-for-root
|
// #enddocregion import-for-root
|
||||||
/*
|
/*
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* tslint:disable */
|
// #docplaster
|
||||||
// Same directive name and selector as
|
// Same directive name and selector as
|
||||||
// HighlightDirective in parent AppModule
|
// HighlightDirective in parent AppModule
|
||||||
// It selects for both input boxes and 'highlight' attr
|
// It selects for both input boxes and 'highlight' attr
|
||||||
@ -7,12 +7,14 @@
|
|||||||
// #docregion
|
// #docregion
|
||||||
import { Directive, ElementRef } from '@angular/core';
|
import { Directive, ElementRef } from '@angular/core';
|
||||||
|
|
||||||
|
// Highlight the host element or any InputElement in blue
|
||||||
@Directive({ selector: '[highlight], input' })
|
@Directive({ selector: '[highlight], input' })
|
||||||
/** Highlight the attached element or an InputElement in blue */
|
export class ContactHighlightDirective {
|
||||||
export class HighlightDirective {
|
|
||||||
constructor(el: ElementRef) {
|
constructor(el: ElementRef) {
|
||||||
el.nativeElement.style.backgroundColor = 'powderblue';
|
el.nativeElement.style.backgroundColor = 'powderblue';
|
||||||
console.log(
|
// #enddocregion
|
||||||
`* Contact highlight called for ${el.nativeElement.tagName}`);
|
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';
|
import { ContactComponent } from './contact.component.3';
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{ path: 'contact', component: ContactComponent}
|
||||||
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forChild([
|
imports: [ RouterModule.forChild(routes) ],
|
||||||
{ path: 'contact', component: ContactComponent}
|
exports: [ RouterModule ]
|
||||||
])],
|
|
||||||
exports: [RouterModule]
|
|
||||||
})
|
})
|
||||||
export class ContactRoutingModule {}
|
export class ContactRoutingModule {}
|
||||||
|
@ -4,11 +4,13 @@ import { RouterModule } from '@angular/router';
|
|||||||
import { ContactComponent } from './contact.component';
|
import { ContactComponent } from './contact.component';
|
||||||
|
|
||||||
// #docregion routing
|
// #docregion routing
|
||||||
|
const routes = [
|
||||||
|
{ path: 'contact', component: ContactComponent}
|
||||||
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forChild([
|
imports: [ RouterModule.forChild(routes) ],
|
||||||
{ path: 'contact', component: ContactComponent }
|
exports: [ RouterModule ]
|
||||||
])],
|
|
||||||
exports: [RouterModule]
|
|
||||||
})
|
})
|
||||||
export class ContactRoutingModule {}
|
export class ContactRoutingModule {}
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
|
@ -21,7 +21,7 @@ export class ContactComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.contactService.getContacts().then(contacts => {
|
this.contactService.getContacts().subscribe(contacts => {
|
||||||
this.msg = '';
|
this.msg = '';
|
||||||
this.contacts = contacts;
|
this.contacts = contacts;
|
||||||
this.contact = contacts[0];
|
this.contact = contacts[0];
|
||||||
|
@ -27,3 +27,6 @@
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
@ -6,18 +6,32 @@
|
|||||||
<!-- #docregion awesome -->
|
<!-- #docregion awesome -->
|
||||||
<h3 highlight>{{ contact.name | awesome }}</h3>
|
<h3 highlight>{{ contact.name | awesome }}</h3>
|
||||||
<!-- #enddocregion awesome -->
|
<!-- #enddocregion awesome -->
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="name">Name</label>
|
<label for="name">Name</label>
|
||||||
|
|
||||||
|
<!-- #docregion ngModel -->
|
||||||
<input type="text" class="form-control" required
|
<input type="text" class="form-control" required
|
||||||
[(ngModel)]="contact.name"
|
[(ngModel)]="contact.name"
|
||||||
name="name" #name="ngModel" >
|
name="name" #name="ngModel" >
|
||||||
|
<!-- #enddocregion ngModel -->
|
||||||
|
|
||||||
<div [hidden]="name.valid" class="alert alert-danger">
|
<div [hidden]="name.valid" class="alert alert-danger">
|
||||||
Name is required
|
Name is required
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
|
||||||
<button type="submit" class="btn btn-default" [disabled]="!contactForm.form.valid">Save</button>
|
<div class="button-group">
|
||||||
<button type="button" class="btn" (click)="next()" [disabled]="!contactForm.form.valid">Next Contact</button>
|
<button type="submit" class="btn btn-default"
|
||||||
<button type="button" class="btn" (click)="newContact()">New Contact</button>
|
[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>
|
</form>
|
||||||
<!-- #enddocregion -->
|
<!-- #enddocregion -->
|
||||||
|
@ -22,7 +22,7 @@ export class ContactComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.contactService.getContacts().then(contacts => {
|
this.contactService.getContacts().subscribe(contacts => {
|
||||||
this.msg = '';
|
this.msg = '';
|
||||||
this.contacts = contacts;
|
this.contacts = contacts;
|
||||||
this.contact = contacts[0];
|
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 { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
import { AwesomePipe } from './awesome.pipe';
|
import { AwesomePipe } from './awesome.pipe';
|
||||||
|
|
||||||
import
|
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
{ ContactComponent } from './contact.component.3';
|
import { ContactComponent } from './contact.component.3';
|
||||||
/*
|
/*
|
||||||
// #docregion
|
// #docregion
|
||||||
{ ContactComponent } from './contact.component';
|
import { ContactComponent } from './contact.component';
|
||||||
// #enddocregion
|
// #enddocregion
|
||||||
*/
|
*/
|
||||||
// #docregion
|
// #docregion
|
||||||
|
import { ContactHighlightDirective } from './contact-highlight.directive';
|
||||||
import { ContactService } from './contact.service';
|
import { ContactService } from './contact.service';
|
||||||
import { HighlightDirective } from './highlight.directive';
|
|
||||||
|
|
||||||
// #docregion class
|
// #docregion class
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [ CommonModule, FormsModule ],
|
imports: [
|
||||||
declarations: [ ContactComponent, HighlightDirective, AwesomePipe ],
|
CommonModule,
|
||||||
exports: [ ContactComponent ],
|
FormsModule
|
||||||
providers: [ ContactService ]
|
],
|
||||||
|
declarations: [
|
||||||
|
AwesomePipe,
|
||||||
|
ContactComponent,
|
||||||
|
ContactHighlightDirective
|
||||||
|
],
|
||||||
|
// #docregion exports
|
||||||
|
exports: [ ContactComponent ],
|
||||||
|
// #enddocregion exports
|
||||||
|
providers: [ ContactService ]
|
||||||
})
|
})
|
||||||
export class ContactModule { }
|
export class ContactModule { }
|
||||||
// #enddocregion class
|
// #enddocregion class
|
||||||
|
@ -1,21 +1,43 @@
|
|||||||
|
// #docplaster
|
||||||
// #docregion
|
// #docregion
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
import { AwesomePipe } from './awesome.pipe';
|
import { AwesomePipe } from './awesome.pipe';
|
||||||
|
// #enddocregion
|
||||||
import { ContactComponent } from './contact.component.3';
|
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 { ContactService } from './contact.service';
|
||||||
import { HighlightDirective } from './highlight.directive';
|
|
||||||
|
|
||||||
|
// #enddocregion
|
||||||
import { ContactRoutingModule } from './contact-routing.module.3';
|
import { ContactRoutingModule } from './contact-routing.module.3';
|
||||||
|
/*
|
||||||
|
// #docregion
|
||||||
|
import { ContactRoutingModule } from './contact-routing.module';
|
||||||
|
// #enddocregion
|
||||||
|
*/
|
||||||
|
// #docregion
|
||||||
|
|
||||||
// #docregion class
|
// #docregion class
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [ CommonModule, FormsModule, ContactRoutingModule ],
|
imports: [
|
||||||
declarations: [ ContactComponent, HighlightDirective, AwesomePipe ],
|
CommonModule,
|
||||||
providers: [ ContactService ]
|
FormsModule,
|
||||||
|
ContactRoutingModule
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
AwesomePipe,
|
||||||
|
ContactComponent,
|
||||||
|
ContactHighlightDirective
|
||||||
|
],
|
||||||
|
providers: [ ContactService ]
|
||||||
})
|
})
|
||||||
export class ContactModule { }
|
export class ContactModule { }
|
||||||
// #enddocregion class
|
// #enddocregion class
|
||||||
|
@ -8,7 +8,10 @@ import { ContactRoutingModule } from './contact-routing.module';
|
|||||||
|
|
||||||
// #docregion class
|
// #docregion class
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [ SharedModule, ContactRoutingModule ],
|
imports: [
|
||||||
|
SharedModule,
|
||||||
|
ContactRoutingModule
|
||||||
|
],
|
||||||
declarations: [ ContactComponent ],
|
declarations: [ ContactComponent ],
|
||||||
providers: [ ContactService ]
|
providers: [ ContactService ]
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
|
// #docplaster
|
||||||
// #docregion
|
// #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 {
|
export class Contact {
|
||||||
constructor(public id: number, public name: string) { }
|
constructor(public id: number, public name: string) { }
|
||||||
@ -13,17 +18,21 @@ const CONTACTS: Contact[] = [
|
|||||||
|
|
||||||
const FETCH_LATENCY = 500;
|
const FETCH_LATENCY = 500;
|
||||||
|
|
||||||
|
/** Simulate a data service that retrieves contacts from a server */
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ContactService {
|
export class ContactService implements OnDestroy {
|
||||||
|
// #enddocregion
|
||||||
|
constructor() { console.log('ContactService instance created.'); }
|
||||||
|
ngOnDestroy() { console.log('ContactService instance destroyed.'); }
|
||||||
|
|
||||||
getContacts() {
|
// #docregion
|
||||||
return new Promise<Contact[]>(resolve => {
|
getContacts(): Observable<Contact[]> {
|
||||||
setTimeout(() => { resolve(CONTACTS); }, FETCH_LATENCY);
|
return of(CONTACTS).pipe(delay(FETCH_LATENCY));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getContact(id: number | string) {
|
getContact(id: number | string): Observable<Contact> {
|
||||||
return this.getContacts()
|
return of(CONTACTS.find(contact => contact.id === +id))
|
||||||
.then(heroes => heroes.find(hero => hero.id === +id));
|
.pipe(delay(FETCH_LATENCY));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// #enddocregion
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!-- Exact copy from earlier app.component.html -->
|
<!-- Exact copy from earlier app.component.html -->
|
||||||
<h1 highlight>{{title}} {{subtitle}}</h1>
|
<h1 highlight>{{title}}</h1>
|
||||||
<p *ngIf="user">
|
<p *ngIf="user">
|
||||||
<i>Welcome, {{user}}</i>
|
<i>Welcome, {{user}}</i>
|
||||||
<p>
|
<p>
|
||||||
|
@ -7,7 +7,6 @@ import { UserService } from '../core/user.service';
|
|||||||
templateUrl: './title.component.html',
|
templateUrl: './title.component.html',
|
||||||
})
|
})
|
||||||
export class TitleComponent {
|
export class TitleComponent {
|
||||||
@Input() subtitle = '';
|
|
||||||
title = 'Angular Modules';
|
title = 'Angular Modules';
|
||||||
user = '';
|
user = '';
|
||||||
|
|
||||||
|
@ -1,22 +1,21 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
import { Crisis,
|
import { Crisis,
|
||||||
CrisisService } from './crisis.service';
|
CrisisService } from './crisis.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
<h3 highlight>Crisis List</h3>
|
<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>
|
<a routerLink="{{'../' + crisis.id}}">{{crisis.id}} - {{crisis.name}}</a>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
export class CrisisListComponent implements OnInit {
|
export class CrisisListComponent {
|
||||||
crisises: Promise<Crisis[]>;
|
crises: Observable<Crisis[]>;
|
||||||
|
|
||||||
constructor(private crisisService: CrisisService) { }
|
constructor(private crisisService: CrisisService) {
|
||||||
|
this.crises = this.crisisService.getCrises();
|
||||||
ngOnInit() {
|
|
||||||
this.crisises = 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 {
|
export class Crisis {
|
||||||
constructor(public id: number, public name: string) { }
|
constructor(public id: number, public name: string) { }
|
||||||
@ -13,18 +17,18 @@ const CRISES: Crisis[] = [
|
|||||||
|
|
||||||
const FETCH_LATENCY = 500;
|
const FETCH_LATENCY = 500;
|
||||||
|
|
||||||
|
/** Simulate a data service that retrieves crises from a server */
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CrisisService {
|
export class CrisisService implements OnDestroy {
|
||||||
|
constructor() { console.log('CrisisService instance created.'); }
|
||||||
|
ngOnDestroy() { console.log('CrisisService instance destroyed.'); }
|
||||||
|
|
||||||
getCrises() {
|
getCrises(): Observable<Crisis[]> {
|
||||||
return new Promise<Crisis[]>(resolve => {
|
return of(CRISES).pipe(delay(FETCH_LATENCY));
|
||||||
setTimeout(() => { resolve(CRISES); }, FETCH_LATENCY);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getCrisis(id: number | string) {
|
getCrisis(id: number | string): Observable<Crisis> {
|
||||||
return this.getCrises()
|
return of(CRISES.find(crisis => crisis.id === +id))
|
||||||
.then(heroes => heroes.find(hero => hero.id === +id));
|
.pipe(delay(FETCH_LATENCY));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,6 @@ export class HeroDetailComponent implements OnInit {
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
let id = parseInt(this.route.snapshot.paramMap.get('id'), 10);
|
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,
|
import { Hero,
|
||||||
HeroService } from './hero.service';
|
HeroService } from './hero.service';
|
||||||
@ -11,11 +12,9 @@ import { Hero,
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
export class HeroListComponent implements OnInit {
|
export class HeroListComponent {
|
||||||
heroes: Promise<Hero[]>;
|
heroes: Observable<Hero[]>;
|
||||||
constructor(private heroService: HeroService) { }
|
constructor(private heroService: HeroService) {
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.heroes = this.heroService.getHeroes();
|
this.heroes = this.heroService.getHeroes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,10 @@ import { FormsModule } from '@angular/forms';
|
|||||||
import { HeroComponent } from './hero.component.3';
|
import { HeroComponent } from './hero.component.3';
|
||||||
import { HeroDetailComponent } from './hero-detail.component';
|
import { HeroDetailComponent } from './hero-detail.component';
|
||||||
import { HeroListComponent } from './hero-list.component';
|
import { HeroListComponent } from './hero-list.component';
|
||||||
import { HighlightDirective } from './highlight.directive';
|
|
||||||
import { HeroRoutingModule } from './hero-routing.module.3';
|
import { HeroRoutingModule } from './hero-routing.module.3';
|
||||||
|
|
||||||
|
import { HighlightDirective } from './highlight.directive';
|
||||||
|
|
||||||
// #docregion class
|
// #docregion class
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [ CommonModule, FormsModule, HeroRoutingModule ],
|
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 {
|
export class Hero {
|
||||||
constructor(public id: number, public name: string) { }
|
constructor(public id: number, public name: string) { }
|
||||||
@ -15,18 +19,19 @@ const HEROES: Hero[] = [
|
|||||||
|
|
||||||
const FETCH_LATENCY = 500;
|
const FETCH_LATENCY = 500;
|
||||||
|
|
||||||
|
/** Simulate a data service that retrieves heroes from a server */
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class HeroService {
|
export class HeroService implements OnDestroy {
|
||||||
|
|
||||||
getHeroes() {
|
constructor() { console.log('HeroService instance created.'); }
|
||||||
return new Promise<Hero[]>(resolve => {
|
ngOnDestroy() { console.log('HeroService instance destroyed.'); }
|
||||||
setTimeout(() => { resolve(HEROES); }, FETCH_LATENCY);
|
|
||||||
});
|
getHeroes(): Observable<Hero[]> {
|
||||||
|
return of(HEROES).pipe(delay(FETCH_LATENCY));
|
||||||
}
|
}
|
||||||
|
|
||||||
getHero(id: number | string) {
|
getHero(id: number | string): Observable<Hero> {
|
||||||
return this.getHeroes()
|
return of(HEROES.find(hero => hero.id === +id))
|
||||||
.then(heroes => heroes.find(hero => hero.id === +id));
|
.pipe(delay(FETCH_LATENCY));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
|
// #docplaster
|
||||||
// #docregion
|
// #docregion
|
||||||
import { Directive, ElementRef } from '@angular/core';
|
import { Directive, ElementRef } from '@angular/core';
|
||||||
|
|
||||||
|
// Highlight the host element in gold
|
||||||
@Directive({ selector: '[highlight]' })
|
@Directive({ selector: '[highlight]' })
|
||||||
/** Highlight the attached element in gold */
|
|
||||||
export class HighlightDirective {
|
export class HighlightDirective {
|
||||||
constructor(el: ElementRef) {
|
constructor(el: ElementRef) {
|
||||||
el.nativeElement.style.backgroundColor = 'gold';
|
el.nativeElement.style.backgroundColor = 'gold';
|
||||||
console.log(
|
// #enddocregion
|
||||||
`* AppRoot highlight called for ${el.nativeElement.tagName}`);
|
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
|
// Exact copy of contact/highlight.directive except for color and message
|
||||||
import { Directive, ElementRef } from '@angular/core';
|
import { Directive, ElementRef } from '@angular/core';
|
||||||
|
|
||||||
@Directive({ selector: '[highlight], input' })
|
@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 {
|
export class HighlightDirective {
|
||||||
constructor(el: ElementRef) {
|
constructor(el: ElementRef) {
|
||||||
el.nativeElement.style.backgroundColor = 'lightgray';
|
el.nativeElement.style.backgroundColor = 'lightgray';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<!-- #docregion -->
|
<!-- #docregion -->
|
||||||
<!-- #docregion v1 -->
|
<!-- #docregion v1 -->
|
||||||
<h1 highlight>{{title}} {{subtitle}}</h1>
|
<h1 highlight>{{title}}</h1>
|
||||||
<!-- #enddocregion v1 -->
|
<!-- #enddocregion v1 -->
|
||||||
<!-- #docregion ngIf -->
|
<!-- #docregion ngIf -->
|
||||||
<p *ngIf="user">
|
<p *ngIf="user">
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
// #docplaster
|
// #docplaster
|
||||||
// #docregion
|
// #docregion
|
||||||
// #docregion v1
|
// #docregion v1
|
||||||
import { Component, Input } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
// #enddocregion v1
|
// #enddocregion v1
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
// #docregion v1
|
// #docregion v1
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-title',
|
selector: 'app-title',
|
||||||
templateUrl: './title.component.html',
|
templateUrl: './title.component.html'
|
||||||
})
|
})
|
||||||
export class TitleComponent {
|
export class TitleComponent {
|
||||||
@Input() subtitle = '';
|
title = 'Angular Modules';
|
||||||
title = 'NgModules';
|
|
||||||
// #enddocregion v1
|
// #enddocregion v1
|
||||||
user = '';
|
user = '';
|
||||||
|
|
||||||
|
@ -9,6 +9,6 @@ export class MessageService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.messages.length = 0;
|
this.messages = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,6 @@ export class MessageService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.messages.length = 0;
|
this.messages = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,6 @@ export class MessageService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.messages.length = 0;
|
this.messages = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,6 @@ export class MessageService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
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.
|
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")
|
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
|
### Metadata rewriting
|
||||||
|
|
||||||
|
@ -160,7 +160,7 @@ Providing the `UserService` with an Angular module is a good choice.
|
|||||||
<div class="l-sub-section">
|
<div class="l-sub-section">
|
||||||
|
|
||||||
To be precise, Angular module providers are registered with the root injector
|
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,
|
In this sample, all modules are _eagerly loaded_ when the application starts,
|
||||||
so all module providers are registered with the app's root injector.
|
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 -->
|
<!-- Announcement Bar -->
|
||||||
<div class="homepage-container">
|
<div class="homepage-container">
|
||||||
<div class="announcement-bar">
|
<div class="announcement-bar">
|
||||||
<img src="generated/images/marketing/home/angular-connect.png">
|
<img src="generated/images/marketing/home/ng-atl.png">
|
||||||
<p>Join us in London for AngularConnect<br>November 7-8, 2017</p>
|
<p>Join us in Atlanta for ngATL<br>Jan 30 - Feb 2, 2018</p>
|
||||||
<a class="button" href="https://angularconnect.com/">Learn More</a>
|
<a class="button" href="http://ng-atl.org/">Learn More</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -534,6 +534,12 @@
|
|||||||
"rev": true,
|
"rev": true,
|
||||||
"title": "Ultimate Angular",
|
"title": "Ultimate Angular",
|
||||||
"url": "https://ultimateangular.com/"
|
"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,
|
"rev": true,
|
||||||
"title": "SFEIR School (French)",
|
"title": "SFEIR School (French)",
|
||||||
"url": "https://school.sfeir.com/project/sa200/"
|
"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",
|
"title": "NgModules",
|
||||||
"tooltip": "Learn how to use NgModules to make your apps efficient.",
|
"tooltip": "Modularize your app with NgModules.",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"url": "guide/ngmodule",
|
"url": "guide/ngmodule",
|
||||||
"title": "NgModules",
|
"title": "NgModules",
|
||||||
"tooltip": "Define application modules with @NgModule."
|
"tooltip": "Define application modules with the NgModule."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"url": "guide/ngmodule-faq",
|
"url": "guide/ngmodule-faq",
|
||||||
"title": "NgModule FAQs",
|
"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>
|
</code-example>
|
||||||
|
|
||||||
Once you've finished setting up, the router will match that URL to `path: 'heroes'`
|
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()_
|
### _RouterModule.forRoot()_
|
||||||
|
|
||||||
|
@ -98,6 +98,7 @@
|
|||||||
"codelyzer": "~2.0.0",
|
"codelyzer": "~2.0.0",
|
||||||
"concurrently": "^3.4.0",
|
"concurrently": "^3.4.0",
|
||||||
"cross-spawn": "^5.1.0",
|
"cross-spawn": "^5.1.0",
|
||||||
|
"css-selector-parser": "^1.3.0",
|
||||||
"dgeni": "^0.4.7",
|
"dgeni": "^0.4.7",
|
||||||
"dgeni-packages": "0.22.0",
|
"dgeni-packages": "0.22.0",
|
||||||
"entities": "^1.1.1",
|
"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
|
# Track payload size functions
|
||||||
source ../scripts/ci/payload-size.sh
|
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`
|
with that script, the author would need to specify the new build command in the `example-config.json`
|
||||||
as shown earlier.
|
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
|
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
|
`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
|
To not run all tests, you can use the `--filter=name` flag to run the example's e2e that contains
|
||||||
that name.
|
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`.
|
the latest `webdriver`.
|
||||||
|
|
||||||
It will create a `/aio/protractor-results-txt` file when it finishes running tests.
|
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
|
* @dgProcessor addMetadataAliases
|
||||||
*
|
*
|
||||||
@ -28,11 +30,18 @@ module.exports = function addMetadataAliasesProcessor() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function extractSelectors(selectors) {
|
function extractSelectors(selectors) {
|
||||||
if (selectors) {
|
const selectorAST = cssParser.parse(stripQuotes(selectors));
|
||||||
return stripQuotes(selectors).split(',').map(selector => selector.replace(/^\W*([\w-]+)\W*$/, '$1'));
|
const rules = selectorAST.selectors ? selectorAST.selectors.map(ruleSet => ruleSet.rule) : [selectorAST.rule];
|
||||||
} else {
|
const aliases = {};
|
||||||
return [];
|
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) {
|
function stripQuotes(value) {
|
||||||
|
@ -45,8 +45,8 @@ describe('addSelectorsAsAliases processor', () => {
|
|||||||
expect(docs[2].aliases).toEqual([docs[2].name]);
|
expect(docs[2].aliases).toEqual([docs[2].name]);
|
||||||
expect(docs[3].aliases).toEqual([docs[3].name]);
|
expect(docs[3].aliases).toEqual([docs[3].name]);
|
||||||
expect(docs[4].aliases).toEqual([docs[4].name, 'myPipe']);
|
expect(docs[4].aliases).toEqual([docs[4].name, 'myPipe']);
|
||||||
expect(docs[5].aliases).toEqual([docs[5].name, 'my-directive', 'myDirective', 'my-directive']);
|
expect(docs[5].aliases).toEqual([docs[5].name, 'my-directive', 'myDirective']);
|
||||||
expect(docs[6].aliases).toEqual([docs[6].name, '[ngModel]:not([formControlName]):not([formControl])']);
|
expect(docs[6].aliases).toEqual([docs[6].name, 'ngModel']);
|
||||||
expect(docs[7].aliases).toEqual([docs[7].name, 'my-component']);
|
expect(docs[7].aliases).toEqual([docs[7].name, 'my-component']);
|
||||||
expect(docs[8].aliases).toEqual([docs[8].name]);
|
expect(docs[8].aliases).toEqual([docs[8].name]);
|
||||||
expect(docs[9].aliases).toEqual([docs[9].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('packageInfo', function() { return require(path.resolve(PROJECT_ROOT, 'package.json')); })
|
||||||
.factory(require('./readers/json'))
|
.factory(require('./readers/json'))
|
||||||
.factory(require('./services/copyFolder'))
|
.factory(require('./services/copyFolder'))
|
||||||
|
.factory(require('./services/filterPipes'))
|
||||||
|
.factory(require('./services/filterAmbiguousDirectiveAliases'))
|
||||||
.factory(require('./services/getImageDimensions'))
|
.factory(require('./services/getImageDimensions'))
|
||||||
|
|
||||||
.factory(require('./post-processors/add-image-dimensions'))
|
.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');
|
addImageDimensions.basePath = path.resolve(AIO_PATH, 'src');
|
||||||
|
autoLinkCode.customFilters = [filterPipes, filterAmbiguousDirectiveAliases];
|
||||||
postProcessHtml.plugins = [
|
postProcessHtml.plugins = [
|
||||||
require('./post-processors/autolink-headings'),
|
require('./post-processors/autolink-headings'),
|
||||||
addImageDimensions,
|
addImageDimensions,
|
||||||
|
@ -10,12 +10,19 @@ const textContent = require('hast-util-to-string');
|
|||||||
* Only docs that have one of these docTypes will be linked to.
|
* 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.
|
* 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.
|
* @property codeElements an array of strings.
|
||||||
* Only text contained in these elements will be linked to.
|
* Only text contained in these elements will be linked to.
|
||||||
* Usually set to "code" but also "code-example" for angular.io.
|
* Usually set to "code" but also "code-example" for angular.io.
|
||||||
*/
|
*/
|
||||||
module.exports = function autoLinkCode(getDocFromAlias) {
|
module.exports = function autoLinkCode(getDocFromAlias) {
|
||||||
autoLinkCodeImpl.docTypes = [];
|
autoLinkCodeImpl.docTypes = [];
|
||||||
|
autoLinkCodeImpl.customFilters = [];
|
||||||
autoLinkCodeImpl.codeElements = ['code'];
|
autoLinkCodeImpl.codeElements = ['code'];
|
||||||
return autoLinkCodeImpl;
|
return autoLinkCodeImpl;
|
||||||
|
|
||||||
@ -38,12 +45,13 @@ module.exports = function autoLinkCode(getDocFromAlias) {
|
|||||||
parent.children.splice(index, 1, createLinkNode(docs[0], node.value));
|
parent.children.splice(index, 1, createLinkNode(docs[0], node.value));
|
||||||
} else {
|
} else {
|
||||||
// Parse the text for words that we can convert to links
|
// 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)
|
.filter(word => word.length)
|
||||||
.map(word => {
|
.map((word, index, words) => {
|
||||||
const docs = getDocFromAlias(word);
|
// remove docs that fail the custom filter tests
|
||||||
return foundValidDoc(docs) ?
|
const filteredDocs = autoLinkCodeImpl.customFilters.reduce((docs, filter) => filter(docs, words, index), getDocFromAlias(word));
|
||||||
createLinkNode(docs[0], word) : // Create a link wrapping the text node.
|
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
|
{ 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');
|
var Dgeni = require('dgeni');
|
||||||
|
|
||||||
describe('autoLinkCode post-processor', () => {
|
describe('autoLinkCode post-processor', () => {
|
||||||
let processor, autoLinkCode, aliasMap;
|
let processor, autoLinkCode, aliasMap, filterPipes;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const testPackage = createTestPackage('angular-base-package');
|
const testPackage = createTestPackage('angular-base-package');
|
||||||
@ -14,6 +14,7 @@ describe('autoLinkCode post-processor', () => {
|
|||||||
processor = injector.get('postProcessHtml');
|
processor = injector.get('postProcessHtml');
|
||||||
processor.docTypes = ['test-doc'];
|
processor.docTypes = ['test-doc'];
|
||||||
processor.plugins = [autoLinkCode];
|
processor.plugins = [autoLinkCode];
|
||||||
|
filterPipes = injector.get('filterPipes');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should insert an anchor into every code item that matches the id of an API doc', () => {
|
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>');
|
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', () => {
|
it('should insert anchors for individual text nodes within a code block', () => {
|
||||||
aliasMap.addDoc({ docType: 'class', id: 'MyClass', aliases: ['MyClass'], path: 'a/b/myclass' });
|
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>' };
|
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"
|
domutils "1.5.1"
|
||||||
nth-check "~1.0.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:
|
css-selector-tokenizer@^0.7.0:
|
||||||
version "0.7.0"
|
version "0.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86"
|
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
|
# Triage Process and GitHub Labels for Angular
|
||||||
|
|
||||||
This document describes how the Angular team uses labels and milestones
|
This document describes how the Angular team uses labels and milestones to triage issues on github.
|
||||||
to triage issues on github. The basic idea of the process is that
|
The basic idea of the process is that caretaker only assigns a component (`comp: *`) label.
|
||||||
caretaker only assigns a component and type (bug, feature) label. The
|
The owner of the component is then responsible for the secondary / component-level triage.
|
||||||
owner of the component than is in full control of how the issues should
|
|
||||||
be triaged further.
|
|
||||||
|
|
||||||
Once this process is implemented and in use, we will revisit it to see
|
|
||||||
if further labeling is needed.
|
|
||||||
|
|
||||||
## Label Types
|
## Label Types
|
||||||
|
|
||||||
### Components
|
### Components
|
||||||
|
|
||||||
A caretaker should be able to determine which component the issue
|
The caretaker should be able to determine which component the issue belongs to.
|
||||||
belongs to. The components have a clear piece of source code associated
|
The components have a clear piece of source code associated with it within the `/packages/` folder of this repo.
|
||||||
with it within the `/packages/` folder of this repo.
|
|
||||||
|
|
||||||
* `comp: aio` - the angular.io application
|
* `comp: aio` - the angular.io application
|
||||||
* `comp: animations`
|
* `comp: animations`
|
||||||
* `comp: bazel`
|
* `comp: bazel` - @angular/bazel rules
|
||||||
* `comp: benchpress`
|
* `comp: benchpress`
|
||||||
* `comp: common` - this includes core components / pipes
|
* `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
|
browser-platforms are very intertwined, we will be treating them as one
|
||||||
* `comp: forms`
|
* `comp: forms`
|
||||||
* `comp: http`
|
* `comp: http`
|
||||||
* `comp: i18n`
|
* `comp: i18n`
|
||||||
* `comp: language service`
|
* `comp: language-service`
|
||||||
|
* `comp: metadata-extractor`
|
||||||
* `comp: router`
|
* `comp: router`
|
||||||
|
* `comp: server`
|
||||||
|
* `comp: service-worker`
|
||||||
* `comp: testing`
|
* `comp: testing`
|
||||||
* `comp: upgrade`
|
* `comp: upgrade/dynamic`
|
||||||
|
* `comp: upgrade/static`
|
||||||
* `comp: web-worker`
|
* `comp: web-worker`
|
||||||
* `comp: zones`
|
* `comp: zones`
|
||||||
|
|
||||||
There are few components which are cross-cutting. They don't have
|
There are few components which are cross-cutting.
|
||||||
a clear location in the source tree. We will treat them as a component
|
They don't have a clear location in the source tree.
|
||||||
even thought no specific source tree is associated with them.
|
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: docs` - documentation, including API docs, guides, tutorial
|
||||||
* `comp: packaging`
|
* `comp: packaging` - packaging format of @angular/* npm packages
|
||||||
* `comp: performance`
|
* `comp: performance`
|
||||||
* `comp: security`
|
* `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
|
### Type
|
||||||
|
|
||||||
@ -56,27 +59,27 @@ What kind of problem is this?
|
|||||||
* `type: performance`
|
* `type: performance`
|
||||||
* `type: refactor`
|
* `type: refactor`
|
||||||
|
|
||||||
## Caretaker Triage Process
|
|
||||||
|
|
||||||
It is the caretaker's responsibility to assign `comp: *` to each new
|
## Caretaker Triage Process (Primary Triage)
|
||||||
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
|
It is the caretaker's responsibility to assign `comp: *` to each new issue as they come in.
|
||||||
knowledge the caretaker could mislabel issues or lack knowledge of
|
|
||||||
duplicate issues.
|
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
|
## Component's owner Triage Process
|
||||||
|
|
||||||
At this point we are leaving each component owner to determine their own
|
The component owner is responsible for assigning one of the labels from each of these categories:
|
||||||
process for their component.
|
|
||||||
|
|
||||||
It will be up to the component owner to determine the order in which the
|
- `type: *`
|
||||||
issues within the component will be resolved.
|
- `frequency: *`
|
||||||
|
- `severity: *`
|
||||||
|
|
||||||
Several owners have adopted the issue categorization based on
|
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.
|
||||||
[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:
|
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`
|
`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
|
## Triaging PRs
|
||||||
primary area the PR effects.
|
|
||||||
|
|
||||||
Because of the cumulative pain associated with rebasing PRs, we triage PRs daily, and
|
Triaging PRs is the same as triaging issues, except that PRs have additional label categories that should be used to signal their state.
|
||||||
closing or reviewing PRs is a top priority ahead of other ongoing work.
|
|
||||||
|
|
||||||
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: review` - work is complete and comment is needed from the reviewers.
|
||||||
* `PR action: cleanup` - more work is needed from the current assignee.
|
* `PR action: cleanup` - more work is needed from the author.
|
||||||
* `PR action: discuss` - discussion is needed, to be led by the current assignee.
|
* `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.
|
* `PR action: merge` - the PR is ready to be merged by the caretaker.
|
||||||
|
|
||||||
In addition, PRs can have the following states:
|
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: 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
|
## 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:
|
To communicate the target we use the following labels:
|
||||||
|
|
||||||
* `PR target: master-only`
|
* `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: patch-only`
|
* `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: master & patch`
|
* `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: TBD` - the target is yet to be determined
|
* `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
|
## 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.
|
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
|
Note that approved state does not mean a PR is ready to be merged.
|
||||||
approve the PR but request a minor tweak that doesn't need further review, e.g., a rebase or small
|
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.
|
||||||
uncontroversial change.
|
Only the `PR action: merge` label means that the PR is ready for merging.
|
||||||
|
|
||||||
|
|
||||||
## Special Labels
|
## Special Labels
|
||||||
|
|
||||||
### action:design
|
### `cla: yes`, `cla: no`
|
||||||
More active discussion is needed before the issue can be worked on further. Typically used for
|
Managed by googlebot.
|
||||||
`type: feature` or `type: RFC/discussion/question`
|
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)
|
### `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)
|
||||||
### 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.
|
|
||||||
|
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`
|
cd `dirname $0`
|
||||||
|
|
||||||
|
readonly thisDir=$(cd $(dirname $0); pwd)
|
||||||
|
|
||||||
# Track payload size functions
|
# Track payload size functions
|
||||||
source ../scripts/ci/payload-size.sh
|
source ../scripts/ci/payload-size.sh
|
||||||
source ./_payload-limits.sh
|
|
||||||
|
|
||||||
# Workaround https://github.com/yarnpkg/yarn/issues/2165
|
# Workaround https://github.com/yarnpkg/yarn/issues/2165
|
||||||
# Yarn will cache file://dist URIs and not update Angular code
|
# 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
|
if [[ $testDir == cli-hello-world ]]; then
|
||||||
yarn build
|
yarn build
|
||||||
fi
|
fi
|
||||||
trackPayloadSize "$testDir" "dist/*.js" true false
|
trackPayloadSize "$testDir" "dist/*.js" true false "${thisDir}/_payload-limits.json"
|
||||||
fi
|
fi
|
||||||
)
|
)
|
||||||
done
|
done
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "angular-srcs",
|
"name": "angular-srcs",
|
||||||
"version": "5.0.2",
|
"version": "5.0.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"branchPattern": "2.0.*",
|
"branchPattern": "2.0.*",
|
||||||
"description": "Angular - a web framework for modern web apps",
|
"description": "Angular - a web framework for modern web apps",
|
||||||
|
@ -90,6 +90,11 @@ export class AnimationAstBuilderVisitor implements AnimationDslVisitor {
|
|||||||
let depCount = context.depCount = 0;
|
let depCount = context.depCount = 0;
|
||||||
const states: StateAst[] = [];
|
const states: StateAst[] = [];
|
||||||
const transitions: TransitionAst[] = [];
|
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 => {
|
metadata.definitions.forEach(def => {
|
||||||
this._resetContextStyleTimingState(context);
|
this._resetContextStyleTimingState(context);
|
||||||
if (def.type == AnimationMetadataType.State) {
|
if (def.type == AnimationMetadataType.State) {
|
||||||
|
@ -986,11 +986,15 @@ export class TransitionAnimationEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const allPreviousPlayersMap = new Map<any, TransitionAnimationPlayer[]>();
|
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 => {
|
queuedInstructions.forEach(entry => {
|
||||||
const element = entry.element;
|
const element = entry.element;
|
||||||
if (subTimelines.has(element)) {
|
if (subTimelines.has(element)) {
|
||||||
sortedParentElements.unshift(element);
|
animationElementMap.set(element, element);
|
||||||
this._beforeAnimationBuild(
|
this._beforeAnimationBuild(
|
||||||
entry.player.namespaceId, entry.instruction, allPreviousPlayersMap);
|
entry.player.namespaceId, entry.instruction, allPreviousPlayersMap);
|
||||||
}
|
}
|
||||||
@ -1041,6 +1045,7 @@ export class TransitionAnimationEngine {
|
|||||||
|
|
||||||
const rootPlayers: TransitionAnimationPlayer[] = [];
|
const rootPlayers: TransitionAnimationPlayer[] = [];
|
||||||
const subPlayers: TransitionAnimationPlayer[] = [];
|
const subPlayers: TransitionAnimationPlayer[] = [];
|
||||||
|
const NO_PARENT_ANIMATION_ELEMENT_DETECTED = {};
|
||||||
queuedInstructions.forEach(entry => {
|
queuedInstructions.forEach(entry => {
|
||||||
const {element, player, instruction} = entry;
|
const {element, player, instruction} = entry;
|
||||||
// this means that it was never consumed by a parent animation which
|
// this means that it was never consumed by a parent animation which
|
||||||
@ -1052,29 +1057,41 @@ export class TransitionAnimationEngine {
|
|||||||
return;
|
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(
|
const innerPlayer = this._buildAnimation(
|
||||||
player.namespaceId, instruction, allPreviousPlayersMap, skippedPlayersMap, preStylesMap,
|
player.namespaceId, instruction, allPreviousPlayersMap, skippedPlayersMap, preStylesMap,
|
||||||
postStylesMap);
|
postStylesMap);
|
||||||
|
|
||||||
player.setRealPlayer(innerPlayer);
|
player.setRealPlayer(innerPlayer);
|
||||||
|
|
||||||
let parentHasPriority: any = null;
|
if (parentWithAnimation === NO_PARENT_ANIMATION_ELEMENT_DETECTED) {
|
||||||
for (let i = 0; i < sortedParentElements.length; i++) {
|
rootPlayers.push(player);
|
||||||
const parent = sortedParentElements[i];
|
} else {
|
||||||
if (parent === element) break;
|
const parentPlayers = this.playersByElement.get(parentWithAnimation);
|
||||||
if (this.driver.containsElement(parent, element)) {
|
|
||||||
parentHasPriority = parent;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parentHasPriority) {
|
|
||||||
const parentPlayers = this.playersByElement.get(parentHasPriority);
|
|
||||||
if (parentPlayers && parentPlayers.length) {
|
if (parentPlayers && parentPlayers.length) {
|
||||||
player.parentPlayer = optimizeGroupPlayer(parentPlayers);
|
player.parentPlayer = optimizeGroupPlayer(parentPlayers);
|
||||||
}
|
}
|
||||||
skippedPlayers.push(player);
|
skippedPlayers.push(player);
|
||||||
} else {
|
|
||||||
rootPlayers.push(player);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
eraseStyles(element, instruction.fromStyles);
|
eraseStyles(element, instruction.fromStyles);
|
||||||
@ -1105,7 +1122,7 @@ export class TransitionAnimationEngine {
|
|||||||
// fire the start/done transition callback events
|
// fire the start/done transition callback events
|
||||||
skippedPlayers.forEach(player => {
|
skippedPlayers.forEach(player => {
|
||||||
if (player.parentPlayer) {
|
if (player.parentPlayer) {
|
||||||
player.parentPlayer.onDestroy(() => player.destroy());
|
player.syncPlayerEvents(player.parentPlayer);
|
||||||
} else {
|
} else {
|
||||||
player.destroy();
|
player.destroy();
|
||||||
}
|
}
|
||||||
@ -1366,6 +1383,15 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
|
|||||||
|
|
||||||
getRealPlayer() { return this._player; }
|
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 {
|
private _queueEvent(name: string, callback: (event: any) => any): void {
|
||||||
getOrSetAsInMap(this._queuedCallbacks, name, []).push(callback);
|
getOrSetAsInMap(this._queuedCallbacks, name, []).push(callback);
|
||||||
}
|
}
|
||||||
@ -1419,6 +1445,14 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
|
|||||||
getPosition(): number { return this.queued ? 0 : this._player.getPosition(); }
|
getPosition(): number { return this.queued ? 0 : this._player.getPosition(); }
|
||||||
|
|
||||||
get totalTime(): number { return this._player.totalTime; }
|
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) {
|
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;
|
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 {
|
function _computeStyle(element: any, prop: string): string {
|
||||||
|
@ -119,6 +119,14 @@ export function main() {
|
|||||||
expect(() => validateAndThrowAnimationSequence(steps)).not.toThrow();
|
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', () => {
|
it('should throw an error if an animation time is invalid', () => {
|
||||||
const steps = [animate('500xs', style({opacity: 1}))];
|
const steps = [animate('500xs', style({opacity: 1}))];
|
||||||
|
|
||||||
|
@ -45,6 +45,26 @@ export function main() {
|
|||||||
const p = innerPlayer !;
|
const p = innerPlayer !;
|
||||||
expect(p.log).toEqual(['play']);
|
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;
|
parentPlayer: AnimationPlayer|null;
|
||||||
readonly totalTime: number;
|
readonly totalTime: number;
|
||||||
beforeDestroy?: () => any;
|
beforeDestroy?: () => any;
|
||||||
|
/* @internal */
|
||||||
|
triggerCallback?: (phaseName: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -91,4 +93,11 @@ export class NoopAnimationPlayer implements AnimationPlayer {
|
|||||||
reset(): void {}
|
reset(): void {}
|
||||||
setPosition(p: number): void {}
|
setPosition(p: number): void {}
|
||||||
getPosition(): number { return 0; }
|
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();
|
player.destroy();
|
||||||
expect(log).toEqual(['started', 'done', '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()
|
@Injectable()
|
||||||
export class PerflogMetric extends Metric {
|
export class PerflogMetric extends Metric {
|
||||||
static SET_TIMEOUT = new InjectionToken('PerflogMetric.setTimeout');
|
static SET_TIMEOUT = new InjectionToken('PerflogMetric.setTimeout');
|
||||||
|
static IGNORE_NAVIGATION = new InjectionToken('PerflogMetric.ignoreNavigation');
|
||||||
static PROVIDERS = [
|
static PROVIDERS = [
|
||||||
{
|
{
|
||||||
provide: PerflogMetric,
|
provide: PerflogMetric,
|
||||||
deps: [
|
deps: [
|
||||||
WebDriverExtension, PerflogMetric.SET_TIMEOUT, Options.MICRO_METRICS, Options.FORCE_GC,
|
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,
|
provide: PerflogMetric.SET_TIMEOUT,
|
||||||
useValue: (fn: Function, millis: number) => <any>setTimeout(fn, millis)
|
useValue: (fn: Function, millis: number) => <any>setTimeout(fn, millis)
|
||||||
}
|
},
|
||||||
|
{provide: PerflogMetric.IGNORE_NAVIGATION, useValue: false}
|
||||||
];
|
];
|
||||||
|
|
||||||
private _remainingEvents: PerfLogEvent[];
|
private _remainingEvents: PerfLogEvent[];
|
||||||
@ -41,6 +44,8 @@ export class PerflogMetric extends Metric {
|
|||||||
* @param driverExtension
|
* @param driverExtension
|
||||||
* @param setTimeout
|
* @param setTimeout
|
||||||
* @param microMetrics Name and description of metrics provided via console.time / console.timeEnd
|
* @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(
|
constructor(
|
||||||
private _driverExtension: WebDriverExtension,
|
private _driverExtension: WebDriverExtension,
|
||||||
@ -49,7 +54,8 @@ export class PerflogMetric extends Metric {
|
|||||||
@Inject(Options.FORCE_GC) private _forceGc: boolean,
|
@Inject(Options.FORCE_GC) private _forceGc: boolean,
|
||||||
@Inject(Options.CAPTURE_FRAMES) private _captureFrames: boolean,
|
@Inject(Options.CAPTURE_FRAMES) private _captureFrames: boolean,
|
||||||
@Inject(Options.RECEIVED_DATA) private _receivedData: 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();
|
super();
|
||||||
|
|
||||||
this._remainingEvents = [];
|
this._remainingEvents = [];
|
||||||
@ -231,7 +237,7 @@ export class PerflogMetric extends Metric {
|
|||||||
const name = event['name'];
|
const name = event['name'];
|
||||||
if (ph === 'B' && name === markName) {
|
if (ph === 'B' && name === markName) {
|
||||||
markStartEvent = event;
|
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
|
// if a benchmark measures reload of a page, use the last
|
||||||
// navigationStart as begin event
|
// navigationStart as begin event
|
||||||
markStartEvent = event;
|
markStartEvent = event;
|
||||||
|
@ -18,12 +18,13 @@ export function main() {
|
|||||||
|
|
||||||
function createMetric(
|
function createMetric(
|
||||||
perfLogs: PerfLogEvent[], perfLogFeatures: PerfLogFeatures,
|
perfLogs: PerfLogEvent[], perfLogFeatures: PerfLogFeatures,
|
||||||
{microMetrics, forceGc, captureFrames, receivedData, requestCount}: {
|
{microMetrics, forceGc, captureFrames, receivedData, requestCount, ignoreNavigation}: {
|
||||||
microMetrics?: {[key: string]: string},
|
microMetrics?: {[key: string]: string},
|
||||||
forceGc?: boolean,
|
forceGc?: boolean,
|
||||||
captureFrames?: boolean,
|
captureFrames?: boolean,
|
||||||
receivedData?: boolean,
|
receivedData?: boolean,
|
||||||
requestCount?: boolean
|
requestCount?: boolean,
|
||||||
|
ignoreNavigation?: boolean
|
||||||
} = {}): Metric {
|
} = {}): Metric {
|
||||||
commandLog = [];
|
commandLog = [];
|
||||||
if (!perfLogFeatures) {
|
if (!perfLogFeatures) {
|
||||||
@ -59,6 +60,9 @@ export function main() {
|
|||||||
if (requestCount != null) {
|
if (requestCount != null) {
|
||||||
providers.push({provide: Options.REQUEST_COUNT, useValue: requestCount});
|
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);
|
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) => {
|
it('should restart timing', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||||
const events = [
|
const events = [
|
||||||
[
|
[
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {EventEmitter, Injectable} from '@angular/core';
|
import {EventEmitter, Injectable} from '@angular/core';
|
||||||
|
import {ISubscription} from 'rxjs/Subscription';
|
||||||
|
|
||||||
import {LocationStrategy} from './location_strategy';
|
import {LocationStrategy} from './location_strategy';
|
||||||
|
|
||||||
@ -129,7 +130,7 @@ export class Location {
|
|||||||
*/
|
*/
|
||||||
subscribe(
|
subscribe(
|
||||||
onNext: (value: PopStateEvent) => void, onThrow?: ((exception: any) => void)|null,
|
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});
|
return this._subject.subscribe({next: onNext, error: onThrow, complete: onReturn});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,8 @@ const globals = {
|
|||||||
'@angular/core': 'ng.core',
|
'@angular/core': 'ng.core',
|
||||||
'@angular/common': 'ng.common',
|
'@angular/common': 'ng.common',
|
||||||
'rxjs/Observable': 'Rx',
|
'rxjs/Observable': 'Rx',
|
||||||
'rxjs/Subject': 'Rx'
|
'rxjs/Subject': 'Rx',
|
||||||
|
'rxjs/Subscription': 'Rx'
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import {Location, LocationStrategy} from '@angular/common';
|
import {Location, LocationStrategy} from '@angular/common';
|
||||||
import {EventEmitter, Injectable} from '@angular/core';
|
import {EventEmitter, Injectable} from '@angular/core';
|
||||||
|
import {ISubscription} from 'rxjs/Subscription';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -107,7 +108,7 @@ export class SpyLocation implements Location {
|
|||||||
|
|
||||||
subscribe(
|
subscribe(
|
||||||
onNext: (value: any) => void, onThrow?: ((error: any) => void)|null,
|
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});
|
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';
|
import {TestSupport, expectNoDiagnostics, setup} from '../test_support';
|
||||||
|
|
||||||
|
type MockFiles = {
|
||||||
|
[fileName: string]: string
|
||||||
|
};
|
||||||
|
|
||||||
describe('ng type checker', () => {
|
describe('ng type checker', () => {
|
||||||
let errorSpy: jasmine.Spy&((s: string) => void);
|
let errorSpy: jasmine.Spy&((s: string) => void);
|
||||||
let testSupport: TestSupport;
|
let testSupport: TestSupport;
|
||||||
|
|
||||||
function compileAndCheck(
|
function compileAndCheck(
|
||||||
mockDirs: {[fileName: string]: string}[],
|
mockDirs: MockFiles[], overrideOptions: ng.CompilerOptions = {}): ng.Diagnostics {
|
||||||
overrideOptions: ng.CompilerOptions = {}): ng.Diagnostics {
|
|
||||||
testSupport.writeFiles(...mockDirs);
|
testSupport.writeFiles(...mockDirs);
|
||||||
const fileNames: string[] = [];
|
const fileNames: string[] = [];
|
||||||
mockDirs.forEach((dir) => {
|
mockDirs.forEach((dir) => {
|
||||||
@ -40,13 +43,12 @@ describe('ng type checker', () => {
|
|||||||
testSupport = setup();
|
testSupport = setup();
|
||||||
});
|
});
|
||||||
|
|
||||||
function accept(
|
function accept(files: MockFiles = {}, overrideOptions: ng.CompilerOptions = {}) {
|
||||||
files: {[fileName: string]: string} = {}, overrideOptions: ng.CompilerOptions = {}) {
|
|
||||||
expectNoDiagnostics({}, compileAndCheck([QUICKSTART, files], overrideOptions));
|
expectNoDiagnostics({}, compileAndCheck([QUICKSTART, files], overrideOptions));
|
||||||
}
|
}
|
||||||
|
|
||||||
function reject(
|
function reject(
|
||||||
message: string | RegExp, location: RegExp, files: {[fileName: string]: string},
|
message: string | RegExp, location: RegExp, files: MockFiles,
|
||||||
overrideOptions: ng.CompilerOptions = {}) {
|
overrideOptions: ng.CompilerOptions = {}) {
|
||||||
const diagnostics = compileAndCheck([QUICKSTART, files], overrideOptions);
|
const diagnostics = compileAndCheck([QUICKSTART, files], overrideOptions);
|
||||||
if (!diagnostics || !diagnostics.length) {
|
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)', () => {
|
describe('with modified quickstart (fullTemplateTypeCheck: false)', () => {
|
||||||
addTests({fullTemplateTypeCheck: false});
|
addTests({fullTemplateTypeCheck: false});
|
||||||
});
|
});
|
||||||
@ -87,6 +124,23 @@ describe('ng type checker', () => {
|
|||||||
addTests({fullTemplateTypeCheck: true});
|
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 addTests(config: {fullTemplateTypeCheck: boolean}) {
|
||||||
function a(template: string) { accept({'src/app.component.html': template}, config); }
|
function a(template: string) { accept({'src/app.component.html': template}, config); }
|
||||||
|
|
||||||
@ -234,6 +288,7 @@ const QUICKSTART = {
|
|||||||
`,
|
`,
|
||||||
'src/app.module.ts': `
|
'src/app.module.ts': `
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
import { AppComponent, APipe, ADirective } from './app.component';
|
import { AppComponent, APipe, ADirective } from './app.component';
|
||||||
import { LibDirective, LibPipe } from './lib';
|
import { LibDirective, LibPipe } from './lib';
|
||||||
|
|
||||||
@ -246,7 +301,7 @@ const QUICKSTART = {
|
|||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [ AppComponent, APipe, ADirective ],
|
declarations: [ AppComponent, APipe, ADirective ],
|
||||||
bootstrap: [ AppComponent ],
|
bootstrap: [ AppComponent ],
|
||||||
imports: [ LibModule ]
|
imports: [ LibModule, CommonModule ]
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
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