Compare commits
81 Commits
5.1.2
...
manughub-r
Author | SHA1 | Date | |
---|---|---|---|
d07c288e2e | |||
abca7c0243 | |||
1f5256e745 | |||
6990354047 | |||
135ead6c97 | |||
33c0ee3441 | |||
5efea2f6a0 | |||
5f23a1223f | |||
30208759cd | |||
e4c53f8529 | |||
b61e3e9d20 | |||
f593552cfe | |||
8458647232 | |||
a693c5614c | |||
8ceffd8b48 | |||
2986e25abb | |||
f8fe53aeb0 | |||
74e3115686 | |||
3846f19f22 | |||
057513536b | |||
82bcd83566 | |||
e48f477477 | |||
20e1cc049f | |||
0b2d636b75 | |||
f5bb999319 | |||
a4742763b9 | |||
05ff6c09ca | |||
d91ff17adc | |||
d213a20dfc | |||
2e7e935b02 | |||
b89e7c2cb7 | |||
b4db2e25d6 | |||
a33eaf6e07 | |||
0d47c39609 | |||
cbe7e39bbe | |||
6d57cb04f6 | |||
6e2a8a2ba4 | |||
7874697b6c | |||
767141761a | |||
b3eb1db6dd | |||
ee0dab025b | |||
b7738e1fe5 | |||
634d33f5dd | |||
3401283399 | |||
981947d104 | |||
8c52088346 | |||
add3589451 | |||
81d497ce1f | |||
70cd124ede | |||
7363b3d4b5 | |||
f05937db4d | |||
d684f55423 | |||
db06cb170f | |||
77a1f9f2e8 | |||
13e663c232 | |||
d098cf5a8b | |||
3ce3b4d2af | |||
e7d9cb3e4c | |||
e544742156 | |||
c9ad529afc | |||
75e468494c | |||
ddada6e2be | |||
22ae17bb0b | |||
d546be48e1 | |||
753a130aaa | |||
94e2ea7361 | |||
1539cd8819 | |||
131c8ab6be | |||
7d81309e11 | |||
225baf4686 | |||
70b061be2e | |||
46aa0a1cf6 | |||
661fdcd3e2 | |||
590d93b30d | |||
c26e1bba1d | |||
10771d0bd8 | |||
d8cc09b76c | |||
d41d2c460a | |||
4efc32dabf | |||
ef534c0cc1 | |||
073f485c72 |
@ -25,7 +25,6 @@
|
||||
# tinayuangao - Tina Gao
|
||||
# vicb - Victor Berchet
|
||||
# vikerman - Vikram Subramanian
|
||||
# wardbell - Ward Bell
|
||||
|
||||
|
||||
version: 2
|
||||
@ -312,7 +311,6 @@ groups:
|
||||
- juleskremer #primary
|
||||
- Foxandxss
|
||||
- stephenfluin
|
||||
- wardbell
|
||||
- petebacondarwin
|
||||
- gkalpak
|
||||
- IgorMinar #fallback
|
||||
|
31
CHANGELOG.md
31
CHANGELOG.md
@ -1,3 +1,20 @@
|
||||
<a name="5.2.0-beta.1"></a>
|
||||
# [5.2.0-beta.1](https://github.com/angular/angular/compare/5.2.0-beta.0...5.2.0-beta.1) (2017-12-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** generate the correct imports for summary type-check ([d91ff17](https://github.com/angular/angular/commit/d91ff17))
|
||||
* **forms:** avoid producing an error with hostBindingTypeCheck ([d213a20](https://github.com/angular/angular/commit/d213a20))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler:** allow ngIf to use the ngIf expression directly as a guard ([82bcd83](https://github.com/angular/angular/commit/82bcd83))
|
||||
* **router:** add "paramsInheritanceStrategy" router configuration option ([5efea2f](https://github.com/angular/angular/commit/5efea2f)), closes [#20572](https://github.com/angular/angular/issues/20572)
|
||||
|
||||
|
||||
|
||||
<a name="5.1.2"></a>
|
||||
## [5.1.2](https://github.com/angular/angular/compare/5.1.1...5.1.2) (2017-12-20)
|
||||
|
||||
@ -14,6 +31,20 @@
|
||||
|
||||
|
||||
|
||||
<a name="5.2.0-beta.0"></a>
|
||||
# [5.2.0-beta.0](https://github.com/angular/angular/compare/5.1.0...5.2.0-beta.0) (2017-12-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **animations:** re-introduce support for transition matching functions ([#20723](https://github.com/angular/angular/issues/20723)) ([590d93b](https://github.com/angular/angular/commit/590d93b)), closes [#18959](https://github.com/angular/angular/issues/18959)
|
||||
* **compiler:** add a pseudo $any() function to disable type checking ([#20876](https://github.com/angular/angular/issues/20876)) ([70cd124](https://github.com/angular/angular/commit/70cd124))
|
||||
* **compiler:** narrow types of expressions used in *ngIf ([#20702](https://github.com/angular/angular/issues/20702)) ([e7d9cb3](https://github.com/angular/angular/commit/e7d9cb3))
|
||||
* **core:** add source to `StaticInjectorError` message ([#20817](https://github.com/angular/angular/issues/20817)) ([b7738e1](https://github.com/angular/angular/commit/b7738e1)), closes [#19302](https://github.com/angular/angular/issues/19302)
|
||||
* **forms:** allow nulls on setAsyncValidators ([#20327](https://github.com/angular/angular/issues/20327)) ([d41d2c4](https://github.com/angular/angular/commit/d41d2c4)), closes [#20296](https://github.com/angular/angular/issues/20296)
|
||||
|
||||
|
||||
|
||||
<a name="5.1.1"></a>
|
||||
## [5.1.1](https://github.com/angular/angular/compare/5.1.0...5.1.1) (2017-12-13)
|
||||
|
||||
|
@ -17,7 +17,7 @@ In such case, we'll update this document accordingly.
|
||||
|
||||
The dates are just a guidance and might be adjusted slightly if necessary.
|
||||
|
||||
## Tentative Schedule Until September 2017
|
||||
## Tentative Schedule Until December 2017
|
||||
|
||||
<!--
|
||||
The table below is formatted so that it's easy to read and edit in both markdown and rendered html form.
|
||||
@ -25,43 +25,54 @@ The table below is formatted so that it's easy to read and edit in both markdown
|
||||
In order to deal with undesirable line breaks, two special characters are occasionally used:
|
||||
|
||||
- non-breaking hyphen: "‑" http://www.fileformat.info/info/unicode/char/2011/index.htm
|
||||
- non-breaking space: " " http://www.fileformat.info/info/unicode/char/00a0/index.htm
|
||||
- non-breaking space: " " http://www.fileformat.info/info/unicode/char/00a0/index.htm
|
||||
|
||||
If you see undesirable wrapping issues in the rendered form, please copy&paste the quoted characters and use them in the table below where needed.
|
||||
-->
|
||||
|
||||
Week Of | Stable Release<br>(@latest npm tag) | Beta/RC Release<br>(@next npm tag) | Note
|
||||
Week Of | Stable Release<br>(@latest npm tag) | Beta/RC Release<br>(@next npm tag) | Note
|
||||
------------- | ----------------------------------- | ---------------------------------- | ---------------------
|
||||
2017‑05‑01 | 4.1.1 | 4.2.0‑beta.0 |
|
||||
2017‑05‑08 | 4.1.2 | 4.2.0‑beta.1 |
|
||||
2017‑05‑15 | 4.1.3 | 4.2.0‑rc.0 |
|
||||
2017‑05‑26 | ‑ | 4.2.0‑rc.1 |
|
||||
2017‑06‑01 | ‑ | 4.2.0‑rc.2 |
|
||||
2017‑06‑05 | 4.2.0 | ‑ | Minor Version Release
|
||||
*2017‑06‑09* | 4.2.1 | ‑ | *Regression Patch Release*
|
||||
2017‑05‑01 | 4.1.1 | 4.2.0‑beta.0 |
|
||||
2017‑05‑08 | 4.1.2 | 4.2.0‑beta.1 |
|
||||
2017‑05‑15 | 4.1.3 | 4.2.0‑rc.0 |
|
||||
2017‑05‑26 | ‑ | 4.2.0‑rc.1 |
|
||||
2017‑06‑01 | ‑ | 4.2.0‑rc.2 |
|
||||
2017‑06‑05 | 4.2.0 | ‑ | Minor Version Release
|
||||
*2017‑06‑09* | 4.2.1 | ‑ | *Regression Patch Release*
|
||||
2017-06-12 | 4.2.2 | ‑ |
|
||||
*2017-06-16* | 4.2.3 | ‑ | *Regression Patch Release*
|
||||
2017‑06‑19 | 4.2.4 | 4.3.0‑beta.0 |
|
||||
2017‑06‑26 | 4.2.5 | 4.3.0‑beta.1 |
|
||||
2017‑07‑03 | 4.2.6 | 4.3.0‑rc.0 |
|
||||
2017‑07‑10 | 4.3.0 | - | Minor Version Release
|
||||
2017‑07‑17 | 4.3.1 | 5.0.0‑beta.0 |
|
||||
2017‑07‑24 | 4.3.2 | 5.0.0‑beta.1 |
|
||||
2017‑07‑31 | 4.3.3 | 5.0.0‑beta.2 |
|
||||
2017‑08‑07 | 4.3.4 | 5.0.0‑beta.3 |
|
||||
2017‑08‑14 | 4.3.5 | 5.0.0‑beta.4 |
|
||||
2017‑08‑21 | 4.3.6 | - |
|
||||
2017‑08‑28 | - | 5.0.0‑beta.5 |
|
||||
2017‑09‑04 | - | 5.0.0‑beta.6 |
|
||||
2017‑09‑11 | 4.4.0 | 5.0.0-beta.7 |
|
||||
2017‑09‑18 | 4.4.1 | 5.0.0‑beta.8 |
|
||||
2017‑09‑25 | 4.4.2 | 5.0.0‑rc.0 |
|
||||
2017‑10‑02 | 4.4.3 | 5.0.0‑rc.1 |
|
||||
2017‑10‑09 | 4.4.4 | 5.0.0‑rc.2 |
|
||||
2017‑10‑16 | 4.4.5 | 5.0.0‑rc.3 |
|
||||
2017‑10‑23 | 5.0.0 | ‑ | Major Version Release
|
||||
*2017-06-16* | 4.2.3 | ‑ | *Regression Patch Release*
|
||||
2017‑06‑19 | 4.2.4 | 4.3.0‑beta.0 |
|
||||
2017‑06‑26 | 4.2.5 | 4.3.0‑beta.1 |
|
||||
2017‑07‑03 | 4.2.6 | 4.3.0‑rc.0 |
|
||||
2017‑07‑10 | 4.3.0 | - | Minor Version Release
|
||||
2017‑07‑17 | 4.3.1 | 5.0.0‑beta.0 |
|
||||
2017‑07‑24 | 4.3.2 | 5.0.0‑beta.1 |
|
||||
2017‑07‑31 | 4.3.3 | 5.0.0‑beta.2 |
|
||||
2017‑08‑07 | 4.3.4 | 5.0.0‑beta.3 |
|
||||
2017‑08‑14 | 4.3.5 | 5.0.0‑beta.4 |
|
||||
2017‑08‑21 | 4.3.6 | - |
|
||||
2017‑08‑28 | - | 5.0.0‑beta.5 |
|
||||
2017‑09‑04 | - | 5.0.0‑beta.6 |
|
||||
2017‑09‑11 | 4.4.0 | 5.0.0-beta.7 |
|
||||
2017‑09‑18 | 4.4.1 | 5.0.0‑beta.8 |
|
||||
2017‑09‑25 | 4.4.2 | 5.0.0‑rc.0 |
|
||||
2017‑10‑02 | 4.4.3 | 5.0.0‑rc.1 |
|
||||
2017‑10‑09 | 4.4.4 | 5.0.0‑rc.2 |
|
||||
2017‑10‑16 | 4.4.5 | 5.0.0‑rc.3 |
|
||||
2017‑11-01 | 5.0.0 | ‑ | Major Version Release
|
||||
2017-11-08 | 5.0.1 | 5.1.0.Beta.0 | *Regression Patch Release*
|
||||
2017-11-16 | 5.0.2 | 5.1.0.Beta.1 | *Regression Patch Release*
|
||||
2017-11-22 | 5.0.3 | 5.1.0.beta.2 | *Regression Patch Release*
|
||||
2017-11-30 | 5.0.4 | 5.1.0.rc.0 |
|
||||
2017-12-01 | 5.0.5 | 5.1.0.rc.1 |
|
||||
2017-12-06 | 5.1.0 | | Minor Version Release
|
||||
2017-12-13 | 5.1.1 | 5.2.0.beta.0 |
|
||||
2017-12-20 | 5.1.2 | |
|
||||
|
||||
## Tentative Schedule After September 2017
|
||||
|
||||
|
||||
|
||||
## Tentative Schedule After December 2017
|
||||
|
||||
Date | Stable Release | Compatibility`*`
|
||||
---------------------- | -------------- | ----------------
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "5.1.2",
|
||||
"version": "5.2.0-beta.1",
|
||||
"private": true,
|
||||
"branchPattern": "2.0.*",
|
||||
"description": "Angular - a web framework for modern web apps",
|
||||
|
@ -1393,9 +1393,9 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
|
||||
|
||||
public markedForDestroy: boolean = false;
|
||||
|
||||
constructor(public namespaceId: string, public triggerName: string, public element: any) {}
|
||||
readonly queued: boolean = true;
|
||||
|
||||
get queued() { return this._containsRealPlayer == false; }
|
||||
constructor(public namespaceId: string, public triggerName: string, public element: any) {}
|
||||
|
||||
setRealPlayer(player: AnimationPlayer) {
|
||||
if (this._containsRealPlayer) return;
|
||||
@ -1407,6 +1407,7 @@ export class TransitionAnimationPlayer implements AnimationPlayer {
|
||||
});
|
||||
this._queuedCallbacks = {};
|
||||
this._containsRealPlayer = true;
|
||||
(this as{queued: boolean}).queued = false;
|
||||
}
|
||||
|
||||
getRealPlayer() { return this._player; }
|
||||
|
@ -115,7 +115,7 @@ export interface AnimationStateMetadata extends AnimationMetadata {
|
||||
* @experimental Animation support is experimental.
|
||||
*/
|
||||
export interface AnimationTransitionMetadata extends AnimationMetadata {
|
||||
expr: string;
|
||||
expr: string|((fromState: string, toState: string) => boolean);
|
||||
animation: AnimationMetadata|AnimationMetadata[];
|
||||
options: AnimationOptions|null;
|
||||
}
|
||||
@ -836,7 +836,8 @@ export function keyframes(steps: AnimationStyleMetadata[]): AnimationKeyframesSe
|
||||
* @experimental Animation support is experimental.
|
||||
*/
|
||||
export function transition(
|
||||
stateChangeExpr: string, steps: AnimationMetadata | AnimationMetadata[],
|
||||
stateChangeExpr: string | ((fromState: string, toState: string) => boolean),
|
||||
steps: AnimationMetadata | AnimationMetadata[],
|
||||
options: AnimationOptions | null = null): AnimationTransitionMetadata {
|
||||
return {type: AnimationMetadataType.Transition, expr: stateChangeExpr, animation: steps, options};
|
||||
}
|
||||
|
@ -23,13 +23,12 @@ export class MockScriptElement {
|
||||
|
||||
export class MockDocument {
|
||||
mock: MockScriptElement|null;
|
||||
readonly body: any = this;
|
||||
|
||||
createElement(tag: 'script'): HTMLScriptElement {
|
||||
return new MockScriptElement() as any as HTMLScriptElement;
|
||||
}
|
||||
|
||||
get body(): any { return this; }
|
||||
|
||||
appendChild(node: any): void { this.mock = node; }
|
||||
|
||||
removeNode(node: any): void {
|
||||
@ -41,4 +40,4 @@ export class MockDocument {
|
||||
mockLoad(): void { this.mock !.listeners.load !(null as any); }
|
||||
|
||||
mockError(err: Error) { this.mock !.listeners.error !(err); }
|
||||
}
|
||||
}
|
||||
|
@ -151,6 +151,9 @@ export class NgIf {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
public static ngIfUseIfTypeGuard: void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -48,11 +48,11 @@ describe('ng type checker', () => {
|
||||
}
|
||||
|
||||
function reject(
|
||||
message: string | RegExp, location: RegExp, files: MockFiles,
|
||||
message: string | RegExp, location: RegExp | null, files: MockFiles,
|
||||
overrideOptions: ng.CompilerOptions = {}) {
|
||||
const diagnostics = compileAndCheck([QUICKSTART, files], overrideOptions);
|
||||
if (!diagnostics || !diagnostics.length) {
|
||||
throw new Error('Expected a diagnostic erorr message');
|
||||
throw new Error('Expected a diagnostic error message');
|
||||
} else {
|
||||
const matches: (d: ng.Diagnostic) => boolean = typeof message === 'string' ?
|
||||
d => ng.isNgDiagnostic(d)&& d.messageText == message :
|
||||
@ -63,11 +63,13 @@ describe('ng type checker', () => {
|
||||
`Expected a diagnostics matching ${message}, received\n ${diagnostics.map(d => d.messageText).join('\n ')}`);
|
||||
}
|
||||
|
||||
const span = matchingDiagnostics[0].span;
|
||||
if (!span) {
|
||||
throw new Error('Expected a sourceSpan');
|
||||
if (location) {
|
||||
const span = matchingDiagnostics[0].span;
|
||||
if (!span) {
|
||||
throw new Error('Expected a sourceSpan');
|
||||
}
|
||||
expect(`${span.start.file.url}@${span.start.line}:${span.start.offset}`).toMatch(location);
|
||||
}
|
||||
expect(`${span.start.file.url}@${span.start.line}:${span.start.offset}`).toMatch(location);
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,6 +83,543 @@ describe('ng type checker', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('type narrowing', () => {
|
||||
const a = (files: MockFiles, options: object = {}) => {
|
||||
accept(files, {fullTemplateTypeCheck: true, ...options});
|
||||
};
|
||||
|
||||
it('should narrow an *ngIf like directive', () => {
|
||||
a({
|
||||
'src/app.component.ts': '',
|
||||
'src/lib.ts': '',
|
||||
'src/app.module.ts': `
|
||||
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
|
||||
|
||||
export interface Person {
|
||||
name: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: '<div *myIf="person"> {{person.name}} </div>'
|
||||
})
|
||||
export class MainComp {
|
||||
person?: Person;
|
||||
}
|
||||
|
||||
export class MyIfContext {
|
||||
public $implicit: any = null;
|
||||
public myIf: any = null;
|
||||
}
|
||||
|
||||
@Directive({selector: '[myIf]'})
|
||||
export class MyIf {
|
||||
constructor(templateRef: TemplateRef<MyIfContext>) {}
|
||||
|
||||
@Input()
|
||||
set myIf(condition: any) {}
|
||||
|
||||
static myIfTypeGuard: <T>(v: T | null | undefined | false) => v is T;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MainComp, MyIf],
|
||||
})
|
||||
export class MainModule {}`
|
||||
});
|
||||
});
|
||||
|
||||
it('should narrow a renamed *ngIf like directive', () => {
|
||||
a({
|
||||
'src/app.component.ts': '',
|
||||
'src/lib.ts': '',
|
||||
'src/app.module.ts': `
|
||||
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
|
||||
|
||||
export interface Person {
|
||||
name: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: '<div *my-if="person"> {{person.name}} </div>'
|
||||
})
|
||||
export class MainComp {
|
||||
person?: Person;
|
||||
}
|
||||
|
||||
export class MyIfContext {
|
||||
public $implicit: any = null;
|
||||
public myIf: any = null;
|
||||
}
|
||||
|
||||
@Directive({selector: '[my-if]'})
|
||||
export class MyIf {
|
||||
constructor(templateRef: TemplateRef<MyIfContext>) {}
|
||||
|
||||
@Input('my-if')
|
||||
set myIf(condition: any) {}
|
||||
|
||||
static myIfTypeGuard: <T>(v: T | null | undefined | false) => v is T;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MainComp, MyIf],
|
||||
})
|
||||
export class MainModule {}`
|
||||
});
|
||||
});
|
||||
|
||||
it('should narrow a type in a nested *ngIf like directive', () => {
|
||||
a({
|
||||
'src/app.component.ts': '',
|
||||
'src/lib.ts': '',
|
||||
'src/app.module.ts': `
|
||||
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
|
||||
|
||||
export interface Address {
|
||||
street: string;
|
||||
}
|
||||
|
||||
export interface Person {
|
||||
name: string;
|
||||
address?: Address;
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: '<div *myIf="person"> {{person.name}} <span *myIf="person.address">{{person.address.street}}</span></div>'
|
||||
})
|
||||
export class MainComp {
|
||||
person?: Person;
|
||||
}
|
||||
|
||||
export class MyIfContext {
|
||||
public $implicit: any = null;
|
||||
public myIf: any = null;
|
||||
}
|
||||
|
||||
@Directive({selector: '[myIf]'})
|
||||
export class MyIf {
|
||||
constructor(templateRef: TemplateRef<MyIfContext>) {}
|
||||
|
||||
@Input()
|
||||
set myIf(condition: any) {}
|
||||
|
||||
static myIfTypeGuard: <T>(v: T | null | undefined | false) => v is T;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MainComp, MyIf],
|
||||
})
|
||||
export class MainModule {}`
|
||||
});
|
||||
});
|
||||
|
||||
it('should narrow an *ngIf like directive with UseIf', () => {
|
||||
a({
|
||||
'src/app.component.ts': '',
|
||||
'src/lib.ts': '',
|
||||
'src/app.module.ts': `
|
||||
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
|
||||
|
||||
export interface Person {
|
||||
name: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: '<div *myIf="person"> {{person.name}} </div>'
|
||||
})
|
||||
export class MainComp {
|
||||
person?: Person;
|
||||
}
|
||||
|
||||
export class MyIfContext {
|
||||
public $implicit: any = null;
|
||||
public myIf: any = null;
|
||||
}
|
||||
|
||||
@Directive({selector: '[myIf]'})
|
||||
export class MyIf {
|
||||
constructor(templateRef: TemplateRef<MyIfContext>) {}
|
||||
|
||||
@Input()
|
||||
set myIf(condition: any) {}
|
||||
|
||||
static myIfUseIfTypeGuard: void;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MainComp, MyIf],
|
||||
})
|
||||
export class MainModule {}`
|
||||
});
|
||||
});
|
||||
|
||||
it('should narrow a renamed *ngIf like directive with UseIf', () => {
|
||||
a({
|
||||
'src/app.component.ts': '',
|
||||
'src/lib.ts': '',
|
||||
'src/app.module.ts': `
|
||||
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
|
||||
|
||||
export interface Person {
|
||||
name: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: '<div *my-if="person"> {{person.name}} </div>'
|
||||
})
|
||||
export class MainComp {
|
||||
person?: Person;
|
||||
}
|
||||
|
||||
export class MyIfContext {
|
||||
public $implicit: any = null;
|
||||
public myIf: any = null;
|
||||
}
|
||||
|
||||
@Directive({selector: '[my-if]'})
|
||||
export class MyIf {
|
||||
constructor(templateRef: TemplateRef<MyIfContext>) {}
|
||||
|
||||
@Input('my-if')
|
||||
set myIf(condition: any) {}
|
||||
|
||||
static myIfUseIfTypeGuard: void;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MainComp, MyIf],
|
||||
})
|
||||
export class MainModule {}`
|
||||
});
|
||||
});
|
||||
|
||||
it('should narrow a type in a nested *ngIf like directive with UseIf', () => {
|
||||
a({
|
||||
'src/app.component.ts': '',
|
||||
'src/lib.ts': '',
|
||||
'src/app.module.ts': `
|
||||
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
|
||||
|
||||
export interface Address {
|
||||
street: string;
|
||||
}
|
||||
|
||||
export interface Person {
|
||||
name: string;
|
||||
address?: Address;
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: '<div *myIf="person"> {{person.name}} <span *myIf="person.address">{{person.address.street}}</span></div>'
|
||||
})
|
||||
export class MainComp {
|
||||
person?: Person;
|
||||
}
|
||||
|
||||
export class MyIfContext {
|
||||
public $implicit: any = null;
|
||||
public myIf: any = null;
|
||||
}
|
||||
|
||||
@Directive({selector: '[myIf]'})
|
||||
export class MyIf {
|
||||
constructor(templateRef: TemplateRef<MyIfContext>) {}
|
||||
|
||||
@Input()
|
||||
set myIf(condition: any) {}
|
||||
|
||||
static myIfUseIfTypeGuard: void;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MainComp, MyIf],
|
||||
})
|
||||
export class MainModule {}`
|
||||
});
|
||||
});
|
||||
|
||||
it('should narrow an *ngIf like directive with UseIf and &&', () => {
|
||||
a({
|
||||
'src/app.component.ts': '',
|
||||
'src/lib.ts': '',
|
||||
'src/app.module.ts': `
|
||||
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
|
||||
|
||||
export interface Address {
|
||||
street: string;
|
||||
}
|
||||
|
||||
export interface Person {
|
||||
name: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: '<div *myIf="person && address"> {{person.name}} lives at {{address.street}} </div>'
|
||||
})
|
||||
export class MainComp {
|
||||
person?: Person;
|
||||
address?: Address;
|
||||
}
|
||||
|
||||
export class MyIfContext {
|
||||
public $implicit: any = null;
|
||||
public myIf: any = null;
|
||||
}
|
||||
|
||||
@Directive({selector: '[myIf]'})
|
||||
export class MyIf {
|
||||
constructor(templateRef: TemplateRef<MyIfContext>) {}
|
||||
|
||||
@Input()
|
||||
set myIf(condition: any) {}
|
||||
|
||||
static myIfUseIfTypeGuard: void;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MainComp, MyIf],
|
||||
})
|
||||
export class MainModule {}`
|
||||
});
|
||||
});
|
||||
|
||||
it('should narrow an *ngIf like directive with UseIf and !!', () => {
|
||||
a({
|
||||
'src/app.component.ts': '',
|
||||
'src/lib.ts': '',
|
||||
'src/app.module.ts': `
|
||||
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
|
||||
|
||||
export interface Person {
|
||||
name: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: '<div *myIf="!!person"> {{person.name}} </div>'
|
||||
})
|
||||
export class MainComp {
|
||||
person?: Person;
|
||||
}
|
||||
|
||||
export class MyIfContext {
|
||||
public $implicit: any = null;
|
||||
public myIf: any = null;
|
||||
}
|
||||
|
||||
@Directive({selector: '[myIf]'})
|
||||
export class MyIf {
|
||||
constructor(templateRef: TemplateRef<MyIfContext>) {}
|
||||
|
||||
@Input()
|
||||
set myIf(condition: any) {}
|
||||
|
||||
static myIfUseIfTypeGuard: void;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MainComp, MyIf],
|
||||
})
|
||||
export class MainModule {}`
|
||||
});
|
||||
});
|
||||
|
||||
it('should narrow an *ngIf like directive with UseIf and != null', () => {
|
||||
a({
|
||||
'src/app.component.ts': '',
|
||||
'src/lib.ts': '',
|
||||
'src/app.module.ts': `
|
||||
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
|
||||
|
||||
export interface Person {
|
||||
name: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: '<div *myIf="person != null"> {{person.name}} </div>'
|
||||
})
|
||||
export class MainComp {
|
||||
person: Person | null = null;
|
||||
}
|
||||
|
||||
export class MyIfContext {
|
||||
public $implicit: any = null;
|
||||
public myIf: any = null;
|
||||
}
|
||||
|
||||
@Directive({selector: '[myIf]'})
|
||||
export class MyIf {
|
||||
constructor(templateRef: TemplateRef<MyIfContext>) {}
|
||||
|
||||
@Input()
|
||||
set myIf(condition: any) {}
|
||||
|
||||
static myIfUseIfTypeGuard: void;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MainComp, MyIf],
|
||||
})
|
||||
export class MainModule {}`
|
||||
});
|
||||
});
|
||||
|
||||
it('should narrow an *ngIf like directive with UseIf and != undefined', () => {
|
||||
a({
|
||||
'src/app.component.ts': '',
|
||||
'src/lib.ts': '',
|
||||
'src/app.module.ts': `
|
||||
import {NgModule, Component, Directive, HostListener, TemplateRef, Input} from '@angular/core';
|
||||
|
||||
export interface Person {
|
||||
name: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: '<div *myIf="person != undefined"> {{person.name}} </div>'
|
||||
})
|
||||
export class MainComp {
|
||||
person?: Person;
|
||||
}
|
||||
|
||||
export class MyIfContext {
|
||||
public $implicit: any = null;
|
||||
public myIf: any = null;
|
||||
}
|
||||
|
||||
@Directive({selector: '[myIf]'})
|
||||
export class MyIf {
|
||||
constructor(templateRef: TemplateRef<MyIfContext>) {}
|
||||
|
||||
@Input()
|
||||
set myIf(condition: any) {}
|
||||
|
||||
static myIfUseIfTypeGuard: void;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MainComp, MyIf],
|
||||
})
|
||||
export class MainModule {}`
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('casting $any', () => {
|
||||
const a = (files: MockFiles, options: object = {}) => {
|
||||
accept(
|
||||
{'src/app.component.ts': '', 'src/lib.ts': '', ...files},
|
||||
{fullTemplateTypeCheck: true, ...options});
|
||||
};
|
||||
|
||||
const r =
|
||||
(message: string | RegExp, location: RegExp | null, files: MockFiles,
|
||||
options: object = {}) => {
|
||||
reject(
|
||||
message, location, {'src/app.component.ts': '', 'src/lib.ts': '', ...files},
|
||||
{fullTemplateTypeCheck: true, ...options});
|
||||
};
|
||||
|
||||
it('should allow member access of an expression', () => {
|
||||
a({
|
||||
'src/app.module.ts': `
|
||||
import {NgModule, Component} from '@angular/core';
|
||||
|
||||
export interface Person {
|
||||
name: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: ' {{$any(person).address}}'
|
||||
})
|
||||
export class MainComp {
|
||||
person: Person;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MainComp],
|
||||
})
|
||||
export class MainModule {
|
||||
}`
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow invalid this.member access', () => {
|
||||
a({
|
||||
'src/app.module.ts': `
|
||||
import {NgModule, Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: ' {{$any(this).missing}}'
|
||||
})
|
||||
export class MainComp { }
|
||||
|
||||
@NgModule({
|
||||
declarations: [MainComp],
|
||||
})
|
||||
export class MainModule {
|
||||
}`
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject too few parameters to $any', () => {
|
||||
r(/Invalid call to \$any, expected 1 argument but received none/, null, {
|
||||
'src/app.module.ts': `
|
||||
import {NgModule, Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: ' {{$any().missing}}'
|
||||
})
|
||||
export class MainComp { }
|
||||
|
||||
@NgModule({
|
||||
declarations: [MainComp],
|
||||
})
|
||||
export class MainModule {
|
||||
}`
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject too many parameters to $any', () => {
|
||||
r(/Invalid call to \$any, expected 1 argument but received 2/, null, {
|
||||
'src/app.module.ts': `
|
||||
import {NgModule, Component} from '@angular/core';
|
||||
|
||||
export interface Person {
|
||||
name: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'comp',
|
||||
template: ' {{$any(person, 12).missing}}'
|
||||
})
|
||||
export class MainComp {
|
||||
person: Person;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [MainComp],
|
||||
})
|
||||
export class MainModule {
|
||||
}`
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('regressions ', () => {
|
||||
const a = (files: MockFiles, options: object = {}) => {
|
||||
accept(files, {fullTemplateTypeCheck: true, ...options});
|
||||
|
@ -1038,6 +1038,25 @@ describe('Collector', () => {
|
||||
expect(metadata).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should collect type guards', () => {
|
||||
const metadata = collectSource(`
|
||||
import {Directive, Input, TemplateRef} from '@angular/core';
|
||||
|
||||
@Directive({selector: '[myIf]'})
|
||||
export class MyIf {
|
||||
|
||||
constructor(private templateRef: TemplateRef) {}
|
||||
|
||||
@Input() myIf: any;
|
||||
|
||||
static typeGuard: <T>(v: T | null | undefined): v is T;
|
||||
}
|
||||
`);
|
||||
|
||||
expect((metadata.metadata.MyIf as any).statics.typeGuard)
|
||||
.not.toBeUndefined('typeGuard was not collected');
|
||||
});
|
||||
|
||||
it('should be able to collect an invalid access expression', () => {
|
||||
const source = createSource(`
|
||||
import {Component} from '@angular/core';
|
||||
|
@ -12,6 +12,7 @@ import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
import * as ng from '../index';
|
||||
|
||||
// TEST_TMPDIR is set by bazel.
|
||||
const tmpdir = process.env.TEST_TMPDIR || os.tmpdir();
|
||||
|
||||
function getNgRootDir() {
|
||||
@ -21,7 +22,6 @@ function getNgRootDir() {
|
||||
}
|
||||
|
||||
export function writeTempFile(name: string, contents: string): string {
|
||||
// TEST_TMPDIR is set by bazel.
|
||||
const id = (Math.random() * 1000000).toFixed(0);
|
||||
const fn = path.join(tmpdir, `tmp.${id}.${name}`);
|
||||
fs.writeFileSync(fn, contents);
|
||||
@ -29,8 +29,12 @@ export function writeTempFile(name: string, contents: string): string {
|
||||
}
|
||||
|
||||
export function makeTempDir(): string {
|
||||
const id = (Math.random() * 1000000).toFixed(0);
|
||||
const dir = path.join(tmpdir, `tmp.${id}`);
|
||||
let dir: string;
|
||||
while (true) {
|
||||
const id = (Math.random() * 1000000).toFixed(0);
|
||||
dir = path.join(tmpdir, `tmp.${id}`);
|
||||
if (!fs.existsSync(dir)) break;
|
||||
}
|
||||
fs.mkdirSync(dir);
|
||||
return dir;
|
||||
}
|
||||
|
@ -218,15 +218,14 @@ export class AotCompiler {
|
||||
|
||||
const externalReferenceVars = new Map<any, string>();
|
||||
externalReferences.forEach((ref, typeIndex) => {
|
||||
if (this._host.isSourceFile(ref.filePath)) {
|
||||
externalReferenceVars.set(ref, `_decl${ngModuleIndex}_${typeIndex}`);
|
||||
}
|
||||
externalReferenceVars.set(ref, `_decl${ngModuleIndex}_${typeIndex}`);
|
||||
});
|
||||
externalReferenceVars.forEach((varName, reference) => {
|
||||
outputCtx.statements.push(
|
||||
o.variable(varName)
|
||||
.set(o.NULL_EXPR.cast(o.DYNAMIC_TYPE))
|
||||
.toDeclStmt(o.expressionType(outputCtx.importExpr(reference))));
|
||||
.toDeclStmt(o.expressionType(outputCtx.importExpr(
|
||||
reference, /* typeParams */ null, /* useSummaries */ false))));
|
||||
});
|
||||
|
||||
if (emitFlags & StubEmitFlags.TypeCheck) {
|
||||
@ -271,7 +270,7 @@ export class AotCompiler {
|
||||
const {template: parsedTemplate, pipes: usedPipes} =
|
||||
this._parseTemplate(compMeta, moduleMeta, directives);
|
||||
ctx.statements.push(...this._typeCheckCompiler.compileComponent(
|
||||
componentId, compMeta, parsedTemplate, usedPipes, externalReferenceVars));
|
||||
componentId, compMeta, parsedTemplate, usedPipes, externalReferenceVars, ctx));
|
||||
}
|
||||
|
||||
emitMessageBundle(analyzeResult: NgAnalyzedModules, locale: string|null): MessageBundle {
|
||||
@ -515,35 +514,38 @@ export class AotCompiler {
|
||||
}
|
||||
|
||||
private _createOutputContext(genFilePath: string): OutputContext {
|
||||
const importExpr = (symbol: StaticSymbol, typeParams: o.Type[] | null = null) => {
|
||||
if (!(symbol instanceof StaticSymbol)) {
|
||||
throw new Error(`Internal error: unknown identifier ${JSON.stringify(symbol)}`);
|
||||
}
|
||||
const arity = this._symbolResolver.getTypeArity(symbol) || 0;
|
||||
const {filePath, name, members} = this._symbolResolver.getImportAs(symbol) || symbol;
|
||||
const importModule = this._fileNameToModuleName(filePath, genFilePath);
|
||||
const importExpr =
|
||||
(symbol: StaticSymbol, typeParams: o.Type[] | null = null,
|
||||
useSummaries: boolean = true) => {
|
||||
if (!(symbol instanceof StaticSymbol)) {
|
||||
throw new Error(`Internal error: unknown identifier ${JSON.stringify(symbol)}`);
|
||||
}
|
||||
const arity = this._symbolResolver.getTypeArity(symbol) || 0;
|
||||
const {filePath, name, members} =
|
||||
this._symbolResolver.getImportAs(symbol, useSummaries) || symbol;
|
||||
const importModule = this._fileNameToModuleName(filePath, genFilePath);
|
||||
|
||||
// It should be good enough to compare filePath to genFilePath and if they are equal
|
||||
// there is a self reference. However, ngfactory files generate to .ts but their
|
||||
// symbols have .d.ts so a simple compare is insufficient. They should be canonical
|
||||
// and is tracked by #17705.
|
||||
const selfReference = this._fileNameToModuleName(genFilePath, genFilePath);
|
||||
const moduleName = importModule === selfReference ? null : importModule;
|
||||
// It should be good enough to compare filePath to genFilePath and if they are equal
|
||||
// there is a self reference. However, ngfactory files generate to .ts but their
|
||||
// symbols have .d.ts so a simple compare is insufficient. They should be canonical
|
||||
// and is tracked by #17705.
|
||||
const selfReference = this._fileNameToModuleName(genFilePath, genFilePath);
|
||||
const moduleName = importModule === selfReference ? null : importModule;
|
||||
|
||||
// If we are in a type expression that refers to a generic type then supply
|
||||
// the required type parameters. If there were not enough type parameters
|
||||
// supplied, supply any as the type. Outside a type expression the reference
|
||||
// should not supply type parameters and be treated as a simple value reference
|
||||
// to the constructor function itself.
|
||||
const suppliedTypeParams = typeParams || [];
|
||||
const missingTypeParamsCount = arity - suppliedTypeParams.length;
|
||||
const allTypeParams =
|
||||
suppliedTypeParams.concat(new Array(missingTypeParamsCount).fill(o.DYNAMIC_TYPE));
|
||||
return members.reduce(
|
||||
(expr, memberName) => expr.prop(memberName),
|
||||
<o.Expression>o.importExpr(
|
||||
new o.ExternalReference(moduleName, name, null), allTypeParams));
|
||||
};
|
||||
// If we are in a type expression that refers to a generic type then supply
|
||||
// the required type parameters. If there were not enough type parameters
|
||||
// supplied, supply any as the type. Outside a type expression the reference
|
||||
// should not supply type parameters and be treated as a simple value reference
|
||||
// to the constructor function itself.
|
||||
const suppliedTypeParams = typeParams || [];
|
||||
const missingTypeParamsCount = arity - suppliedTypeParams.length;
|
||||
const allTypeParams =
|
||||
suppliedTypeParams.concat(new Array(missingTypeParamsCount).fill(o.DYNAMIC_TYPE));
|
||||
return members.reduce(
|
||||
(expr, memberName) => expr.prop(memberName),
|
||||
<o.Expression>o.importExpr(
|
||||
new o.ExternalReference(moduleName, name, null), allTypeParams));
|
||||
};
|
||||
|
||||
return {statements: [], genFilePath, importExpr};
|
||||
}
|
||||
|
@ -29,6 +29,8 @@ const IGNORE = {
|
||||
const USE_VALUE = 'useValue';
|
||||
const PROVIDE = 'provide';
|
||||
const REFERENCE_SET = new Set([USE_VALUE, 'useFactory', 'data']);
|
||||
const TYPEGUARD_POSTFIX = 'TypeGuard';
|
||||
const USE_IF = 'UseIf';
|
||||
|
||||
function shouldIgnore(value: any): boolean {
|
||||
return value && value.__symbolic == 'ignore';
|
||||
@ -43,6 +45,7 @@ export class StaticReflector implements CompileReflector {
|
||||
private propertyCache = new Map<StaticSymbol, {[key: string]: any[]}>();
|
||||
private parameterCache = new Map<StaticSymbol, any[]>();
|
||||
private methodCache = new Map<StaticSymbol, {[key: string]: boolean}>();
|
||||
private staticCache = new Map<StaticSymbol, string[]>();
|
||||
private conversionMap = new Map<StaticSymbol, (context: StaticSymbol, args: any[]) => any>();
|
||||
private injectionToken: StaticSymbol;
|
||||
private opaqueToken: StaticSymbol;
|
||||
@ -251,6 +254,18 @@ export class StaticReflector implements CompileReflector {
|
||||
return methodNames;
|
||||
}
|
||||
|
||||
private _staticMembers(type: StaticSymbol): string[] {
|
||||
let staticMembers = this.staticCache.get(type);
|
||||
if (!staticMembers) {
|
||||
const classMetadata = this.getTypeMetadata(type);
|
||||
const staticMemberData = classMetadata['statics'] || {};
|
||||
staticMembers = Object.keys(staticMemberData);
|
||||
this.staticCache.set(type, staticMembers);
|
||||
}
|
||||
return staticMembers;
|
||||
}
|
||||
|
||||
|
||||
private findParentType(type: StaticSymbol, classMetadata: any): StaticSymbol|undefined {
|
||||
const parentType = this.trySimplify(type, classMetadata['extends']);
|
||||
if (parentType instanceof StaticSymbol) {
|
||||
@ -273,6 +288,30 @@ export class StaticReflector implements CompileReflector {
|
||||
}
|
||||
}
|
||||
|
||||
guards(type: any): {[key: string]: StaticSymbol} {
|
||||
if (!(type instanceof StaticSymbol)) {
|
||||
this.reportError(
|
||||
new Error(`guards received ${JSON.stringify(type)} which is not a StaticSymbol`), type);
|
||||
return {};
|
||||
}
|
||||
const staticMembers = this._staticMembers(type);
|
||||
const result: {[key: string]: StaticSymbol} = {};
|
||||
for (let name of staticMembers) {
|
||||
if (name.endsWith(TYPEGUARD_POSTFIX)) {
|
||||
let property = name.substr(0, name.length - TYPEGUARD_POSTFIX.length);
|
||||
let value: any;
|
||||
if (property.endsWith(USE_IF)) {
|
||||
property = name.substr(0, property.length - USE_IF.length);
|
||||
value = USE_IF;
|
||||
} else {
|
||||
value = this.getStaticSymbol(type.filePath, type.name, [name]);
|
||||
}
|
||||
result[property] = value;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _registerDecoratorOrConstructor(type: StaticSymbol, ctor: any): void {
|
||||
this.conversionMap.set(type, (context: StaticSymbol, args: any[]) => new ctor(...args));
|
||||
}
|
||||
|
@ -98,10 +98,10 @@ export class StaticSymbolResolver {
|
||||
*
|
||||
* @param staticSymbol the symbol for which to generate a import symbol
|
||||
*/
|
||||
getImportAs(staticSymbol: StaticSymbol): StaticSymbol|null {
|
||||
getImportAs(staticSymbol: StaticSymbol, useSummaries: boolean = true): StaticSymbol|null {
|
||||
if (staticSymbol.members.length) {
|
||||
const baseSymbol = this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name);
|
||||
const baseImportAs = this.getImportAs(baseSymbol);
|
||||
const baseImportAs = this.getImportAs(baseSymbol, useSummaries);
|
||||
return baseImportAs ?
|
||||
this.getStaticSymbol(baseImportAs.filePath, baseImportAs.name, staticSymbol.members) :
|
||||
null;
|
||||
@ -111,14 +111,14 @@ export class StaticSymbolResolver {
|
||||
const summarizedName = stripSummaryForJitNameSuffix(staticSymbol.name);
|
||||
const baseSymbol =
|
||||
this.getStaticSymbol(summarizedFileName, summarizedName, staticSymbol.members);
|
||||
const baseImportAs = this.getImportAs(baseSymbol);
|
||||
const baseImportAs = this.getImportAs(baseSymbol, useSummaries);
|
||||
return baseImportAs ?
|
||||
this.getStaticSymbol(
|
||||
summaryForJitFileName(baseImportAs.filePath), summaryForJitName(baseImportAs.name),
|
||||
baseSymbol.members) :
|
||||
null;
|
||||
}
|
||||
let result = this.summaryResolver.getImportAs(staticSymbol);
|
||||
let result = (useSummaries && this.summaryResolver.getImportAs(staticSymbol)) || null;
|
||||
if (!result) {
|
||||
result = this.importAs.get(staticSymbol) !;
|
||||
}
|
||||
|
@ -254,6 +254,7 @@ export interface CompileDirectiveSummary extends CompileTypeSummary {
|
||||
providers: CompileProviderMetadata[];
|
||||
viewProviders: CompileProviderMetadata[];
|
||||
queries: CompileQueryMetadata[];
|
||||
guards: {[key: string]: any};
|
||||
viewQueries: CompileQueryMetadata[];
|
||||
entryComponents: CompileEntryComponentMetadata[];
|
||||
changeDetection: ChangeDetectionStrategy|null;
|
||||
@ -268,8 +269,8 @@ export interface CompileDirectiveSummary extends CompileTypeSummary {
|
||||
*/
|
||||
export class CompileDirectiveMetadata {
|
||||
static create({isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs,
|
||||
host, providers, viewProviders, queries, viewQueries, entryComponents, template,
|
||||
componentViewType, rendererType, componentFactory}: {
|
||||
host, providers, viewProviders, queries, guards, viewQueries, entryComponents,
|
||||
template, componentViewType, rendererType, componentFactory}: {
|
||||
isHost: boolean,
|
||||
type: CompileTypeMetadata,
|
||||
isComponent: boolean,
|
||||
@ -282,6 +283,7 @@ export class CompileDirectiveMetadata {
|
||||
providers: CompileProviderMetadata[],
|
||||
viewProviders: CompileProviderMetadata[],
|
||||
queries: CompileQueryMetadata[],
|
||||
guards: {[key: string]: any};
|
||||
viewQueries: CompileQueryMetadata[],
|
||||
entryComponents: CompileEntryComponentMetadata[],
|
||||
template: CompileTemplateMetadata,
|
||||
@ -336,6 +338,7 @@ export class CompileDirectiveMetadata {
|
||||
providers,
|
||||
viewProviders,
|
||||
queries,
|
||||
guards,
|
||||
viewQueries,
|
||||
entryComponents,
|
||||
template,
|
||||
@ -358,6 +361,7 @@ export class CompileDirectiveMetadata {
|
||||
providers: CompileProviderMetadata[];
|
||||
viewProviders: CompileProviderMetadata[];
|
||||
queries: CompileQueryMetadata[];
|
||||
guards: {[key: string]: any};
|
||||
viewQueries: CompileQueryMetadata[];
|
||||
entryComponents: CompileEntryComponentMetadata[];
|
||||
|
||||
@ -367,10 +371,27 @@ export class CompileDirectiveMetadata {
|
||||
rendererType: StaticSymbol|object|null;
|
||||
componentFactory: StaticSymbol|object|null;
|
||||
|
||||
constructor({isHost, type, isComponent, selector, exportAs,
|
||||
changeDetection, inputs, outputs, hostListeners, hostProperties,
|
||||
hostAttributes, providers, viewProviders, queries, viewQueries,
|
||||
entryComponents, template, componentViewType, rendererType, componentFactory}: {
|
||||
constructor({isHost,
|
||||
type,
|
||||
isComponent,
|
||||
selector,
|
||||
exportAs,
|
||||
changeDetection,
|
||||
inputs,
|
||||
outputs,
|
||||
hostListeners,
|
||||
hostProperties,
|
||||
hostAttributes,
|
||||
providers,
|
||||
viewProviders,
|
||||
queries,
|
||||
guards,
|
||||
viewQueries,
|
||||
entryComponents,
|
||||
template,
|
||||
componentViewType,
|
||||
rendererType,
|
||||
componentFactory}: {
|
||||
isHost: boolean,
|
||||
type: CompileTypeMetadata,
|
||||
isComponent: boolean,
|
||||
@ -385,6 +406,7 @@ export class CompileDirectiveMetadata {
|
||||
providers: CompileProviderMetadata[],
|
||||
viewProviders: CompileProviderMetadata[],
|
||||
queries: CompileQueryMetadata[],
|
||||
guards: {[key: string]: any},
|
||||
viewQueries: CompileQueryMetadata[],
|
||||
entryComponents: CompileEntryComponentMetadata[],
|
||||
template: CompileTemplateMetadata|null,
|
||||
@ -406,6 +428,7 @@ export class CompileDirectiveMetadata {
|
||||
this.providers = _normalizeArray(providers);
|
||||
this.viewProviders = _normalizeArray(viewProviders);
|
||||
this.queries = _normalizeArray(queries);
|
||||
this.guards = guards;
|
||||
this.viewQueries = _normalizeArray(viewQueries);
|
||||
this.entryComponents = _normalizeArray(entryComponents);
|
||||
this.template = template;
|
||||
@ -430,6 +453,7 @@ export class CompileDirectiveMetadata {
|
||||
providers: this.providers,
|
||||
viewProviders: this.viewProviders,
|
||||
queries: this.queries,
|
||||
guards: this.guards,
|
||||
viewQueries: this.viewQueries,
|
||||
entryComponents: this.entryComponents,
|
||||
changeDetection: this.changeDetection,
|
||||
|
@ -17,6 +17,7 @@ export abstract class CompileReflector {
|
||||
abstract annotations(typeOrFunc: /*Type*/ any): any[];
|
||||
abstract propMetadata(typeOrFunc: /*Type*/ any): {[key: string]: any[]};
|
||||
abstract hasLifecycleHook(type: any, lcProperty: string): boolean;
|
||||
abstract guards(typeOrFunc: /* Type */ any): {[key: string]: any};
|
||||
abstract componentModuleUrl(type: /*Type*/ any, cmpMetadata: Component): string;
|
||||
abstract resolveExternalReference(ref: o.ExternalReference): any;
|
||||
}
|
||||
|
@ -90,6 +90,14 @@ export class ConvertPropertyBindingResult {
|
||||
constructor(public stmts: o.Statement[], public currValExpr: o.Expression) {}
|
||||
}
|
||||
|
||||
export enum BindingForm {
|
||||
// The general form of binding expression, supports all expressions.
|
||||
General,
|
||||
|
||||
// Try to generate a simple binding (no temporaries or statements)
|
||||
// otherise generate a general binding
|
||||
TrySimple,
|
||||
}
|
||||
/**
|
||||
* Converts the given expression AST into an executable output AST, assuming the expression
|
||||
* is used in property binding. The expression has to be preprocessed via
|
||||
@ -97,7 +105,8 @@ export class ConvertPropertyBindingResult {
|
||||
*/
|
||||
export function convertPropertyBinding(
|
||||
localResolver: LocalResolver | null, implicitReceiver: o.Expression,
|
||||
expressionWithoutBuiltins: cdAst.AST, bindingId: string): ConvertPropertyBindingResult {
|
||||
expressionWithoutBuiltins: cdAst.AST, bindingId: string,
|
||||
form: BindingForm): ConvertPropertyBindingResult {
|
||||
if (!localResolver) {
|
||||
localResolver = new DefaultLocalResolver();
|
||||
}
|
||||
@ -110,9 +119,11 @@ export function convertPropertyBinding(
|
||||
for (let i = 0; i < visitor.temporaryCount; i++) {
|
||||
stmts.push(temporaryDeclaration(bindingId, i));
|
||||
}
|
||||
} else if (form == BindingForm.TrySimple) {
|
||||
return new ConvertPropertyBindingResult([], outputExpr);
|
||||
}
|
||||
|
||||
stmts.push(currValExpr.set(outputExpr).toDeclStmt(null, [o.StmtModifier.Final]));
|
||||
stmts.push(currValExpr.set(outputExpr).toDeclStmt(o.DYNAMIC_TYPE, [o.StmtModifier.Final]));
|
||||
return new ConvertPropertyBindingResult(stmts, currValExpr);
|
||||
}
|
||||
|
||||
@ -323,12 +334,27 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
}
|
||||
|
||||
visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any {
|
||||
return convertToStatementIfNeeded(mode, o.literal(ast.value));
|
||||
// For literal values of null, undefined, true, or false allow type inteference
|
||||
// to infer the type.
|
||||
const type =
|
||||
ast.value === null || ast.value === undefined || ast.value === true || ast.value === true ?
|
||||
o.INFERRED_TYPE :
|
||||
undefined;
|
||||
return convertToStatementIfNeeded(mode, o.literal(ast.value, type));
|
||||
}
|
||||
|
||||
private _getLocal(name: string): o.Expression|null { return this._localResolver.getLocal(name); }
|
||||
|
||||
visitMethodCall(ast: cdAst.MethodCall, mode: _Mode): any {
|
||||
if (ast.receiver instanceof cdAst.ImplicitReceiver && ast.name == '$any') {
|
||||
const args = this.visitAll(ast.args, _Mode.Expression) as any[];
|
||||
if (args.length != 1) {
|
||||
throw new Error(
|
||||
`Invalid call to $any, expected 1 argument but received ${args.length || 'none'}`);
|
||||
}
|
||||
return (args[0] as o.Expression).cast(o.DYNAMIC_TYPE);
|
||||
}
|
||||
|
||||
const leftMostSafe = this.leftMostSafeNode(ast);
|
||||
if (leftMostSafe) {
|
||||
return this.convertSafeAccess(ast, leftMostSafe, mode);
|
||||
|
@ -51,6 +51,7 @@ export interface Directive {
|
||||
providers?: Provider[];
|
||||
exportAs?: string;
|
||||
queries?: {[key: string]: any};
|
||||
guards?: {[key: string]: any};
|
||||
}
|
||||
export const createDirective =
|
||||
makeMetadataFactory<Directive>('Directive', (dir: Directive = {}) => dir);
|
||||
|
@ -44,7 +44,8 @@ export class DirectiveResolver {
|
||||
const metadata = findLast(typeMetadata, isDirectiveMetadata);
|
||||
if (metadata) {
|
||||
const propertyMetadata = this._reflector.propMetadata(type);
|
||||
return this._mergeWithPropertyMetadata(metadata, propertyMetadata, type);
|
||||
const guards = this._reflector.guards(type);
|
||||
return this._mergeWithPropertyMetadata(metadata, propertyMetadata, guards, type);
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,12 +57,12 @@ export class DirectiveResolver {
|
||||
}
|
||||
|
||||
private _mergeWithPropertyMetadata(
|
||||
dm: Directive, propertyMetadata: {[key: string]: any[]}, directiveType: Type): Directive {
|
||||
dm: Directive, propertyMetadata: {[key: string]: any[]}, guards: {[key: string]: any},
|
||||
directiveType: Type): Directive {
|
||||
const inputs: string[] = [];
|
||||
const outputs: string[] = [];
|
||||
const host: {[key: string]: string} = {};
|
||||
const queries: {[key: string]: any} = {};
|
||||
|
||||
Object.keys(propertyMetadata).forEach((propName: string) => {
|
||||
const input = findLast(propertyMetadata[propName], (a) => createInput.isTypeOf(a));
|
||||
if (input) {
|
||||
@ -105,18 +106,20 @@ export class DirectiveResolver {
|
||||
queries[propName] = query;
|
||||
}
|
||||
});
|
||||
return this._merge(dm, inputs, outputs, host, queries, directiveType);
|
||||
return this._merge(dm, inputs, outputs, host, queries, guards, directiveType);
|
||||
}
|
||||
|
||||
private _extractPublicName(def: string) { return splitAtColon(def, [null !, def])[1].trim(); }
|
||||
|
||||
private _dedupeBindings(bindings: string[]): string[] {
|
||||
const names = new Set<string>();
|
||||
const publicNames = new Set<string>();
|
||||
const reversedResult: string[] = [];
|
||||
// go last to first to allow later entries to overwrite previous entries
|
||||
for (let i = bindings.length - 1; i >= 0; i--) {
|
||||
const binding = bindings[i];
|
||||
const name = this._extractPublicName(binding);
|
||||
publicNames.add(name);
|
||||
if (!names.has(name)) {
|
||||
names.add(name);
|
||||
reversedResult.push(binding);
|
||||
@ -127,14 +130,13 @@ export class DirectiveResolver {
|
||||
|
||||
private _merge(
|
||||
directive: Directive, inputs: string[], outputs: string[], host: {[key: string]: string},
|
||||
queries: {[key: string]: any}, directiveType: Type): Directive {
|
||||
queries: {[key: string]: any}, guards: {[key: string]: any}, directiveType: Type): Directive {
|
||||
const mergedInputs =
|
||||
this._dedupeBindings(directive.inputs ? directive.inputs.concat(inputs) : inputs);
|
||||
const mergedOutputs =
|
||||
this._dedupeBindings(directive.outputs ? directive.outputs.concat(outputs) : outputs);
|
||||
const mergedHost = directive.host ? {...directive.host, ...host} : host;
|
||||
const mergedQueries = directive.queries ? {...directive.queries, ...queries} : queries;
|
||||
|
||||
if (createComponent.isTypeOf(directive)) {
|
||||
const comp = directive as Component;
|
||||
return createComponent({
|
||||
@ -166,7 +168,7 @@ export class DirectiveResolver {
|
||||
host: mergedHost,
|
||||
exportAs: directive.exportAs,
|
||||
queries: mergedQueries,
|
||||
providers: directive.providers
|
||||
providers: directive.providers, guards
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -208,6 +208,7 @@ export class CompileMetadataResolver {
|
||||
providers: [],
|
||||
viewProviders: [],
|
||||
queries: [],
|
||||
guards: {},
|
||||
viewQueries: [],
|
||||
componentViewType: hostViewType,
|
||||
rendererType:
|
||||
@ -240,6 +241,7 @@ export class CompileMetadataResolver {
|
||||
providers: metadata.providers,
|
||||
viewProviders: metadata.viewProviders,
|
||||
queries: metadata.queries,
|
||||
guards: metadata.guards,
|
||||
viewQueries: metadata.viewQueries,
|
||||
entryComponents: metadata.entryComponents,
|
||||
componentViewType: metadata.componentViewType,
|
||||
@ -383,6 +385,7 @@ export class CompileMetadataResolver {
|
||||
providers: providers || [],
|
||||
viewProviders: viewProviders || [],
|
||||
queries: queries || [],
|
||||
guards: dirMeta.guards || {},
|
||||
viewQueries: viewQueries || [],
|
||||
entryComponents: entryComponentMetadata,
|
||||
componentViewType: nonNormalizedTemplateMetadata ? this.getComponentViewClass(directiveType) :
|
||||
|
@ -152,7 +152,7 @@ export function utf8Encode(str: string): string {
|
||||
export interface OutputContext {
|
||||
genFilePath: string;
|
||||
statements: o.Statement[];
|
||||
importExpr(reference: any, typeParams?: o.Type[]|null): o.Expression;
|
||||
importExpr(reference: any, typeParams?: o.Type[]|null, useSummaries?: boolean): o.Expression;
|
||||
}
|
||||
|
||||
export function stringify(token: any): string {
|
||||
|
@ -10,13 +10,15 @@ import {AotCompilerOptions} from '../aot/compiler_options';
|
||||
import {StaticReflector} from '../aot/static_reflector';
|
||||
import {StaticSymbol} from '../aot/static_symbol';
|
||||
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompilePipeSummary} from '../compile_metadata';
|
||||
import {BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
||||
import {BindingForm, BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
||||
import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast';
|
||||
import {Identifiers} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {convertValueToOutputAst} from '../output/value_util';
|
||||
import {ParseSourceSpan} from '../parse_util';
|
||||
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ProviderAst, ProviderAstType, QueryMatch, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
|
||||
import {OutputContext} from '../util';
|
||||
|
||||
|
||||
/**
|
||||
* Generates code that is used to type check templates.
|
||||
@ -34,27 +36,34 @@ export class TypeCheckCompiler {
|
||||
*/
|
||||
compileComponent(
|
||||
componentId: string, component: CompileDirectiveMetadata, template: TemplateAst[],
|
||||
usedPipes: CompilePipeSummary[],
|
||||
externalReferenceVars: Map<StaticSymbol, string>): o.Statement[] {
|
||||
usedPipes: CompilePipeSummary[], externalReferenceVars: Map<StaticSymbol, string>,
|
||||
ctx: OutputContext): o.Statement[] {
|
||||
const pipes = new Map<string, StaticSymbol>();
|
||||
usedPipes.forEach(p => pipes.set(p.name, p.type.reference));
|
||||
let embeddedViewCount = 0;
|
||||
const viewBuilderFactory = (parent: ViewBuilder | null): ViewBuilder => {
|
||||
const embeddedViewIndex = embeddedViewCount++;
|
||||
return new ViewBuilder(
|
||||
this.options, this.reflector, externalReferenceVars, parent, component.type.reference,
|
||||
component.isHost, embeddedViewIndex, pipes, viewBuilderFactory);
|
||||
};
|
||||
const viewBuilderFactory =
|
||||
(parent: ViewBuilder | null, guards: GuardExpression[]): ViewBuilder => {
|
||||
const embeddedViewIndex = embeddedViewCount++;
|
||||
return new ViewBuilder(
|
||||
this.options, this.reflector, externalReferenceVars, parent, component.type.reference,
|
||||
component.isHost, embeddedViewIndex, pipes, guards, ctx, viewBuilderFactory);
|
||||
};
|
||||
|
||||
const visitor = viewBuilderFactory(null);
|
||||
const visitor = viewBuilderFactory(null, []);
|
||||
visitor.visitAll([], template);
|
||||
|
||||
return visitor.build(componentId);
|
||||
}
|
||||
}
|
||||
|
||||
interface GuardExpression {
|
||||
guard: StaticSymbol;
|
||||
useIf: boolean;
|
||||
expression: Expression;
|
||||
}
|
||||
|
||||
interface ViewBuilderFactory {
|
||||
(parent: ViewBuilder): ViewBuilder;
|
||||
(parent: ViewBuilder, guards: GuardExpression[]): ViewBuilder;
|
||||
}
|
||||
|
||||
// Note: This is used as key in Map and should therefore be
|
||||
@ -94,6 +103,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
private externalReferenceVars: Map<StaticSymbol, string>, private parent: ViewBuilder|null,
|
||||
private component: StaticSymbol, private isHostComponent: boolean,
|
||||
private embeddedViewIndex: number, private pipes: Map<string, StaticSymbol>,
|
||||
private guards: GuardExpression[], private ctx: OutputContext,
|
||||
private viewBuilderFactory: ViewBuilderFactory) {}
|
||||
|
||||
private getOutputVar(type: o.BuiltinTypeName|StaticSymbol): string {
|
||||
@ -112,6 +122,24 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
return varName;
|
||||
}
|
||||
|
||||
private getTypeGuardExpressions(ast: EmbeddedTemplateAst): GuardExpression[] {
|
||||
const result = [...this.guards];
|
||||
for (let directive of ast.directives) {
|
||||
for (let input of directive.inputs) {
|
||||
const guard = directive.directive.guards[input.directiveName];
|
||||
if (guard) {
|
||||
const useIf = guard === 'UseIf';
|
||||
result.push({
|
||||
guard,
|
||||
useIf,
|
||||
expression: {context: this.component, value: input.value} as Expression
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
visitAll(variables: VariableAst[], astNodes: TemplateAst[]) {
|
||||
this.variables = variables;
|
||||
templateVisitAll(this, astNodes);
|
||||
@ -119,7 +147,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
|
||||
build(componentId: string, targetStatements: o.Statement[] = []): o.Statement[] {
|
||||
this.children.forEach((child) => child.build(componentId, targetStatements));
|
||||
const viewStmts: o.Statement[] =
|
||||
let viewStmts: o.Statement[] =
|
||||
[o.variable(DYNAMIC_VAR_NAME).set(o.NULL_EXPR).toDeclStmt(o.DYNAMIC_TYPE)];
|
||||
let bindingCount = 0;
|
||||
this.updates.forEach((expression) => {
|
||||
@ -127,7 +155,8 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
const bindingId = `${bindingCount++}`;
|
||||
const nameResolver = context === this.component ? this : defaultResolver;
|
||||
const {stmts, currValExpr} = convertPropertyBinding(
|
||||
nameResolver, o.variable(this.getOutputVar(context)), value, bindingId);
|
||||
nameResolver, o.variable(this.getOutputVar(context)), value, bindingId,
|
||||
BindingForm.General);
|
||||
stmts.push(new o.ExpressionStatement(currValExpr));
|
||||
viewStmts.push(...stmts.map(
|
||||
(stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
|
||||
@ -142,6 +171,28 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
(stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
|
||||
});
|
||||
|
||||
if (this.guards.length) {
|
||||
let guardExpression: o.Expression|undefined = undefined;
|
||||
for (const guard of this.guards) {
|
||||
const {context, value} = this.preprocessUpdateExpression(guard.expression);
|
||||
const bindingId = `${bindingCount++}`;
|
||||
const nameResolver = context === this.component ? this : defaultResolver;
|
||||
// We only support support simple expressions and ignore others as they
|
||||
// are unlikely to affect type narrowing.
|
||||
const {stmts, currValExpr} = convertPropertyBinding(
|
||||
nameResolver, o.variable(this.getOutputVar(context)), value, bindingId,
|
||||
BindingForm.TrySimple);
|
||||
if (stmts.length == 0) {
|
||||
const guardClause =
|
||||
guard.useIf ? currValExpr : this.ctx.importExpr(guard.guard).callFn([currValExpr]);
|
||||
guardExpression = guardExpression ? guardExpression.and(guardClause) : guardClause;
|
||||
}
|
||||
}
|
||||
if (guardExpression) {
|
||||
viewStmts = [new o.IfStmt(guardExpression, viewStmts)];
|
||||
}
|
||||
}
|
||||
|
||||
const viewName = `_View_${componentId}_${this.embeddedViewIndex}`;
|
||||
const viewFactory = new o.DeclareFunctionStmt(viewName, [], viewStmts);
|
||||
targetStatements.push(viewFactory);
|
||||
@ -163,7 +214,12 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
// for the context in any embedded view.
|
||||
// We keep this behaivor behind a flag for now.
|
||||
if (this.options.fullTemplateTypeCheck) {
|
||||
const childVisitor = this.viewBuilderFactory(this);
|
||||
// Find any applicable type guards. For example, NgIf has a type guard on ngIf
|
||||
// (see NgIf.ngIfTypeGuard) that can be used to indicate that a template is only
|
||||
// stamped out if ngIf is truthy so any bindings in the template can assume that,
|
||||
// if a nullable type is used for ngIf, that expression is not null or undefined.
|
||||
const guards = this.getTypeGuardExpressions(ast);
|
||||
const childVisitor = this.viewBuilderFactory(this, guards);
|
||||
this.children.push(childVisitor);
|
||||
childVisitor.visitAll(ast.variables, ast.children);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {CompileDirectiveMetadata, CompilePipeSummary, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata';
|
||||
import {CompileReflector} from '../compile_reflector';
|
||||
import {BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
||||
import {BindingForm, BuiltinConverter, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
|
||||
import {ArgumentType, BindingFlags, ChangeDetectionStrategy, NodeFlags, QueryBindingType, QueryValueType, ViewFlags} from '../core';
|
||||
import {AST, ASTWithSource, Interpolation} from '../expression_parser/ast';
|
||||
import {Identifiers} from '../identifiers';
|
||||
@ -859,7 +859,7 @@ class ViewBuilder implements TemplateAstVisitor, LocalResolver {
|
||||
const bindingId = `${updateBindingCount++}`;
|
||||
const nameResolver = context === COMP_VAR ? self : null;
|
||||
const {stmts, currValExpr} =
|
||||
convertPropertyBinding(nameResolver, context, value, bindingId);
|
||||
convertPropertyBinding(nameResolver, context, value, bindingId, BindingForm.General);
|
||||
updateStmts.push(...stmts.map(
|
||||
(stmt: o.Statement) => o.applySourceSpanToStatementIfNeeded(stmt, sourceSpan)));
|
||||
return o.applySourceSpanToExpressionIfNeeded(currValExpr, sourceSpan);
|
||||
|
@ -196,6 +196,25 @@ describe('StaticSymbolResolver', () => {
|
||||
.toBe(symbolCache.get('/test3.d.ts', 'b'));
|
||||
});
|
||||
|
||||
it('should ignore summaries for inputAs if requested', () => {
|
||||
init(
|
||||
{
|
||||
'/test.ts': `
|
||||
export {a} from './test2';
|
||||
`
|
||||
},
|
||||
[], [{
|
||||
symbol: symbolCache.get('/test2.d.ts', 'a'),
|
||||
importAs: symbolCache.get('/test3.d.ts', 'b')
|
||||
}]);
|
||||
|
||||
symbolResolver.getSymbolsOf('/test.ts');
|
||||
|
||||
expect(
|
||||
symbolResolver.getImportAs(symbolCache.get('/test2.d.ts', 'a'), /* useSummaries */ false))
|
||||
.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should calculate importAs for symbols with members based on importAs for symbols without',
|
||||
() => {
|
||||
init(
|
||||
|
@ -126,6 +126,7 @@ export function main() {
|
||||
outputs: [],
|
||||
host: {},
|
||||
queries: {},
|
||||
guards: {},
|
||||
exportAs: undefined,
|
||||
providers: undefined
|
||||
}));
|
||||
@ -154,6 +155,7 @@ export function main() {
|
||||
outputs: [],
|
||||
host: {},
|
||||
queries: {},
|
||||
guards: {},
|
||||
exportAs: undefined,
|
||||
providers: undefined
|
||||
}));
|
||||
@ -164,6 +166,7 @@ export function main() {
|
||||
outputs: [],
|
||||
host: {},
|
||||
queries: {},
|
||||
guards: {},
|
||||
exportAs: undefined,
|
||||
providers: undefined
|
||||
}));
|
||||
|
@ -38,28 +38,29 @@ function createTypeMeta({reference, diDeps}: {reference: any, diDeps?: any[]}):
|
||||
return {reference: reference, diDeps: diDeps || [], lifecycleHooks: []};
|
||||
}
|
||||
|
||||
function compileDirectiveMetadataCreate({isHost, type, isComponent, selector, exportAs,
|
||||
changeDetection, inputs, outputs, host, providers,
|
||||
viewProviders, queries, viewQueries, entryComponents,
|
||||
template, componentViewType, rendererType}: {
|
||||
isHost?: boolean,
|
||||
type?: CompileTypeMetadata,
|
||||
isComponent?: boolean,
|
||||
selector?: string | null,
|
||||
exportAs?: string | null,
|
||||
changeDetection?: ChangeDetectionStrategy | null,
|
||||
inputs?: string[],
|
||||
outputs?: string[],
|
||||
host?: {[key: string]: string},
|
||||
providers?: CompileProviderMetadata[] | null,
|
||||
viewProviders?: CompileProviderMetadata[] | null,
|
||||
queries?: CompileQueryMetadata[] | null,
|
||||
viewQueries?: CompileQueryMetadata[],
|
||||
entryComponents?: CompileEntryComponentMetadata[],
|
||||
template?: CompileTemplateMetadata,
|
||||
componentViewType?: StaticSymbol | ProxyClass | null,
|
||||
rendererType?: StaticSymbol | RendererType2 | null,
|
||||
}) {
|
||||
function compileDirectiveMetadataCreate(
|
||||
{isHost, type, isComponent, selector, exportAs, changeDetection, inputs, outputs, host,
|
||||
providers, viewProviders, queries, guards, viewQueries, entryComponents, template,
|
||||
componentViewType, rendererType}: {
|
||||
isHost?: boolean,
|
||||
type?: CompileTypeMetadata,
|
||||
isComponent?: boolean,
|
||||
selector?: string | null,
|
||||
exportAs?: string | null,
|
||||
changeDetection?: ChangeDetectionStrategy | null,
|
||||
inputs?: string[],
|
||||
outputs?: string[],
|
||||
host?: {[key: string]: string},
|
||||
providers?: CompileProviderMetadata[] | null,
|
||||
viewProviders?: CompileProviderMetadata[] | null,
|
||||
queries?: CompileQueryMetadata[] | null,
|
||||
guards?: {[key: string]: any},
|
||||
viewQueries?: CompileQueryMetadata[],
|
||||
entryComponents?: CompileEntryComponentMetadata[],
|
||||
template?: CompileTemplateMetadata,
|
||||
componentViewType?: StaticSymbol | ProxyClass | null,
|
||||
rendererType?: StaticSymbol | RendererType2 | null,
|
||||
}) {
|
||||
return CompileDirectiveMetadata.create({
|
||||
isHost: !!isHost,
|
||||
type: noUndefined(type) !,
|
||||
@ -73,6 +74,7 @@ function compileDirectiveMetadataCreate({isHost, type, isComponent, selector, ex
|
||||
providers: providers || [],
|
||||
viewProviders: viewProviders || [],
|
||||
queries: queries || [],
|
||||
guards: guards || {},
|
||||
viewQueries: viewQueries || [],
|
||||
entryComponents: entryComponents || [],
|
||||
template: noUndefined(template) !,
|
||||
@ -390,6 +392,7 @@ export function main() {
|
||||
providers: [],
|
||||
viewProviders: [],
|
||||
queries: [],
|
||||
guards: {},
|
||||
viewQueries: [],
|
||||
entryComponents: [],
|
||||
componentViewType: null,
|
||||
|
@ -102,7 +102,8 @@ export function createPlatformFactory(
|
||||
parentPlatformFactory: ((extraProviders?: StaticProvider[]) => PlatformRef) | null,
|
||||
name: string, providers: StaticProvider[] = []): (extraProviders?: StaticProvider[]) =>
|
||||
PlatformRef {
|
||||
const marker = new InjectionToken(`Platform: ${name}`);
|
||||
const desc = `Platform: ${name}`;
|
||||
const marker = new InjectionToken(desc);
|
||||
return (extraProviders: StaticProvider[] = []) => {
|
||||
let platform = getPlatform();
|
||||
if (!platform || platform.injector.get(ALLOW_MULTIPLE_PLATFORMS, false)) {
|
||||
@ -110,8 +111,9 @@ export function createPlatformFactory(
|
||||
parentPlatformFactory(
|
||||
providers.concat(extraProviders).concat({provide: marker, useValue: true}));
|
||||
} else {
|
||||
createPlatform(Injector.create(
|
||||
providers.concat(extraProviders).concat({provide: marker, useValue: true})));
|
||||
const injectedProviders: StaticProvider[] =
|
||||
providers.concat(extraProviders).concat({provide: marker, useValue: true});
|
||||
createPlatform(Injector.create({providers: injectedProviders, name: desc}));
|
||||
}
|
||||
}
|
||||
return assertPlatform(marker);
|
||||
@ -224,10 +226,12 @@ export class PlatformRef {
|
||||
// pass that as parent to the NgModuleFactory.
|
||||
const ngZoneOption = options ? options.ngZone : undefined;
|
||||
const ngZone = getNgZone(ngZoneOption);
|
||||
const providers: StaticProvider[] = [{provide: NgZone, useValue: ngZone}];
|
||||
// Attention: Don't use ApplicationRef.run here,
|
||||
// as we want to be sure that all possible constructor calls are inside `ngZone.run`!
|
||||
return ngZone.run(() => {
|
||||
const ngZoneInjector = Injector.create([{provide: NgZone, useValue: ngZone}], this.injector);
|
||||
const ngZoneInjector = Injector.create(
|
||||
{providers: providers, parent: this.injector, name: moduleFactory.moduleType.name});
|
||||
const moduleRef = <InternalNgModuleRef<M>>moduleFactory.create(ngZoneInjector);
|
||||
const exceptionHandler: ErrorHandler = moduleRef.injector.get(ErrorHandler, null);
|
||||
if (!exceptionHandler) {
|
||||
|
@ -8,12 +8,12 @@
|
||||
|
||||
import {Type} from '../type';
|
||||
import {stringify} from '../util';
|
||||
|
||||
import {resolveForwardRef} from './forward_ref';
|
||||
import {InjectionToken} from './injection_token';
|
||||
import {Inject, Optional, Self, SkipSelf} from './metadata';
|
||||
import {ConstructorProvider, ExistingProvider, FactoryProvider, StaticClassProvider, StaticProvider, ValueProvider} from './provider';
|
||||
|
||||
export const SOURCE = '__source';
|
||||
const _THROW_IF_NOT_FOUND = new Object();
|
||||
export const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
|
||||
|
||||
@ -64,6 +64,13 @@ export abstract class Injector {
|
||||
*/
|
||||
abstract get(token: any, notFoundValue?: any): any;
|
||||
|
||||
/**
|
||||
* @deprecated from v5 use the new signature Injector.create(options)
|
||||
*/
|
||||
static create(providers: StaticProvider[], parent?: Injector): Injector;
|
||||
|
||||
static create(options: {providers: StaticProvider[], parent?: Injector, name?: string}): Injector;
|
||||
|
||||
/**
|
||||
* Create a new Injector which is configure using `StaticProvider`s.
|
||||
*
|
||||
@ -71,8 +78,14 @@ export abstract class Injector {
|
||||
*
|
||||
* {@example core/di/ts/provider_spec.ts region='ConstructorProvider'}
|
||||
*/
|
||||
static create(providers: StaticProvider[], parent?: Injector): Injector {
|
||||
return new StaticInjector(providers, parent);
|
||||
static create(
|
||||
options: StaticProvider[]|{providers: StaticProvider[], parent?: Injector, name?: string},
|
||||
parent?: Injector): Injector {
|
||||
if (Array.isArray(options)) {
|
||||
return new StaticInjector(options, parent);
|
||||
} else {
|
||||
return new StaticInjector(options.providers, options.parent, options.name || null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,11 +116,14 @@ const NO_NEW_LINE = 'ɵ';
|
||||
|
||||
export class StaticInjector implements Injector {
|
||||
readonly parent: Injector;
|
||||
readonly source: string|null;
|
||||
|
||||
private _records: Map<any, Record>;
|
||||
|
||||
constructor(providers: StaticProvider[], parent: Injector = NULL_INJECTOR) {
|
||||
constructor(
|
||||
providers: StaticProvider[], parent: Injector = NULL_INJECTOR, source: string|null = null) {
|
||||
this.parent = parent;
|
||||
this.source = source;
|
||||
const records = this._records = new Map<any, Record>();
|
||||
records.set(
|
||||
Injector, <Record>{token: Injector, fn: IDENT, deps: EMPTY, value: this, useNew: false});
|
||||
@ -122,7 +138,10 @@ export class StaticInjector implements Injector {
|
||||
return tryResolveToken(token, record, this._records, this.parent, notFoundValue);
|
||||
} catch (e) {
|
||||
const tokenPath: any[] = e[NG_TEMP_TOKEN_PATH];
|
||||
e.message = formatError('\n' + e.message, tokenPath);
|
||||
if (token[SOURCE]) {
|
||||
tokenPath.unshift(token[SOURCE]);
|
||||
}
|
||||
e.message = formatError('\n' + e.message, tokenPath, this.source);
|
||||
e[NG_TOKEN_PATH] = tokenPath;
|
||||
e[NG_TEMP_TOKEN_PATH] = null;
|
||||
throw e;
|
||||
@ -336,7 +355,7 @@ function computeDeps(provider: StaticProvider): DependencyRecord[] {
|
||||
return deps;
|
||||
}
|
||||
|
||||
function formatError(text: string, obj: any): string {
|
||||
function formatError(text: string, obj: any, source: string | null = null): string {
|
||||
text = text && text.charAt(0) === '\n' && text.charAt(1) == NO_NEW_LINE ? text.substr(2) : text;
|
||||
let context = stringify(obj);
|
||||
if (obj instanceof Array) {
|
||||
@ -352,7 +371,7 @@ function formatError(text: string, obj: any): string {
|
||||
}
|
||||
context = `{${parts.join(', ')}}`;
|
||||
}
|
||||
return `StaticInjectorError[${context}]: ${text.replace(NEW_LINE, '\n ')}`;
|
||||
return `StaticInjectorError${source ? '(' + source + ')' : ''}[${context}]: ${text.replace(NEW_LINE, '\n ')}`;
|
||||
}
|
||||
|
||||
function staticError(text: string, obj: any): Error {
|
||||
|
@ -71,11 +71,13 @@ export interface ResolvedReflectiveProvider {
|
||||
}
|
||||
|
||||
export class ResolvedReflectiveProvider_ implements ResolvedReflectiveProvider {
|
||||
readonly resolvedFactory: ResolvedReflectiveFactory;
|
||||
|
||||
constructor(
|
||||
public key: ReflectiveKey, public resolvedFactories: ResolvedReflectiveFactory[],
|
||||
public multiProvider: boolean) {}
|
||||
|
||||
get resolvedFactory(): ResolvedReflectiveFactory { return this.resolvedFactories[0]; }
|
||||
public multiProvider: boolean) {
|
||||
this.resolvedFactory = this.resolvedFactories[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -66,13 +66,20 @@ export class CodegenComponentFactoryResolver implements ComponentFactoryResolver
|
||||
}
|
||||
|
||||
export class ComponentFactoryBoundToModule<C> extends ComponentFactory<C> {
|
||||
constructor(private factory: ComponentFactory<C>, private ngModule: NgModuleRef<any>) { super(); }
|
||||
readonly selector: string;
|
||||
readonly componentType: Type<any>;
|
||||
readonly ngContentSelectors: string[];
|
||||
readonly inputs: {propName: string, templateName: string}[];
|
||||
readonly outputs: {propName: string, templateName: string}[];
|
||||
|
||||
get selector() { return this.factory.selector; }
|
||||
get componentType() { return this.factory.componentType; }
|
||||
get ngContentSelectors() { return this.factory.ngContentSelectors; }
|
||||
get inputs() { return this.factory.inputs; }
|
||||
get outputs() { return this.factory.outputs; }
|
||||
constructor(private factory: ComponentFactory<C>, private ngModule: NgModuleRef<any>) {
|
||||
super();
|
||||
this.selector = factory.selector;
|
||||
this.componentType = factory.componentType;
|
||||
this.ngContentSelectors = factory.ngContentSelectors;
|
||||
this.inputs = factory.inputs;
|
||||
this.outputs = factory.outputs;
|
||||
}
|
||||
|
||||
create(
|
||||
injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string|any,
|
||||
|
@ -41,9 +41,9 @@ export class QueryList<T>/* implements Iterable<T> */ {
|
||||
private _results: Array<T> = [];
|
||||
public readonly changes: Observable<any> = new EventEmitter();
|
||||
|
||||
get length(): number { return this._results.length; }
|
||||
get first(): T { return this._results[0]; }
|
||||
get last(): T { return this._results[this.length - 1]; }
|
||||
readonly length: number;
|
||||
readonly first: T;
|
||||
readonly last: T;
|
||||
|
||||
/**
|
||||
* See
|
||||
@ -98,6 +98,9 @@ export class QueryList<T>/* implements Iterable<T> */ {
|
||||
reset(res: Array<T|any[]>): void {
|
||||
this._results = flatten(res);
|
||||
(this as{dirty: boolean}).dirty = false;
|
||||
(this as{length: number}).length = this._results.length;
|
||||
(this as{last: T}).last = this._results[this.length - 1];
|
||||
(this as{first: T}).first = this._results[0];
|
||||
}
|
||||
|
||||
notifyOnChanges(): void { (this.changes as EventEmitter<any>).emit(this); }
|
||||
|
@ -13,6 +13,7 @@ export interface PlatformReflectionCapabilities {
|
||||
isReflectionEnabled(): boolean;
|
||||
factory(type: Type<any>): Function;
|
||||
hasLifecycleHook(type: any, lcProperty: string): boolean;
|
||||
guards(type: any): {[key: string]: any};
|
||||
|
||||
/**
|
||||
* Return a list of annotations/types for constructor parameters
|
||||
|
@ -207,6 +207,8 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
||||
return type instanceof Type && lcProperty in type.prototype;
|
||||
}
|
||||
|
||||
guards(type: any): {[key: string]: any} { return {}; }
|
||||
|
||||
getter(name: string): GetterFn { return <GetterFn>new Function('o', 'return o.' + name + ';'); }
|
||||
|
||||
setter(name: string): SetterFn {
|
||||
|
@ -9,6 +9,7 @@
|
||||
import {resolveForwardRef} from '../di/forward_ref';
|
||||
import {Injector} from '../di/injector';
|
||||
import {NgModuleRef} from '../linker/ng_module_factory';
|
||||
import {stringify} from '../util';
|
||||
|
||||
import {DepDef, DepFlags, NgModuleData, NgModuleDefinition, NgModuleProviderDef, NodeFlags} from './types';
|
||||
import {splitDepsDsl, tokenKey} from './util';
|
||||
@ -25,7 +26,7 @@ export function moduleProvideDef(
|
||||
// lowered the expression and then stopped evaluating it,
|
||||
// i.e. also didn't unwrap it.
|
||||
value = resolveForwardRef(value);
|
||||
const depDefs = splitDepsDsl(deps);
|
||||
const depDefs = splitDepsDsl(deps, stringify(token));
|
||||
return {
|
||||
// will bet set by the module definition
|
||||
index: -1,
|
||||
|
@ -12,7 +12,7 @@ import {ElementRef} from '../linker/element_ref';
|
||||
import {TemplateRef} from '../linker/template_ref';
|
||||
import {ViewContainerRef} from '../linker/view_container_ref';
|
||||
import {Renderer as RendererV1, Renderer2} from '../render/api';
|
||||
|
||||
import {stringify} from '../util';
|
||||
import {createChangeDetectorRef, createInjector, createRendererV1} from './refs';
|
||||
import {BindingDef, BindingFlags, DepDef, DepFlags, NodeDef, NodeFlags, OutputDef, OutputType, ProviderData, QueryValueType, Services, ViewData, ViewFlags, ViewState, asElementData, asProviderData, shouldCallLifecycleInitHook} from './types';
|
||||
import {calcBindingFlags, checkBinding, dispatchEvent, isComponentView, splitDepsDsl, splitMatchedQueriesDsl, tokenKey, viewParentEl} from './util';
|
||||
@ -83,7 +83,7 @@ export function _def(
|
||||
// i.e. also didn't unwrap it.
|
||||
value = resolveForwardRef(value);
|
||||
|
||||
const depDefs = splitDepsDsl(deps);
|
||||
const depDefs = splitDepsDsl(deps, stringify(token));
|
||||
|
||||
return {
|
||||
// will bet set by the view definition
|
||||
|
@ -481,6 +481,8 @@ class NgModuleRef_ implements NgModuleData, InternalNgModuleRef<any> {
|
||||
/** @internal */
|
||||
_providers: any[];
|
||||
|
||||
readonly injector: Injector = this;
|
||||
|
||||
constructor(
|
||||
private _moduleType: Type<any>, public _parent: Injector,
|
||||
public _bootstrapComponents: Type<any>[], public _def: NgModuleDefinition) {
|
||||
@ -496,8 +498,6 @@ class NgModuleRef_ implements NgModuleData, InternalNgModuleRef<any> {
|
||||
|
||||
get componentFactoryResolver() { return this.get(ComponentFactoryResolver); }
|
||||
|
||||
get injector(): Injector { return this; }
|
||||
|
||||
destroy(): void {
|
||||
if (this._destroyed) {
|
||||
throw new Error(
|
||||
|
@ -649,9 +649,8 @@ class DebugRendererFactory2 implements RendererFactory2 {
|
||||
|
||||
|
||||
class DebugRenderer2 implements Renderer2 {
|
||||
constructor(private delegate: Renderer2) {}
|
||||
|
||||
get data() { return this.delegate.data; }
|
||||
readonly data: {[key: string]: any};
|
||||
constructor(private delegate: Renderer2) { this.data = this.delegate.data; }
|
||||
|
||||
destroyNode(node: any) {
|
||||
removeDebugNodeFromIndex(getDebugNode(node) !);
|
||||
|
@ -7,10 +7,10 @@
|
||||
*/
|
||||
|
||||
import {WrappedValue, devModeEqual} from '../change_detection/change_detection';
|
||||
import {SOURCE} from '../di/injector';
|
||||
import {ViewEncapsulation} from '../metadata/view';
|
||||
import {RendererType2} from '../render/api';
|
||||
import {looseIdentical, stringify} from '../util';
|
||||
|
||||
import {expressionChangedAfterItHasBeenCheckedError} from './errors';
|
||||
import {BindingDef, BindingFlags, Definition, DefinitionFactory, DepDef, DepFlags, ElementData, NodeDef, NodeFlags, QueryValueType, Services, ViewData, ViewDefinition, ViewDefinitionFactory, ViewFlags, ViewState, asElementData, asTextData} from './types';
|
||||
|
||||
@ -209,7 +209,7 @@ export function splitMatchedQueriesDsl(
|
||||
return {matchedQueries, references, matchedQueryIds};
|
||||
}
|
||||
|
||||
export function splitDepsDsl(deps: ([DepFlags, any] | any)[]): DepDef[] {
|
||||
export function splitDepsDsl(deps: ([DepFlags, any] | any)[], sourceName?: string): DepDef[] {
|
||||
return deps.map(value => {
|
||||
let token: any;
|
||||
let flags: DepFlags;
|
||||
@ -219,6 +219,9 @@ export function splitDepsDsl(deps: ([DepFlags, any] | any)[]): DepDef[] {
|
||||
flags = DepFlags.None;
|
||||
token = value;
|
||||
}
|
||||
if (token && (typeof token === 'function' || typeof token === 'object') && sourceName) {
|
||||
Object.defineProperty(token, SOURCE, {value: sourceName, configurable: true});
|
||||
}
|
||||
return {flags, token, tokenKey: tokenKey(token)};
|
||||
});
|
||||
}
|
||||
|
@ -255,6 +255,45 @@ export function main() {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should allow a transition to use a function to determine what method to run', () => {
|
||||
let valueToMatch = '';
|
||||
const transitionFn =
|
||||
(fromState: string, toState: string) => { return toState == valueToMatch; };
|
||||
|
||||
@Component({
|
||||
selector: 'if-cmp',
|
||||
template: '<div [@myAnimation]="exp"></div>',
|
||||
animations: [
|
||||
trigger('myAnimation', [transition(
|
||||
transitionFn,
|
||||
[style({opacity: 0}), animate(1234, style({opacity: 1}))])]),
|
||||
]
|
||||
})
|
||||
class Cmp {
|
||||
exp: any = '';
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [Cmp]});
|
||||
|
||||
const fixture = TestBed.createComponent(Cmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
valueToMatch = cmp.exp = 'something';
|
||||
fixture.detectChanges();
|
||||
|
||||
let players = getLog();
|
||||
expect(players.length).toEqual(1);
|
||||
let [p1] = players;
|
||||
expect(p1.totalTime).toEqual(1234);
|
||||
resetLog();
|
||||
|
||||
valueToMatch = 'something-else';
|
||||
cmp.exp = 'this-wont-match';
|
||||
fixture.detectChanges();
|
||||
|
||||
players = getLog();
|
||||
expect(players.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should allow a state value to be `0`', () => {
|
||||
@Component({
|
||||
selector: 'if-cmp',
|
||||
|
@ -147,8 +147,8 @@ export function main() {
|
||||
|
||||
expect(() => createAndGetRootNodes(compViewDef(rootElNodes)))
|
||||
.toThrowError(
|
||||
'StaticInjectorError[Dep]: \n' +
|
||||
' StaticInjectorError[Dep]: \n' +
|
||||
'StaticInjectorError(DynamicTestModule)[SomeService -> Dep]: \n' +
|
||||
' StaticInjectorError(Platform: core)[SomeService -> Dep]: \n' +
|
||||
' NullInjectorError: No provider for Dep!');
|
||||
|
||||
const nonRootElNodes = [
|
||||
@ -161,8 +161,8 @@ export function main() {
|
||||
|
||||
expect(() => createAndGetRootNodes(compViewDef(nonRootElNodes)))
|
||||
.toThrowError(
|
||||
'StaticInjectorError[Dep]: \n' +
|
||||
' StaticInjectorError[Dep]: \n' +
|
||||
'StaticInjectorError(DynamicTestModule)[SomeService -> Dep]: \n' +
|
||||
' StaticInjectorError(Platform: core)[SomeService -> Dep]: \n' +
|
||||
' NullInjectorError: No provider for Dep!');
|
||||
});
|
||||
|
||||
@ -186,8 +186,8 @@ export function main() {
|
||||
directiveDef(1, NodeFlags.None, null, 0, SomeService, ['nonExistingDep'])
|
||||
])))
|
||||
.toThrowError(
|
||||
'StaticInjectorError[nonExistingDep]: \n' +
|
||||
' StaticInjectorError[nonExistingDep]: \n' +
|
||||
'StaticInjectorError(DynamicTestModule)[nonExistingDep]: \n' +
|
||||
' StaticInjectorError(Platform: core)[nonExistingDep]: \n' +
|
||||
' NullInjectorError: No provider for nonExistingDep!');
|
||||
});
|
||||
|
||||
|
@ -355,8 +355,12 @@ export class TestBed implements Injector {
|
||||
}
|
||||
|
||||
const ngZone = new NgZone({enableLongStackTrace: true});
|
||||
const ngZoneInjector =
|
||||
Injector.create([{provide: NgZone, useValue: ngZone}], this.platform.injector);
|
||||
const providers: StaticProvider[] = [{provide: NgZone, useValue: ngZone}];
|
||||
const ngZoneInjector = Injector.create({
|
||||
providers: providers,
|
||||
parent: this.platform.injector,
|
||||
name: this._moduleFactory.moduleType.name
|
||||
});
|
||||
this._moduleRef = this._moduleFactory.create(ngZoneInjector);
|
||||
// ApplicationInitStatus.runInitializers() is marked @internal to core. So casting to any
|
||||
// before accessing it.
|
||||
|
@ -135,7 +135,7 @@ export function main() {
|
||||
name = 'square';
|
||||
}
|
||||
|
||||
const injector = Injector.create([{provide: Square, deps: []}]);
|
||||
const injector = Injector.create({providers: [{provide: Square, deps: []}]});
|
||||
|
||||
const shape: Square = injector.get(Square);
|
||||
expect(shape.name).toEqual('square');
|
||||
|
@ -49,10 +49,10 @@ export const COMPOSITION_BUFFER_MODE = new InjectionToken<boolean>('CompositionE
|
||||
// https://github.com/angular/angular/issues/3011 is implemented
|
||||
// selector: '[ngModel],[formControl],[formControlName]',
|
||||
host: {
|
||||
'(input)': '_handleInput($event.target.value)',
|
||||
'(input)': '$any(this)._handleInput($event.target.value)',
|
||||
'(blur)': 'onTouched()',
|
||||
'(compositionstart)': '_compositionStart()',
|
||||
'(compositionend)': '_compositionEnd($event.target.value)'
|
||||
'(compositionstart)': '$any(this)._compositionStart()',
|
||||
'(compositionend)': '$any(this)._compositionEnd($event.target.value)'
|
||||
},
|
||||
providers: [DEFAULT_VALUE_ACCESSOR]
|
||||
})
|
||||
|
@ -253,7 +253,7 @@ export abstract class AbstractControl {
|
||||
* Sets the async validators that are active on this control. Calling this
|
||||
* will overwrite any existing async validators.
|
||||
*/
|
||||
setAsyncValidators(newValidator: AsyncValidatorFn|AsyncValidatorFn[]): void {
|
||||
setAsyncValidators(newValidator: AsyncValidatorFn|AsyncValidatorFn[]|null): void {
|
||||
this.asyncValidator = coerceToAsyncValidator(newValidator);
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,7 @@ export class JitReflector implements CompileReflector {
|
||||
hasLifecycleHook(type: any, lcProperty: string): boolean {
|
||||
return this.reflectionCapabilities.hasLifecycleHook(type, lcProperty);
|
||||
}
|
||||
guards(type: any): {[key: string]: any} { return this.reflectionCapabilities.guards(type); }
|
||||
resolveExternalReference(ref: ExternalReference): any {
|
||||
return builtinExternalReferences.get(ref) || ref.runtime;
|
||||
}
|
||||
|
@ -7,12 +7,12 @@
|
||||
*/
|
||||
|
||||
import {isPlatformBrowser} from '@angular/common';
|
||||
import {APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, Directive, ErrorHandler, Inject, Input, LOCALE_ID, NgModule, OnDestroy, PLATFORM_ID, PLATFORM_INITIALIZER, Pipe, Provider, StaticProvider, VERSION, createPlatformFactory, ɵstringify as stringify} from '@angular/core';
|
||||
import {APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA, Compiler, Component, Directive, ErrorHandler, Inject, Input, LOCALE_ID, NgModule, OnDestroy, PLATFORM_ID, PLATFORM_INITIALIZER, Pipe, Provider, StaticProvider, Type, VERSION, createPlatformFactory} from '@angular/core';
|
||||
import {ApplicationRef, destroyPlatform} from '@angular/core/src/application_ref';
|
||||
import {Console} from '@angular/core/src/console';
|
||||
import {ComponentRef} from '@angular/core/src/linker/component_factory';
|
||||
import {Testability, TestabilityRegistry} from '@angular/core/src/testability/testability';
|
||||
import {AsyncTestCompleter, Log, afterEach, beforeEach, beforeEachProviders, ddescribe, describe, iit, inject, it} from '@angular/core/testing/src/testing_internal';
|
||||
import {AsyncTestCompleter, Log, afterEach, beforeEach, beforeEachProviders, describe, iit, inject, it} from '@angular/core/testing/src/testing_internal';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
@ -112,10 +112,11 @@ class DummyConsole implements Console {
|
||||
|
||||
|
||||
class TestModule {}
|
||||
function bootstrap(cmpType: any, providers: Provider[] = [], platformProviders: StaticProvider[] = [
|
||||
]): Promise<any> {
|
||||
function bootstrap(
|
||||
cmpType: any, providers: Provider[] = [], platformProviders: StaticProvider[] = [],
|
||||
imports: Type<any>[] = []): Promise<any> {
|
||||
@NgModule({
|
||||
imports: [BrowserModule],
|
||||
imports: [BrowserModule, ...imports],
|
||||
declarations: [cmpType],
|
||||
bootstrap: [cmpType],
|
||||
providers: providers,
|
||||
@ -183,6 +184,40 @@ export function main() {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should throw if no provider', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
const logger = new MockConsole();
|
||||
const errorHandler = new ErrorHandler();
|
||||
errorHandler._console = logger as any;
|
||||
|
||||
class IDontExist {}
|
||||
|
||||
@Component({selector: 'cmp', template: 'Cmp'})
|
||||
class CustomCmp {
|
||||
constructor(iDontExist: IDontExist) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'hello-app',
|
||||
template: '<cmp></cmp>',
|
||||
})
|
||||
class RootCmp {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [CustomCmp], exports: [CustomCmp]})
|
||||
class CustomModule {
|
||||
}
|
||||
|
||||
bootstrap(RootCmp, [{provide: ErrorHandler, useValue: errorHandler}], [], [
|
||||
CustomModule
|
||||
]).then(null, (e: Error) => {
|
||||
expect(e.message).toContain(`StaticInjectorError(TestModule)[CustomCmp -> IDontExist]:
|
||||
StaticInjectorError(Platform: core)[CustomCmp -> IDontExist]:
|
||||
NullInjectorError: No provider for IDontExist!`);
|
||||
async.done();
|
||||
return null;
|
||||
});
|
||||
}));
|
||||
|
||||
if (getDOM().supportsDOMEvents()) {
|
||||
it('should forward the error to promise when bootstrap fails',
|
||||
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
|
||||
|
@ -21,13 +21,15 @@ import {reduce} from 'rxjs/operator/reduce';
|
||||
import {LoadedRouterConfig, ResolveData, RunGuardsAndResolvers} from './config';
|
||||
import {ActivationStart, ChildActivationStart, Event} from './events';
|
||||
import {ChildrenOutletContexts, OutletContext} from './router_outlet_context';
|
||||
import {ActivatedRouteSnapshot, RouterStateSnapshot, equalParamsAndUrlSegments, inheritedParamsDataResolve} from './router_state';
|
||||
import {ActivatedRouteSnapshot, ParamsInheritanceStrategy, RouterStateSnapshot, equalParamsAndUrlSegments, inheritedParamsDataResolve} from './router_state';
|
||||
import {andObservables, forEach, shallowEqual, wrapIntoObservable} from './utils/collection';
|
||||
import {TreeNode, nodeChildrenAsMap} from './utils/tree';
|
||||
|
||||
class CanActivate {
|
||||
constructor(public path: ActivatedRouteSnapshot[]) {}
|
||||
get route(): ActivatedRouteSnapshot { return this.path[this.path.length - 1]; }
|
||||
readonly route: ActivatedRouteSnapshot;
|
||||
constructor(public path: ActivatedRouteSnapshot[]) {
|
||||
this.route = this.path[this.path.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
class CanDeactivate {
|
||||
@ -61,11 +63,11 @@ export class PreActivation {
|
||||
(canDeactivate: boolean) => canDeactivate ? this.runCanActivateChecks() : of (false));
|
||||
}
|
||||
|
||||
resolveData(): Observable<any> {
|
||||
resolveData(paramsInheritanceStrategy: ParamsInheritanceStrategy): Observable<any> {
|
||||
if (!this.isActivating()) return of (null);
|
||||
const checks$ = from(this.canActivateChecks);
|
||||
const runningChecks$ =
|
||||
concatMap.call(checks$, (check: CanActivate) => this.runResolve(check.route));
|
||||
const runningChecks$ = concatMap.call(
|
||||
checks$, (check: CanActivate) => this.runResolve(check.route, paramsInheritanceStrategy));
|
||||
return reduce.call(runningChecks$, (_: any, __: any) => _);
|
||||
}
|
||||
|
||||
@ -304,11 +306,14 @@ export class PreActivation {
|
||||
return every.call(canDeactivate$, (result: any) => result === true);
|
||||
}
|
||||
|
||||
private runResolve(future: ActivatedRouteSnapshot): Observable<any> {
|
||||
private runResolve(
|
||||
future: ActivatedRouteSnapshot,
|
||||
paramsInheritanceStrategy: ParamsInheritanceStrategy): Observable<any> {
|
||||
const resolve = future._resolve;
|
||||
return map.call(this.resolveNode(resolve, future), (resolvedData: any): any => {
|
||||
future._resolvedData = resolvedData;
|
||||
future.data = {...future.data, ...inheritedParamsDataResolve(future).resolve};
|
||||
future.data = {...future.data,
|
||||
...inheritedParamsDataResolve(future, paramsInheritanceStrategy).resolve};
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import {Observer} from 'rxjs/Observer';
|
||||
import {of } from 'rxjs/observable/of';
|
||||
|
||||
import {Data, ResolveData, Route, Routes} from './config';
|
||||
import {ActivatedRouteSnapshot, RouterStateSnapshot, inheritedParamsDataResolve} from './router_state';
|
||||
import {ActivatedRouteSnapshot, ParamsInheritanceStrategy, RouterStateSnapshot, inheritedParamsDataResolve} from './router_state';
|
||||
import {PRIMARY_OUTLET, defaultUrlMatcher} from './shared';
|
||||
import {UrlSegment, UrlSegmentGroup, UrlTree, mapChildrenIntoArray} from './url_tree';
|
||||
import {forEach, last} from './utils/collection';
|
||||
@ -21,15 +21,17 @@ import {TreeNode} from './utils/tree';
|
||||
class NoMatch {}
|
||||
|
||||
export function recognize(
|
||||
rootComponentType: Type<any>| null, config: Routes, urlTree: UrlTree,
|
||||
url: string): Observable<RouterStateSnapshot> {
|
||||
return new Recognizer(rootComponentType, config, urlTree, url).recognize();
|
||||
rootComponentType: Type<any>| null, config: Routes, urlTree: UrlTree, url: string,
|
||||
paramsInheritanceStrategy: ParamsInheritanceStrategy =
|
||||
'emptyOnly'): Observable<RouterStateSnapshot> {
|
||||
return new Recognizer(rootComponentType, config, urlTree, url, paramsInheritanceStrategy)
|
||||
.recognize();
|
||||
}
|
||||
|
||||
class Recognizer {
|
||||
constructor(
|
||||
private rootComponentType: Type<any>|null, private config: Routes, private urlTree: UrlTree,
|
||||
private url: string) {}
|
||||
private url: string, private paramsInheritanceStrategy: ParamsInheritanceStrategy) {}
|
||||
|
||||
recognize(): Observable<RouterStateSnapshot> {
|
||||
try {
|
||||
@ -55,7 +57,7 @@ class Recognizer {
|
||||
inheritParamsAndData(routeNode: TreeNode<ActivatedRouteSnapshot>): void {
|
||||
const route = routeNode.value;
|
||||
|
||||
const i = inheritedParamsDataResolve(route);
|
||||
const i = inheritedParamsDataResolve(route, this.paramsInheritanceStrategy);
|
||||
route.params = Object.freeze(i.params);
|
||||
route.data = Object.freeze(i.data);
|
||||
|
||||
|
@ -27,7 +27,7 @@ import {recognize} from './recognize';
|
||||
import {DefaultRouteReuseStrategy, DetachedRouteHandleInternal, RouteReuseStrategy} from './route_reuse_strategy';
|
||||
import {RouterConfigLoader} from './router_config_loader';
|
||||
import {ChildrenOutletContexts} from './router_outlet_context';
|
||||
import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState} from './router_state';
|
||||
import {ActivatedRoute, ActivatedRouteSnapshot, RouterState, RouterStateSnapshot, advanceActivatedRoute, createEmptyState, inheritedParamsDataResolve} from './router_state';
|
||||
import {Params, isNavigationCancelingError} from './shared';
|
||||
import {DefaultUrlHandlingStrategy, UrlHandlingStrategy} from './url_handling_strategy';
|
||||
import {UrlSerializer, UrlTree, containsTree, createEmptyUrlTree} from './url_tree';
|
||||
@ -249,6 +249,16 @@ export class Router {
|
||||
*/
|
||||
onSameUrlNavigation: 'reload'|'ignore' = 'ignore';
|
||||
|
||||
/**
|
||||
* Defines how the router merges params, data and resolved data from parent to child
|
||||
* routes. Available options are:
|
||||
*
|
||||
* - `'emptyOnly'`, the default, only inherits parent params for path-less or component-less
|
||||
* routes.
|
||||
* - `'always'`, enables unconditional inheritance of parent params.
|
||||
*/
|
||||
paramsInheritanceStrategy: 'emptyOnly'|'always' = 'emptyOnly';
|
||||
|
||||
/**
|
||||
* Creates the router service.
|
||||
*/
|
||||
@ -611,7 +621,8 @@ export class Router {
|
||||
urlAndSnapshot$ = mergeMap.call(redirectsApplied$, (appliedUrl: UrlTree) => {
|
||||
return map.call(
|
||||
recognize(
|
||||
this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl)),
|
||||
this.rootComponentType, this.config, appliedUrl, this.serializeUrl(appliedUrl),
|
||||
this.paramsInheritanceStrategy),
|
||||
(snapshot: any) => {
|
||||
|
||||
(this.events as Subject<Event>)
|
||||
@ -667,7 +678,7 @@ export class Router {
|
||||
if (p.shouldActivate && preActivation.isActivating()) {
|
||||
this.triggerEvent(
|
||||
new ResolveStart(id, this.serializeUrl(url), p.appliedUrl, p.snapshot));
|
||||
return map.call(preActivation.resolveData(), () => {
|
||||
return map.call(preActivation.resolveData(this.paramsInheritanceStrategy), () => {
|
||||
this.triggerEvent(
|
||||
new ResolveEnd(id, this.serializeUrl(url), p.appliedUrl, p.snapshot));
|
||||
return p;
|
||||
|
@ -278,6 +278,16 @@ export interface ExtraOptions {
|
||||
* current URL. Default is 'ignore'.
|
||||
*/
|
||||
onSameUrlNavigation?: 'reload'|'ignore';
|
||||
|
||||
/**
|
||||
* Defines how the router merges params, data and resolved data from parent to child
|
||||
* routes. Available options are:
|
||||
*
|
||||
* - `'emptyOnly'`, the default, only inherits parent params for path-less or component-less
|
||||
* routes.
|
||||
* - `'always'`, enables unconditional inheritance of parent params.
|
||||
*/
|
||||
paramsInheritanceStrategy?: 'emptyOnly'|'always';
|
||||
}
|
||||
|
||||
export function setupRouter(
|
||||
@ -314,6 +324,10 @@ export function setupRouter(
|
||||
router.onSameUrlNavigation = opts.onSameUrlNavigation;
|
||||
}
|
||||
|
||||
if (opts.paramsInheritanceStrategy) {
|
||||
router.paramsInheritanceStrategy = opts.paramsInheritanceStrategy;
|
||||
}
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ import {shallowEqual, shallowEqualArrays} from './utils/collection';
|
||||
import {Tree, TreeNode} from './utils/tree';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @whatItDoes Represents the state of the router.
|
||||
*
|
||||
@ -174,6 +175,9 @@ export class ActivatedRoute {
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export type ParamsInheritanceStrategy = 'emptyOnly' | 'always';
|
||||
|
||||
/** @internal */
|
||||
export type Inherited = {
|
||||
params: Params,
|
||||
@ -181,29 +185,43 @@ export type Inherited = {
|
||||
resolve: Data,
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
export function inheritedParamsDataResolve(route: ActivatedRouteSnapshot): Inherited {
|
||||
const pathToRoot = route.pathFromRoot;
|
||||
/**
|
||||
* Returns the inherited params, data, and resolve for a given route.
|
||||
* By default, this only inherits values up to the nearest path-less or component-less route.
|
||||
* @internal
|
||||
*/
|
||||
export function inheritedParamsDataResolve(
|
||||
route: ActivatedRouteSnapshot,
|
||||
paramsInheritanceStrategy: ParamsInheritanceStrategy = 'emptyOnly'): Inherited {
|
||||
const pathFromRoot = route.pathFromRoot;
|
||||
|
||||
let inhertingStartingFrom = pathToRoot.length - 1;
|
||||
let inheritingStartingFrom = 0;
|
||||
if (paramsInheritanceStrategy !== 'always') {
|
||||
inheritingStartingFrom = pathFromRoot.length - 1;
|
||||
|
||||
while (inhertingStartingFrom >= 1) {
|
||||
const current = pathToRoot[inhertingStartingFrom];
|
||||
const parent = pathToRoot[inhertingStartingFrom - 1];
|
||||
// current route is an empty path => inherits its parent's params and data
|
||||
if (current.routeConfig && current.routeConfig.path === '') {
|
||||
inhertingStartingFrom--;
|
||||
while (inheritingStartingFrom >= 1) {
|
||||
const current = pathFromRoot[inheritingStartingFrom];
|
||||
const parent = pathFromRoot[inheritingStartingFrom - 1];
|
||||
// current route is an empty path => inherits its parent's params and data
|
||||
if (current.routeConfig && current.routeConfig.path === '') {
|
||||
inheritingStartingFrom--;
|
||||
|
||||
// parent is componentless => current route should inherit its params and data
|
||||
} else if (!parent.component) {
|
||||
inhertingStartingFrom--;
|
||||
// parent is componentless => current route should inherit its params and data
|
||||
} else if (!parent.component) {
|
||||
inheritingStartingFrom--;
|
||||
|
||||
} else {
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pathToRoot.slice(inhertingStartingFrom).reduce((res, curr) => {
|
||||
return flattenInherited(pathFromRoot.slice(inheritingStartingFrom));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
function flattenInherited(pathFromRoot: ActivatedRouteSnapshot[]): Inherited {
|
||||
return pathFromRoot.reduce((res, curr) => {
|
||||
const params = {...res.params, ...curr.params};
|
||||
const data = {...res.data, ...curr.data};
|
||||
const resolve = {...res.resolve, ...curr._resolvedData};
|
||||
@ -352,7 +370,7 @@ function setRouterState<U, T extends{_routerState: U}>(state: U, node: TreeNode<
|
||||
}
|
||||
|
||||
function serializeNode(node: TreeNode<ActivatedRouteSnapshot>): string {
|
||||
const c = node.children.length > 0 ? ` { ${node.children.map(serializeNode).join(", ")} } ` : '';
|
||||
const c = node.children.length > 0 ? ` { ${node.children.map(serializeNode).join(', ')} } ` : '';
|
||||
return `${node.value}${c}`;
|
||||
}
|
||||
|
||||
|
@ -3794,6 +3794,19 @@ describe('Integration', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing router options', () => {
|
||||
describe('paramsInheritanceStrategy', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule(
|
||||
{imports: [RouterTestingModule.withRoutes([], {paramsInheritanceStrategy: 'always'})]});
|
||||
});
|
||||
|
||||
it('should configure the router', fakeAsync(inject([Router], (router: Router) => {
|
||||
expect(router.paramsInheritanceStrategy).toEqual('always');
|
||||
})));
|
||||
});
|
||||
});
|
||||
|
||||
function expectEvents(events: Event[], pairs: any[]) {
|
||||
expect(events.length).toEqual(pairs.length);
|
||||
for (let i = 0; i < events.length; ++i) {
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {Routes} from '../src/config';
|
||||
import {recognize} from '../src/recognize';
|
||||
import {ActivatedRouteSnapshot, RouterStateSnapshot} from '../src/router_state';
|
||||
import {ActivatedRouteSnapshot, ParamsInheritanceStrategy, RouterStateSnapshot, inheritedParamsDataResolve} from '../src/router_state';
|
||||
import {PRIMARY_OUTLET, Params} from '../src/shared';
|
||||
import {DefaultUrlSerializer, UrlTree} from '../src/url_tree';
|
||||
|
||||
@ -201,7 +201,7 @@ describe('recognize', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should merge componentless route\'s data', () => {
|
||||
it('should inherit componentless route\'s data', () => {
|
||||
checkRecognize(
|
||||
[{
|
||||
path: 'a',
|
||||
@ -214,6 +214,34 @@ describe('recognize', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should not inherit route\'s data if it has component', () => {
|
||||
checkRecognize(
|
||||
[{
|
||||
path: 'a',
|
||||
component: ComponentA,
|
||||
data: {one: 1},
|
||||
children: [{path: 'b', data: {two: 2}, component: ComponentB}]
|
||||
}],
|
||||
'a/b', (s: RouterStateSnapshot) => {
|
||||
const r: ActivatedRouteSnapshot = s.firstChild(<any>s.firstChild(s.root)) !;
|
||||
expect(r.data).toEqual({two: 2});
|
||||
});
|
||||
});
|
||||
|
||||
it('should inherit route\'s data if paramsInheritanceStrategy is \'always\'', () => {
|
||||
checkRecognize(
|
||||
[{
|
||||
path: 'a',
|
||||
component: ComponentA,
|
||||
data: {one: 1},
|
||||
children: [{path: 'b', data: {two: 2}, component: ComponentB}]
|
||||
}],
|
||||
'a/b', (s: RouterStateSnapshot) => {
|
||||
const r: ActivatedRouteSnapshot = s.firstChild(<any>s.firstChild(s.root)) !;
|
||||
expect(r.data).toEqual({one: 1, two: 2});
|
||||
}, 'always');
|
||||
});
|
||||
|
||||
it('should set resolved data', () => {
|
||||
checkRecognize(
|
||||
[{path: 'a', resolve: {one: 'some-token'}, component: ComponentA}], 'a',
|
||||
@ -307,7 +335,7 @@ describe('recognize', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should match (non-termianl) when both primary and secondary and primary has a child',
|
||||
it('should match (non-terminal) when both primary and secondary and primary has a child',
|
||||
() => {
|
||||
const config = [{
|
||||
path: 'parent',
|
||||
@ -579,7 +607,7 @@ describe('recognize', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should merge params until encounters a normal route', () => {
|
||||
it('should inherit params until encounters a normal route', () => {
|
||||
checkRecognize(
|
||||
[{
|
||||
path: 'p/:id',
|
||||
@ -606,6 +634,25 @@ describe('recognize', () => {
|
||||
checkActivatedRoute(c, 'c', {}, ComponentC);
|
||||
});
|
||||
});
|
||||
|
||||
it('should inherit all params if paramsInheritanceStrategy is \'always\'', () => {
|
||||
checkRecognize(
|
||||
[{
|
||||
path: 'p/:id',
|
||||
children: [{
|
||||
path: 'a/:name',
|
||||
children: [{
|
||||
path: 'b',
|
||||
component: ComponentB,
|
||||
children: [{path: 'c', component: ComponentC}]
|
||||
}]
|
||||
}]
|
||||
}],
|
||||
'p/11/a/victor/b/c', (s: RouterStateSnapshot) => {
|
||||
const c = s.firstChild(s.firstChild(s.firstChild(s.firstChild(s.root) !) !) !) !;
|
||||
checkActivatedRoute(c, 'c', {id: '11', name: 'victor'}, ComponentC);
|
||||
}, 'always');
|
||||
});
|
||||
});
|
||||
|
||||
describe('empty URL leftovers', () => {
|
||||
@ -722,8 +769,11 @@ describe('recognize', () => {
|
||||
});
|
||||
});
|
||||
|
||||
function checkRecognize(config: Routes, url: string, callback: any): void {
|
||||
recognize(RootComponent, config, tree(url), url).subscribe(callback, e => { throw e; });
|
||||
function checkRecognize(
|
||||
config: Routes, url: string, callback: any,
|
||||
paramsInheritanceStrategy?: ParamsInheritanceStrategy): void {
|
||||
recognize(RootComponent, config, tree(url), url, paramsInheritanceStrategy)
|
||||
.subscribe(callback, e => { throw e; });
|
||||
}
|
||||
|
||||
function checkActivatedRoute(
|
||||
|
@ -498,7 +498,7 @@ function checkResolveData(
|
||||
future: RouterStateSnapshot, curr: RouterStateSnapshot, injector: any, check: any): void {
|
||||
const p = new PreActivation(future, curr, injector);
|
||||
p.initialize(new ChildrenOutletContexts());
|
||||
p.resolveData().subscribe(check, (e) => { throw e; });
|
||||
p.resolveData('emptyOnly').subscribe(check, (e) => { throw e; });
|
||||
}
|
||||
|
||||
function checkGuards(
|
||||
|
@ -9,7 +9,7 @@
|
||||
import {Location, LocationStrategy} from '@angular/common';
|
||||
import {MockLocationStrategy, SpyLocation} from '@angular/common/testing';
|
||||
import {Compiler, Injectable, Injector, ModuleWithProviders, NgModule, NgModuleFactory, NgModuleFactoryLoader, Optional} from '@angular/core';
|
||||
import {ChildrenOutletContexts, NoPreloading, PreloadingStrategy, ROUTES, Route, Router, RouterModule, Routes, UrlHandlingStrategy, UrlSerializer, provideRoutes, ɵROUTER_PROVIDERS as ROUTER_PROVIDERS, ɵflatten as flatten} from '@angular/router';
|
||||
import {ChildrenOutletContexts, ExtraOptions, NoPreloading, PreloadingStrategy, ROUTER_CONFIGURATION, ROUTES, Route, Router, RouterModule, Routes, UrlHandlingStrategy, UrlSerializer, provideRoutes, ɵROUTER_PROVIDERS as ROUTER_PROVIDERS, ɵflatten as flatten} from '@angular/router';
|
||||
|
||||
|
||||
|
||||
@ -76,6 +76,13 @@ export class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader {
|
||||
}
|
||||
}
|
||||
|
||||
function isUrlHandlingStrategy(opts: ExtraOptions | UrlHandlingStrategy):
|
||||
opts is UrlHandlingStrategy {
|
||||
// This property check is needed because UrlHandlingStrategy is an interface and doesn't exist at
|
||||
// runtime.
|
||||
return 'shouldProcessUrl' in opts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Router setup factory function used for testing.
|
||||
*
|
||||
@ -84,9 +91,39 @@ export class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader {
|
||||
export function setupTestingRouter(
|
||||
urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts, location: Location,
|
||||
loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][],
|
||||
urlHandlingStrategy?: UrlHandlingStrategy) {
|
||||
opts?: ExtraOptions, urlHandlingStrategy?: UrlHandlingStrategy): Router;
|
||||
|
||||
/**
|
||||
* Router setup factory function used for testing.
|
||||
*
|
||||
* @deprecated As of v5.2. The 2nd-to-last argument should be `ExtraOptions`, not
|
||||
* `UrlHandlingStrategy`
|
||||
*/
|
||||
export function setupTestingRouter(
|
||||
urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts, location: Location,
|
||||
loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][],
|
||||
urlHandlingStrategy?: UrlHandlingStrategy): Router;
|
||||
|
||||
/**
|
||||
* Router setup factory function used for testing.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export function setupTestingRouter(
|
||||
urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts, location: Location,
|
||||
loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][],
|
||||
opts?: ExtraOptions | UrlHandlingStrategy, urlHandlingStrategy?: UrlHandlingStrategy) {
|
||||
const router = new Router(
|
||||
null !, urlSerializer, contexts, location, injector, loader, compiler, flatten(routes));
|
||||
// Handle deprecated argument ordering.
|
||||
if (opts) {
|
||||
if (isUrlHandlingStrategy(opts)) {
|
||||
router.urlHandlingStrategy = opts;
|
||||
} else if (opts.paramsInheritanceStrategy) {
|
||||
router.paramsInheritanceStrategy = opts.paramsInheritanceStrategy;
|
||||
}
|
||||
}
|
||||
|
||||
if (urlHandlingStrategy) {
|
||||
router.urlHandlingStrategy = urlHandlingStrategy;
|
||||
}
|
||||
@ -128,14 +165,20 @@ export function setupTestingRouter(
|
||||
useFactory: setupTestingRouter,
|
||||
deps: [
|
||||
UrlSerializer, ChildrenOutletContexts, Location, NgModuleFactoryLoader, Compiler, Injector,
|
||||
ROUTES, [UrlHandlingStrategy, new Optional()]
|
||||
ROUTES, ROUTER_CONFIGURATION, [UrlHandlingStrategy, new Optional()]
|
||||
]
|
||||
},
|
||||
{provide: PreloadingStrategy, useExisting: NoPreloading}, provideRoutes([])
|
||||
]
|
||||
})
|
||||
export class RouterTestingModule {
|
||||
static withRoutes(routes: Routes): ModuleWithProviders {
|
||||
return {ngModule: RouterTestingModule, providers: [provideRoutes(routes)]};
|
||||
static withRoutes(routes: Routes, config?: ExtraOptions): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: RouterTestingModule,
|
||||
providers: [
|
||||
provideRoutes(routes),
|
||||
{provide: ROUTER_CONFIGURATION, useValue: config ? config : {}},
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ApplicationRef, ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injector, OnChanges, SimpleChange, SimpleChanges, Testability, TestabilityRegistry, Type} from '@angular/core';
|
||||
import {ApplicationRef, ChangeDetectorRef, ComponentFactory, ComponentRef, EventEmitter, Injector, OnChanges, SimpleChange, SimpleChanges, StaticProvider, Testability, TestabilityRegistry, Type} from '@angular/core';
|
||||
|
||||
import * as angular from './angular1';
|
||||
import {PropertyBinding} from './component_info';
|
||||
@ -54,8 +54,9 @@ export class DowngradeComponentAdapter {
|
||||
}
|
||||
|
||||
createComponent(projectableNodes: Node[][]) {
|
||||
const childInjector =
|
||||
Injector.create([{provide: $SCOPE, useValue: this.componentScope}], this.parentInjector);
|
||||
const providers: StaticProvider[] = [{provide: $SCOPE, useValue: this.componentScope}];
|
||||
const childInjector = Injector.create(
|
||||
{providers: providers, parent: this.parentInjector, name: 'DowngradeComponentAdapter'});
|
||||
|
||||
this.componentRef =
|
||||
this.componentFactory.create(childInjector, projectableNodes, this.element[0]);
|
||||
|
@ -172,7 +172,7 @@ export interface AnimationStyleMetadata extends AnimationMetadata {
|
||||
/** @experimental */
|
||||
export interface AnimationTransitionMetadata extends AnimationMetadata {
|
||||
animation: AnimationMetadata | AnimationMetadata[];
|
||||
expr: string;
|
||||
expr: string | ((fromState: string, toState: string) => boolean);
|
||||
options: AnimationOptions | null;
|
||||
}
|
||||
|
||||
@ -240,7 +240,7 @@ export declare function style(tokens: '*' | {
|
||||
}>): AnimationStyleMetadata;
|
||||
|
||||
/** @experimental */
|
||||
export declare function transition(stateChangeExpr: string, steps: AnimationMetadata | AnimationMetadata[], options?: AnimationOptions | null): AnimationTransitionMetadata;
|
||||
export declare function transition(stateChangeExpr: string | ((fromState: string, toState: string) => boolean), steps: AnimationMetadata | AnimationMetadata[], options?: AnimationOptions | null): AnimationTransitionMetadata;
|
||||
|
||||
/** @experimental */
|
||||
export declare function trigger(name: string, definitions: AnimationMetadata[]): AnimationTriggerMetadata;
|
||||
|
7
tools/public_api_guard/core/core.d.ts
vendored
7
tools/public_api_guard/core/core.d.ts
vendored
@ -476,7 +476,12 @@ export declare abstract class Injector {
|
||||
/** @deprecated */ abstract get(token: any, notFoundValue?: any): any;
|
||||
static NULL: Injector;
|
||||
static THROW_IF_NOT_FOUND: Object;
|
||||
static create(providers: StaticProvider[], parent?: Injector): Injector;
|
||||
/** @deprecated */ static create(providers: StaticProvider[], parent?: Injector): Injector;
|
||||
static create(options: {
|
||||
providers: StaticProvider[];
|
||||
parent?: Injector;
|
||||
name?: string;
|
||||
}): Injector;
|
||||
}
|
||||
|
||||
/** @stable */
|
||||
|
2
tools/public_api_guard/forms/forms.d.ts
vendored
2
tools/public_api_guard/forms/forms.d.ts
vendored
@ -50,7 +50,7 @@ export declare abstract class AbstractControl {
|
||||
}): void;
|
||||
abstract patchValue(value: any, options?: Object): void;
|
||||
abstract reset(value?: any, options?: Object): void;
|
||||
setAsyncValidators(newValidator: AsyncValidatorFn | AsyncValidatorFn[]): void;
|
||||
setAsyncValidators(newValidator: AsyncValidatorFn | AsyncValidatorFn[] | null): void;
|
||||
setErrors(errors: ValidationErrors | null, opts?: {
|
||||
emitEvent?: boolean;
|
||||
}): void;
|
||||
|
2
tools/public_api_guard/router/router.d.ts
vendored
2
tools/public_api_guard/router/router.d.ts
vendored
@ -127,6 +127,7 @@ export interface ExtraOptions {
|
||||
errorHandler?: ErrorHandler;
|
||||
initialNavigation?: InitialNavigation;
|
||||
onSameUrlNavigation?: 'reload' | 'ignore';
|
||||
paramsInheritanceStrategy?: 'emptyOnly' | 'always';
|
||||
preloadingStrategy?: any;
|
||||
useHash?: boolean;
|
||||
}
|
||||
@ -329,6 +330,7 @@ export declare class Router {
|
||||
readonly events: Observable<Event>;
|
||||
navigated: boolean;
|
||||
onSameUrlNavigation: 'reload' | 'ignore';
|
||||
paramsInheritanceStrategy: 'emptyOnly' | 'always';
|
||||
routeReuseStrategy: RouteReuseStrategy;
|
||||
readonly routerState: RouterState;
|
||||
readonly url: string;
|
||||
|
4
tools/public_api_guard/router/testing.d.ts
vendored
4
tools/public_api_guard/router/testing.d.ts
vendored
@ -1,10 +1,10 @@
|
||||
/** @stable */
|
||||
export declare class RouterTestingModule {
|
||||
static withRoutes(routes: Routes): ModuleWithProviders;
|
||||
static withRoutes(routes: Routes, config?: ExtraOptions): ModuleWithProviders;
|
||||
}
|
||||
|
||||
/** @stable */
|
||||
export declare function setupTestingRouter(urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts, location: Location, loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][], urlHandlingStrategy?: UrlHandlingStrategy): Router;
|
||||
export declare function setupTestingRouter(urlSerializer: UrlSerializer, contexts: ChildrenOutletContexts, location: Location, loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, routes: Route[][], opts?: ExtraOptions, urlHandlingStrategy?: UrlHandlingStrategy): Router;
|
||||
|
||||
/** @stable */
|
||||
export declare class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader {
|
||||
|
Reference in New Issue
Block a user