Compare commits

..

34 Commits
2.4.2 ... 2.4.4

Author SHA1 Message Date
84542d8ae7 docs(changelog): add changelog for 2.4.4 2017-01-18 18:35:54 -06:00
17cb3ec565 chore(release): cut the 2.4.4 release 2017-01-18 18:32:57 -06:00
015878afe6 fix(http): don't create a blob out of ArrayBuffer when type is application/octet-stream (#13992)
Closes #13973
2017-01-18 18:28:37 -06:00
2af58622c1 fix(router): enable loadChildren with function in aot (#13909)
Closes #11075
2017-01-18 18:28:02 -06:00
7ffd10541d refactor(core): remove an unused import in application_ref (#13901) 2017-01-18 18:27:52 -06:00
481b099d82 docs(CHANGELOG): added reference to closed issue in CHANGELOG for informational purposes (#13985) 2017-01-18 18:27:25 -06:00
49c4b0fa92 fix(router): routerLinkActive should not throw when not initialized (#13273)
Fixes #13270

PR Close #13273
2017-01-18 18:27:14 -06:00
b8b6b1d27a refactor(router): clean up RouterLinkActive (#13273)
PR Close #13273
2017-01-18 18:27:03 -06:00
892b5ba950 chore(tsc-wrapped): update tsickle to latest (#13471) 2017-01-18 18:26:37 -06:00
bd15110c7d feat(security): allow calc and gradient functions. (#13943)
PR Close #13943

Also includes support for # color notation in function arguments (common
in gradient functions).
2017-01-18 18:25:45 -06:00
2250082fd7 fix(upgrade): detect async downgrade component changes (#13812)
This commit effectively reverts 7e0f02f96e
as it was an invalid fix for #6385, that created a more significant
bug, which was that changes were not always being detected.

Angular 1 digests should be run inside the ngZone to ensure
that async changes are detected.

We don't know how to fix #6385 without breaking change detection
at this stage. That issue is triggered by async operations, such as
`setTimeout`, being triggered inside scope watcher functions.

One could argue that watcher functions should be pure and not do
work such as triggering async operations. It is possible that the
original use case could be supported by moving the debounce
logic into the watch listener function, which is only called if the
watched value actually changes.

Closes #10660, #12318, #12034

PR Close #13812
2017-01-18 18:21:29 -06:00
87316c52db test(upgrade): reorganise test layout (#13812) 2017-01-18 18:21:24 -06:00
606b76d9bb chore(compiler-cli): Move calculateEmitPath into CompilerHost (#13904)
This is so that it can be overriden in an environment specific CompilerHost(like within Google) to customize the output paths.

PR Close #13904
2017-01-18 18:21:09 -06:00
3d0b1b8184 fix(common): support numeric value as discrete cases for NgPlural (#13876)
PR Close #13876
2017-01-18 18:20:56 -06:00
261fd16780 fix(animations): fix internal jscompiler issue and AOT quoting (#13798)
CL #143630929
PR Close #13798
2017-01-18 18:20:47 -06:00
104cc42f6d docs(http): Spelling Fix #13867 2017-01-18 18:20:30 -06:00
a7d28044c5 docs(changelog): add changelog for 2.4.3 2017-01-11 13:38:23 -08:00
055bea2969 chore(release): cut v2.4.3 2017-01-11 13:38:16 -08:00
dad0d21b89 chore(owners): configure pullapprove.com 2017-01-11 11:35:31 -08:00
313683f6f3 fix(compiler-cli): avoid handling functions in loadChildren as lazy load routes paths
The change avoids the compiler CLI internal API from mismatching the following case as lazy loading

```
import { NonLazyLoadedModule } from './non-lazy-loaded/non-lazy-loaded.module';

export function getNonLazyLoadedModule() { return NonLazyLoadedModule; }

export const routes = [
{ path: '/some-path', loadChildren: getNonLazyLoadedModule }
];
```

The output of the check is later passed to `RouteDef.fromString()`, so, it makes sense to be only a string.

Fixes angular/angular-cli#3204
2017-01-11 11:35:23 -08:00
338be6d6a5 refactor(common): remove some facade usages 2017-01-11 11:34:03 -08:00
4b56f79328 refactor(test): <template>/<ng-container>/*-directives
- remove outer `<div>` in tests,
- use `<ng-container>` instead of `<template>` where possible,
- use *... instead of template (tag or attr) where possible.

Fixes #13816
2017-01-11 11:33:30 -08:00
d7f2a3c71b fix(i18n): translate attributes inside elements marked for translation 2017-01-10 17:15:42 -08:00
1c929ae244 docs(NgPlural): fix API docs
Fixes #13786
2017-01-10 16:51:52 -08:00
83d0ff6d13 refactor(Compiler): misc cleanup 2017-01-10 16:50:20 -08:00
d43e5dd44d chore(benchmarks): change var to let 2017-01-09 16:08:33 -08:00
61ba223c1a fix(router): throw an error when navigate to null/undefined path
Closes #10560
Fixes #13384
2017-01-09 18:55:31 -05:00
6164eb25f3 fix(compiler-cli): add support for more than 2 levels of nested lazy routes
This change adds Compiler CLI support for any level of nesting for lazy routes.

For example `{app-root}/lazy-loaded-module-1/lazy-loaded-module-2/lazy-loaded-module-3`

Where `lazy-loaded-module-3` is lazy loaded from `lazy-loaded-module-2`,
and `lazy-loaded-module-2` is lazy loaded from module `lazy-loaded-module-1`,
and `lazy-loaded-module-1` is lazy loaded from `AppModule`

Fixes angular/angular-cli#3663
2017-01-09 18:06:26 -05:00
5e9d3dba3a fix(compiler): avoid evaluating arguments to unknown decorators
Fixes #13605
2017-01-09 18:06:11 -05:00
16922655ca fix(Router): fix checking for object intersection 2017-01-09 18:06:03 -05:00
7dc12b93fe fix(Compiler): fix template binding parsing (*directive="-...")
fixes #13800
2017-01-09 16:50:14 -05:00
1c82b58185 fix(router): RouterLink mirrors input target as attribute
Closes #13837
2017-01-09 16:50:06 -05:00
d6c414c08f fix: correctly show error when karma fails to load 2017-01-09 16:49:01 -05:00
d25d1730c7 chore(tsc-wrapped): bump version number to 0.5.1 2017-01-06 12:46:07 -08:00
86 changed files with 6117 additions and 3003 deletions

32
.pullapprove.yml Normal file
View File

@ -0,0 +1,32 @@
# Configuration for pullapprove.com
# See ownership spreadsheet:
# https://docs.google.com/spreadsheets/d/1-HIlzfbPYGsPr9KuYMe6bLfc4LXzPjpoALqtYRYTZB0/edit?pli=1#gid=0&vpid=A5
version: 2
group_defaults:
required: 1
reset_on_reopened:
enabled: true
approve_by_comment:
enabled: true
approve_regex: '^(Approved|:\+1:|LGTM)'
groups:
config:
conditions:
files:
- "*.yml"
- "*.json"
teams:
- repoowners
compiler:
conditions:
files:
- "tools/@angular/tsc-wrapped/*"
- "modules/@angular/compiler/*"
- "modules/@angular/compiler-cli/*"
teams:
- compiler-owners
- repoowners

View File

@ -1,3 +1,36 @@
<a name="2.4.4"></a>
## [2.4.4](https://github.com/angular/angular/compare/2.4.3...2.4.4) (2017-01-19)
### Bug Fixes
* **animations:** fix internal jscompiler issue and AOT quoting ([#13798](https://github.com/angular/angular/issues/13798)) ([261fd16](https://github.com/angular/angular/commit/261fd16))
* **common:** support numeric value as discrete cases for NgPlural ([#13876](https://github.com/angular/angular/issues/13876)) ([3d0b1b8](https://github.com/angular/angular/commit/3d0b1b8))
* **http:** don't create a blob out of ArrayBuffer when type is application/octet-stream ([#13992](https://github.com/angular/angular/issues/13992)) ([015878a](https://github.com/angular/angular/commit/015878a)), closes [#13973](https://github.com/angular/angular/issues/13973)
* **router:** enable loadChildren with function in aot ([#13909](https://github.com/angular/angular/issues/13909)) ([2af5862](https://github.com/angular/angular/commit/2af5862)), closes [#11075](https://github.com/angular/angular/issues/11075)
* **router:** routerLinkActive should not throw when not initialized ([#13273](https://github.com/angular/angular/issues/13273)) ([49c4b0f](https://github.com/angular/angular/commit/49c4b0f)), closes [#13270](https://github.com/angular/angular/issues/13270)
* **security:** allow calc and gradient functions. ([#13943](https://github.com/angular/angular/issues/13943)) ([bd15110](https://github.com/angular/angular/commit/bd15110))
* **upgrade:** detect async downgrade component changes ([#13812](https://github.com/angular/angular/issues/13812)) ([2250082](https://github.com/angular/angular/commit/2250082)), closes [#6385](https://github.com/angular/angular/issues/6385) [#6385](https://github.com/angular/angular/issues/6385) [#10660](https://github.com/angular/angular/issues/10660) [#12318](https://github.com/angular/angular/issues/12318) [#12034](https://github.com/angular/angular/issues/12034)
<a name="2.4.3"></a>
## [2.4.3](https://github.com/angular/angular/compare/2.4.2...2.4.3) (2017-01-11)
### Bug Fixes
* **compiler:** avoid evaluating arguments to unknown decorators ([5e9d3db](https://github.com/angular/angular/commit/5e9d3db)), closes [#13605](https://github.com/angular/angular/issues/13605)
* **compiler:** fix template binding parsing (`*directive="-..."`) ([7dc12b9](https://github.com/angular/angular/commit/7dc12b9)), closes [#13800](https://github.com/angular/angular/issues/13800)
* **compiler-cli:** add support for more than 2 levels of nested lazy routes ([6164eb2](https://github.com/angular/angular/commit/6164eb2)), closes [angular/angular-cli#3663](https://github.com/angular/angular-cli/issues/3663)
* **compiler-cli:** avoid handling functions in loadChildren as lazy load routes paths ([313683f](https://github.com/angular/angular/commit/313683f)), closes [angular/angular-cli#3204](https://github.com/angular/angular-cli/issues/3204)
* **i18n:** translate attributes inside elements marked for translation ([d7f2a3c](https://github.com/angular/angular/commit/d7f2a3c))
* **router:** RouterLink mirrors input `target` as attribute ([1c82b58](https://github.com/angular/angular/commit/1c82b58)), closes [#13837](https://github.com/angular/angular/issues/13837)
* **router:** throw an error when navigate to null/undefined path ([61ba223](https://github.com/angular/angular/commit/61ba223)), closes [#10560](https://github.com/angular/angular/issues/10560) [#13384](https://github.com/angular/angular/issues/13384)
* **router:** fix checking for object intersection ([1692265](https://github.com/angular/angular/commit/1692265))
<a name="2.4.2"></a> <a name="2.4.2"></a>
## [2.4.2](https://github.com/angular/angular/compare/2.4.1...2.4.2) (2017-01-06) ## [2.4.2](https://github.com/angular/angular/compare/2.4.1...2.4.2) (2017-01-06)
@ -9,7 +42,7 @@
* **common:** allow null/undefined values for `NgForTrackBy` ([6be55cc](https://github.com/angular/angular/commit/6be55cc)), closes [#13641](https://github.com/angular/angular/issues/13641) * **common:** allow null/undefined values for `NgForTrackBy` ([6be55cc](https://github.com/angular/angular/commit/6be55cc)), closes [#13641](https://github.com/angular/angular/issues/13641)
* **compiler:** dont throw when using `ANALYZE_FOR_ENTRY_COMPONENTS` with user classes ([#13679](https://github.com/angular/angular/issues/13679)) ([230e33f](https://github.com/angular/angular/commit/230e33f)), closes [#13565](https://github.com/angular/angular/issues/13565) * **compiler:** dont throw when using `ANALYZE_FOR_ENTRY_COMPONENTS` with user classes ([#13679](https://github.com/angular/angular/issues/13679)) ([230e33f](https://github.com/angular/angular/commit/230e33f)), closes [#13565](https://github.com/angular/angular/issues/13565)
* **compiler:** query `<template>` elements before their children. ([#13677](https://github.com/angular/angular/issues/13677)) ([1cd73c7](https://github.com/angular/angular/commit/1cd73c7)), closes [#13118](https://github.com/angular/angular/issues/13118) [#13167](https://github.com/angular/angular/issues/13167) * **compiler:** query `<template>` elements before their children. ([#13677](https://github.com/angular/angular/issues/13677)) ([1cd73c7](https://github.com/angular/angular/commit/1cd73c7)), closes [#13118](https://github.com/angular/angular/issues/13118) [#13167](https://github.com/angular/angular/issues/13167)
* **compiler:** allow "." in attribute selectors ([#13653](https://github.com/angular/angular/issues/13653)) ([29ffdfd](https://github.com/angular/angular/commit/29ffdfd)), closes [#13645](https://github.com/angular/angular/issues/13645) * **compiler:** allow "." in attribute selectors ([#13653](https://github.com/angular/angular/issues/13653)) ([29ffdfd](https://github.com/angular/angular/commit/29ffdfd)), closes [#13645](https://github.com/angular/angular/issues/13645) [#13982](https://github.com/angular/angular/issues/13982)
* **core:** animations no longer silently exits if the element is not apart of the DOM ([#13763](https://github.com/angular/angular/issues/13763)) ([f1cde43](https://github.com/angular/angular/commit/f1cde43)) * **core:** animations no longer silently exits if the element is not apart of the DOM ([#13763](https://github.com/angular/angular/issues/13763)) ([f1cde43](https://github.com/angular/angular/commit/f1cde43))
* **core:** animations should blend in all previously transitioned styles into next animation if interrupted ([#13148](https://github.com/angular/angular/issues/13148)) ([b245b92](https://github.com/angular/angular/commit/b245b92)) * **core:** animations should blend in all previously transitioned styles into next animation if interrupted ([#13148](https://github.com/angular/angular/issues/13148)) ([b245b92](https://github.com/angular/angular/commit/b245b92))
* **core:** remove reference to "Angular 2" in dev mode warning ([#13751](https://github.com/angular/angular/issues/13751)) ([21f5f05](https://github.com/angular/angular/commit/21f5f05)) * **core:** remove reference to "Angular 2" in dev mode warning ([#13751](https://github.com/angular/angular/issues/13751)) ([21f5f05](https://github.com/angular/angular/commit/21f5f05))

View File

@ -9,7 +9,7 @@
import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core'; import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
import {isListLikeIterable} from '../facade/collection'; import {isListLikeIterable} from '../facade/collection';
import {isPresent, stringify} from '../facade/lang'; import {stringify} from '../facade/lang';
/** /**
* @ngModule CommonModule * @ngModule CommonModule
@ -134,7 +134,7 @@ export class NgClass implements DoCheck {
(<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, !isCleanup)); (<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, !isCleanup));
} else { } else {
Object.keys(rawClassVal).forEach(klass => { Object.keys(rawClassVal).forEach(klass => {
if (isPresent(rawClassVal[klass])) this._toggleClass(klass, !isCleanup); if (rawClassVal[klass] != null) this._toggleClass(klass, !isCleanup);
}); });
} }
} }

View File

@ -21,10 +21,9 @@ import {SwitchView} from './ng_switch';
* @howToUse * @howToUse
* ``` * ```
* <some-element [ngPlural]="value"> * <some-element [ngPlural]="value">
* <ng-container *ngPluralCase="'=0'">there is nothing</ng-container> * <template ngPluralCase="=0">there is nothing</template>
* <ng-container *ngPluralCase="'=1'">there is one</ng-container> * <template ngPluralCase="=1">there is one</template>
* <ng-container *ngPluralCase="'few'">there are a few</ng-container> * <template ngPluralCase="few">there are a few</template>
* <ng-container *ngPluralCase="'other'">there are exactly #</ng-container>
* </some-element> * </some-element>
* ``` * ```
* *
@ -90,8 +89,8 @@ export class NgPlural {
* @howToUse * @howToUse
* ``` * ```
* <some-element [ngPlural]="value"> * <some-element [ngPlural]="value">
* <ng-container *ngPluralCase="'=0'">...</ng-container> * <template ngPluralCase="=0">...</template>
* <ng-container *ngPluralCase="'other'">...</ng-container> * <template ngPluralCase="other">...</template>
* </some-element> * </some-element>
*``` *```
* *
@ -104,6 +103,7 @@ export class NgPluralCase {
constructor( constructor(
@Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>, @Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>,
viewContainer: ViewContainerRef, @Host() ngPlural: NgPlural) { viewContainer: ViewContainerRef, @Host() ngPlural: NgPlural) {
ngPlural.addCase(value, new SwitchView(viewContainer, template)); const isANumber: boolean = !isNaN(Number(value));
ngPlural.addCase(isANumber ? `=${value}` : value, new SwitchView(viewContainer, template));
} }
} }

View File

@ -7,7 +7,6 @@
*/ */
import {Pipe, PipeTransform} from '@angular/core'; import {Pipe, PipeTransform} from '@angular/core';
import {isBlank} from '../facade/lang';
import {NgLocalization, getPluralCategory} from '../localization'; import {NgLocalization, getPluralCategory} from '../localization';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error'; import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
@ -35,7 +34,7 @@ export class I18nPluralPipe implements PipeTransform {
constructor(private _localization: NgLocalization) {} constructor(private _localization: NgLocalization) {}
transform(value: number, pluralMap: {[count: string]: string}): string { transform(value: number, pluralMap: {[count: string]: string}): string {
if (isBlank(value)) return ''; if (value == null) return '';
if (typeof pluralMap !== 'object' || pluralMap === null) { if (typeof pluralMap !== 'object' || pluralMap === null) {
throw new InvalidPipeArgumentError(I18nPluralPipe, pluralMap); throw new InvalidPipeArgumentError(I18nPluralPipe, pluralMap);

View File

@ -8,7 +8,7 @@
import {Inject, LOCALE_ID, Pipe, PipeTransform, Type} from '@angular/core'; import {Inject, LOCALE_ID, Pipe, PipeTransform, Type} from '@angular/core';
import {NumberWrapper, isBlank, isPresent} from '../facade/lang'; import {NumberWrapper} from '../facade/lang';
import {NumberFormatStyle, NumberFormatter} from './intl'; import {NumberFormatStyle, NumberFormatter} from './intl';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error'; import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
@ -18,7 +18,7 @@ const _NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(-(\d+))?)?$/;
function formatNumber( function formatNumber(
pipe: Type<any>, locale: string, value: number | string, style: NumberFormatStyle, pipe: Type<any>, locale: string, value: number | string, style: NumberFormatStyle,
digits: string, currency: string = null, currencyAsSymbol: boolean = false): string { digits: string, currency: string = null, currencyAsSymbol: boolean = false): string {
if (isBlank(value)) return null; if (value == null) return null;
// Convert strings to numbers // Convert strings to numbers
value = typeof value === 'string' && NumberWrapper.isNumeric(value) ? +value : value; value = typeof value === 'string' && NumberWrapper.isNumeric(value) ? +value : value;
@ -41,13 +41,13 @@ function formatNumber(
if (parts === null) { if (parts === null) {
throw new Error(`${digits} is not a valid digit info for number pipes`); throw new Error(`${digits} is not a valid digit info for number pipes`);
} }
if (isPresent(parts[1])) { // min integer digits if (parts[1] != null) { // min integer digits
minInt = NumberWrapper.parseIntAutoRadix(parts[1]); minInt = NumberWrapper.parseIntAutoRadix(parts[1]);
} }
if (isPresent(parts[3])) { // min fraction digits if (parts[3] != null) { // min fraction digits
minFraction = NumberWrapper.parseIntAutoRadix(parts[3]); minFraction = NumberWrapper.parseIntAutoRadix(parts[3]);
} }
if (isPresent(parts[5])) { // max fraction digits if (parts[5] != null) { // max fraction digits
maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]); maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]);
} }
} }

View File

@ -7,7 +7,6 @@
*/ */
import {Pipe, PipeTransform} from '@angular/core'; import {Pipe, PipeTransform} from '@angular/core';
import {isBlank} from '../facade/lang';
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error'; import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
/** /**
@ -58,7 +57,7 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
@Pipe({name: 'slice', pure: false}) @Pipe({name: 'slice', pure: false})
export class SlicePipe implements PipeTransform { export class SlicePipe implements PipeTransform {
transform(value: any, start: number, end?: number): any { transform(value: any, start: number, end?: number): any {
if (isBlank(value)) return value; if (value == null) return value;
if (!this.supports(value)) { if (!this.supports(value)) {
throw new InvalidPipeArgumentError(SlicePipe, value); throw new InvalidPipeArgumentError(SlicePipe, value);

View File

@ -7,7 +7,7 @@
*/ */
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {Component, ContentChild, TemplateRef} from '@angular/core'; import {Component} from '@angular/core';
import {ComponentFixture, TestBed, async} from '@angular/core/testing'; import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by'; import {By} from '@angular/platform-browser/src/dom/debug/by';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
@ -29,10 +29,7 @@ export function main() {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ declarations: [TestComponent],
TestComponent,
ComponentUsingTestComponent,
],
imports: [CommonModule], imports: [CommonModule],
}); });
}); });
@ -77,7 +74,7 @@ export function main() {
})); }));
it('should iterate over an array of objects', async(() => { it('should iterate over an array of objects', async(() => {
const template = '<ul><li template="ngFor let item of items">{{item["name"]}};</li></ul>'; const template = '<ul><li *ngFor="let item of items">{{item["name"]}};</li></ul>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
// INIT // INIT
@ -95,7 +92,7 @@ export function main() {
})); }));
it('should gracefully handle nulls', async(() => { it('should gracefully handle nulls', async(() => {
const template = '<ul><li template="ngFor let item of null">{{item}};</li></ul>'; const template = '<ul><li *ngFor="let item of null">{{item}};</li></ul>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
detectChangesAndExpectText(''); detectChangesAndExpectText('');
@ -140,12 +137,8 @@ export function main() {
})); }));
it('should repeat over nested arrays', async(() => { it('should repeat over nested arrays', async(() => {
const template = '<div>' + const template = '<div *ngFor="let item of items">' +
'<div template="ngFor let item of items">' + '<div *ngFor="let subitem of item">{{subitem}}-{{item.length}};</div>|' +
'<div template="ngFor let subitem of item">' +
'{{subitem}}-{{item.length}};' +
'</div>|' +
'</div>' +
'</div>'; '</div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
@ -157,10 +150,9 @@ export function main() {
})); }));
it('should repeat over nested arrays with no intermediate element', async(() => { it('should repeat over nested arrays with no intermediate element', async(() => {
const template = '<div><template ngFor let-item [ngForOf]="items">' + const template = '<div *ngFor="let item of items">' +
'<div template="ngFor let subitem of item">' + '<div *ngFor="let subitem of item">{{subitem}}-{{item.length}};</div>' +
'{{subitem}}-{{item.length}};' + '</div>';
'</div></template></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().items = [['a', 'b'], ['c']]; getComponent().items = [['a', 'b'], ['c']];
@ -170,10 +162,11 @@ export function main() {
detectChangesAndExpectText('e-1;f-2;g-2;'); detectChangesAndExpectText('e-1;f-2;g-2;');
})); }));
it('should repeat over nested ngIf that are the last node in the ngFor temlate', async(() => { it('should repeat over nested ngIf that are the last node in the ngFor template', async(() => {
const template = const template = `<div *ngFor="let item of items; let i=index">` +
`<div><template ngFor let-item [ngForOf]="items" let-i="index"><div>{{i}}|</div>` + `<div>{{i}}|</div>` +
`<div *ngIf="i % 2 == 0">even|</div></template></div>`; `<div *ngIf="i % 2 == 0">even|</div>` +
`</div>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
@ -189,8 +182,7 @@ export function main() {
})); }));
it('should display indices correctly', async(() => { it('should display indices correctly', async(() => {
const template = const template = '<span *ngFor ="let item of items; let i=index">{{i.toString()}}</span>';
'<div><span template="ngFor: let item of items; let i=index">{{i.toString()}}</span></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; getComponent().items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
@ -202,7 +194,7 @@ export function main() {
it('should display first item correctly', async(() => { it('should display first item correctly', async(() => {
const template = const template =
'<div><span template="ngFor: let item of items; let isFirst=first">{{isFirst.toString()}}</span></div>'; '<span *ngFor="let item of items; let isFirst=first">{{isFirst.toString()}}</span>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().items = [0, 1, 2]; getComponent().items = [0, 1, 2];
@ -214,7 +206,7 @@ export function main() {
it('should display last item correctly', async(() => { it('should display last item correctly', async(() => {
const template = const template =
'<div><span template="ngFor: let item of items; let isLast=last">{{isLast.toString()}}</span></div>'; '<span *ngFor="let item of items; let isLast=last">{{isLast.toString()}}</span>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().items = [0, 1, 2]; getComponent().items = [0, 1, 2];
@ -226,7 +218,7 @@ export function main() {
it('should display even items correctly', async(() => { it('should display even items correctly', async(() => {
const template = const template =
'<div><span template="ngFor: let item of items; let isEven=even">{{isEven.toString()}}</span></div>'; '<span *ngFor="let item of items; let isEven=even">{{isEven.toString()}}</span>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().items = [0, 1, 2]; getComponent().items = [0, 1, 2];
@ -238,7 +230,7 @@ export function main() {
it('should display odd items correctly', async(() => { it('should display odd items correctly', async(() => {
const template = const template =
'<div><span template="ngFor: let item of items; let isOdd=odd">{{isOdd.toString()}}</span></div>'; '<span *ngFor="let item of items; let isOdd=odd">{{isOdd.toString()}}</span>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().items = [0, 1, 2, 3]; getComponent().items = [0, 1, 2, 3];
@ -249,55 +241,38 @@ export function main() {
})); }));
it('should allow to use a custom template', async(() => { it('should allow to use a custom template', async(() => {
const tcTemplate = const template =
'<ul><template ngFor [ngForOf]="items" [ngForTemplate]="contentTpl"></template></ul>'; '<ng-container *ngFor="let item of items; template: tpl"></ng-container>' +
TestBed.overrideComponent(TestComponent, {set: {template: tcTemplate}}); '<template let-item let-i="index" #tpl><p>{{i}}: {{item}};</p></template>';
const cutTemplate = fixture = createTestComponent(template);
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>'; getComponent().items = ['a', 'b', 'c'];
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
fixture = TestBed.createComponent(ComponentUsingTestComponent);
const testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges(); fixture.detectChanges();
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;'); detectChangesAndExpectText('0: a;1: b;2: c;');
})); }));
it('should use a default template if a custom one is null', async(() => { it('should use a default template if a custom one is null', async(() => {
const testTemplate = `<ul><template ngFor let-item [ngForOf]="items" const template =
[ngForTemplate]="contentTpl" let-i="index">{{i}}: {{item}};</template></ul>`; `<ul><ng-container *ngFor="let item of items; template: null; let i=index">{{i}}: {{item}};</ng-container></ul>`;
TestBed.overrideComponent(TestComponent, {set: {template: testTemplate}}); fixture = createTestComponent(template);
const cutTemplate = getComponent().items = ['a', 'b', 'c'];
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>';
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
fixture = TestBed.createComponent(ComponentUsingTestComponent);
const testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges(); fixture.detectChanges();
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;'); detectChangesAndExpectText('0: a;1: b;2: c;');
})); }));
it('should use a custom template when both default and a custom one are present', async(() => { it('should use a custom template when both default and a custom one are present', async(() => {
const testTemplate = `<ul><template ngFor let-item [ngForOf]="items" const template =
[ngForTemplate]="contentTpl" let-i="index">{{i}}=> {{item}};</template></ul>`; '<ng-container *ngFor="let item of items; template: tpl">{{i}};</ng-container>' +
TestBed.overrideComponent(TestComponent, {set: {template: testTemplate}}); '<template let-item let-i="index" #tpl>{{i}}: {{item}};</template>';
const cutTemplate = fixture = createTestComponent(template);
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>'; getComponent().items = ['a', 'b', 'c'];
TestBed.overrideComponent(ComponentUsingTestComponent, {set: {template: cutTemplate}});
fixture = TestBed.createComponent(ComponentUsingTestComponent);
const testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges(); fixture.detectChanges();
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;'); detectChangesAndExpectText('0: a;1: b;2: c;');
})); }));
describe('track by', () => { describe('track by', () => {
it('should console.warn if trackBy is not a function', async(() => { it('should console.warn if trackBy is not a function', async(() => {
// TODO(vicb): expect a warning message when we have a proper log service // TODO(vicb): expect a warning message when we have a proper log service
const template = const template = `<p *ngFor="let item of items; trackBy: value"></p>`;
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="value"></template>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.componentInstance.value = 0; fixture.componentInstance.value = 0;
fixture.detectChanges(); fixture.detectChanges();
@ -305,8 +280,7 @@ export function main() {
it('should track by identity when trackBy is to `null` or `undefined`', async(() => { it('should track by identity when trackBy is to `null` or `undefined`', async(() => {
// TODO(vicb): expect no warning message when we have a proper log service // TODO(vicb): expect no warning message when we have a proper log service
const template = const template = `<p *ngFor="let item of items; trackBy: value">{{ item }}</p>`;
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="value">{{ item }}</template>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.componentInstance.items = ['a', 'b', 'c']; fixture.componentInstance.items = ['a', 'b', 'c'];
fixture.componentInstance.value = null; fixture.componentInstance.value = null;
@ -317,7 +291,7 @@ export function main() {
it('should set the context to the component instance', async(() => { it('should set the context to the component instance', async(() => {
const template = const template =
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByContext.bind(this)"></template>`; `<p *ngFor="let item of items; trackBy: trackByContext.bind(this)"></p>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
thisArg = null; thisArg = null;
@ -327,9 +301,7 @@ export function main() {
it('should not replace tracked items', async(() => { it('should not replace tracked items', async(() => {
const template = const template =
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById" let-i="index"> `<p *ngFor="let item of items; trackBy: trackById; let i=index">{{items[i]}}</p>`;
<p>{{items[i]}}</p>
</template>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
const buildItemList = () => { const buildItemList = () => {
@ -345,7 +317,7 @@ export function main() {
it('should update implicit local variable on view', async(() => { it('should update implicit local variable on view', async(() => {
const template = const template =
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`; `<div *ngFor="let item of items; trackBy: trackById">{{item['color']}}</div>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().items = [{'id': 'a', 'color': 'blue'}]; getComponent().items = [{'id': 'a', 'color': 'blue'}];
@ -357,7 +329,7 @@ export function main() {
it('should move items around and keep them updated ', async(() => { it('should move items around and keep them updated ', async(() => {
const template = const template =
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`; `<div *ngFor="let item of items; trackBy: trackById">{{item['color']}}</div>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().items = [{'id': 'a', 'color': 'blue'}, {'id': 'b', 'color': 'yellow'}]; getComponent().items = [{'id': 'a', 'color': 'blue'}, {'id': 'b', 'color': 'yellow'}];
@ -368,8 +340,7 @@ export function main() {
})); }));
it('should handle added and removed items properly when tracking by index', async(() => { it('should handle added and removed items properly when tracking by index', async(() => {
const template = const template = `<div *ngFor="let item of items; trackBy: trackByIndex">{{item}}</div>`;
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByIndex">{{item}}</template></div>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().items = ['a', 'b', 'c', 'd']; getComponent().items = ['a', 'b', 'c', 'd'];
@ -389,7 +360,6 @@ class Foo {
@Component({selector: 'test-cmp', template: ''}) @Component({selector: 'test-cmp', template: ''})
class TestComponent { class TestComponent {
@ContentChild(TemplateRef) contentTpl: TemplateRef<Object>;
value: any; value: any;
items: any[] = [1, 2]; items: any[] = [1, 2];
trackById(index: number, item: any): string { return item['id']; } trackById(index: number, item: any): string { return item['id']; }
@ -397,12 +367,7 @@ class TestComponent {
trackByContext(): void { thisArg = this; } trackByContext(): void { thisArg = this; }
} }
@Component({selector: 'outer-cmp', template: ''}) const TEMPLATE = '<div><span *ngFor="let item of items">{{item.toString()}};</span></div>';
class ComponentUsingTestComponent {
items: any = [1, 2];
}
const TEMPLATE = '<div><span template="ngFor let item of items">{{item.toString()}};</span></div>';
function createTestComponent(template: string = TEMPLATE): ComponentFixture<TestComponent> { function createTestComponent(template: string = TEMPLATE): ComponentFixture<TestComponent> {
return TestBed.overrideComponent(TestComponent, {set: {template: template}}) return TestBed.overrideComponent(TestComponent, {set: {template: template}})

View File

@ -9,6 +9,7 @@
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {Component} from '@angular/core'; import {Component} from '@angular/core';
import {ComponentFixture, TestBed, async} from '@angular/core/testing'; import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter'; import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
@ -28,131 +29,114 @@ export function main() {
}); });
it('should work in a template attribute', async(() => { it('should work in a template attribute', async(() => {
const template = '<div><span template="ngIf booleanCondition">hello</span></div>'; const template = '<span *ngIf="booleanCondition">hello</span>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(fixture.nativeElement).toHaveText('hello'); expect(fixture.nativeElement).toHaveText('hello');
})); }));
it('should work in a template element', async(() => { it('should work on a template element', async(() => {
const template = const template = '<template [ngIf]="booleanCondition">hello2</template>';
'<div><template [ngIf]="booleanCondition"><span>hello2</span></template></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
expect(fixture.nativeElement).toHaveText('hello2'); expect(fixture.nativeElement).toHaveText('hello2');
})); }));
it('should toggle node when condition changes', async(() => { it('should toggle node when condition changes', async(() => {
const template = '<div><span template="ngIf booleanCondition">hello</span></div>'; const template = '<span *ngIf="booleanCondition">hello</span>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().booleanCondition = false; getComponent().booleanCondition = false;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
expect(fixture.nativeElement).toHaveText(''); expect(fixture.nativeElement).toHaveText('');
getComponent().booleanCondition = true; getComponent().booleanCondition = true;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(fixture.nativeElement).toHaveText('hello'); expect(fixture.nativeElement).toHaveText('hello');
getComponent().booleanCondition = false; getComponent().booleanCondition = false;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
expect(fixture.nativeElement).toHaveText(''); expect(fixture.nativeElement).toHaveText('');
})); }));
it('should handle nested if correctly', async(() => { it('should handle nested if correctly', async(() => {
const template = const template =
'<div><template [ngIf]="booleanCondition"><span *ngIf="nestedBooleanCondition">hello</span></template></div>'; '<div *ngIf="booleanCondition"><span *ngIf="nestedBooleanCondition">hello</span></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().booleanCondition = false; getComponent().booleanCondition = false;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
expect(fixture.nativeElement).toHaveText(''); expect(fixture.nativeElement).toHaveText('');
getComponent().booleanCondition = true; getComponent().booleanCondition = true;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(fixture.nativeElement).toHaveText('hello'); expect(fixture.nativeElement).toHaveText('hello');
getComponent().nestedBooleanCondition = false; getComponent().nestedBooleanCondition = false;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
expect(fixture.nativeElement).toHaveText(''); expect(fixture.nativeElement).toHaveText('');
getComponent().nestedBooleanCondition = true; getComponent().nestedBooleanCondition = true;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(fixture.nativeElement).toHaveText('hello'); expect(fixture.nativeElement).toHaveText('hello');
getComponent().booleanCondition = false; getComponent().booleanCondition = false;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(0); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(0);
expect(fixture.nativeElement).toHaveText(''); expect(fixture.nativeElement).toHaveText('');
})); }));
it('should update several nodes with if', async(() => { it('should update several nodes with if', async(() => {
const template = '<div>' + const template = '<span *ngIf="numberCondition + 1 >= 2">helloNumber</span>' +
'<span template="ngIf numberCondition + 1 >= 2">helloNumber</span>' + '<span *ngIf="stringCondition == \'foo\'">helloString</span>' +
'<span template="ngIf stringCondition == \'foo\'">helloString</span>' + '<span *ngIf="functionCondition(stringCondition, numberCondition)">helloFunction</span>';
'<span template="ngIf functionCondition(stringCondition, numberCondition)">helloFunction</span>' +
'</div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(3); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(3);
expect(getDOM().getText(fixture.nativeElement)) expect(getDOM().getText(fixture.nativeElement))
.toEqual('helloNumberhelloStringhelloFunction'); .toEqual('helloNumberhelloStringhelloFunction');
getComponent().numberCondition = 0; getComponent().numberCondition = 0;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(fixture.nativeElement).toHaveText('helloString'); expect(fixture.nativeElement).toHaveText('helloString');
getComponent().numberCondition = 1; getComponent().numberCondition = 1;
getComponent().stringCondition = 'bar'; getComponent().stringCondition = 'bar';
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1); expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(fixture.nativeElement).toHaveText('helloNumber'); expect(fixture.nativeElement).toHaveText('helloNumber');
})); }));
it('should not add the element twice if the condition goes from true to true (JS)', it('should not add the element twice if the condition goes from truthy to truthy', async(() => {
async(() => { const template = '<span *ngIf="numberCondition">hello</span>';
const template = '<div><span template="ngIf numberCondition">hello</span></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1); let els = fixture.debugElement.queryAll(By.css('span'));
expect(els.length).toEqual(1);
getDOM().addClass(els[0].nativeElement, 'marker');
expect(fixture.nativeElement).toHaveText('hello'); expect(fixture.nativeElement).toHaveText('hello');
getComponent().numberCondition = 2; getComponent().numberCondition = 2;
fixture.detectChanges(); fixture.detectChanges();
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1); els = fixture.debugElement.queryAll(By.css('span'));
expect(els.length).toEqual(1);
expect(getDOM().hasClass(els[0].nativeElement, 'marker')).toBe(true);
expect(fixture.nativeElement).toHaveText('hello'); expect(fixture.nativeElement).toHaveText('hello');
})); }));
it('should not recreate the element if the condition goes from true to true (JS)', async(() => {
const template = '<div><span template="ngIf numberCondition">hello</span></div>';
fixture = createTestComponent(template);
fixture.detectChanges();
getDOM().addClass(getDOM().querySelector(fixture.nativeElement, 'span'), 'foo');
getComponent().numberCondition = 2;
fixture.detectChanges();
expect(getDOM().hasClass(getDOM().querySelector(fixture.nativeElement, 'span'), 'foo'))
.toBe(true);
}));
}); });
} }

View File

@ -12,7 +12,7 @@ import {ComponentFixture, TestBed, async} from '@angular/core/testing';
import {expect} from '@angular/platform-browser/testing/matchers'; import {expect} from '@angular/platform-browser/testing/matchers';
export function main() { export function main() {
describe('switch', () => { describe('ngPlural', () => {
let fixture: ComponentFixture<any>; let fixture: ComponentFixture<any>;
function getComponent(): TestComponent { return fixture.componentInstance; } function getComponent(): TestComponent { return fixture.componentInstance; }
@ -33,10 +33,25 @@ export function main() {
}); });
it('should display the template according to the exact value', async(() => { it('should display the template according to the exact value', async(() => {
const template = '<div>' + const template = '<ul [ngPlural]="switchValue">' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="=0"><li>you have no messages.</li></template>' + '<template ngPluralCase="=0"><li>you have no messages.</li></template>' +
'<template ngPluralCase="=1"><li>you have one message.</li></template>' + '<template ngPluralCase="=1"><li>you have one message.</li></template>' +
'</ul>';
fixture = createTestComponent(template);
getComponent().switchValue = 0;
detectChangesAndExpectText('you have no messages.');
getComponent().switchValue = 1;
detectChangesAndExpectText('you have one message.');
}));
it('should display the template according to the exact numeric value', async(() => {
const template = '<div>' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="0"><li>you have no messages.</li></template>' +
'<template ngPluralCase="1"><li>you have one message.</li></template>' +
'</ul></div>'; '</ul></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
@ -51,10 +66,9 @@ export function main() {
// https://github.com/angular/angular/issues/9868 // https://github.com/angular/angular/issues/9868
// https://github.com/angular/angular/issues/9882 // https://github.com/angular/angular/issues/9882
it('should not throw when ngPluralCase contains expressions', async(() => { it('should not throw when ngPluralCase contains expressions', async(() => {
const template = '<div>' + const template = '<ul [ngPlural]="switchValue">' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="=0"><li>{{ switchValue }}</li></template>' + '<template ngPluralCase="=0"><li>{{ switchValue }}</li></template>' +
'</ul></div>'; '</ul>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
@ -64,11 +78,10 @@ export function main() {
it('should be applicable to <ng-container> elements', async(() => { it('should be applicable to <ng-container> elements', async(() => {
const template = '<div>' + const template = '<ng-container [ngPlural]="switchValue">' +
'<ng-container [ngPlural]="switchValue">' +
'<template ngPluralCase="=0">you have no messages.</template>' + '<template ngPluralCase="=0">you have no messages.</template>' +
'<template ngPluralCase="=1">you have one message.</template>' + '<template ngPluralCase="=1">you have one message.</template>' +
'</ng-container></div>'; '</ng-container>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
@ -80,11 +93,10 @@ export function main() {
})); }));
it('should display the template according to the category', async(() => { it('should display the template according to the category', async(() => {
const template = '<div>' + const template = '<ul [ngPlural]="switchValue">' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' + '<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="many"><li>you have many messages.</li></template>' + '<template ngPluralCase="many"><li>you have many messages.</li></template>' +
'</ul></div>'; '</ul>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
@ -96,11 +108,10 @@ export function main() {
})); }));
it('should default to other when no matches are found', async(() => { it('should default to other when no matches are found', async(() => {
const template = '<div>' + const template = '<ul [ngPlural]="switchValue">' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' + '<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="other"><li>default message.</li></template>' + '<template ngPluralCase="other"><li>default message.</li></template>' +
'</ul></div>'; '</ul>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
@ -109,11 +120,10 @@ export function main() {
})); }));
it('should prioritize value matches over category matches', async(() => { it('should prioritize value matches over category matches', async(() => {
const template = '<div>' + const template = '<ul [ngPlural]="switchValue">' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' + '<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="=2">you have two messages.</template>' + '<template ngPluralCase="=2">you have two messages.</template>' +
'</ul></div>'; '</ul>';
fixture = createTestComponent(template); fixture = createTestComponent(template);

View File

@ -29,22 +29,19 @@ export function main() {
it('should add styles specified in an object literal', async(() => { it('should add styles specified in an object literal', async(() => {
const template = `<div [ngStyle]="{'max-width': '40px'}"></div>`; const template = `<div [ngStyle]="{'max-width': '40px'}"></div>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.detectChanges(); fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'}); expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
})); }));
it('should add and change styles specified in an object expression', async(() => { it('should add and change styles specified in an object expression', async(() => {
const template = `<div [ngStyle]="expr"></div>`; const template = `<div [ngStyle]="expr"></div>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
let expr: {[k: string]: string};
getComponent().expr = {'max-width': '40px'}; getComponent().expr = {'max-width': '40px'};
fixture.detectChanges(); fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'}); expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
expr = getComponent().expr; let expr = getComponent().expr;
expr['max-width'] = '30%'; expr['max-width'] = '30%';
fixture.detectChanges(); fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '30%'}); expectNativeEl(fixture).toHaveCssStyle({'max-width': '30%'});

View File

@ -33,11 +33,10 @@ export function main() {
describe('switch value changes', () => { describe('switch value changes', () => {
it('should switch amongst when values', () => { it('should switch amongst when values', () => {
const template = '<div>' + const template = '<ul [ngSwitch]="switchValue">' +
'<ul [ngSwitch]="switchValue">' + '<li *ngSwitchCase="\'a\'">when a</li>' +
'<template ngSwitchCase="a"><li>when a</li></template>' + '<li *ngSwitchCase="\'b\'">when b</li>' +
'<template ngSwitchCase="b"><li>when b</li></template>' + '</ul>';
'</ul></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
@ -51,11 +50,10 @@ export function main() {
}); });
it('should switch amongst when values with fallback to default', () => { it('should switch amongst when values with fallback to default', () => {
const template = '<div>' + const template = '<ul [ngSwitch]="switchValue">' +
'<ul [ngSwitch]="switchValue">' + '<li *ngSwitchCase="\'a\'">when a</li>' +
'<li template="ngSwitchCase \'a\'">when a</li>' + '<li *ngSwitchDefault>when default</li>' +
'<li template="ngSwitchDefault">when default</li>' + '</ul>';
'</ul></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
detectChangesAndExpectText('when default'); detectChangesAndExpectText('when default');
@ -71,15 +69,14 @@ export function main() {
}); });
it('should support multiple whens with the same value', () => { it('should support multiple whens with the same value', () => {
const template = '<div>' + const template = '<ul [ngSwitch]="switchValue">' +
'<ul [ngSwitch]="switchValue">' + '<li *ngSwitchCase="\'a\'">when a1;</li>' +
'<template ngSwitchCase="a"><li>when a1;</li></template>' + '<li *ngSwitchCase="\'b\'">when b1;</li>' +
'<template ngSwitchCase="b"><li>when b1;</li></template>' + '<li *ngSwitchCase="\'a\'">when a2;</li>' +
'<template ngSwitchCase="a"><li>when a2;</li></template>' + '<li *ngSwitchCase="\'b\'">when b2;</li>' +
'<template ngSwitchCase="b"><li>when b2;</li></template>' + '<li *ngSwitchDefault>when default1;</li>' +
'<template ngSwitchDefault><li>when default1;</li></template>' + '<li *ngSwitchDefault>when default2;</li>' +
'<template ngSwitchDefault><li>when default2;</li></template>' + '</ul>';
'</ul></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
detectChangesAndExpectText('when default1;when default2;'); detectChangesAndExpectText('when default1;when default2;');
@ -94,12 +91,11 @@ export function main() {
describe('when values changes', () => { describe('when values changes', () => {
it('should switch amongst when values', () => { it('should switch amongst when values', () => {
const template = '<div>' + const template = '<ul [ngSwitch]="switchValue">' +
'<ul [ngSwitch]="switchValue">' + '<li *ngSwitchCase="when1">when 1;</li>' +
'<template [ngSwitchCase]="when1"><li>when 1;</li></template>' + '<li *ngSwitchCase="when2">when 2;</li>' +
'<template [ngSwitchCase]="when2"><li>when 2;</li></template>' + '<li *ngSwitchDefault>when default;</li>' +
'<template ngSwitchDefault><li>when default;</li></template>' + '</ul>';
'</ul></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
getComponent().when1 = 'a'; getComponent().when1 = 'a';
@ -148,11 +144,10 @@ export function main() {
}); });
it('should create the default case if there is no other case', () => { it('should create the default case if there is no other case', () => {
const template = '<div>' + const template = '<ul [ngSwitch]="switchValue">' +
'<ul [ngSwitch]="switchValue">' + '<li *ngSwitchDefault>when default1;</li>' +
'<template ngSwitchDefault><li>when default1;</li></template>' + '<li *ngSwitchDefault>when default2;</li>' +
'<template ngSwitchDefault><li>when default2;</li></template>' + '</ul>';
'</ul></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
detectChangesAndExpectText('when default1;when default2;'); detectChangesAndExpectText('when default1;when default2;');
@ -160,15 +155,14 @@ export function main() {
}); });
it('should allow defaults before cases', () => { it('should allow defaults before cases', () => {
const template = '<div>' + const template = '<ul [ngSwitch]="switchValue">' +
'<ul [ngSwitch]="switchValue">' + '<li *ngSwitchDefault>when default1;</li>' +
'<template ngSwitchDefault><li>when default1;</li></template>' + '<li *ngSwitchDefault>when default2;</li>' +
'<template ngSwitchDefault><li>when default2;</li></template>' + '<li *ngSwitchCase="\'a\'">when a1;</li>' +
'<template ngSwitchCase="a"><li>when a1;</li></template>' + '<li *ngSwitchCase="\'b\'">when b1;</li>' +
'<template ngSwitchCase="b"><li>when b1;</li></template>' + '<li *ngSwitchCase="\'a\'">when a2;</li>' +
'<template ngSwitchCase="a"><li>when a2;</li></template>' + '<li *ngSwitchCase="\'b\'">when b2;</li>' +
'<template ngSwitchCase="b"><li>when b2;</li></template>' + '</ul>';
'</ul></div>';
fixture = createTestComponent(template); fixture = createTestComponent(template);
detectChangesAndExpectText('when default1;when default2;'); detectChangesAndExpectText('when default1;when default2;');

View File

@ -34,29 +34,22 @@ export function main() {
}); });
}); });
it('should do nothing if templateRef is null', async(() => { it('should do nothing if templateRef is `null`', async(() => {
const template = `<template [ngTemplateOutlet]="null"></template>`; const template = `<ng-container [ngTemplateOutlet]="null"></ng-container>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
detectChangesAndExpectText(''); detectChangesAndExpectText('');
})); }));
it('should insert content specified by TemplateRef', async(() => { it('should insert content specified by TemplateRef', async(() => {
const template = const template = `<template #tpl>foo</template>` +
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`; `<ng-container [ngTemplateOutlet]="tpl"></ng-container>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
detectChangesAndExpectText('');
const refs = fixture.debugElement.children[0].references['refs'];
setTplRef(refs.tplRefs.first);
detectChangesAndExpectText('foo'); detectChangesAndExpectText('foo');
})); }));
it('should clear content if TemplateRef becomes null', async(() => { it('should clear content if TemplateRef becomes `null`', async(() => {
const template = const template = `<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs>` +
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`; `<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.detectChanges(); fixture.detectChanges();
const refs = fixture.debugElement.children[0].references['refs']; const refs = fixture.debugElement.children[0].references['refs'];
@ -70,7 +63,8 @@ export function main() {
it('should swap content if TemplateRef changes', async(() => { it('should swap content if TemplateRef changes', async(() => {
const template = const template =
`<tpl-refs #refs="tplRefs"><template>foo</template><template>bar</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`; `<tpl-refs #refs="tplRefs"><template>foo</template><template>bar</template></tpl-refs>` +
`<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.detectChanges(); fixture.detectChanges();
@ -83,70 +77,47 @@ export function main() {
detectChangesAndExpectText('bar'); detectChangesAndExpectText('bar');
})); }));
it('should display template if context is null', async(() => { it('should display template if context is `null`', async(() => {
const template = const template = `<template #tpl>foo</template>` +
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="null"></template>`; `<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="null"></ng-container>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
detectChangesAndExpectText('');
const refs = fixture.debugElement.children[0].references['refs'];
setTplRef(refs.tplRefs.first);
detectChangesAndExpectText('foo'); detectChangesAndExpectText('foo');
})); }));
it('should reflect initial context and changes', async(() => { it('should reflect initial context and changes', async(() => {
const template = const template = `<template let-foo="foo" #tpl>{{foo}}</template>` +
`<tpl-refs #refs="tplRefs"><template let-foo="foo"><span>{{foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`; `<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.detectChanges(); fixture.detectChanges();
const refs = fixture.debugElement.children[0].references['refs'];
setTplRef(refs.tplRefs.first);
detectChangesAndExpectText('bar'); detectChangesAndExpectText('bar');
fixture.componentInstance.context.foo = 'alter-bar'; fixture.componentInstance.context.foo = 'alter-bar';
detectChangesAndExpectText('alter-bar'); detectChangesAndExpectText('alter-bar');
})); }));
it('should reflect user defined $implicit property in the context', async(() => { it('should reflect user defined `$implicit` property in the context', async(() => {
const template = const template = `<template let-ctx #tpl>{{ctx.foo}}</template>` +
`<tpl-refs #refs="tplRefs"><template let-ctx><span>{{ctx.foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`; `<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.componentInstance.context = {$implicit: {foo: 'bra'}};
fixture.detectChanges(); detectChangesAndExpectText('bra');
const refs = fixture.debugElement.children[0].references['refs'];
setTplRef(refs.tplRefs.first);
fixture.componentInstance.context = {$implicit: fixture.componentInstance.context};
detectChangesAndExpectText('bar');
})); }));
it('should reflect context re-binding', async(() => { it('should reflect context re-binding', async(() => {
const template = const template = `<template let-shawshank="shawshank" #tpl>{{shawshank}}</template>` +
`<tpl-refs #refs="tplRefs"><template let-shawshank="shawshank"><span>{{shawshank}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`; `<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
fixture = createTestComponent(template); fixture = createTestComponent(template);
fixture.detectChanges();
const refs = fixture.debugElement.children[0].references['refs'];
setTplRef(refs.tplRefs.first);
fixture.componentInstance.context = {shawshank: 'brooks'}; fixture.componentInstance.context = {shawshank: 'brooks'};
detectChangesAndExpectText('brooks'); detectChangesAndExpectText('brooks');
fixture.componentInstance.context = {shawshank: 'was here'}; fixture.componentInstance.context = {shawshank: 'was here'};
detectChangesAndExpectText('was here'); detectChangesAndExpectText('was here');
})); }));
}); });
} }
@Directive({selector: 'tpl-refs', exportAs: 'tplRefs'}) @Directive({selector: 'tpl-refs', exportAs: 'tplRefs'})
class CaptureTplRefs { class CaptureTplRefs {
@ContentChildren(TemplateRef) tplRefs: QueryList<TemplateRef<any>>; @ContentChildren(TemplateRef) tplRefs: QueryList<TemplateRef<any>>;

View File

@ -0,0 +1,23 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Component, NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
@Component({selector: 'lazy-feature-comp', template: 'lazy feature, nested!'})
export class LazyFeatureNestedComponent {
}
@NgModule({
imports: [RouterModule.forChild([
{path: '', component: LazyFeatureNestedComponent, pathMatch: 'full'},
])],
declarations: [LazyFeatureNestedComponent]
})
export class LazyFeatureNestedModule {
}

View File

@ -16,7 +16,10 @@ export class LazyFeatureComponent {
@NgModule({ @NgModule({
imports: [RouterModule.forChild([ imports: [RouterModule.forChild([
{path: '', component: LazyFeatureComponent, pathMatch: 'full'}, {path: '', component: LazyFeatureComponent, pathMatch: 'full'},
{path: 'feature', loadChildren: './feature.module#FeatureModule'} {path: 'feature', loadChildren: './feature.module#FeatureModule'}, {
path: 'nested-feature',
loadChildren: './lazy-feature-nested.module#LazyFeatureNestedModule'
}
])], ])],
declarations: [LazyFeatureComponent] declarations: [LazyFeatureComponent]
}) })

View File

@ -191,6 +191,8 @@ function lazyRoutesTest() {
'./lazy.module#LazyModule': 'lazy.module.ts', './lazy.module#LazyModule': 'lazy.module.ts',
'./feature/feature.module#FeatureModule': 'feature/feature.module.ts', './feature/feature.module#FeatureModule': 'feature/feature.module.ts',
'./feature/lazy-feature.module#LazyFeatureModule': 'feature/lazy-feature.module.ts', './feature/lazy-feature.module#LazyFeatureModule': 'feature/lazy-feature.module.ts',
'./feature.module#FeatureModule': 'feature/feature.module.ts',
'./lazy-feature-nested.module#LazyFeatureNestedModule': 'feature/lazy-feature-nested.module.ts',
'feature2/feature2.module#Feature2Module': 'feature2/feature2.module.ts', 'feature2/feature2.module#Feature2Module': 'feature2/feature2.module.ts',
'./default.module': 'feature2/default.module.ts', './default.module': 'feature2/default.module.ts',
'feature/feature.module#FeatureModule': 'feature/feature.module.ts' 'feature/feature.module#FeatureModule': 'feature/feature.module.ts'

View File

@ -9,7 +9,7 @@
"ng-xi18n": "./src/extract_i18n.js" "ng-xi18n": "./src/extract_i18n.js"
}, },
"dependencies": { "dependencies": {
"@angular/tsc-wrapped": "0.5.0", "@angular/tsc-wrapped": "0.5.1",
"reflect-metadata": "^0.1.2", "reflect-metadata": "^0.1.2",
"minimist": "^1.2.0" "minimist": "^1.2.0"
}, },

View File

@ -36,29 +36,6 @@ export class CodeGenerator {
public host: ts.CompilerHost, private compiler: compiler.AotCompiler, public host: ts.CompilerHost, private compiler: compiler.AotCompiler,
private ngCompilerHost: CompilerHost) {} private ngCompilerHost: CompilerHost) {}
// Write codegen in a directory structure matching the sources.
private calculateEmitPath(filePath: string): string {
let root = this.options.basePath;
for (const eachRootDir of this.options.rootDirs || []) {
if (this.options.trace) {
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
}
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
root = eachRootDir;
}
}
// transplant the codegen path to be inside the `genDir`
let relativePath: string = path.relative(root, filePath);
while (relativePath.startsWith('..' + path.sep)) {
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
// into `genDir`.
relativePath = relativePath.substr(3);
}
return path.join(this.options.genDir, relativePath);
}
codegen(): Promise<any> { codegen(): Promise<any> {
return this.compiler return this.compiler
.compileAll(this.program.getSourceFiles().map( .compileAll(this.program.getSourceFiles().map(
@ -66,7 +43,7 @@ export class CodeGenerator {
.then(generatedModules => { .then(generatedModules => {
generatedModules.forEach(generatedModule => { generatedModules.forEach(generatedModule => {
const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl); const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl);
const emitPath = this.calculateEmitPath(generatedModule.genFileUrl); const emitPath = this.ngCompilerHost.calculateEmitPath(generatedModule.genFileUrl);
const source = GENERATED_META_FILES.test(emitPath) ? generatedModule.source : const source = GENERATED_META_FILES.test(emitPath) ? generatedModule.source :
PREAMBLE + generatedModule.source; PREAMBLE + generatedModule.source;
this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]); this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]);

View File

@ -261,6 +261,29 @@ export class CompilerHost implements AotCompilerHost {
this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES; this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
return !excludeRegex.test(filePath); return !excludeRegex.test(filePath);
} }
calculateEmitPath(filePath: string): string {
// Write codegen in a directory structure matching the sources.
let root = this.options.basePath;
for (const eachRootDir of this.options.rootDirs || []) {
if (this.options.trace) {
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
}
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
root = eachRootDir;
}
}
// transplant the codegen path to be inside the `genDir`
let relativePath: string = path.relative(root, filePath);
while (relativePath.startsWith('..' + path.sep)) {
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
// into `genDir`.
relativePath = relativePath.substr(3);
}
return path.join(this.options.genDir, relativePath);
}
} }
export class CompilerHostContextAdapter { export class CompilerHostContextAdapter {

View File

@ -15,8 +15,6 @@
import {AotCompilerHost, StaticReflector, StaticSymbol} from '@angular/compiler'; import {AotCompilerHost, StaticReflector, StaticSymbol} from '@angular/compiler';
import {NgModule} from '@angular/core'; import {NgModule} from '@angular/core';
// We cannot depend directly to @angular/router. // We cannot depend directly to @angular/router.
type Route = any; type Route = any;
const ROUTER_MODULE_PATH = '@angular/router/src/router_config_loader'; const ROUTER_MODULE_PATH = '@angular/router/src/router_config_loader';
@ -63,29 +61,37 @@ export function listLazyRoutesOfModule(
const className = entryRouteDef.className; const className = entryRouteDef.className;
// List loadChildren of this single module. // List loadChildren of this single module.
const staticSymbol = reflector.findDeclaration(modulePath, className, containingFile); const appStaticSymbol = reflector.findDeclaration(modulePath, className, containingFile);
const ROUTES = reflector.findDeclaration(ROUTER_MODULE_PATH, ROUTER_ROUTES_SYMBOL_NAME); const ROUTES = reflector.findDeclaration(ROUTER_MODULE_PATH, ROUTER_ROUTES_SYMBOL_NAME);
const lazyRoutes: LazyRoute[] = const lazyRoutes: LazyRoute[] =
_extractLazyRoutesFromStaticModule(staticSymbol, reflector, host, ROUTES); _extractLazyRoutesFromStaticModule(appStaticSymbol, reflector, host, ROUTES);
const routes: LazyRouteMap = {};
lazyRoutes.forEach((lazyRoute: LazyRoute) => { const allLazyRoutes = lazyRoutes.reduce(
const route: string = lazyRoute.routeDef.toString(); function includeLazyRouteAndSubRoutes(allRoutes: LazyRouteMap, lazyRoute: LazyRoute):
_assertRoute(routes, lazyRoute); LazyRouteMap {
routes[route] = lazyRoute; const route: string = lazyRoute.routeDef.toString();
_assertRoute(allRoutes, lazyRoute);
allRoutes[route] = lazyRoute;
const lazyModuleSymbol = reflector.findDeclaration( // StaticReflector does not support discovering annotations like `NgModule` on default
lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default'); // exports
const subRoutes = _extractLazyRoutesFromStaticModule(lazyModuleSymbol, reflector, host, ROUTES); // Which means: if a default export NgModule was lazy-loaded, we can discover it, but,
// we cannot parse its routes to see if they have loadChildren or not.
if (!lazyRoute.routeDef.className) {
return allRoutes;
}
// Populate the map using the routes we just found. const lazyModuleSymbol = reflector.findDeclaration(
subRoutes.forEach(subRoute => { lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default');
_assertRoute(routes, subRoute);
routes[subRoute.routeDef.toString()] = subRoute;
});
});
return routes; const subRoutes =
_extractLazyRoutesFromStaticModule(lazyModuleSymbol, reflector, host, ROUTES);
return subRoutes.reduce(includeLazyRouteAndSubRoutes, allRoutes);
},
{});
return allLazyRoutes;
} }
@ -192,7 +198,7 @@ function _collectRoutes(
*/ */
function _collectLoadChildren(routes: Route[]): string[] { function _collectLoadChildren(routes: Route[]): string[] {
return routes.reduce((m, r) => { return routes.reduce((m, r) => {
if (r.loadChildren) { if (r.loadChildren && typeof r.loadChildren === 'string') {
return m.concat(r.loadChildren); return m.concat(r.loadChildren);
} else if (Array.isArray(r)) { } else if (Array.isArray(r)) {
return m.concat(_collectLoadChildren(r)); return m.concat(_collectLoadChildren(r));

View File

@ -6,8 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {SchemaMetadata} from '@angular/core';
import {AnimationCompiler} from '../animation/animation_compiler'; import {AnimationCompiler} from '../animation/animation_compiler';
import {AnimationParser} from '../animation/animation_parser'; import {AnimationParser} from '../animation/animation_parser';
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileTypeSummary, createHostComponentMeta, identifierModuleUrl, identifierName} from '../compile_metadata'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileTypeSummary, createHostComponentMeta, identifierModuleUrl, identifierName} from '../compile_metadata';

View File

@ -10,8 +10,8 @@ import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, Ho
import {ReflectorReader} from '../private_import_core'; import {ReflectorReader} from '../private_import_core';
import {StaticSymbol, StaticSymbolCache} from './static_symbol'; import {StaticSymbol} from './static_symbol';
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver'; import {StaticSymbolResolver} from './static_symbol_resolver';
const ANGULAR_IMPORT_LOCATIONS = { const ANGULAR_IMPORT_LOCATIONS = {
coreDecorators: '@angular/core/src/metadata', coreDecorators: '@angular/core/src/metadata',
@ -310,6 +310,7 @@ export class StaticReflector implements ReflectorReader {
if (value && (depth != 0 || value.__symbolic != 'error')) { if (value && (depth != 0 || value.__symbolic != 'error')) {
const parameters: string[] = targetFunction['parameters']; const parameters: string[] = targetFunction['parameters'];
const defaults: any[] = targetFunction.defaults; const defaults: any[] = targetFunction.defaults;
args = args.map(arg => simplifyInContext(context, arg, depth + 1));
if (defaults && defaults.length > args.length) { if (defaults && defaults.length > args.length) {
args.push(...defaults.slice(args.length).map((value: any) => simplify(value))); args.push(...defaults.slice(args.length).map((value: any) => simplify(value)));
} }
@ -499,15 +500,15 @@ export class StaticReflector implements ReflectorReader {
return context; return context;
} }
const argExpressions: any[] = expression['arguments'] || []; const argExpressions: any[] = expression['arguments'] || [];
const args =
argExpressions.map(arg => simplifyInContext(context, arg, depth + 1));
let converter = self.conversionMap.get(staticSymbol); let converter = self.conversionMap.get(staticSymbol);
if (converter) { if (converter) {
const args =
argExpressions.map(arg => simplifyInContext(context, arg, depth + 1));
return converter(context, args); return converter(context, args);
} else { } else {
// Determine if the function is one we can simplify. // Determine if the function is one we can simplify.
const targetFunction = resolveReferenceValue(staticSymbol); const targetFunction = resolveReferenceValue(staticSymbol);
return simplifyCall(staticSymbol, targetFunction, args); return simplifyCall(staticSymbol, targetFunction, argExpressions);
} }
} }
break; break;
@ -651,10 +652,6 @@ class PopulatedScope extends BindingScope {
} }
} }
function sameSymbol(a: StaticSymbol, b: StaticSymbol): boolean {
return a === b;
}
function shouldIgnore(value: any): boolean { function shouldIgnore(value: any): boolean {
return value && value.__symbolic == 'ignore'; return value && value.__symbolic == 'ignore';
} }

View File

@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {CompileSummaryKind, CompileTypeSummary} from '../compile_metadata';
import {Summary, SummaryResolver} from '../summary_resolver'; import {Summary, SummaryResolver} from '../summary_resolver';
import {StaticSymbol, StaticSymbolCache} from './static_symbol'; import {StaticSymbol, StaticSymbolCache} from './static_symbol';

View File

@ -5,16 +5,16 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleSummary, CompilePipeSummary, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, identifierModuleUrl, identifierName} from '../compile_metadata'; import {CompileNgModuleSummary, CompileSummaryKind, CompileTypeSummary} from '../compile_metadata';
import {Summary, SummaryResolver} from '../summary_resolver'; import {Summary, SummaryResolver} from '../summary_resolver';
import {ValueTransformer, visitValue} from '../util'; import {ValueTransformer, visitValue} from '../util';
import {GeneratedFile} from './generated_file';
import {StaticSymbol, StaticSymbolCache} from './static_symbol'; import {StaticSymbol, StaticSymbolCache} from './static_symbol';
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver'; import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
export interface AotSummarySerializerHost { export interface AotSummarySerializerHost {
/** /**
* Returns the output file path of a source file. * Returns the output file path of a source file.

View File

@ -5,7 +5,6 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Identifiers, createIdentifier} from '../identifiers'; import {Identifiers, createIdentifier} from '../identifiers';
import {ClassBuilder} from '../output/class_builder'; import {ClassBuilder} from '../output/class_builder';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';

View File

@ -8,7 +8,7 @@
import * as cdAst from '../expression_parser/ast'; import * as cdAst from '../expression_parser/ast';
import {isBlank, isPresent} from '../facade/lang'; import {isBlank} from '../facade/lang';
import {Identifiers, createIdentifier} from '../identifiers'; import {Identifiers, createIdentifier} from '../identifiers';
import {ClassBuilder} from '../output/class_builder'; import {ClassBuilder} from '../output/class_builder';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
@ -338,7 +338,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
const receiver = this.visit(ast.receiver, _Mode.Expression); const receiver = this.visit(ast.receiver, _Mode.Expression);
if (receiver === this._implicitReceiver) { if (receiver === this._implicitReceiver) {
const varExpr = this._getLocal(ast.name); const varExpr = this._getLocal(ast.name);
if (isPresent(varExpr)) { if (varExpr) {
result = varExpr.callFn(args); result = varExpr.callFn(args);
} }
} }
@ -374,7 +374,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
const receiver: o.Expression = this.visit(ast.receiver, _Mode.Expression); const receiver: o.Expression = this.visit(ast.receiver, _Mode.Expression);
if (receiver === this._implicitReceiver) { if (receiver === this._implicitReceiver) {
const varExpr = this._getLocal(ast.name); const varExpr = this._getLocal(ast.name);
if (isPresent(varExpr)) { if (varExpr) {
throw new Error('Cannot assign to a reference or variable!'); throw new Error('Cannot assign to a reference or variable!');
} }
} }

View File

@ -8,7 +8,7 @@
import {Component, ViewEncapsulation} from '@angular/core'; import {Component, ViewEncapsulation} from '@angular/core';
import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from './compile_metadata'; import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata} from './compile_metadata';
import {CompilerConfig} from './config'; import {CompilerConfig} from './config';
import {isBlank, isPresent, stringify} from './facade/lang'; import {isBlank, isPresent, stringify} from './facade/lang';
import {CompilerInjectable} from './injectable'; import {CompilerInjectable} from './injectable';

View File

@ -14,8 +14,6 @@ import {CompilerInjectable} from './injectable';
import {ReflectorReader, reflector} from './private_import_core'; import {ReflectorReader, reflector} from './private_import_core';
import {splitAtColon} from './util'; import {splitAtColon} from './util';
/* /*
* Resolve a `Type` for {@link Directive}. * Resolve a `Type` for {@link Directive}.
* *

View File

@ -128,7 +128,6 @@ class DirectiveWrapperBuilder implements ClassBuilder {
new o.ClassMethod('ngOnDestroy', [], this.destroyStmts), new o.ClassMethod('ngOnDestroy', [], this.destroyStmts),
]; ];
const fields: o.ClassField[] = [ const fields: o.ClassField[] = [
new o.ClassField(EVENT_HANDLER_FIELD_NAME, o.FUNCTION_TYPE, [o.StmtModifier.Private]), new o.ClassField(EVENT_HANDLER_FIELD_NAME, o.FUNCTION_TYPE, [o.StmtModifier.Private]),
new o.ClassField(CONTEXT_FIELD_NAME, o.importType(this.dirMeta.type)), new o.ClassField(CONTEXT_FIELD_NAME, o.importType(this.dirMeta.type)),

View File

@ -7,7 +7,7 @@
*/ */
import * as chars from '../chars'; import * as chars from '../chars';
import {NumberWrapper, isPresent} from '../facade/lang'; import {NumberWrapper} from '../facade/lang';
import {CompilerInjectable} from '../injectable'; import {CompilerInjectable} from '../injectable';
export enum TokenType { export enum TokenType {
@ -241,7 +241,7 @@ class _Scanner {
this.advance(); this.advance();
str += two; str += two;
} }
if (isPresent(threeCode) && this.peek == threeCode) { if (threeCode != null && this.peek == threeCode) {
this.advance(); this.advance();
str += three; str += three;
} }

View File

@ -53,20 +53,22 @@ enum _VisitorMode {
* @internal * @internal
*/ */
class _Visitor implements html.Visitor { class _Visitor implements html.Visitor {
private _depth: number;
// <el i18n>...</el> // <el i18n>...</el>
private _inI18nNode: boolean; private _inI18nNode: boolean;
private _depth: number;
private _inImplicitNode: boolean; private _inImplicitNode: boolean;
// <!--i18n-->...<!--/i18n--> // <!--i18n-->...<!--/i18n-->
private _inI18nBlock: boolean;
private _blockMeaningAndDesc: string; private _blockMeaningAndDesc: string;
private _blockChildren: html.Node[]; private _blockChildren: html.Node[];
private _blockStartDepth: number; private _blockStartDepth: number;
private _inI18nBlock: boolean;
// {<icu message>} // {<icu message>}
private _inIcu: boolean; private _inIcu: boolean;
// set to void 0 when not in a section
private _msgCountAtSectionStart: number; private _msgCountAtSectionStart: number;
private _errors: I18nError[]; private _errors: I18nError[];
private _mode: _VisitorMode; private _mode: _VisitorMode;
@ -208,50 +210,31 @@ class _Visitor implements html.Visitor {
this._depth++; this._depth++;
const wasInI18nNode = this._inI18nNode; const wasInI18nNode = this._inI18nNode;
const wasInImplicitNode = this._inImplicitNode; const wasInImplicitNode = this._inImplicitNode;
let childNodes: html.Node[]; let childNodes: html.Node[] = [];
let translatedChildNodes: html.Node[];
// Extract only top level nodes with the (implicit) "i18n" attribute if not in a block or an ICU // Extract:
// message // - top level nodes with the (implicit) "i18n" attribute if not already in a section
// - ICU messages
const i18nAttr = _getI18nAttr(el); const i18nAttr = _getI18nAttr(el);
const i18nMeta = i18nAttr ? i18nAttr.value : '';
const isImplicit = this._implicitTags.some(tag => el.name === tag) && !this._inIcu && const isImplicit = this._implicitTags.some(tag => el.name === tag) && !this._inIcu &&
!this._isInTranslatableSection; !this._isInTranslatableSection;
const isTopLevelImplicit = !wasInImplicitNode && isImplicit; const isTopLevelImplicit = !wasInImplicitNode && isImplicit;
this._inImplicitNode = this._inImplicitNode || isImplicit; this._inImplicitNode = wasInImplicitNode || isImplicit;
if (!this._isInTranslatableSection && !this._inIcu) { if (!this._isInTranslatableSection && !this._inIcu) {
if (i18nAttr) { if (i18nAttr || isTopLevelImplicit) {
// explicit translation
this._inI18nNode = true; this._inI18nNode = true;
const message = this._addMessage(el.children, i18nAttr.value); const message = this._addMessage(el.children, i18nMeta);
childNodes = this._translateMessage(el, message); translatedChildNodes = this._translateMessage(el, message);
} else if (isTopLevelImplicit) {
// implicit translation
this._inI18nNode = true;
const message = this._addMessage(el.children);
childNodes = this._translateMessage(el, message);
} }
if (this._mode == _VisitorMode.Extract) { if (this._mode == _VisitorMode.Extract) {
const isTranslatable = i18nAttr || isTopLevelImplicit; const isTranslatable = i18nAttr || isTopLevelImplicit;
if (isTranslatable) { if (isTranslatable) this._openTranslatableSection(el);
this._openTranslatableSection(el);
}
html.visitAll(this, el.children); html.visitAll(this, el.children);
if (isTranslatable) { if (isTranslatable) this._closeTranslatableSection(el, el.children);
this._closeTranslatableSection(el, el.children);
}
}
if (this._mode === _VisitorMode.Merge && !i18nAttr && !isTopLevelImplicit) {
childNodes = [];
el.children.forEach(child => {
const visited = child.visit(this, context);
if (visited && !this._isInTranslatableSection) {
// Do not add the children from translatable sections (= i18n blocks here)
// They will be added when the section is close (i.e. on `<!-- /i18n -->`)
childNodes = childNodes.concat(visited);
}
});
} }
} else { } else {
if (i18nAttr || isTopLevelImplicit) { if (i18nAttr || isTopLevelImplicit) {
@ -263,19 +246,18 @@ class _Visitor implements html.Visitor {
// Descend into child nodes for extraction // Descend into child nodes for extraction
html.visitAll(this, el.children); html.visitAll(this, el.children);
} }
}
if (this._mode == _VisitorMode.Merge) { if (this._mode === _VisitorMode.Merge) {
// Translate attributes in ICU messages const visitNodes = translatedChildNodes || el.children;
childNodes = []; visitNodes.forEach(child => {
el.children.forEach(child => { const visited = child.visit(this, context);
const visited = child.visit(this, context); if (visited && !this._isInTranslatableSection) {
if (visited && !this._isInTranslatableSection) { // Do not add the children from translatable sections (= i18n blocks here)
// Do not add the children from translatable sections (= i18n blocks here) // They will be added later in this loop when the block closes (i.e. on `<!-- /i18n -->`)
// They will be added when the section is close (i.e. on `<!-- /i18n -->`) childNodes = childNodes.concat(visited);
childNodes = childNodes.concat(visited); }
} });
});
}
} }
this._visitAttributesOf(el); this._visitAttributesOf(el);
@ -285,7 +267,6 @@ class _Visitor implements html.Visitor {
this._inImplicitNode = wasInImplicitNode; this._inImplicitNode = wasInImplicitNode;
if (this._mode === _VisitorMode.Merge) { if (this._mode === _VisitorMode.Merge) {
// There are no childNodes in translatable sections - those nodes will be replace anyway
const translatedAttrs = this._translateAttributes(el); const translatedAttrs = this._translateAttributes(el);
return new html.Element( return new html.Element(
el.name, translatedAttrs, childNodes, el.sourceSpan, el.startSourceSpan, el.name, translatedAttrs, childNodes, el.sourceSpan, el.startSourceSpan,
@ -432,7 +413,7 @@ class _Visitor implements html.Visitor {
/** /**
* A translatable section could be: * A translatable section could be:
* - a translatable element, * - the content of translatable element,
* - nodes between `<!-- i18n -->` and `<!-- /i18n -->` comments * - nodes between `<!-- i18n -->` and `<!-- /i18n -->` comments
*/ */
private get _isInTranslatableSection(): boolean { private get _isInTranslatableSection(): boolean {

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ANALYZE_FOR_ENTRY_COMPONENTS, AnimationTransitionEvent, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core'; import {ANALYZE_FOR_ENTRY_COMPONENTS, ChangeDetectionStrategy, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, ElementRef, Injector, LOCALE_ID, NgModuleFactory, QueryList, RenderComponentType, Renderer, SecurityContext, SimpleChange, TRANSLATIONS_FORMAT, TemplateRef, ViewContainerRef, ViewEncapsulation} from '@angular/core';
import {StaticSymbol} from './aot/static_symbol'; import {StaticSymbol} from './aot/static_symbol';
import {CompileIdentifierMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName} from './compile_metadata'; import {CompileIdentifierMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName} from './compile_metadata';

View File

@ -283,7 +283,7 @@ class _TreeBuilder {
const tagDef = this.getTagDefinition(el.name); const tagDef = this.getTagDefinition(el.name);
const {parent, container} = this._getParentElementSkippingContainers(); const {parent, container} = this._getParentElementSkippingContainers();
if (isPresent(parent) && tagDef.requireExtraParent(parent.name)) { if (parent && tagDef.requireExtraParent(parent.name)) {
const newParent = new html.Element( const newParent = new html.Element(
tagDef.parentToAdd, [], [], el.sourceSpan, el.startSourceSpan, el.endSourceSpan); tagDef.parentToAdd, [], [], el.sourceSpan, el.startSourceSpan, el.endSourceSpan);
this._insertBeforeContainer(parent, container, newParent); this._insertBeforeContainer(parent, container, newParent);

View File

@ -9,7 +9,7 @@
import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenName, tokenReference} from './compile_metadata'; import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenName, tokenReference} from './compile_metadata';
import {createDiTokenExpression} from './compiler_util/identifier_util'; import {createDiTokenExpression} from './compiler_util/identifier_util';
import {isPresent} from './facade/lang'; import {isPresent} from './facade/lang';
import {Identifiers, createIdentifier, createIdentifierToken, resolveIdentifier} from './identifiers'; import {Identifiers, createIdentifier, resolveIdentifier} from './identifiers';
import {CompilerInjectable} from './injectable'; import {CompilerInjectable} from './injectable';
import {ClassBuilder, createClassStmt} from './output/class_builder'; import {ClassBuilder, createClassStmt} from './output/class_builder';
import * as o from './output/output_ast'; import * as o from './output/output_ast';

View File

@ -9,7 +9,7 @@
import {NgModule, Type} from '@angular/core'; import {NgModule, Type} from '@angular/core';
import {ListWrapper} from './facade/collection'; import {ListWrapper} from './facade/collection';
import {isPresent, stringify} from './facade/lang'; import {stringify} from './facade/lang';
import {CompilerInjectable} from './injectable'; import {CompilerInjectable} from './injectable';
import {ReflectorReader, reflector} from './private_import_core'; import {ReflectorReader, reflector} from './private_import_core';
@ -30,7 +30,7 @@ export class NgModuleResolver {
const ngModuleMeta: NgModule = const ngModuleMeta: NgModule =
ListWrapper.findLast(this._reflector.annotations(type), _isNgModuleMetadata); ListWrapper.findLast(this._reflector.annotations(type), _isNgModuleMetadata);
if (isPresent(ngModuleMeta)) { if (ngModuleMeta) {
return ngModuleMeta; return ngModuleMeta;
} else { } else {
if (throwIfNotFound) { if (throwIfNotFound) {

View File

@ -7,7 +7,6 @@
*/ */
import {CompileIdentifierMetadata} from '../compile_metadata';
import {ValueTransformer, visitValue} from '../util'; import {ValueTransformer, visitValue} from '../util';
import * as o from './output_ast'; import * as o from './output_ast';

View File

@ -9,7 +9,7 @@
import {Pipe, Type, resolveForwardRef} from '@angular/core'; import {Pipe, Type, resolveForwardRef} from '@angular/core';
import {ListWrapper} from './facade/collection'; import {ListWrapper} from './facade/collection';
import {isPresent, stringify} from './facade/lang'; import {stringify} from './facade/lang';
import {CompilerInjectable} from './injectable'; import {CompilerInjectable} from './injectable';
import {ReflectorReader, reflector} from './private_import_core'; import {ReflectorReader, reflector} from './private_import_core';
@ -38,9 +38,9 @@ export class PipeResolver {
*/ */
resolve(type: Type<any>, throwIfNotFound = true): Pipe { resolve(type: Type<any>, throwIfNotFound = true): Pipe {
const metas = this._reflector.annotations(resolveForwardRef(type)); const metas = this._reflector.annotations(resolveForwardRef(type));
if (isPresent(metas)) { if (metas) {
const annotation = ListWrapper.findLast(metas, _isPipeMetadata); const annotation = ListWrapper.findLast(metas, _isPipeMetadata);
if (isPresent(annotation)) { if (annotation) {
return annotation; return annotation;
} }
} }

View File

@ -9,7 +9,7 @@
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenName, tokenReference} from './compile_metadata'; import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenName, tokenReference} from './compile_metadata';
import {isBlank, isPresent} from './facade/lang'; import {isBlank, isPresent} from './facade/lang';
import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers'; import {Identifiers, resolveIdentifier} from './identifiers';
import {ParseError, ParseSourceSpan} from './parse_util'; import {ParseError, ParseSourceSpan} from './parse_util';
import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, ReferenceAst} from './template_parser/template_ast'; import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, ReferenceAst} from './template_parser/template_ast';
@ -114,7 +114,7 @@ export class ProviderElementContext {
let queries: CompileQueryMetadata[]; let queries: CompileQueryMetadata[];
while (currentEl !== null) { while (currentEl !== null) {
queries = currentEl._contentQueries.get(tokenReference(token)); queries = currentEl._contentQueries.get(tokenReference(token));
if (isPresent(queries)) { if (queries) {
result.push(...queries.filter((query) => query.descendants || distance <= 1)); result.push(...queries.filter((query) => query.descendants || distance <= 1));
} }
if (currentEl._directiveAsts.length > 0) { if (currentEl._directiveAsts.length > 0) {
@ -123,7 +123,7 @@ export class ProviderElementContext {
currentEl = currentEl._parent; currentEl = currentEl._parent;
} }
queries = this.viewContext.viewQueries.get(tokenReference(token)); queries = this.viewContext.viewQueries.get(tokenReference(token));
if (isPresent(queries)) { if (queries) {
result.push(...queries); result.push(...queries);
} }
return result; return result;
@ -143,7 +143,7 @@ export class ProviderElementContext {
return null; return null;
} }
let transformedProviderAst = this._transformedProviders.get(tokenReference(token)); let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
if (isPresent(transformedProviderAst)) { if (transformedProviderAst) {
return transformedProviderAst; return transformedProviderAst;
} }
if (isPresent(this._seenProviders.get(tokenReference(token)))) { if (isPresent(this._seenProviders.get(tokenReference(token)))) {
@ -165,11 +165,11 @@ export class ProviderElementContext {
transformedUseExisting = null; transformedUseExisting = null;
transformedUseValue = existingDiDep.value; transformedUseValue = existingDiDep.value;
} }
} else if (isPresent(provider.useFactory)) { } else if (provider.useFactory) {
const deps = provider.deps || provider.useFactory.diDeps; const deps = provider.deps || provider.useFactory.diDeps;
transformedDeps = transformedDeps =
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager)); deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
} else if (isPresent(provider.useClass)) { } else if (provider.useClass) {
const deps = provider.deps || provider.useClass.diDeps; const deps = provider.deps || provider.useClass.diDeps;
transformedDeps = transformedDeps =
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager)); deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
@ -235,7 +235,7 @@ export class ProviderElementContext {
} }
} else { } else {
// check parent elements // check parent elements
while (!result && isPresent(currElement._parent)) { while (!result && currElement._parent) {
const prevElement = currElement; const prevElement = currElement;
currElement = currElement._parent; currElement = currElement._parent;
if (prevElement._isViewRoot) { if (prevElement._isViewRoot) {
@ -301,7 +301,7 @@ export class NgModuleProviderAnalyzer {
return null; return null;
} }
let transformedProviderAst = this._transformedProviders.get(tokenReference(token)); let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
if (isPresent(transformedProviderAst)) { if (transformedProviderAst) {
return transformedProviderAst; return transformedProviderAst;
} }
if (isPresent(this._seenProviders.get(tokenReference(token)))) { if (isPresent(this._seenProviders.get(tokenReference(token)))) {
@ -324,11 +324,11 @@ export class NgModuleProviderAnalyzer {
transformedUseExisting = null; transformedUseExisting = null;
transformedUseValue = existingDiDep.value; transformedUseValue = existingDiDep.value;
} }
} else if (isPresent(provider.useFactory)) { } else if (provider.useFactory) {
const deps = provider.deps || provider.useFactory.diDeps; const deps = provider.deps || provider.useFactory.diDeps;
transformedDeps = transformedDeps =
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan)); deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
} else if (isPresent(provider.useClass)) { } else if (provider.useClass) {
const deps = provider.deps || provider.useClass.diDeps; const deps = provider.deps || provider.useClass.diDeps;
transformedDeps = transformedDeps =
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan)); deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
@ -454,7 +454,7 @@ function _resolveProviders(
function _getViewQueries(component: CompileDirectiveMetadata): Map<any, CompileQueryMetadata[]> { function _getViewQueries(component: CompileDirectiveMetadata): Map<any, CompileQueryMetadata[]> {
const viewQueries = new Map<any, CompileQueryMetadata[]>(); const viewQueries = new Map<any, CompileQueryMetadata[]>();
if (isPresent(component.viewQueries)) { if (component.viewQueries) {
component.viewQueries.forEach((query) => _addQueryToTokenMap(viewQueries, query)); component.viewQueries.forEach((query) => _addQueryToTokenMap(viewQueries, query));
} }
return viewQueries; return viewQueries;
@ -464,7 +464,7 @@ function _getContentQueries(directives: CompileDirectiveSummary[]):
Map<any, CompileQueryMetadata[]> { Map<any, CompileQueryMetadata[]> {
const contentQueries = new Map<any, CompileQueryMetadata[]>(); const contentQueries = new Map<any, CompileQueryMetadata[]>();
directives.forEach(directive => { directives.forEach(directive => {
if (isPresent(directive.queries)) { if (directive.queries) {
directive.queries.forEach((query) => _addQueryToTokenMap(contentQueries, query)); directive.queries.forEach((query) => _addQueryToTokenMap(contentQueries, query));
} }
}); });

View File

@ -11,7 +11,6 @@ import {SecurityContext} from '@angular/core';
import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata'; import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata';
import {ASTWithSource, BindingPipe, EmptyExpr, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast'; import {ASTWithSource, BindingPipe, EmptyExpr, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
import {Parser} from '../expression_parser/parser'; import {Parser} from '../expression_parser/parser';
import {isPresent} from '../facade/lang';
import {InterpolationConfig} from '../ml_parser/interpolation_config'; import {InterpolationConfig} from '../ml_parser/interpolation_config';
import {mergeNsAndName} from '../ml_parser/tags'; import {mergeNsAndName} from '../ml_parser/tags';
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util'; import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
@ -111,14 +110,14 @@ export class BindingParser {
} }
parseInlineTemplateBinding( parseInlineTemplateBinding(
name: string, prefixToken: string, value: string, sourceSpan: ParseSourceSpan, prefixToken: string, value: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetProps: BoundProperty[], targetVars: VariableAst[]) { targetMatchableAttrs: string[][], targetProps: BoundProperty[], targetVars: VariableAst[]) {
const bindings = this._parseTemplateBindings(prefixToken, value, sourceSpan); const bindings = this._parseTemplateBindings(prefixToken, value, sourceSpan);
for (let i = 0; i < bindings.length; i++) { for (let i = 0; i < bindings.length; i++) {
const binding = bindings[i]; const binding = bindings[i];
if (binding.keyIsVar) { if (binding.keyIsVar) {
targetVars.push(new VariableAst(binding.key, binding.name, sourceSpan)); targetVars.push(new VariableAst(binding.key, binding.name, sourceSpan));
} else if (isPresent(binding.expression)) { } else if (binding.expression) {
this._parsePropertyAst( this._parsePropertyAst(
binding.key, binding.expression, sourceSpan, targetMatchableAttrs, targetProps); binding.key, binding.expression, sourceSpan, targetMatchableAttrs, targetProps);
} else { } else {
@ -136,7 +135,7 @@ export class BindingParser {
const bindingsResult = this._exprParser.parseTemplateBindings(prefixToken, value, sourceInfo); const bindingsResult = this._exprParser.parseTemplateBindings(prefixToken, value, sourceInfo);
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan); this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
bindingsResult.templateBindings.forEach((binding) => { bindingsResult.templateBindings.forEach((binding) => {
if (isPresent(binding.expression)) { if (binding.expression) {
this._checkPipes(binding.expression, sourceSpan); this._checkPipes(binding.expression, sourceSpan);
} }
}); });
@ -193,7 +192,7 @@ export class BindingParser {
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][], name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
targetProps: BoundProperty[]): boolean { targetProps: BoundProperty[]): boolean {
const expr = this.parseInterpolation(value, sourceSpan); const expr = this.parseInterpolation(value, sourceSpan);
if (isPresent(expr)) { if (expr) {
this._parsePropertyAst(name, expr, sourceSpan, targetMatchableAttrs, targetProps); this._parsePropertyAst(name, expr, sourceSpan, targetMatchableAttrs, targetProps);
return true; return true;
} }
@ -374,7 +373,7 @@ export class BindingParser {
} }
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) { private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) {
if (isPresent(ast)) { if (ast) {
const collector = new PipeCollector(); const collector = new PipeCollector();
ast.visit(collector); ast.visit(collector);
collector.pipes.forEach((ast, pipeName) => { collector.pipes.forEach((ast, pipeName) => {

View File

@ -276,7 +276,7 @@ class TemplateParseVisitor implements html.Visitor {
templateBindingsSource = attr.value; templateBindingsSource = attr.value;
} else if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) { } else if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
templateBindingsSource = attr.value; templateBindingsSource = attr.value;
prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length); prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length) + ':';
} }
const hasTemplateBinding = isPresent(templateBindingsSource); const hasTemplateBinding = isPresent(templateBindingsSource);
@ -288,7 +288,7 @@ class TemplateParseVisitor implements html.Visitor {
} }
hasInlineTemplates = true; hasInlineTemplates = true;
this._bindingParser.parseInlineTemplateBinding( this._bindingParser.parseInlineTemplateBinding(
attr.name, prefixToken, templateBindingsSource, attr.sourceSpan, templateMatchableAttrs, prefixToken, templateBindingsSource, attr.sourceSpan, templateMatchableAttrs,
templateElementOrDirectiveProps, templateElementVars); templateElementOrDirectiveProps, templateElementVars);
} }
@ -309,9 +309,11 @@ class TemplateParseVisitor implements html.Visitor {
const elementProps: BoundElementPropertyAst[] = const elementProps: BoundElementPropertyAst[] =
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts); this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts);
const isViewRoot = parent.isTemplateElement || hasInlineTemplates; const isViewRoot = parent.isTemplateElement || hasInlineTemplates;
const providerContext = new ProviderElementContext( const providerContext = new ProviderElementContext(
this.providerViewContext, parent.providerContext, isViewRoot, directiveAsts, attrs, this.providerViewContext, parent.providerContext, isViewRoot, directiveAsts, attrs,
references, element.sourceSpan); references, element.sourceSpan);
const children = html.visitAll( const children = html.visitAll(
preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children, preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children,
ElementContext.create( ElementContext.create(

View File

@ -19,7 +19,7 @@ import {ProviderAst, ProviderAstType, ReferenceAst, TemplateAst} from '../templa
import {CompileMethod} from './compile_method'; import {CompileMethod} from './compile_method';
import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query'; import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query';
import {CompileView, CompileViewRootNode} from './compile_view'; import {CompileView, CompileViewRootNode} from './compile_view';
import {InjectMethodVars, ViewProperties} from './constants'; import {InjectMethodVars} from './constants';
import {ComponentFactoryDependency, DirectiveWrapperDependency} from './deps'; import {ComponentFactoryDependency, DirectiveWrapperDependency} from './deps';
import {getPropertyInView, injectFromViewParentInjector} from './util'; import {getPropertyInView, injectFromViewParentInjector} from './util';
@ -195,7 +195,7 @@ export class CompileElement extends CompileNode {
const propName = const propName =
`_${tokenName(resolvedProvider.token)}_${this.nodeIndex}_${this.instances.size}`; `_${tokenName(resolvedProvider.token)}_${this.nodeIndex}_${this.instances.size}`;
const instance = createProviderProperty( const instance = createProviderProperty(
propName, resolvedProvider, providerValueExpressions, resolvedProvider.multiProvider, propName, providerValueExpressions, resolvedProvider.multiProvider,
resolvedProvider.eager, this); resolvedProvider.eager, this);
if (isDirectiveWrapper) { if (isDirectiveWrapper) {
this.directiveWrapperInstance.set(tokenReference(resolvedProvider.token), instance); this.directiveWrapperInstance.set(tokenReference(resolvedProvider.token), instance);
@ -289,7 +289,7 @@ export class CompileElement extends CompileNode {
CompileQuery { CompileQuery {
const propName = const propName =
`_query_${tokenName(queryMeta.selectors[0])}_${this.nodeIndex}_${this._queryCount++}`; `_query_${tokenName(queryMeta.selectors[0])}_${this.nodeIndex}_${this._queryCount++}`;
const queryList = createQueryList(queryMeta, directiveInstance, propName, this.view); const queryList = createQueryList(propName, this.view);
const query = new CompileQuery(queryMeta, queryList, directiveInstance, this.view); const query = new CompileQuery(queryMeta, queryList, directiveInstance, this.view);
addQueryToTokenMap(this._queries, query); addQueryToTokenMap(this._queries, query);
return query; return query;
@ -369,8 +369,8 @@ function createInjectInternalCondition(
} }
function createProviderProperty( function createProviderProperty(
propName: string, provider: ProviderAst, providerValueExpressions: o.Expression[], propName: string, providerValueExpressions: o.Expression[], isMulti: boolean, isEager: boolean,
isMulti: boolean, isEager: boolean, compileElement: CompileElement): o.Expression { compileElement: CompileElement): o.Expression {
const view = compileElement.view; const view = compileElement.view;
let resolvedProviderValueExpr: o.Expression; let resolvedProviderValueExpr: o.Expression;
let type: o.Type; let type: o.Type;

View File

@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {isPresent} from '../facade/lang';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {TemplateAst} from '../template_parser/template_ast'; import {TemplateAst} from '../template_parser/template_ast';
@ -34,7 +33,7 @@ export class CompileMethod {
if (this._newState.nodeIndex !== this._currState.nodeIndex || if (this._newState.nodeIndex !== this._currState.nodeIndex ||
this._newState.sourceAst !== this._currState.sourceAst) { this._newState.sourceAst !== this._currState.sourceAst) {
const expr = this._updateDebugContext(this._newState); const expr = this._updateDebugContext(this._newState);
if (isPresent(expr)) { if (expr) {
this._bodyStatements.push(expr.toStmt()); this._bodyStatements.push(expr.toStmt());
} }
} }
@ -43,13 +42,12 @@ export class CompileMethod {
private _updateDebugContext(newState: _DebugState): o.Expression { private _updateDebugContext(newState: _DebugState): o.Expression {
this._currState = this._newState = newState; this._currState = this._newState = newState;
if (this._debugEnabled) { if (this._debugEnabled) {
const sourceLocation = const sourceLocation = newState.sourceAst ? newState.sourceAst.sourceSpan.start : null;
isPresent(newState.sourceAst) ? newState.sourceAst.sourceSpan.start : null;
return o.THIS_EXPR.callMethod('debug', [ return o.THIS_EXPR.callMethod('debug', [
o.literal(newState.nodeIndex), o.literal(newState.nodeIndex),
isPresent(sourceLocation) ? o.literal(sourceLocation.line) : o.NULL_EXPR, sourceLocation ? o.literal(sourceLocation.line) : o.NULL_EXPR,
isPresent(sourceLocation) ? o.literal(sourceLocation.col) : o.NULL_EXPR sourceLocation ? o.literal(sourceLocation.col) : o.NULL_EXPR
]); ]);
} else { } else {
return null; return null;

View File

@ -8,7 +8,6 @@
import {CompileQueryMetadata, tokenReference} from '../compile_metadata'; import {CompileQueryMetadata, tokenReference} from '../compile_metadata';
import {ListWrapper} from '../facade/collection'; import {ListWrapper} from '../facade/collection';
import {isPresent} from '../facade/lang';
import {Identifiers, createIdentifier} from '../identifiers'; import {Identifiers, createIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
@ -33,7 +32,7 @@ export class CompileQuery {
addValue(value: o.Expression, view: CompileView) { addValue(value: o.Expression, view: CompileView) {
let currentView = view; let currentView = view;
const elPath: CompileElement[] = []; const elPath: CompileElement[] = [];
while (isPresent(currentView) && currentView !== this.view) { while (currentView && currentView !== this.view) {
const parentEl = currentView.declarationElement; const parentEl = currentView.declarationElement;
elPath.unshift(parentEl); elPath.unshift(parentEl);
currentView = parentEl.view; currentView = parentEl.view;
@ -67,7 +66,7 @@ export class CompileQuery {
generateStatements(targetStaticMethod: CompileMethod, targetDynamicMethod: CompileMethod) { generateStatements(targetStaticMethod: CompileMethod, targetDynamicMethod: CompileMethod) {
const values = createQueryValues(this._values); const values = createQueryValues(this._values);
const updateStmts = [this.queryList.callMethod('reset', [o.literalArr(values)]).toStmt()]; const updateStmts = [this.queryList.callMethod('reset', [o.literalArr(values)]).toStmt()];
if (isPresent(this.ownerDirectiveExpression)) { if (this.ownerDirectiveExpression) {
const valueExpr = this.meta.first ? this.queryList.prop('first') : this.queryList; const valueExpr = this.meta.first ? this.queryList.prop('first') : this.queryList;
updateStmts.push( updateStmts.push(
this.ownerDirectiveExpression.prop(this.meta.propertyName).set(valueExpr).toStmt()); this.ownerDirectiveExpression.prop(this.meta.propertyName).set(valueExpr).toStmt());
@ -110,9 +109,7 @@ function mapNestedViews(
]); ]);
} }
export function createQueryList( export function createQueryList(propertyName: string, compileView: CompileView): o.Expression {
query: CompileQueryMetadata, directiveInstance: o.Expression, propertyName: string,
compileView: CompileView): o.Expression {
compileView.fields.push(new o.ClassField( compileView.fields.push(new o.ClassField(
propertyName, o.importType(createIdentifier(Identifiers.QueryList), [o.DYNAMIC_TYPE]))); propertyName, o.importType(createIdentifier(Identifiers.QueryList), [o.DYNAMIC_TYPE])));
const expr = o.THIS_EXPR.prop(propertyName); const expr = o.THIS_EXPR.prop(propertyName);

View File

@ -12,7 +12,6 @@ import {EventHandlerVars, NameResolver} from '../compiler_util/expression_conver
import {createPureProxy} from '../compiler_util/identifier_util'; import {createPureProxy} from '../compiler_util/identifier_util';
import {CompilerConfig} from '../config'; import {CompilerConfig} from '../config';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {Identifiers, createIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {ViewType} from '../private_import_core'; import {ViewType} from '../private_import_core';
@ -119,7 +118,7 @@ export class CompileView implements NameResolver {
const directiveInstance = o.THIS_EXPR.prop('context'); const directiveInstance = o.THIS_EXPR.prop('context');
this.component.viewQueries.forEach((queryMeta, queryIndex) => { this.component.viewQueries.forEach((queryMeta, queryIndex) => {
const propName = `_viewQuery_${tokenName(queryMeta.selectors[0])}_${queryIndex}`; const propName = `_viewQuery_${tokenName(queryMeta.selectors[0])}_${queryIndex}`;
const queryList = createQueryList(queryMeta, directiveInstance, propName, this); const queryList = createQueryList(propName, this);
const query = new CompileQuery(queryMeta, queryList, directiveInstance, this); const query = new CompileQuery(queryMeta, queryList, directiveInstance, this);
addQueryToTokenMap(viewQueries, query); addQueryToTokenMap(viewQueries, query);
}); });

View File

@ -11,7 +11,7 @@ import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core';
import {createEnumExpression} from '../compiler_util/identifier_util'; import {createEnumExpression} from '../compiler_util/identifier_util';
import {Identifiers} from '../identifiers'; import {Identifiers} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {ChangeDetectorStatus, ViewType} from '../private_import_core'; import {ViewType} from '../private_import_core';
export class ViewTypeEnum { export class ViewTypeEnum {
static fromValue(value: ViewType): o.Expression { static fromValue(value: ViewType): o.Expression {
@ -25,12 +25,6 @@ export class ViewEncapsulationEnum {
} }
} }
export class ChangeDetectionStrategyEnum {
static fromValue(value: ChangeDetectionStrategy): o.Expression {
return createEnumExpression(Identifiers.ChangeDetectionStrategy, value);
}
}
export class ChangeDetectorStatusEnum { export class ChangeDetectorStatusEnum {
static fromValue(value: ChangeDetectorStatusEnum): o.Expression { static fromValue(value: ChangeDetectorStatusEnum): o.Expression {
return createEnumExpression(Identifiers.ChangeDetectorStatus, value); return createEnumExpression(Identifiers.ChangeDetectorStatus, value);

View File

@ -15,7 +15,6 @@ import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast';
import {CompileElement} from './compile_element'; import {CompileElement} from './compile_element';
import {CompileMethod} from './compile_method'; import {CompileMethod} from './compile_method';
import {ViewProperties} from './constants';
import {getHandleEventMethodName} from './util'; import {getHandleEventMethodName} from './util';
export function bindOutputs( export function bindOutputs(

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {CompileQueryMetadata, CompileTokenMetadata, tokenReference} from '../compile_metadata'; import {CompileTokenMetadata, tokenReference} from '../compile_metadata';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {CompileElement} from './compile_element'; import {CompileElement} from './compile_element';
@ -17,16 +17,18 @@ import {CompileQuery} from './compile_query';
// as we create embedded views before the <template> elements themselves. // as we create embedded views before the <template> elements themselves.
export function bindQueryValues(ce: CompileElement) { export function bindQueryValues(ce: CompileElement) {
const queriesWithReads: _QueryWithRead[] = []; const queriesWithReads: _QueryWithRead[] = [];
ce.getProviderTokens().forEach((token) => { ce.getProviderTokens().forEach((token) => {
const queriesForProvider = ce.getQueriesFor(token); const queriesForProvider = ce.getQueriesFor(token);
queriesWithReads.push(...queriesForProvider.map(query => new _QueryWithRead(query, token))); queriesWithReads.push(...queriesForProvider.map(query => new _QueryWithRead(query, token)));
}); });
Object.keys(ce.referenceTokens).forEach(varName => { Object.keys(ce.referenceTokens).forEach(varName => {
const token = ce.referenceTokens[varName];
const varToken = {value: varName}; const varToken = {value: varName};
queriesWithReads.push( queriesWithReads.push(
...ce.getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken))); ...ce.getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken)));
}); });
queriesWithReads.forEach((queryWithRead) => { queriesWithReads.forEach((queryWithRead) => {
let value: o.Expression; let value: o.Expression;
if (queryWithRead.read.identifier) { if (queryWithRead.read.identifier) {

View File

@ -9,8 +9,6 @@
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileTokenMetadata, identifierName} from '../compile_metadata'; import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileTokenMetadata, identifierName} from '../compile_metadata';
import {createDiTokenExpression} from '../compiler_util/identifier_util'; import {createDiTokenExpression} from '../compiler_util/identifier_util';
import {isPresent} from '../facade/lang';
import {Identifiers, createIdentifier} from '../identifiers';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {ViewType} from '../private_import_core'; import {ViewType} from '../private_import_core';
@ -23,7 +21,7 @@ export function getPropertyInView(
} else { } else {
let viewProp: o.Expression = o.THIS_EXPR; let viewProp: o.Expression = o.THIS_EXPR;
let currView: CompileView = callingView; let currView: CompileView = callingView;
while (currView !== definedView && isPresent(currView.declarationElement.view)) { while (currView !== definedView && currView.declarationElement.view) {
currView = currView.declarationElement.view; currView = currView.declarationElement.view;
viewProp = viewProp.prop('parentView'); viewProp = viewProp.prop('parentView');
} }

View File

@ -15,7 +15,6 @@ import {isPresent} from '../facade/lang';
import {Identifiers, createIdentifier, identifierToken} from '../identifiers'; import {Identifiers, createIdentifier, identifierToken} from '../identifiers';
import {createClassStmt} from '../output/class_builder'; import {createClassStmt} from '../output/class_builder';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {ParseSourceSpan} from '../parse_util';
import {ChangeDetectorStatus, ViewType, isDefaultChangeDetectionStrategy} from '../private_import_core'; import {ChangeDetectorStatus, ViewType, isDefaultChangeDetectionStrategy} from '../private_import_core';
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast'; import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from '../template_parser/template_ast';
@ -405,8 +404,9 @@ function createViewTopLevelStmts(view: CompileView, targetStatements: o.Statemen
o.literal(view.component.template.ngContentSelectors.length), o.literal(view.component.template.ngContentSelectors.length),
ViewEncapsulationEnum.fromValue(view.component.template.encapsulation), ViewEncapsulationEnum.fromValue(view.component.template.encapsulation),
view.styles, view.styles,
o.literalMap(view.animations.map( o.literalMap(
(entry): [string, o.Expression] => [entry.name, entry.fnExp])), view.animations.map((entry): [string, o.Expression] => [entry.name, entry.fnExp]),
null, true),
])) ]))
.toDeclStmt(o.importType(createIdentifier(Identifiers.RenderComponentType)))); .toDeclStmt(o.importType(createIdentifier(Identifiers.RenderComponentType))));
} }
@ -695,7 +695,6 @@ function generateCreateEmbeddedViewsMethod(view: CompileView): o.ClassMethod {
view.nodes.forEach((node) => { view.nodes.forEach((node) => {
if (node instanceof CompileElement) { if (node instanceof CompileElement) {
if (node.embeddedView) { if (node.embeddedView) {
const parentNodeIndex = node.isRootElement() ? null : node.parent.nodeIndex;
stmts.push(new o.IfStmt( stmts.push(new o.IfStmt(
nodeIndexVar.equals(o.literal(node.nodeIndex)), nodeIndexVar.equals(o.literal(node.nodeIndex)),
[new o.ReturnStatement(node.embeddedView.classExpr.instantiate([ [new o.ReturnStatement(node.embeddedView.classExpr.instantiate([

View File

@ -449,6 +449,40 @@ describe('StaticReflector', () => {
expect(annotations[0].providers[0].useValue.members[0]).toEqual('staticMethod'); expect(annotations[0].providers[0].useValue.members[0]).toEqual('staticMethod');
}); });
// #13605
it('should not throw on unknown decorators', () => {
const data = Object.create(DEFAULT_TEST_DATA);
const file = '/tmp/src/app.component.ts';
data[file] = `
import { Component } from '@angular/core';
export const enum TypeEnum {
type
}
export function MyValidationDecorator(p1: any, p2: any): any {
return null;
}
export function ValidationFunction(a1: any): any {
return null;
}
@Component({
selector: 'my-app',
template: "<h1>Hello {{name}}</h1>",
})
export class AppComponent {
name = 'Angular';
@MyValidationDecorator( TypeEnum.type, ValidationFunction({option: 'value'}))
myClassProp: number;
}`;
init(data);
const appComponent = reflector.getStaticSymbol(file, 'AppComponent');
expect(() => reflector.propMetadata(appComponent)).not.toThrow();
});
describe('inheritance', () => { describe('inheritance', () => {
class ClassDecorator { class ClassDecorator {
constructor(public value: any) {} constructor(public value: any) {}

View File

@ -210,5 +210,56 @@ const XMB = `
<ph name="START_TAG_DIV_1"><ex>&lt;div&gt;</ex></ph><ph name="ICU"/><ph name="CLOSE_TAG_DIV"><ex>&lt;/div&gt;</ex></ph> <ph name="START_TAG_DIV_1"><ex>&lt;div&gt;</ex></ph><ph name="ICU"/><ph name="CLOSE_TAG_DIV"><ex>&lt;/div&gt;</ex></ph>
</msg> </msg>
<msg id="1491627405349178954">it <ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>should<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph> work</msg> <msg id="1491627405349178954">it <ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>should<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph> work</msg>
</messagebundle> </messagebundle>`;
const HTML = `
<div>
<h1 i18n>i18n attribute on tags</h1>
<div id="i18n-1"><p i18n>nested</p></div>
<div id="i18n-2"><p i18n="different meaning|">nested</p></div>
<div id="i18n-3"><p i18n><i>with placeholders</i></p></div>
<div id="i18n-3b"><p i18n><i class="preserved-on-placeholders">with placeholders</i></p></div>
<div>
<p id="i18n-4" i18n-title title="on not translatable node"></p>
<p id="i18n-5" i18n i18n-title title="on translatable node"></p>
<p id="i18n-6" i18n-title title></p>
</div>
<!-- no ph below because the ICU node is the only child of the div, i.e. no text nodes -->
<div i18n id="i18n-7">{count, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>}}</div>
<div i18n id="i18n-8">
{sex, select, m {male} f {female}}
</div>
<div i18n id="i18n-8b">
{sexB, select, m {male} f {female}}
</div>
<div i18n id="i18n-9">{{ "count = " + count }}</div>
<div i18n id="i18n-10">sex = {{ sex }}</div>
<div i18n id="i18n-11">{{ "custom name" //i18n(ph="CUSTOM_NAME") }}</div>
</div>
<!-- i18n -->
<h1 id="i18n-12" >Markers in html comments</h1>
<div id="i18n-13" i18n-title title="in a translatable section"></div>
<div id="i18n-14">{count, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>}}</div>
<!-- /i18n -->
<div id="i18n-15"><ng-container i18n>it <b>should</b> work</ng-container></div>
<!-- make sure that ICU messages are not treated as text nodes -->
<div i18n="desc">{
response.getItemsList().length,
plural,
=0 {Found no results}
=1 {Found one result}
other {Found {{response.getItemsList().length}} results}
}</div>
<div i18n id="i18n-18">foo<a i18n-title title="in a translatable section">bar</a></div>
`; `;

View File

@ -1323,17 +1323,30 @@ Reference "#a" is defined several times ("<div #a></div><div [ERROR ->]#a></div>
}); });
it('should work with *... and use the attribute name as property binding name', () => { it('should work with *... and use the attribute name as property binding name', () => {
expect(humanizeTplAst(parse('<div *ngIf="test">', [ngIf]))).toEqual([ expect(humanizeTplAst(parse('<div *ngIf="test">', [ngIf]))).toEqual([
[EmbeddedTemplateAst], [DirectiveAst, ngIf], [EmbeddedTemplateAst],
[BoundDirectivePropertyAst, 'ngIf', 'test'], [ElementAst, 'div'] [DirectiveAst, ngIf],
[BoundDirectivePropertyAst, 'ngIf', 'test'],
[ElementAst, 'div'],
]);
// https://github.com/angular/angular/issues/13800
expect(humanizeTplAst(parse('<div *ngIf="-1">', [ngIf]))).toEqual([
[EmbeddedTemplateAst],
[DirectiveAst, ngIf],
[BoundDirectivePropertyAst, 'ngIf', '0 - 1'],
[ElementAst, 'div'],
]); ]);
}); });
it('should work with *... and empty value', () => { it('should work with *... and empty value', () => {
expect(humanizeTplAst(parse('<div *ngIf>', [ngIf]))).toEqual([ expect(humanizeTplAst(parse('<div *ngIf>', [ngIf]))).toEqual([
[EmbeddedTemplateAst], [DirectiveAst, ngIf], [EmbeddedTemplateAst],
[BoundDirectivePropertyAst, 'ngIf', 'null'], [ElementAst, 'div'] [DirectiveAst, ngIf],
[BoundDirectivePropertyAst, 'ngIf', 'null'],
[ElementAst, 'div'],
]); ]);
}); });
}); });

View File

@ -14,7 +14,6 @@ import {isPromise} from '../src/util/lang';
import {ApplicationInitStatus} from './application_init'; import {ApplicationInitStatus} from './application_init';
import {APP_BOOTSTRAP_LISTENER, PLATFORM_INITIALIZER} from './application_tokens'; import {APP_BOOTSTRAP_LISTENER, PLATFORM_INITIALIZER} from './application_tokens';
import {ChangeDetectorRef} from './change_detection/change_detector_ref';
import {Console} from './console'; import {Console} from './console';
import {Injectable, Injector, OpaqueToken, Optional, Provider, ReflectiveInjector} from './di'; import {Injectable, Injector, OpaqueToken, Optional, Provider, ReflectiveInjector} from './di';
import {CompilerFactory, CompilerOptions} from './linker/compiler'; import {CompilerFactory, CompilerOptions} from './linker/compiler';

View File

@ -13,7 +13,6 @@ import {Type} from '../type';
import {ElementRef} from './element_ref'; import {ElementRef} from './element_ref';
import {AppView} from './view'; import {AppView} from './view';
import {ViewContainer} from './view_container';
import {ViewRef} from './view_ref'; import {ViewRef} from './view_ref';
import {ViewUtils} from './view_utils'; import {ViewUtils} from './view_utils';
@ -86,11 +85,6 @@ export class ComponentRef_<C> extends ComponentRef<C> {
onDestroy(callback: Function): void { this.hostView.onDestroy(callback); } onDestroy(callback: Function): void { this.hostView.onDestroy(callback); }
} }
/**
* @experimental
*/
const EMPTY_CONTEXT = new Object();
/** /**
* @stable * @stable
*/ */

View File

@ -8,7 +8,6 @@
import {ElementRef} from './element_ref'; import {ElementRef} from './element_ref';
import {AppView} from './view'; import {AppView} from './view';
import {ViewContainer} from './view_container';
import {EmbeddedViewRef} from './view_ref'; import {EmbeddedViewRef} from './view_ref';

View File

@ -11,7 +11,7 @@ import {ChangeDetectorRef, ChangeDetectorStatus} from '../change_detection/chang
import {Injector, THROW_IF_NOT_FOUND} from '../di/injector'; import {Injector, THROW_IF_NOT_FOUND} from '../di/injector';
import {isPresent} from '../facade/lang'; import {isPresent} from '../facade/lang';
import {WtfScopeFn, wtfCreateScope, wtfLeave} from '../profile/profile'; import {WtfScopeFn, wtfCreateScope, wtfLeave} from '../profile/profile';
import {DirectRenderer, RenderComponentType, RenderDebugInfo, Renderer} from '../render/api'; import {DirectRenderer, RenderComponentType, Renderer} from '../render/api';
import {AnimationViewContext} from './animation_view_context'; import {AnimationViewContext} from './animation_view_context';
import {ComponentRef} from './component_factory'; import {ComponentRef} from './component_factory';

View File

@ -7,10 +7,8 @@
*/ */
import {Injector} from '../di/injector'; import {Injector} from '../di/injector';
import {isPresent} from '../facade/lang';
import {ElementRef} from './element_ref'; import {ElementRef} from './element_ref';
import {QueryList} from './query_list';
import {AppView} from './view'; import {AppView} from './view';
import {ViewContainerRef_} from './view_container_ref'; import {ViewContainerRef_} from './view_container_ref';
import {ViewType} from './view_type'; import {ViewType} from './view_type';

View File

@ -15,7 +15,6 @@ import {ViewEncapsulation} from '../metadata/view';
import {RenderComponentType, RenderDebugInfo, Renderer, RootRenderer} from '../render/api'; import {RenderComponentType, RenderDebugInfo, Renderer, RootRenderer} from '../render/api';
import {Sanitizer} from '../security'; import {Sanitizer} from '../security';
import {VERSION} from '../version'; import {VERSION} from '../version';
import {NgZone} from '../zone/ng_zone';
import {ExpressionChangedAfterItHasBeenCheckedError} from './errors'; import {ExpressionChangedAfterItHasBeenCheckedError} from './errors';
import {AppView} from './view'; import {AppView} from './view';
@ -23,7 +22,6 @@ import {AppView} from './view';
@Injectable() @Injectable()
export class ViewUtils { export class ViewUtils {
sanitizer: Sanitizer; sanitizer: Sanitizer;
private _nextCompTypeId: number = 0;
constructor( constructor(
private _renderer: RootRenderer, sanitizer: Sanitizer, private _renderer: RootRenderer, sanitizer: Sanitizer,

View File

@ -138,7 +138,7 @@ export function main() {
@Component({ @Component({
selector: 'child', selector: 'child',
template: template:
'(<template [ngTemplateOutlet]="templateRef" [ngOutletContext]="templateCtx"></template>)' '(<ng-container [ngTemplateOutlet]="templateRef" [ngOutletContext]="templateCtx"></ng-container>)'
}) })
class Child { class Child {
@Input() @Input()

View File

@ -23,8 +23,15 @@ function declareTests({useJit}: {useJit: boolean}) {
beforeEach(() => { beforeEach(() => {
TestBed.configureCompiler({useJit: useJit}); TestBed.configureCompiler({useJit: useJit});
TestBed.configureTestingModule( TestBed.configureTestingModule({
{declarations: [MyComp, NeedsContentChildren, NeedsViewChildren, TextDirective, Simple]}); declarations: [
MyComp,
NeedsContentChildren,
NeedsViewChildren,
TextDirective,
Simple,
],
});
}); });
it('should support the "i18n" attribute', () => { it('should support the "i18n" attribute', () => {

View File

@ -421,7 +421,7 @@ export function main() {
TestBed.overrideDirective( TestBed.overrideDirective(
SimpleDirective, {set: {providers: [{provide: 'service', useValue: 'parentService'}]}}); SimpleDirective, {set: {providers: [{provide: 'service', useValue: 'parentService'}]}});
const el = createComponent( const el = createComponent(
'<div simpleDirective><template [ngIf]="true"><div *ngIf="true" needsService></div></template></div>'); '<div simpleDirective><ng-container *ngIf="true"><div *ngIf="true" needsService></div></ng-container></div>');
expect(el.children[0].children[0].injector.get(NeedsService).service) expect(el.children[0].children[0].injector.get(NeedsService).service)
.toEqual('parentService'); .toEqual('parentService');
}); });

View File

@ -112,7 +112,7 @@ export class Request extends Body {
case 'text/html': case 'text/html':
return ContentType.TEXT; return ContentType.TEXT;
case 'application/octet-stream': case 'application/octet-stream':
return ContentType.BLOB; return this._body instanceof ArrayBuffer ? ContentType.ARRAY_BUFFER : ContentType.BLOB;
default: default:
return this.detectContentTypeFromBody(); return this.detectContentTypeFromBody();
} }
@ -132,7 +132,7 @@ export class Request extends Body {
return ContentType.BLOB; return ContentType.BLOB;
} else if (this._body instanceof ArrayBuffer) { } else if (this._body instanceof ArrayBuffer) {
return ContentType.ARRAY_BUFFER; return ContentType.ARRAY_BUFFER;
} else if (this._body && typeof this._body == 'object') { } else if (this._body && typeof this._body === 'object') {
return ContentType.JSON; return ContentType.JSON;
} else { } else {
return ContentType.TEXT; return ContentType.TEXT;
@ -167,4 +167,4 @@ const noop = function() {};
const w = typeof window == 'object' ? window : noop; const w = typeof window == 'object' ? window : noop;
const FormData = (w as any /** TODO #9100 */)['FormData'] || noop; const FormData = (w as any /** TODO #9100 */)['FormData'] || noop;
const Blob = (w as any /** TODO #9100 */)['Blob'] || noop; const Blob = (w as any /** TODO #9100 */)['Blob'] || noop;
const ArrayBuffer = (w as any /** TODO #9100 */)['ArrayBuffer'] || noop; export const ArrayBuffer = (w as any /** TODO #9100 */)['ArrayBuffer'] || noop;

View File

@ -36,7 +36,7 @@ import {Headers} from './headers';
*/ */
export class Response extends Body { export class Response extends Body {
/** /**
* One of "basic", "cors", "default", "error, or "opaque". * One of "basic", "cors", "default", "error", or "opaque".
* *
* Defaults to "default". * Defaults to "default".
*/ */

View File

@ -11,7 +11,7 @@ import {describe, expect, it} from '@angular/core/testing/testing_internal';
import {RequestOptions} from '../src/base_request_options'; import {RequestOptions} from '../src/base_request_options';
import {ContentType} from '../src/enums'; import {ContentType} from '../src/enums';
import {Headers} from '../src/headers'; import {Headers} from '../src/headers';
import {Request} from '../src/static_request'; import {ArrayBuffer, Request} from '../src/static_request';
export function main() { export function main() {
describe('Request', () => { describe('Request', () => {
@ -76,6 +76,17 @@ export function main() {
expect(req.detectContentType()).toEqual(ContentType.BLOB); expect(req.detectContentType()).toEqual(ContentType.BLOB);
}); });
it('should not create a blob out of ArrayBuffer', () => {
const req = new Request(new RequestOptions({
url: 'test',
method: 'GET',
body: new ArrayBuffer(1),
headers: new Headers({'content-type': 'application/octet-stream'})
}));
expect(req.detectContentType()).toEqual(ContentType.ARRAY_BUFFER);
});
}); });
it('should return empty string if no body is present', () => { it('should return empty string if no body is present', () => {

View File

@ -96,7 +96,9 @@ export class WebAnimationsPlayer implements AnimationPlayer {
/** @internal */ /** @internal */
_triggerWebAnimation(element: any, keyframes: any[], options: any): DomAnimatePlayer { _triggerWebAnimation(element: any, keyframes: any[], options: any): DomAnimatePlayer {
return <DomAnimatePlayer>element.animate(keyframes, options); // jscompiler doesn't seem to know animate is a native property because it's not fully
// supported yet across common browsers (we polyfill it for Edge/Safari) [CL #143630929]
return <DomAnimatePlayer>element['animate'](keyframes, options);
} }
get domPlayer() { return this._player; } get domPlayer() { return this._player; }

View File

@ -30,9 +30,14 @@ import {sanitizeUrl} from './url_sanitizer';
const VALUES = '[-,."\'%_!# a-zA-Z0-9]+'; const VALUES = '[-,."\'%_!# a-zA-Z0-9]+';
const TRANSFORMATION_FNS = '(?:matrix|translate|scale|rotate|skew|perspective)(?:X|Y|3d)?'; const TRANSFORMATION_FNS = '(?:matrix|translate|scale|rotate|skew|perspective)(?:X|Y|3d)?';
const COLOR_FNS = '(?:rgb|hsl)a?'; const COLOR_FNS = '(?:rgb|hsl)a?';
const FN_ARGS = '\\([-0-9.%, a-zA-Z]+\\)'; const GRADIENTS = '(?:repeating-)?(?:linear|radial)-gradient';
const SAFE_STYLE_VALUE = const CSS3_FNS = '(?:calc|attr)';
new RegExp(`^(${VALUES}|(?:${TRANSFORMATION_FNS}|${COLOR_FNS})${FN_ARGS})$`, 'g'); const FN_ARGS = '\\([-0-9.%, #a-zA-Z]+\\)';
const SAFE_STYLE_VALUE = new RegExp(
`^(${VALUES}|` +
`(?:${TRANSFORMATION_FNS}|${COLOR_FNS}|${GRADIENTS}|${CSS3_FNS})` +
`${FN_ARGS})$`,
'g');
/** /**
* Matches a `url(...)` value with an arbitrary argument as long as it does * Matches a `url(...)` value with an arbitrary argument as long as it does

View File

@ -39,6 +39,16 @@ export function main() {
expectSanitize('translateX(12px, -5px)').toEqual('translateX(12px, -5px)'); expectSanitize('translateX(12px, -5px)').toEqual('translateX(12px, -5px)');
expectSanitize('scale3d(1, 1, 2)').toEqual('scale3d(1, 1, 2)'); expectSanitize('scale3d(1, 1, 2)').toEqual('scale3d(1, 1, 2)');
}); });
t.it('accepts gradients', () => {
expectSanitize('linear-gradient(to bottom, #fg34a1, #bada55)')
.toEqual('linear-gradient(to bottom, #fg34a1, #bada55)');
expectSanitize('repeating-radial-gradient(ellipse cover, black, red, black, red)')
.toEqual('repeating-radial-gradient(ellipse cover, black, red, black, red)');
});
t.it('accepts calc', () => { expectSanitize('calc(90%-123px)').toEqual('calc(90%-123px)'); });
t.it('accepts attr', () => {
expectSanitize('attr(value string)').toEqual('attr(value string)');
});
t.it('sanitizes URLs', () => { t.it('sanitizes URLs', () => {
expectSanitize('url(foo/bar.png)').toEqual('url(foo/bar.png)'); expectSanitize('url(foo/bar.png)').toEqual('url(foo/bar.png)');
expectSanitize('url( foo/bar.png\n )').toEqual('url( foo/bar.png\n )'); expectSanitize('url( foo/bar.png\n )').toEqual('url( foo/bar.png\n )');

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Component, ComponentRef, Injectable, Injector} from '@angular/core'; import {Component, ComponentRef, Injectable} from '@angular/core';
import {DebugDomRootRenderer} from '@angular/core/src/debug/debug_renderer'; import {DebugDomRootRenderer} from '@angular/core/src/debug/debug_renderer';
import {RootRenderer} from '@angular/core/src/render/api'; import {RootRenderer} from '@angular/core/src/render/api';
import {TestBed} from '@angular/core/testing'; import {TestBed} from '@angular/core/testing';
@ -152,7 +152,7 @@ export function main() {
it('should update any template comment property/attributes', () => { it('should update any template comment property/attributes', () => {
TestBed.overrideComponent( TestBed.overrideComponent(
MyComp2, {set: {template: '<template [ngIf]="ctxBoolProp"></template>'}}); MyComp2, {set: {template: '<ng-container *ngIf="ctxBoolProp"></ng-container>'}});
const fixture = TestBed.createComponent(MyComp2); const fixture = TestBed.createComponent(MyComp2);
(<MyComp2>fixture.componentInstance).ctxBoolProp = true; (<MyComp2>fixture.componentInstance).ctxBoolProp = true;
@ -163,7 +163,7 @@ export function main() {
it('should add and remove fragments', () => { it('should add and remove fragments', () => {
TestBed.overrideComponent( TestBed.overrideComponent(
MyComp2, {set: {template: '<template [ngIf]="ctxBoolProp">hello</template>'}}); MyComp2, {set: {template: '<ng-container *ngIf="ctxBoolProp">hello</ng-container>'}});
const fixture = TestBed.createComponent(MyComp2); const fixture = TestBed.createComponent(MyComp2);
const rootEl = getRenderElement(fixture.nativeElement); const rootEl = getRenderElement(fixture.nativeElement);

View File

@ -71,4 +71,4 @@ Promise
return Promise.all( return Promise.all(
allSpecFiles.map(function(moduleName) { return System.import(moduleName); })); allSpecFiles.map(function(moduleName) { return System.import(moduleName); }));
}) })
.then(__karma__.start, __karma__.error); .then(__karma__.start, (v) => console.error(v));

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Type} from '@angular/core'; import {NgModuleFactory, Type} from '@angular/core';
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
import {PRIMARY_OUTLET} from './shared'; import {PRIMARY_OUTLET} from './shared';
import {UrlSegment, UrlSegmentGroup} from './url_tree'; import {UrlSegment, UrlSegmentGroup} from './url_tree';
@ -310,7 +310,8 @@ export type ResolveData = {
* See {@link Routes} for more details. * See {@link Routes} for more details.
* @stable * @stable
*/ */
export type LoadChildrenCallback = () => Type<any>| Promise<Type<any>>| Observable<Type<any>>; export type LoadChildrenCallback = () =>
Type<any>| NgModuleFactory<any>| Promise<Type<any>>| Observable<Type<any>>;
/** /**
* @whatItDoes The type of `loadChildren`. * @whatItDoes The type of `loadChildren`.

View File

@ -133,7 +133,7 @@ export class RouterLink {
*/ */
@Directive({selector: 'a[routerLink]'}) @Directive({selector: 'a[routerLink]'})
export class RouterLinkWithHref implements OnChanges, OnDestroy { export class RouterLinkWithHref implements OnChanges, OnDestroy {
@Input() target: string; @HostBinding('attr.target') @Input() target: string;
@Input() queryParams: {[k: string]: any}; @Input() queryParams: {[k: string]: any};
@Input() fragment: string; @Input() fragment: string;
@Input() preserveQueryParams: boolean; @Input() preserveQueryParams: boolean;

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AfterContentInit, ContentChildren, Directive, ElementRef, Input, OnChanges, OnDestroy, QueryList, Renderer} from '@angular/core'; import {AfterContentInit, ChangeDetectorRef, ContentChildren, Directive, ElementRef, Input, OnChanges, OnDestroy, QueryList, Renderer, SimpleChanges} from '@angular/core';
import {Subscription} from 'rxjs/Subscription'; import {Subscription} from 'rxjs/Subscription';
import {NavigationEnd, Router} from '../router'; import {NavigationEnd, Router} from '../router';
@ -14,6 +14,7 @@ import {NavigationEnd, Router} from '../router';
import {RouterLink, RouterLinkWithHref} from './router_link'; import {RouterLink, RouterLinkWithHref} from './router_link';
/** /**
* @whatItDoes Lets you add a CSS class to an element when the link's route becomes active. * @whatItDoes Lets you add a CSS class to an element when the link's route becomes active.
* *
@ -89,10 +90,13 @@ export class RouterLinkActive implements OnChanges,
private classes: string[] = []; private classes: string[] = [];
private subscription: Subscription; private subscription: Subscription;
private active: boolean = false;
@Input() routerLinkActiveOptions: {exact: boolean} = {exact: false}; @Input() routerLinkActiveOptions: {exact: boolean} = {exact: false};
constructor(private router: Router, private element: ElementRef, private renderer: Renderer) { constructor(
private router: Router, private element: ElementRef, private renderer: Renderer,
private cdr: ChangeDetectorRef) {
this.subscription = router.events.subscribe(s => { this.subscription = router.events.subscribe(s => {
if (s instanceof NavigationEnd) { if (s instanceof NavigationEnd) {
this.update(); this.update();
@ -100,35 +104,34 @@ export class RouterLinkActive implements OnChanges,
}); });
} }
get isActive(): boolean { return this.hasActiveLink(); } get isActive(): boolean { return this.active; }
ngAfterContentInit(): void { ngAfterContentInit(): void {
this.links.changes.subscribe(s => this.update()); this.links.changes.subscribe(_ => this.update());
this.linksWithHrefs.changes.subscribe(s => this.update()); this.linksWithHrefs.changes.subscribe(_ => this.update());
this.update(); this.update();
} }
@Input() @Input()
set routerLinkActive(data: string[]|string) { set routerLinkActive(data: string[]|string) {
if (Array.isArray(data)) { const classes = Array.isArray(data) ? data : data.split(' ');
this.classes = <any>data; this.classes = classes.filter(c => !!c);
} else {
this.classes = data.split(' ');
}
} }
ngOnChanges(changes: {}): any { this.update(); } ngOnChanges(changes: SimpleChanges): void { this.update(); }
ngOnDestroy(): any { this.subscription.unsubscribe(); } ngOnDestroy(): void { this.subscription.unsubscribe(); }
private update(): void { private update(): void {
if (!this.links || !this.linksWithHrefs || !this.router.navigated) return; if (!this.links || !this.linksWithHrefs || !this.router.navigated) return;
const hasActiveLinks = this.hasActiveLinks();
const isActive = this.hasActiveLink(); // react only when status has changed to prevent unnecessary dom updates
this.classes.forEach(c => { if (this.active !== hasActiveLinks) {
if (c) { this.active = hasActiveLinks;
this.renderer.setElementClass(this.element.nativeElement, c, isActive); this.classes.forEach(
} c => this.renderer.setElementClass(this.element.nativeElement, c, hasActiveLinks));
}); this.cdr.detectChanges();
}
} }
private isLinkActive(router: Router): (link: (RouterLink|RouterLinkWithHref)) => boolean { private isLinkActive(router: Router): (link: (RouterLink|RouterLinkWithHref)) => boolean {
@ -136,7 +139,7 @@ export class RouterLinkActive implements OnChanges,
router.isActive(link.urlTree, this.routerLinkActiveOptions.exact); router.isActive(link.urlTree, this.routerLinkActiveOptions.exact);
} }
private hasActiveLink(): boolean { private hasActiveLinks(): boolean {
return this.links.some(this.isLinkActive(this.router)) || return this.links.some(this.isLinkActive(this.router)) ||
this.linksWithHrefs.some(this.isLinkActive(this.router)); this.linksWithHrefs.some(this.isLinkActive(this.router));
} }

View File

@ -526,6 +526,7 @@ export class Router {
*/ */
navigate(commands: any[], extras: NavigationExtras = {skipLocationChange: false}): navigate(commands: any[], extras: NavigationExtras = {skipLocationChange: false}):
Promise<boolean> { Promise<boolean> {
validateCommands(commands);
if (typeof extras.queryParams === 'object' && extras.queryParams !== null) { if (typeof extras.queryParams === 'object' && extras.queryParams !== null) {
extras.queryParams = this.removeEmptyProps(extras.queryParams); extras.queryParams = this.removeEmptyProps(extras.queryParams);
} }
@ -1237,3 +1238,12 @@ function getOutlet(outletMap: RouterOutletMap, route: ActivatedRoute): RouterOut
} }
return outlet; return outlet;
} }
function validateCommands(commands: string[]): void {
for (let i = 0; i < commands.length; i++) {
const cmd = commands[i];
if (cmd == null) {
throw new Error(`The requested path contains ${cmd} segment at index ${i}`);
}
}
}

View File

@ -40,7 +40,7 @@ function equalSegmentGroups(container: UrlSegmentGroup, containee: UrlSegmentGro
function containsQueryParams( function containsQueryParams(
container: {[k: string]: string}, containee: {[k: string]: string}): boolean { container: {[k: string]: string}, containee: {[k: string]: string}): boolean {
return Object.keys(containee) <= Object.keys(container) && return Object.keys(containee).length <= Object.keys(container).length &&
Object.keys(containee).every(key => containee[key] === container[key]); Object.keys(containee).every(key => containee[key] === container[key]);
} }

View File

@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {NgModuleFactory} from '@angular/core';
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
import {fromPromise} from 'rxjs/observable/fromPromise'; import {fromPromise} from 'rxjs/observable/fromPromise';
import {of } from 'rxjs/observable/of'; import {of } from 'rxjs/observable/of';
@ -126,7 +127,8 @@ export function andObservables(observables: Observable<Observable<any>>): Observ
return every.call(merged$, (result: any) => result === true); return every.call(merged$, (result: any) => result === true);
} }
export function wrapIntoObservable<T>(value: T | Promise<T>| Observable<T>): Observable<T> { export function wrapIntoObservable<T>(value: T | NgModuleFactory<T>| Promise<T>| Observable<T>):
Observable<T> {
if (value instanceof Observable) { if (value instanceof Observable) {
return value; return value;
} }

View File

@ -536,6 +536,17 @@ describe('Integration', () => {
expect(cmp.recordedParams).toEqual([{name: '1'}]); expect(cmp.recordedParams).toEqual([{name: '1'}]);
}))); })));
it('should throw an error when one of the commands is null/undefined',
fakeAsync(inject([Router], (router: Router) => {
createRoot(router, RootCmp);
router.resetConfig([{path: 'query', component: EmptyQueryParamsCmp}]);
expect(() => router.navigate([
undefined, 'query'
])).toThrowError(`The requested path contains undefined segment at index 0`);
})));
it('should push params only when they change', fakeAsync(inject([Router], (router: Router) => { it('should push params only when they change', fakeAsync(inject([Router], (router: Router) => {
const fixture = createRoot(router, RootCmp); const fixture = createRoot(router, RootCmp);
@ -998,6 +1009,7 @@ describe('Integration', () => {
const native = fixture.nativeElement.querySelector('a'); const native = fixture.nativeElement.querySelector('a');
expect(native.getAttribute('href')).toEqual('/team/33/simple'); expect(native.getAttribute('href')).toEqual('/team/33/simple');
expect(native.getAttribute('target')).toEqual('_self');
native.click(); native.click();
advance(fixture); advance(fixture);
@ -2020,6 +2032,8 @@ describe('Integration', () => {
@Component({ @Component({
template: `<a routerLink="/team" routerLinkActive #rla="routerLinkActive"></a> template: `<a routerLink="/team" routerLinkActive #rla="routerLinkActive"></a>
<p>{{rla.isActive}}</p> <p>{{rla.isActive}}</p>
<span *ngIf="rla.isActive"></span>
<span [ngClass]="{'highlight': rla.isActive}"></span>
<router-outlet></router-outlet>` <router-outlet></router-outlet>`
}) })
class ComponentWithRouterLink { class ComponentWithRouterLink {
@ -2039,15 +2053,15 @@ describe('Integration', () => {
} }
]); ]);
const f = TestBed.createComponent(ComponentWithRouterLink); const fixture = TestBed.createComponent(ComponentWithRouterLink);
router.navigateByUrl('/team'); router.navigateByUrl('/team');
advance(f); expect(() => advance(fixture)).not.toThrow();
const paragraph = f.nativeElement.querySelector('p'); const paragraph = fixture.nativeElement.querySelector('p');
expect(paragraph.textContent).toEqual('true'); expect(paragraph.textContent).toEqual('true');
router.navigateByUrl('/otherteam'); router.navigateByUrl('/otherteam');
advance(f); advance(fixture);
expect(paragraph.textContent).toEqual('false'); expect(paragraph.textContent).toEqual('false');
})); }));
@ -2646,7 +2660,8 @@ function expectEvents(events: Event[], pairs: any[]) {
} }
} }
@Component({selector: 'link-cmp', template: `<a routerLink="/team/33/simple">link</a>`}) @Component(
{selector: 'link-cmp', template: `<a routerLink="/team/33/simple" [target]="'_self'">link</a>`})
class StringLinkCmp { class StringLinkCmp {
} }

View File

@ -102,8 +102,8 @@ describe('UrlTree', () => {
}); });
it('should return true when container contains containees queryParams', () => { it('should return true when container contains containees queryParams', () => {
const t1 = serializer.parse('/one/two?test=1&page=5'); const t1 = serializer.parse('/one/two?test=1&u=5');
const t2 = serializer.parse('/one/two?test=1'); const t2 = serializer.parse('/one/two?u=5');
expect(containsTree(t1, t2, false)).toBe(true); expect(containsTree(t1, t2, false)).toBe(true);
}); });

View File

@ -577,10 +577,6 @@ export class UpgradeAdapter {
}) })
.then((ref: NgModuleRef<any>) => { .then((ref: NgModuleRef<any>) => {
this.moduleRef = ref; this.moduleRef = ref;
let subscription = this.ngZone.onMicrotaskEmpty.subscribe({
next: (_: any) => this.ngZone.runOutsideAngular(() => rootScope.$evalAsync())
});
rootScope.$on('$destroy', () => { subscription.unsubscribe(); });
this.ngZone.run(() => { this.ngZone.run(() => {
if (rootScopePrototype) { if (rootScopePrototype) {
rootScopePrototype.$apply = original$applyFn; // restore original $apply rootScopePrototype.$apply = original$applyFn; // restore original $apply
@ -591,7 +587,12 @@ export class UpgradeAdapter {
} }
}); });
}) })
.then(() => this.ng2BootstrapDeferred.resolve(ng1Injector), onError); .then(() => this.ng2BootstrapDeferred.resolve(ng1Injector), onError)
.then(() => {
let subscription =
this.ngZone.onMicrotaskEmpty.subscribe({next: () => rootScope.$digest()});
rootScope.$on('$destroy', () => { subscription.unsubscribe(); });
});
}) })
.catch((e) => this.ng2BootstrapDeferred.reject(e)); .catch((e) => this.ng2BootstrapDeferred.reject(e));
} }

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ChangeDetectorRef, Class, Component, EventEmitter, NO_ERRORS_SCHEMA, NgModule, SimpleChange, SimpleChanges, Testability, destroyPlatform, forwardRef} from '@angular/core'; import {ChangeDetectorRef, Class, Component, EventEmitter, Input, NO_ERRORS_SCHEMA, NgModule, NgZone, SimpleChange, SimpleChanges, Testability, destroyPlatform, forwardRef} from '@angular/core';
import {async, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing'; import {async, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
import {BrowserModule} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
@ -18,7 +18,89 @@ export function main() {
beforeEach(() => destroyPlatform()); beforeEach(() => destroyPlatform());
afterEach(() => destroyPlatform()); afterEach(() => destroyPlatform());
it('should have angular 1 loaded', () => expect(angular.version.major).toBe(1)); describe('(basic use)', () => {
it('should have angular 1 loaded', () => expect(angular.version.major).toBe(1));
it('should instantiate ng2 in ng1 template and project content', async(() => {
const ng1Module = angular.module('ng1', []);
const Ng2 = Component({
selector: 'ng2',
template: `{{ 'NG2' }}(<ng-content></ng-content>)`,
}).Class({constructor: function() {}});
const Ng2Module = NgModule({declarations: [Ng2], imports: [BrowserModule]}).Class({
constructor: function() {}
});
const element =
html('<div>{{ \'ng1[\' }}<ng2>~{{ \'ng-content\' }}~</ng2>{{ \']\' }}</div>');
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module);
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
adapter.bootstrap(element, ['ng1']).ready((ref) => {
expect(document.body.textContent).toEqual('ng1[NG2(~ng-content~)]');
ref.dispose();
});
}));
it('should instantiate ng1 in ng2 template and project content', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module('ng1', []);
const Ng2 = Component({
selector: 'ng2',
template: `{{ 'ng2(' }}<ng1>{{'transclude'}}</ng1>{{ ')' }}`,
}).Class({constructor: function Ng2() {}});
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule],
}).Class({constructor: function Ng2Module() {}});
ng1Module.directive('ng1', () => {
return {transclude: true, template: '{{ "ng1" }}(<ng-transclude></ng-transclude>)'};
});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
const element = html('<div>{{\'ng1(\'}}<ng2></ng2>{{\')\'}}</div>');
adapter.bootstrap(element, ['ng1']).ready((ref) => {
expect(document.body.textContent).toEqual('ng1(ng2(ng1(transclude)))');
ref.dispose();
});
}));
it('supports the compilerOptions argument', async(() => {
const platformRef = platformBrowserDynamic();
spyOn(platformRef, '_bootstrapModuleWithZone').and.callThrough();
const ng1Module = angular.module('ng1', []);
const Ng2 = Component({
selector: 'ng2',
template: `{{ 'NG2' }}(<ng-content></ng-content>)`
}).Class({constructor: function() {}});
const element =
html('<div>{{ \'ng1[\' }}<ng2>~{{ \'ng-content\' }}~</ng2>{{ \']\' }}</div>');
const Ng2AppModule =
NgModule({
declarations: [Ng2],
imports: [BrowserModule],
}).Class({constructor: function Ng2AppModule() {}, ngDoBootstrap: function() {}});
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2AppModule, {providers: []});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
adapter.bootstrap(element, ['ng1']).ready((ref) => {
expect((platformRef as any)._bootstrapModuleWithZone)
.toHaveBeenCalledWith(
jasmine.any(Function), {providers: []}, jasmine.any(Object),
jasmine.any(Function));
ref.dispose();
});
}));
});
describe('bootstrap errors', () => { describe('bootstrap errors', () => {
let adapter: UpgradeAdapter; let adapter: UpgradeAdapter;
@ -51,98 +133,18 @@ export function main() {
})); }));
it('should output an error message to the console and re-throw', fakeAsync(() => { it('should output an error message to the console and re-throw', fakeAsync(() => {
let consoleErrorSpy: jasmine.Spy = spyOn(console, 'error'); const consoleErrorSpy: jasmine.Spy = spyOn(console, 'error');
expect(() => { expect(() => {
adapter.bootstrap(html('<ng2></ng2>'), ['ng1']); adapter.bootstrap(html('<ng2></ng2>'), ['ng1']);
flushMicrotasks(); flushMicrotasks();
}).toThrowError(); }).toThrowError();
let args: any[] = consoleErrorSpy.calls.mostRecent().args; const args: any[] = consoleErrorSpy.calls.mostRecent().args;
expect(consoleErrorSpy).toHaveBeenCalled(); expect(consoleErrorSpy).toHaveBeenCalled();
expect(args.length).toBeGreaterThan(0); expect(args.length).toBeGreaterThan(0);
expect(args[0]).toEqual(jasmine.any(Error)); expect(args[0]).toEqual(jasmine.any(Error));
})); }));
}); });
it('should instantiate ng2 in ng1 template and project content', async(() => {
const ng1Module = angular.module('ng1', []);
const Ng2 = Component({
selector: 'ng2',
template: `{{ 'NG2' }}(<ng-content></ng-content>)`,
}).Class({constructor: function() {}});
const Ng2Module = NgModule({declarations: [Ng2], imports: [BrowserModule]}).Class({
constructor: function() {}
});
const element =
html('<div>{{ \'ng1[\' }}<ng2>~{{ \'ng-content\' }}~</ng2>{{ \']\' }}</div>');
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module);
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
adapter.bootstrap(element, ['ng1']).ready((ref) => {
expect(document.body.textContent).toEqual('ng1[NG2(~ng-content~)]');
ref.dispose();
});
}));
it('should instantiate ng1 in ng2 template and project content', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module('ng1', []);
const Ng2 = Component({
selector: 'ng2',
template: `{{ 'ng2(' }}<ng1>{{'transclude'}}</ng1>{{ ')' }}`,
}).Class({constructor: function Ng2() {}});
const Ng2Module = NgModule({
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
imports: [BrowserModule],
}).Class({constructor: function Ng2Module() {}});
ng1Module.directive('ng1', () => {
return {transclude: true, template: '{{ "ng1" }}(<ng-transclude></ng-transclude>)'};
});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
const element = html('<div>{{\'ng1(\'}}<ng2></ng2>{{\')\'}}</div>');
adapter.bootstrap(element, ['ng1']).ready((ref) => {
expect(document.body.textContent).toEqual('ng1(ng2(ng1(transclude)))');
ref.dispose();
});
}));
it('supports the compilerOptions argument', async(() => {
const platformRef = platformBrowserDynamic();
spyOn(platformRef, '_bootstrapModuleWithZone').and.callThrough();
const ng1Module = angular.module('ng1', []);
const Ng2 = Component({
selector: 'ng2',
template: `{{ 'NG2' }}(<ng-content></ng-content>)`
}).Class({constructor: function() {}});
const element =
html('<div>{{ \'ng1[\' }}<ng2>~{{ \'ng-content\' }}~</ng2>{{ \']\' }}</div>');
const Ng2AppModule =
NgModule({
declarations: [Ng2],
imports: [BrowserModule],
}).Class({constructor: function Ng2AppModule() {}, ngDoBootstrap: function() {}});
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2AppModule, {providers: []});
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
adapter.bootstrap(element, ['ng1']).ready((ref) => {
expect((platformRef as any)._bootstrapModuleWithZone)
.toHaveBeenCalledWith(
jasmine.any(Function), {providers: []}, jasmine.any(Object),
jasmine.any(Function));
ref.dispose();
});
}));
describe('scope/component change-detection', () => { describe('scope/component change-detection', () => {
it('should interleave scope and component expressions', async(() => { it('should interleave scope and component expressions', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
@ -184,6 +186,87 @@ export function main() {
ref.dispose(); ref.dispose();
}); });
})); }));
it('should propagate changes to a downgraded component inside the ngZone', async(() => {
let appComponent: AppComponent;
let upgradeRef: UpgradeAdapterRef;
@Component({selector: 'my-app', template: '<my-child [value]="value"></my-child>'})
class AppComponent {
value: number;
constructor() { appComponent = this; }
}
@Component({
selector: 'my-child',
template: '<div>{{valueFromPromise}}',
})
class ChildComponent {
valueFromPromise: number;
@Input()
set value(v: number) { expect(NgZone.isInAngularZone()).toBe(true); }
constructor(private zone: NgZone) {}
ngOnChanges(changes: SimpleChanges) {
if (changes['value'].isFirstChange()) return;
this.zone.onMicrotaskEmpty.subscribe(() => {
expect(element.textContent).toEqual('5');
upgradeRef.dispose();
});
Promise.resolve().then(() => this.valueFromPromise = changes['value'].currentValue);
}
}
@NgModule({declarations: [AppComponent, ChildComponent], imports: [BrowserModule]})
class Ng2Module {
}
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
const ng1Module = angular.module('ng1', []).directive(
'myApp', adapter.downgradeNg2Component(AppComponent));
const element = html('<my-app></my-app>');
adapter.bootstrap(element, ['ng1']).ready((ref) => {
upgradeRef = ref;
appComponent.value = 5;
});
}));
// This test demonstrates https://github.com/angular/angular/issues/6385
// which was invalidly fixed by https://github.com/angular/angular/pull/6386
// it('should not trigger $digest from an async operation in a watcher', async(() => {
// @Component({selector: 'my-app', template: ''})
// class AppComponent {
// }
// @NgModule({declarations: [AppComponent], imports: [BrowserModule]})
// class Ng2Module {
// }
// const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
// const ng1Module = angular.module('ng1', []).directive(
// 'myApp', adapter.downgradeNg2Component(AppComponent));
// const element = html('<my-app></my-app>');
// adapter.bootstrap(element, ['ng1']).ready((ref) => {
// let doTimeout = false;
// let timeoutId: number;
// ref.ng1RootScope.$watch(() => {
// if (doTimeout && !timeoutId) {
// timeoutId = window.setTimeout(function() {
// timeoutId = null;
// }, 10);
// }
// });
// doTimeout = true;
// });
// }));
}); });
describe('downgrade ng2 component', () => { describe('downgrade ng2 component', () => {
@ -388,6 +471,31 @@ export function main() {
ref.dispose(); ref.dispose();
}); });
})); }));
it('should allow attribute selectors for components in ng2', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => MyNg2Module));
const ng1Module = angular.module('myExample', []);
@Component({selector: '[works]', template: 'works!'})
class WorksComponent {
}
@Component({selector: 'root-component', template: 'It <div works></div>'})
class RootComponent {
}
@NgModule({imports: [BrowserModule], declarations: [RootComponent, WorksComponent]})
class MyNg2Module {
}
ng1Module.directive('rootComponent', adapter.downgradeNg2Component(RootComponent));
document.body.innerHTML = '<root-component></root-component>';
adapter.bootstrap(document.body.firstElementChild, ['myExample']).ready((ref) => {
expect(multiTrim(document.body.textContent)).toEqual('It works!');
ref.dispose();
});
}));
}); });
describe('upgrade ng1 component', () => { describe('upgrade ng1 component', () => {
@ -1627,31 +1735,6 @@ export function main() {
})); }));
}); });
it('should allow attribute selectors for components in ng2', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => MyNg2Module));
const ng1Module = angular.module('myExample', []);
@Component({selector: '[works]', template: 'works!'})
class WorksComponent {
}
@Component({selector: 'root-component', template: 'It <div works></div>'})
class RootComponent {
}
@NgModule({imports: [BrowserModule], declarations: [RootComponent, WorksComponent]})
class MyNg2Module {
}
ng1Module.directive('rootComponent', adapter.downgradeNg2Component(RootComponent));
document.body.innerHTML = '<root-component></root-component>';
adapter.bootstrap(document.body.firstElementChild, ['myExample']).ready((ref) => {
expect(multiTrim(document.body.textContent)).toEqual('It works!');
ref.dispose();
});
}));
describe('examples', () => { describe('examples', () => {
it('should verify UpgradeAdapter example', async(() => { it('should verify UpgradeAdapter example', async(() => {
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));

View File

@ -29,7 +29,7 @@ export function init(moduleRef: NgModuleRef<AppModule>) {
} }
function detectChanges() { function detectChanges() {
for (var i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
appRef.tick(); appRef.tick();
} }
detectChangesRuns += 10; detectChangesRuns += 10;

File diff suppressed because it is too large Load Diff

3826
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "angular-srcs", "name": "angular-srcs",
"version": "2.4.2", "version": "2.4.4",
"private": true, "private": true,
"branchPattern": "2.0.*", "branchPattern": "2.0.*",
"description": "Angular 2 - a web framework for modern web apps", "description": "Angular 2 - a web framework for modern web apps",
@ -79,7 +79,7 @@
"source-map-support": "^0.4.2", "source-map-support": "^0.4.2",
"systemjs": "0.18.10", "systemjs": "0.18.10",
"ts-api-guardian": "0.1.4", "ts-api-guardian": "0.1.4",
"tsickle": "^0.2.1", "tsickle": "^0.2.4",
"tslint": "^4.1.1", "tslint": "^4.1.1",
"tslint-eslint-rules": "^3.1.0", "tslint-eslint-rules": "^3.1.0",
"typescript": "^2.0.2", "typescript": "^2.0.2",

View File

@ -82,9 +82,7 @@ System.import('@angular/core/testing')
}); });
})); }));
}) })
.then( .then(function() { __karma__.start(); }, function(error) { console.error(error); });
function() { __karma__.start(); },
function(error) { __karma__.error(error.stack || error); });
function onlySpecFiles(path) { function onlySpecFiles(path) {

View File

@ -1,6 +1,6 @@
{ {
"name": "@angular/tsc-wrapped", "name": "@angular/tsc-wrapped",
"version": "0.5.0", "version": "0.5.1",
"description": "Wraps the tsc CLI, allowing extensions.", "description": "Wraps the tsc CLI, allowing extensions.",
"homepage": "https://github.com/angular/angular/tree/master/tools/tsc-wrapped", "homepage": "https://github.com/angular/angular/tree/master/tools/tsc-wrapped",
"bugs": "https://github.com/angular/angular/issues", "bugs": "https://github.com/angular/angular/issues",

View File

@ -85,7 +85,7 @@ export interface ExtraOptions {
export declare type LoadChildren = string | LoadChildrenCallback; export declare type LoadChildren = string | LoadChildrenCallback;
/** @stable */ /** @stable */
export declare type LoadChildrenCallback = () => Type<any> | Promise<Type<any>> | Observable<Type<any>>; export declare type LoadChildrenCallback = () => Type<any> | NgModuleFactory<any> | Promise<Type<any>> | Observable<Type<any>>;
/** @stable */ /** @stable */
export declare class NavigationCancel { export declare class NavigationCancel {
@ -262,10 +262,10 @@ export declare class RouterLinkActive implements OnChanges, OnDestroy, AfterCont
routerLinkActiveOptions: { routerLinkActiveOptions: {
exact: boolean; exact: boolean;
}; };
constructor(router: Router, element: ElementRef, renderer: Renderer); constructor(router: Router, element: ElementRef, renderer: Renderer, cdr: ChangeDetectorRef);
ngAfterContentInit(): void; ngAfterContentInit(): void;
ngOnChanges(changes: {}): any; ngOnChanges(changes: SimpleChanges): void;
ngOnDestroy(): any; ngOnDestroy(): void;
} }
/** @stable */ /** @stable */