mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ed39ebaaf | |||
| 091f0a5aaa | |||
| 315606e02c | |||
| 5ea373d184 | |||
| 6e36bb7b20 | |||
| 3b2fb23805 | |||
| bd2eecb4de | |||
| 3d351a4f5f | |||
| 5492fada21 | |||
| fd4f9acbcf | |||
| 48528a86e1 | |||
| 80364def27 | |||
| 1803beb4d5 | |||
| 3bcba8a570 | |||
| 84542d8ae7 | |||
| 17cb3ec565 | |||
| 015878afe6 | |||
| 2af58622c1 | |||
| 7ffd10541d | |||
| 481b099d82 | |||
| 49c4b0fa92 | |||
| b8b6b1d27a | |||
| 892b5ba950 | |||
| bd15110c7d | |||
| 2250082fd7 | |||
| 87316c52db | |||
| 606b76d9bb | |||
| 3d0b1b8184 | |||
| 261fd16780 | |||
| 104cc42f6d | |||
| a7d28044c5 | |||
| 055bea2969 | |||
| dad0d21b89 | |||
| 313683f6f3 | |||
| 338be6d6a5 | |||
| 4b56f79328 | |||
| d7f2a3c71b | |||
| 1c929ae244 | |||
| 83d0ff6d13 | |||
| d43e5dd44d | |||
| 61ba223c1a | |||
| 6164eb25f3 | |||
| 5e9d3dba3a | |||
| 16922655ca | |||
| 7dc12b93fe | |||
| 1c82b58185 | |||
| d6c414c08f | |||
| d25d1730c7 |
@@ -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
|
||||
+47
-1
@@ -1,3 +1,49 @@
|
||||
<a name="2.4.5"></a>
|
||||
## [2.4.5](https://github.com/angular/angular/compare/2.4.4...2.4.5) (2017-01-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** [i18n] XMB/XTB placeholder names can contain only A-Z, 0-9, _n ([5492fad](https://github.com/angular/angular/commit/5492fad))
|
||||
* **compiler:** fix regexp to support firefox 31 ([#14082](https://github.com/angular/angular/issues/14082)) ([bd2eecb](https://github.com/angular/angular/commit/bd2eecb)), closes [#14029](https://github.com/angular/angular/issues/14029) [#13900](https://github.com/angular/angular/issues/13900)
|
||||
* **core:** export animation classes required for Renderer impl ([#14002](https://github.com/angular/angular/issues/14002)) ([fd4f9ac](https://github.com/angular/angular/commit/fd4f9ac)), closes [#14001](https://github.com/angular/angular/issues/14001)
|
||||
* **upgrade:** ensure upgraded injector is initialized early enough ([#14065](https://github.com/angular/angular/issues/14065)) ([3b2fb23](https://github.com/angular/angular/commit/3b2fb23)), closes [#13811](https://github.com/angular/angular/issues/13811)
|
||||
|
||||
|
||||
|
||||
<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>
|
||||
## [2.4.2](https://github.com/angular/angular/compare/2.4.1...2.4.2) (2017-01-06)
|
||||
|
||||
@@ -9,7 +55,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)
|
||||
* **compiler:** don’t 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:** 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 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))
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
machine:
|
||||
node:
|
||||
version: 5.4.1
|
||||
version: 6.6.0
|
||||
|
||||
dependencies:
|
||||
pre:
|
||||
- npm install -g npm@3.6.0
|
||||
- npm install -g npm@3.5.3
|
||||
|
||||
test:
|
||||
override:
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
|
||||
|
||||
import {isListLikeIterable} from '../facade/collection';
|
||||
import {isPresent, stringify} from '../facade/lang';
|
||||
import {stringify} from '../facade/lang';
|
||||
|
||||
/**
|
||||
* @ngModule CommonModule
|
||||
@@ -134,7 +134,7 @@ export class NgClass implements DoCheck {
|
||||
(<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, !isCleanup));
|
||||
} else {
|
||||
Object.keys(rawClassVal).forEach(klass => {
|
||||
if (isPresent(rawClassVal[klass])) this._toggleClass(klass, !isCleanup);
|
||||
if (rawClassVal[klass] != null) this._toggleClass(klass, !isCleanup);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,9 @@ import {SwitchView} from './ng_switch';
|
||||
* @howToUse
|
||||
* ```
|
||||
* <some-element [ngPlural]="value">
|
||||
* <ng-container *ngPluralCase="'=0'">there is nothing</ng-container>
|
||||
* <ng-container *ngPluralCase="'=1'">there is one</ng-container>
|
||||
* <ng-container *ngPluralCase="'few'">there are a few</ng-container>
|
||||
* <ng-container *ngPluralCase="'other'">there are exactly #</ng-container>
|
||||
* <template ngPluralCase="=0">there is nothing</template>
|
||||
* <template ngPluralCase="=1">there is one</template>
|
||||
* <template ngPluralCase="few">there are a few</template>
|
||||
* </some-element>
|
||||
* ```
|
||||
*
|
||||
@@ -90,8 +89,8 @@ export class NgPlural {
|
||||
* @howToUse
|
||||
* ```
|
||||
* <some-element [ngPlural]="value">
|
||||
* <ng-container *ngPluralCase="'=0'">...</ng-container>
|
||||
* <ng-container *ngPluralCase="'other'">...</ng-container>
|
||||
* <template ngPluralCase="=0">...</template>
|
||||
* <template ngPluralCase="other">...</template>
|
||||
* </some-element>
|
||||
*```
|
||||
*
|
||||
@@ -104,6 +103,7 @@ export class NgPluralCase {
|
||||
constructor(
|
||||
@Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>,
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
* Where:
|
||||
* - `expression` is a date object or a number (milliseconds since UTC epoch) or an ISO string
|
||||
* (https://www.w3.org/TR/NOTE-datetime).
|
||||
* - `format` indicates which date/time components to include. The format can be predifined as
|
||||
* - `format` indicates which date/time components to include. The format can be predefined as
|
||||
* shown below or custom as shown in the table.
|
||||
* - `'medium'`: equivalent to `'yMMMdjms'` (e.g. `Sep 3, 2010, 12:05:08 PM` for `en-US`)
|
||||
* - `'short'`: equivalent to `'yMdjm'` (e.g. `9/3/2010, 12:05 PM` for `en-US`)
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {isBlank} from '../facade/lang';
|
||||
import {NgLocalization, getPluralCategory} from '../localization';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
@@ -35,7 +34,7 @@ export class I18nPluralPipe implements PipeTransform {
|
||||
constructor(private _localization: NgLocalization) {}
|
||||
|
||||
transform(value: number, pluralMap: {[count: string]: string}): string {
|
||||
if (isBlank(value)) return '';
|
||||
if (value == null) return '';
|
||||
|
||||
if (typeof pluralMap !== 'object' || pluralMap === null) {
|
||||
throw new InvalidPipeArgumentError(I18nPluralPipe, pluralMap);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
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 {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
@@ -18,7 +18,7 @@ const _NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(-(\d+))?)?$/;
|
||||
function formatNumber(
|
||||
pipe: Type<any>, locale: string, value: number | string, style: NumberFormatStyle,
|
||||
digits: string, currency: string = null, currencyAsSymbol: boolean = false): string {
|
||||
if (isBlank(value)) return null;
|
||||
if (value == null) return null;
|
||||
|
||||
// Convert strings to numbers
|
||||
value = typeof value === 'string' && NumberWrapper.isNumeric(value) ? +value : value;
|
||||
@@ -41,13 +41,13 @@ function formatNumber(
|
||||
if (parts === null) {
|
||||
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]);
|
||||
}
|
||||
if (isPresent(parts[3])) { // min fraction digits
|
||||
if (parts[3] != null) { // min fraction digits
|
||||
minFraction = NumberWrapper.parseIntAutoRadix(parts[3]);
|
||||
}
|
||||
if (isPresent(parts[5])) { // max fraction digits
|
||||
if (parts[5] != null) { // max fraction digits
|
||||
maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
import {Pipe, PipeTransform} from '@angular/core';
|
||||
import {isBlank} from '../facade/lang';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
/**
|
||||
@@ -58,7 +57,7 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
@Pipe({name: 'slice', pure: false})
|
||||
export class SlicePipe implements PipeTransform {
|
||||
transform(value: any, start: number, end?: number): any {
|
||||
if (isBlank(value)) return value;
|
||||
if (value == null) return value;
|
||||
|
||||
if (!this.supports(value)) {
|
||||
throw new InvalidPipeArgumentError(SlicePipe, value);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
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 {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
@@ -29,10 +29,7 @@ export function main() {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
TestComponent,
|
||||
ComponentUsingTestComponent,
|
||||
],
|
||||
declarations: [TestComponent],
|
||||
imports: [CommonModule],
|
||||
});
|
||||
});
|
||||
@@ -77,7 +74,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
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);
|
||||
|
||||
// INIT
|
||||
@@ -95,7 +92,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
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);
|
||||
|
||||
detectChangesAndExpectText('');
|
||||
@@ -140,12 +137,8 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should repeat over nested arrays', async(() => {
|
||||
const template = '<div>' +
|
||||
'<div template="ngFor let item of items">' +
|
||||
'<div template="ngFor let subitem of item">' +
|
||||
'{{subitem}}-{{item.length}};' +
|
||||
'</div>|' +
|
||||
'</div>' +
|
||||
const template = '<div *ngFor="let item of items">' +
|
||||
'<div *ngFor="let subitem of item">{{subitem}}-{{item.length}};</div>|' +
|
||||
'</div>';
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
@@ -157,10 +150,9 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should repeat over nested arrays with no intermediate element', async(() => {
|
||||
const template = '<div><template ngFor let-item [ngForOf]="items">' +
|
||||
'<div template="ngFor let subitem of item">' +
|
||||
'{{subitem}}-{{item.length}};' +
|
||||
'</div></template></div>';
|
||||
const template = '<div *ngFor="let item of items">' +
|
||||
'<div *ngFor="let subitem of item">{{subitem}}-{{item.length}};</div>' +
|
||||
'</div>';
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
getComponent().items = [['a', 'b'], ['c']];
|
||||
@@ -170,10 +162,11 @@ export function main() {
|
||||
detectChangesAndExpectText('e-1;f-2;g-2;');
|
||||
}));
|
||||
|
||||
it('should repeat over nested ngIf that are the last node in the ngFor temlate', async(() => {
|
||||
const template =
|
||||
`<div><template ngFor let-item [ngForOf]="items" let-i="index"><div>{{i}}|</div>` +
|
||||
`<div *ngIf="i % 2 == 0">even|</div></template></div>`;
|
||||
it('should repeat over nested ngIf that are the last node in the ngFor template', async(() => {
|
||||
const template = `<div *ngFor="let item of items; let i=index">` +
|
||||
`<div>{{i}}|</div>` +
|
||||
`<div *ngIf="i % 2 == 0">even|</div>` +
|
||||
`</div>`;
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
@@ -189,8 +182,7 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should display indices correctly', async(() => {
|
||||
const template =
|
||||
'<div><span template="ngFor: let item of items; let i=index">{{i.toString()}}</span></div>';
|
||||
const template = '<span *ngFor ="let item of items; let i=index">{{i.toString()}}</span>';
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
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(() => {
|
||||
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);
|
||||
|
||||
getComponent().items = [0, 1, 2];
|
||||
@@ -214,7 +206,7 @@ export function main() {
|
||||
|
||||
it('should display last item correctly', async(() => {
|
||||
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);
|
||||
|
||||
getComponent().items = [0, 1, 2];
|
||||
@@ -226,7 +218,7 @@ export function main() {
|
||||
|
||||
it('should display even items correctly', async(() => {
|
||||
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);
|
||||
|
||||
getComponent().items = [0, 1, 2];
|
||||
@@ -238,7 +230,7 @@ export function main() {
|
||||
|
||||
it('should display odd items correctly', async(() => {
|
||||
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);
|
||||
|
||||
getComponent().items = [0, 1, 2, 3];
|
||||
@@ -249,55 +241,38 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should allow to use a custom template', async(() => {
|
||||
const tcTemplate =
|
||||
'<ul><template ngFor [ngForOf]="items" [ngForTemplate]="contentTpl"></template></ul>';
|
||||
TestBed.overrideComponent(TestComponent, {set: {template: tcTemplate}});
|
||||
const cutTemplate =
|
||||
'<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'];
|
||||
const template =
|
||||
'<ng-container *ngFor="let item of items; template: tpl"></ng-container>' +
|
||||
'<template let-item let-i="index" #tpl><p>{{i}}: {{item}};</p></template>';
|
||||
fixture = createTestComponent(template);
|
||||
getComponent().items = ['a', 'b', 'c'];
|
||||
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(() => {
|
||||
const testTemplate = `<ul><template ngFor let-item [ngForOf]="items"
|
||||
[ngForTemplate]="contentTpl" let-i="index">{{i}}: {{item}};</template></ul>`;
|
||||
TestBed.overrideComponent(TestComponent, {set: {template: testTemplate}});
|
||||
const cutTemplate =
|
||||
'<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'];
|
||||
const template =
|
||||
`<ul><ng-container *ngFor="let item of items; template: null; let i=index">{{i}}: {{item}};</ng-container></ul>`;
|
||||
fixture = createTestComponent(template);
|
||||
getComponent().items = ['a', 'b', 'c'];
|
||||
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(() => {
|
||||
const testTemplate = `<ul><template ngFor let-item [ngForOf]="items"
|
||||
[ngForTemplate]="contentTpl" let-i="index">{{i}}=> {{item}};</template></ul>`;
|
||||
TestBed.overrideComponent(TestComponent, {set: {template: testTemplate}});
|
||||
const cutTemplate =
|
||||
'<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'];
|
||||
const template =
|
||||
'<ng-container *ngFor="let item of items; template: tpl">{{i}};</ng-container>' +
|
||||
'<template let-item let-i="index" #tpl>{{i}}: {{item}};</template>';
|
||||
fixture = createTestComponent(template);
|
||||
getComponent().items = ['a', 'b', 'c'];
|
||||
fixture.detectChanges();
|
||||
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
|
||||
detectChangesAndExpectText('0: a;1: b;2: c;');
|
||||
}));
|
||||
|
||||
describe('track by', () => {
|
||||
it('should console.warn if trackBy is not a function', async(() => {
|
||||
// TODO(vicb): expect a warning message when we have a proper log service
|
||||
const template =
|
||||
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="value"></template>`;
|
||||
const template = `<p *ngFor="let item of items; trackBy: value"></p>`;
|
||||
fixture = createTestComponent(template);
|
||||
fixture.componentInstance.value = 0;
|
||||
fixture.detectChanges();
|
||||
@@ -305,8 +280,7 @@ export function main() {
|
||||
|
||||
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
|
||||
const template =
|
||||
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="value">{{ item }}</template>`;
|
||||
const template = `<p *ngFor="let item of items; trackBy: value">{{ item }}</p>`;
|
||||
fixture = createTestComponent(template);
|
||||
fixture.componentInstance.items = ['a', 'b', 'c'];
|
||||
fixture.componentInstance.value = null;
|
||||
@@ -317,7 +291,7 @@ export function main() {
|
||||
|
||||
it('should set the context to the component instance', async(() => {
|
||||
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);
|
||||
|
||||
thisArg = null;
|
||||
@@ -327,9 +301,7 @@ export function main() {
|
||||
|
||||
it('should not replace tracked items', async(() => {
|
||||
const template =
|
||||
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById" let-i="index">
|
||||
<p>{{items[i]}}</p>
|
||||
</template>`;
|
||||
`<p *ngFor="let item of items; trackBy: trackById; let i=index">{{items[i]}}</p>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
const buildItemList = () => {
|
||||
@@ -345,7 +317,7 @@ export function main() {
|
||||
|
||||
it('should update implicit local variable on view', async(() => {
|
||||
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);
|
||||
|
||||
getComponent().items = [{'id': 'a', 'color': 'blue'}];
|
||||
@@ -357,7 +329,7 @@ export function main() {
|
||||
|
||||
it('should move items around and keep them updated ', async(() => {
|
||||
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);
|
||||
|
||||
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(() => {
|
||||
const template =
|
||||
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByIndex">{{item}}</template></div>`;
|
||||
const template = `<div *ngFor="let item of items; trackBy: trackByIndex">{{item}}</div>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
getComponent().items = ['a', 'b', 'c', 'd'];
|
||||
@@ -389,7 +360,6 @@ class Foo {
|
||||
|
||||
@Component({selector: 'test-cmp', template: ''})
|
||||
class TestComponent {
|
||||
@ContentChild(TemplateRef) contentTpl: TemplateRef<Object>;
|
||||
value: any;
|
||||
items: any[] = [1, 2];
|
||||
trackById(index: number, item: any): string { return item['id']; }
|
||||
@@ -397,12 +367,7 @@ class TestComponent {
|
||||
trackByContext(): void { thisArg = this; }
|
||||
}
|
||||
|
||||
@Component({selector: 'outer-cmp', template: ''})
|
||||
class ComponentUsingTestComponent {
|
||||
items: any = [1, 2];
|
||||
}
|
||||
|
||||
const TEMPLATE = '<div><span template="ngFor let item of items">{{item.toString()}};</span></div>';
|
||||
const TEMPLATE = '<div><span *ngFor="let item of items">{{item.toString()}};</span></div>';
|
||||
|
||||
function createTestComponent(template: string = TEMPLATE): ComponentFixture<TestComponent> {
|
||||
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component} from '@angular/core';
|
||||
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 {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
@@ -28,131 +29,114 @@ export function main() {
|
||||
});
|
||||
|
||||
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.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');
|
||||
}));
|
||||
|
||||
it('should work in a template element', async(() => {
|
||||
const template =
|
||||
'<div><template [ngIf]="booleanCondition"><span>hello2</span></template></div>';
|
||||
|
||||
it('should work on a template element', async(() => {
|
||||
const template = '<template [ngIf]="booleanCondition">hello2</template>';
|
||||
fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
expect(getDOM().querySelectorAll(fixture.nativeElement, 'span').length).toEqual(1);
|
||||
expect(fixture.nativeElement).toHaveText('hello2');
|
||||
}));
|
||||
|
||||
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);
|
||||
getComponent().booleanCondition = false;
|
||||
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('');
|
||||
|
||||
getComponent().booleanCondition = true;
|
||||
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');
|
||||
|
||||
getComponent().booleanCondition = false;
|
||||
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('');
|
||||
}));
|
||||
|
||||
it('should handle nested if correctly', async(() => {
|
||||
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);
|
||||
|
||||
getComponent().booleanCondition = false;
|
||||
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('');
|
||||
|
||||
getComponent().booleanCondition = true;
|
||||
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');
|
||||
|
||||
getComponent().nestedBooleanCondition = false;
|
||||
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('');
|
||||
|
||||
getComponent().nestedBooleanCondition = true;
|
||||
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');
|
||||
|
||||
getComponent().booleanCondition = false;
|
||||
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('');
|
||||
}));
|
||||
|
||||
it('should update several nodes with if', async(() => {
|
||||
const template = '<div>' +
|
||||
'<span template="ngIf numberCondition + 1 >= 2">helloNumber</span>' +
|
||||
'<span template="ngIf stringCondition == \'foo\'">helloString</span>' +
|
||||
'<span template="ngIf functionCondition(stringCondition, numberCondition)">helloFunction</span>' +
|
||||
'</div>';
|
||||
const template = '<span *ngIf="numberCondition + 1 >= 2">helloNumber</span>' +
|
||||
'<span *ngIf="stringCondition == \'foo\'">helloString</span>' +
|
||||
'<span *ngIf="functionCondition(stringCondition, numberCondition)">helloFunction</span>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
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))
|
||||
.toEqual('helloNumberhelloStringhelloFunction');
|
||||
|
||||
getComponent().numberCondition = 0;
|
||||
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');
|
||||
|
||||
getComponent().numberCondition = 1;
|
||||
getComponent().stringCondition = 'bar';
|
||||
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');
|
||||
}));
|
||||
|
||||
it('should not add the element twice if the condition goes from true to true (JS)',
|
||||
async(() => {
|
||||
const template = '<div><span template="ngIf numberCondition">hello</span></div>';
|
||||
it('should not add the element twice if the condition goes from truthy to truthy', async(() => {
|
||||
const template = '<span *ngIf="numberCondition">hello</span>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
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');
|
||||
|
||||
getComponent().numberCondition = 2;
|
||||
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');
|
||||
}));
|
||||
|
||||
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);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('switch', () => {
|
||||
describe('ngPlural', () => {
|
||||
let fixture: ComponentFixture<any>;
|
||||
|
||||
function getComponent(): TestComponent { return fixture.componentInstance; }
|
||||
@@ -33,10 +33,25 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should display the template according to the exact value', async(() => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
const template = '<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="=0"><li>you have no messages.</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>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
@@ -51,10 +66,9 @@ export function main() {
|
||||
// https://github.com/angular/angular/issues/9868
|
||||
// https://github.com/angular/angular/issues/9882
|
||||
it('should not throw when ngPluralCase contains expressions', async(() => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
const template = '<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="=0"><li>{{ switchValue }}</li></template>' +
|
||||
'</ul></div>';
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
@@ -64,11 +78,10 @@ export function main() {
|
||||
|
||||
|
||||
it('should be applicable to <ng-container> elements', async(() => {
|
||||
const template = '<div>' +
|
||||
'<ng-container [ngPlural]="switchValue">' +
|
||||
const template = '<ng-container [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="=0">you have no messages.</template>' +
|
||||
'<template ngPluralCase="=1">you have one message.</template>' +
|
||||
'</ng-container></div>';
|
||||
'</ng-container>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
@@ -80,11 +93,10 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should display the template according to the category', async(() => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
const template = '<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
||||
'<template ngPluralCase="many"><li>you have many messages.</li></template>' +
|
||||
'</ul></div>';
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
@@ -96,11 +108,10 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should default to other when no matches are found', async(() => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
const template = '<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
||||
'<template ngPluralCase="other"><li>default message.</li></template>' +
|
||||
'</ul></div>';
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
@@ -109,11 +120,10 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should prioritize value matches over category matches', async(() => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
const template = '<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
|
||||
'<template ngPluralCase="=2">you have two messages.</template>' +
|
||||
'</ul></div>';
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
|
||||
@@ -29,22 +29,19 @@ export function main() {
|
||||
it('should add styles specified in an object literal', async(() => {
|
||||
const template = `<div [ngStyle]="{'max-width': '40px'}"></div>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
|
||||
}));
|
||||
|
||||
it('should add and change styles specified in an object expression', async(() => {
|
||||
const template = `<div [ngStyle]="expr"></div>`;
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
let expr: {[k: string]: string};
|
||||
|
||||
getComponent().expr = {'max-width': '40px'};
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
|
||||
|
||||
expr = getComponent().expr;
|
||||
let expr = getComponent().expr;
|
||||
expr['max-width'] = '30%';
|
||||
fixture.detectChanges();
|
||||
expectNativeEl(fixture).toHaveCssStyle({'max-width': '30%'});
|
||||
|
||||
@@ -33,11 +33,10 @@ export function main() {
|
||||
|
||||
describe('switch value changes', () => {
|
||||
it('should switch amongst when values', () => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<template ngSwitchCase="a"><li>when a</li></template>' +
|
||||
'<template ngSwitchCase="b"><li>when b</li></template>' +
|
||||
'</ul></div>';
|
||||
const template = '<ul [ngSwitch]="switchValue">' +
|
||||
'<li *ngSwitchCase="\'a\'">when a</li>' +
|
||||
'<li *ngSwitchCase="\'b\'">when b</li>' +
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
@@ -51,11 +50,10 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should switch amongst when values with fallback to default', () => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<li template="ngSwitchCase \'a\'">when a</li>' +
|
||||
'<li template="ngSwitchDefault">when default</li>' +
|
||||
'</ul></div>';
|
||||
const template = '<ul [ngSwitch]="switchValue">' +
|
||||
'<li *ngSwitchCase="\'a\'">when a</li>' +
|
||||
'<li *ngSwitchDefault>when default</li>' +
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
detectChangesAndExpectText('when default');
|
||||
@@ -71,15 +69,14 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should support multiple whens with the same value', () => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<template ngSwitchCase="a"><li>when a1;</li></template>' +
|
||||
'<template ngSwitchCase="b"><li>when b1;</li></template>' +
|
||||
'<template ngSwitchCase="a"><li>when a2;</li></template>' +
|
||||
'<template ngSwitchCase="b"><li>when b2;</li></template>' +
|
||||
'<template ngSwitchDefault><li>when default1;</li></template>' +
|
||||
'<template ngSwitchDefault><li>when default2;</li></template>' +
|
||||
'</ul></div>';
|
||||
const template = '<ul [ngSwitch]="switchValue">' +
|
||||
'<li *ngSwitchCase="\'a\'">when a1;</li>' +
|
||||
'<li *ngSwitchCase="\'b\'">when b1;</li>' +
|
||||
'<li *ngSwitchCase="\'a\'">when a2;</li>' +
|
||||
'<li *ngSwitchCase="\'b\'">when b2;</li>' +
|
||||
'<li *ngSwitchDefault>when default1;</li>' +
|
||||
'<li *ngSwitchDefault>when default2;</li>' +
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
detectChangesAndExpectText('when default1;when default2;');
|
||||
@@ -94,12 +91,11 @@ export function main() {
|
||||
|
||||
describe('when values changes', () => {
|
||||
it('should switch amongst when values', () => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<template [ngSwitchCase]="when1"><li>when 1;</li></template>' +
|
||||
'<template [ngSwitchCase]="when2"><li>when 2;</li></template>' +
|
||||
'<template ngSwitchDefault><li>when default;</li></template>' +
|
||||
'</ul></div>';
|
||||
const template = '<ul [ngSwitch]="switchValue">' +
|
||||
'<li *ngSwitchCase="when1">when 1;</li>' +
|
||||
'<li *ngSwitchCase="when2">when 2;</li>' +
|
||||
'<li *ngSwitchDefault>when default;</li>' +
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
getComponent().when1 = 'a';
|
||||
@@ -148,11 +144,10 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should create the default case if there is no other case', () => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<template ngSwitchDefault><li>when default1;</li></template>' +
|
||||
'<template ngSwitchDefault><li>when default2;</li></template>' +
|
||||
'</ul></div>';
|
||||
const template = '<ul [ngSwitch]="switchValue">' +
|
||||
'<li *ngSwitchDefault>when default1;</li>' +
|
||||
'<li *ngSwitchDefault>when default2;</li>' +
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
detectChangesAndExpectText('when default1;when default2;');
|
||||
@@ -160,15 +155,14 @@ export function main() {
|
||||
});
|
||||
|
||||
it('should allow defaults before cases', () => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngSwitch]="switchValue">' +
|
||||
'<template ngSwitchDefault><li>when default1;</li></template>' +
|
||||
'<template ngSwitchDefault><li>when default2;</li></template>' +
|
||||
'<template ngSwitchCase="a"><li>when a1;</li></template>' +
|
||||
'<template ngSwitchCase="b"><li>when b1;</li></template>' +
|
||||
'<template ngSwitchCase="a"><li>when a2;</li></template>' +
|
||||
'<template ngSwitchCase="b"><li>when b2;</li></template>' +
|
||||
'</ul></div>';
|
||||
const template = '<ul [ngSwitch]="switchValue">' +
|
||||
'<li *ngSwitchDefault>when default1;</li>' +
|
||||
'<li *ngSwitchDefault>when default2;</li>' +
|
||||
'<li *ngSwitchCase="\'a\'">when a1;</li>' +
|
||||
'<li *ngSwitchCase="\'b\'">when b1;</li>' +
|
||||
'<li *ngSwitchCase="\'a\'">when a2;</li>' +
|
||||
'<li *ngSwitchCase="\'b\'">when b2;</li>' +
|
||||
'</ul>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
detectChangesAndExpectText('when default1;when default2;');
|
||||
|
||||
@@ -34,29 +34,22 @@ export function main() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should do nothing if templateRef is null', async(() => {
|
||||
const template = `<template [ngTemplateOutlet]="null"></template>`;
|
||||
it('should do nothing if templateRef is `null`', async(() => {
|
||||
const template = `<ng-container [ngTemplateOutlet]="null"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
detectChangesAndExpectText('');
|
||||
}));
|
||||
|
||||
it('should insert content specified by TemplateRef', async(() => {
|
||||
const template =
|
||||
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
|
||||
const template = `<template #tpl>foo</template>` +
|
||||
`<ng-container [ngTemplateOutlet]="tpl"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
detectChangesAndExpectText('');
|
||||
|
||||
const refs = fixture.debugElement.children[0].references['refs'];
|
||||
|
||||
setTplRef(refs.tplRefs.first);
|
||||
detectChangesAndExpectText('foo');
|
||||
}));
|
||||
|
||||
it('should clear content if TemplateRef becomes null', async(() => {
|
||||
const template =
|
||||
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
|
||||
it('should clear content if TemplateRef becomes `null`', async(() => {
|
||||
const template = `<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs>` +
|
||||
`<ng-container [ngTemplateOutlet]="currentTplRef"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
fixture.detectChanges();
|
||||
const refs = fixture.debugElement.children[0].references['refs'];
|
||||
@@ -70,7 +63,8 @@ export function main() {
|
||||
|
||||
it('should swap content if TemplateRef changes', async(() => {
|
||||
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.detectChanges();
|
||||
@@ -83,70 +77,47 @@ export function main() {
|
||||
detectChangesAndExpectText('bar');
|
||||
}));
|
||||
|
||||
it('should display template if context is null', async(() => {
|
||||
const template =
|
||||
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="null"></template>`;
|
||||
it('should display template if context is `null`', async(() => {
|
||||
const template = `<template #tpl>foo</template>` +
|
||||
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="null"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
detectChangesAndExpectText('');
|
||||
|
||||
const refs = fixture.debugElement.children[0].references['refs'];
|
||||
|
||||
setTplRef(refs.tplRefs.first);
|
||||
detectChangesAndExpectText('foo');
|
||||
}));
|
||||
|
||||
it('should reflect initial context and changes', async(() => {
|
||||
const template =
|
||||
`<tpl-refs #refs="tplRefs"><template let-foo="foo"><span>{{foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
|
||||
const template = `<template let-foo="foo" #tpl>{{foo}}</template>` +
|
||||
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
const refs = fixture.debugElement.children[0].references['refs'];
|
||||
setTplRef(refs.tplRefs.first);
|
||||
|
||||
detectChangesAndExpectText('bar');
|
||||
|
||||
fixture.componentInstance.context.foo = 'alter-bar';
|
||||
|
||||
detectChangesAndExpectText('alter-bar');
|
||||
}));
|
||||
|
||||
it('should reflect user defined $implicit property in the context', async(() => {
|
||||
const template =
|
||||
`<tpl-refs #refs="tplRefs"><template let-ctx><span>{{ctx.foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
|
||||
it('should reflect user defined `$implicit` property in the context', async(() => {
|
||||
const template = `<template let-ctx #tpl>{{ctx.foo}}</template>` +
|
||||
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
const refs = fixture.debugElement.children[0].references['refs'];
|
||||
setTplRef(refs.tplRefs.first);
|
||||
|
||||
fixture.componentInstance.context = {$implicit: fixture.componentInstance.context};
|
||||
detectChangesAndExpectText('bar');
|
||||
fixture.componentInstance.context = {$implicit: {foo: 'bra'}};
|
||||
detectChangesAndExpectText('bra');
|
||||
}));
|
||||
|
||||
it('should reflect context re-binding', async(() => {
|
||||
const template =
|
||||
`<tpl-refs #refs="tplRefs"><template let-shawshank="shawshank"><span>{{shawshank}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
|
||||
const template = `<template let-shawshank="shawshank" #tpl>{{shawshank}}</template>` +
|
||||
`<ng-container [ngTemplateOutlet]="tpl" [ngOutletContext]="context"></ng-container>`;
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
const refs = fixture.debugElement.children[0].references['refs'];
|
||||
setTplRef(refs.tplRefs.first);
|
||||
fixture.componentInstance.context = {shawshank: 'brooks'};
|
||||
|
||||
detectChangesAndExpectText('brooks');
|
||||
|
||||
fixture.componentInstance.context = {shawshank: 'was here'};
|
||||
|
||||
detectChangesAndExpectText('was here');
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Directive({selector: 'tpl-refs', exportAs: 'tplRefs'})
|
||||
class CaptureTplRefs {
|
||||
@ContentChildren(TemplateRef) tplRefs: QueryList<TemplateRef<any>>;
|
||||
@@ -162,4 +133,4 @@ function createTestComponent(template: string): ComponentFixture<TestComponent>
|
||||
return TestBed.overrideComponent(TestComponent, {set: {template: template}})
|
||||
.configureTestingModule({schemas: [NO_ERRORS_SCHEMA]})
|
||||
.createComponent(TestComponent);
|
||||
}
|
||||
}
|
||||
|
||||
+23
@@ -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 {
|
||||
}
|
||||
+4
-1
@@ -16,7 +16,10 @@ export class LazyFeatureComponent {
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild([
|
||||
{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]
|
||||
})
|
||||
|
||||
@@ -191,6 +191,8 @@ function lazyRoutesTest() {
|
||||
'./lazy.module#LazyModule': 'lazy.module.ts',
|
||||
'./feature/feature.module#FeatureModule': 'feature/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',
|
||||
'./default.module': 'feature2/default.module.ts',
|
||||
'feature/feature.module#FeatureModule': 'feature/feature.module.ts'
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"ng-xi18n": "./src/extract_i18n.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/tsc-wrapped": "0.5.0",
|
||||
"@angular/tsc-wrapped": "0.5.1",
|
||||
"reflect-metadata": "^0.1.2",
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
|
||||
@@ -36,29 +36,6 @@ export class CodeGenerator {
|
||||
public host: ts.CompilerHost, private compiler: compiler.AotCompiler,
|
||||
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> {
|
||||
return this.compiler
|
||||
.compileAll(this.program.getSourceFiles().map(
|
||||
@@ -66,7 +43,7 @@ export class CodeGenerator {
|
||||
.then(generatedModules => {
|
||||
generatedModules.forEach(generatedModule => {
|
||||
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 :
|
||||
PREAMBLE + generatedModule.source;
|
||||
this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]);
|
||||
|
||||
@@ -261,6 +261,29 @@ export class CompilerHost implements AotCompilerHost {
|
||||
this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
|
||||
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 {
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
import {AotCompilerHost, StaticReflector, StaticSymbol} from '@angular/compiler';
|
||||
import {NgModule} from '@angular/core';
|
||||
|
||||
|
||||
|
||||
// We cannot depend directly to @angular/router.
|
||||
type Route = any;
|
||||
const ROUTER_MODULE_PATH = '@angular/router/src/router_config_loader';
|
||||
@@ -63,29 +61,37 @@ export function listLazyRoutesOfModule(
|
||||
const className = entryRouteDef.className;
|
||||
|
||||
// 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 lazyRoutes: LazyRoute[] =
|
||||
_extractLazyRoutesFromStaticModule(staticSymbol, reflector, host, ROUTES);
|
||||
const routes: LazyRouteMap = {};
|
||||
_extractLazyRoutesFromStaticModule(appStaticSymbol, reflector, host, ROUTES);
|
||||
|
||||
lazyRoutes.forEach((lazyRoute: LazyRoute) => {
|
||||
const route: string = lazyRoute.routeDef.toString();
|
||||
_assertRoute(routes, lazyRoute);
|
||||
routes[route] = lazyRoute;
|
||||
const allLazyRoutes = lazyRoutes.reduce(
|
||||
function includeLazyRouteAndSubRoutes(allRoutes: LazyRouteMap, lazyRoute: LazyRoute):
|
||||
LazyRouteMap {
|
||||
const route: string = lazyRoute.routeDef.toString();
|
||||
_assertRoute(allRoutes, lazyRoute);
|
||||
allRoutes[route] = lazyRoute;
|
||||
|
||||
const lazyModuleSymbol = reflector.findDeclaration(
|
||||
lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default');
|
||||
const subRoutes = _extractLazyRoutesFromStaticModule(lazyModuleSymbol, reflector, host, ROUTES);
|
||||
// StaticReflector does not support discovering annotations like `NgModule` on default
|
||||
// exports
|
||||
// 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.
|
||||
subRoutes.forEach(subRoute => {
|
||||
_assertRoute(routes, subRoute);
|
||||
routes[subRoute.routeDef.toString()] = subRoute;
|
||||
});
|
||||
});
|
||||
const lazyModuleSymbol = reflector.findDeclaration(
|
||||
lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default');
|
||||
|
||||
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[] {
|
||||
return routes.reduce((m, r) => {
|
||||
if (r.loadChildren) {
|
||||
if (r.loadChildren && typeof r.loadChildren === 'string') {
|
||||
return m.concat(r.loadChildren);
|
||||
} else if (Array.isArray(r)) {
|
||||
return m.concat(_collectLoadChildren(r));
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {SchemaMetadata} from '@angular/core';
|
||||
|
||||
import {AnimationCompiler} from '../animation/animation_compiler';
|
||||
import {AnimationParser} from '../animation/animation_parser';
|
||||
import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, CompileTypeSummary, createHostComponentMeta, identifierModuleUrl, identifierName} from '../compile_metadata';
|
||||
|
||||
@@ -10,8 +10,8 @@ import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, Ho
|
||||
|
||||
import {ReflectorReader} from '../private_import_core';
|
||||
|
||||
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
|
||||
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
|
||||
import {StaticSymbol} from './static_symbol';
|
||||
import {StaticSymbolResolver} from './static_symbol_resolver';
|
||||
|
||||
const ANGULAR_IMPORT_LOCATIONS = {
|
||||
coreDecorators: '@angular/core/src/metadata',
|
||||
@@ -310,6 +310,7 @@ export class StaticReflector implements ReflectorReader {
|
||||
if (value && (depth != 0 || value.__symbolic != 'error')) {
|
||||
const parameters: string[] = targetFunction['parameters'];
|
||||
const defaults: any[] = targetFunction.defaults;
|
||||
args = args.map(arg => simplifyInContext(context, arg, depth + 1));
|
||||
if (defaults && defaults.length > args.length) {
|
||||
args.push(...defaults.slice(args.length).map((value: any) => simplify(value)));
|
||||
}
|
||||
@@ -499,15 +500,15 @@ export class StaticReflector implements ReflectorReader {
|
||||
return context;
|
||||
}
|
||||
const argExpressions: any[] = expression['arguments'] || [];
|
||||
const args =
|
||||
argExpressions.map(arg => simplifyInContext(context, arg, depth + 1));
|
||||
let converter = self.conversionMap.get(staticSymbol);
|
||||
if (converter) {
|
||||
const args =
|
||||
argExpressions.map(arg => simplifyInContext(context, arg, depth + 1));
|
||||
return converter(context, args);
|
||||
} else {
|
||||
// Determine if the function is one we can simplify.
|
||||
const targetFunction = resolveReferenceValue(staticSymbol);
|
||||
return simplifyCall(staticSymbol, targetFunction, args);
|
||||
return simplifyCall(staticSymbol, targetFunction, argExpressions);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -651,10 +652,6 @@ class PopulatedScope extends BindingScope {
|
||||
}
|
||||
}
|
||||
|
||||
function sameSymbol(a: StaticSymbol, b: StaticSymbol): boolean {
|
||||
return a === b;
|
||||
}
|
||||
|
||||
function shouldIgnore(value: any): boolean {
|
||||
return value && value.__symbolic == 'ignore';
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 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 {CompileSummaryKind, CompileTypeSummary} from '../compile_metadata';
|
||||
|
||||
import {Summary, SummaryResolver} from '../summary_resolver';
|
||||
|
||||
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
* 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 {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 {ValueTransformer, visitValue} from '../util';
|
||||
|
||||
import {GeneratedFile} from './generated_file';
|
||||
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
|
||||
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
|
||||
|
||||
const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||
|
||||
|
||||
export interface AotSummarySerializerHost {
|
||||
/**
|
||||
* Returns the output file path of a source file.
|
||||
@@ -180,4 +180,4 @@ class Deserializer extends ValueTransformer {
|
||||
return super.visitStringMap(map, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* 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 {Identifiers, createIdentifier} from '../identifiers';
|
||||
import {ClassBuilder} from '../output/class_builder';
|
||||
import * as o from '../output/output_ast';
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
|
||||
import * as cdAst from '../expression_parser/ast';
|
||||
import {isBlank, isPresent} from '../facade/lang';
|
||||
import {isBlank} from '../facade/lang';
|
||||
import {Identifiers, createIdentifier} from '../identifiers';
|
||||
import {ClassBuilder} from '../output/class_builder';
|
||||
import * as o from '../output/output_ast';
|
||||
@@ -338,7 +338,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
const receiver = this.visit(ast.receiver, _Mode.Expression);
|
||||
if (receiver === this._implicitReceiver) {
|
||||
const varExpr = this._getLocal(ast.name);
|
||||
if (isPresent(varExpr)) {
|
||||
if (varExpr) {
|
||||
result = varExpr.callFn(args);
|
||||
}
|
||||
}
|
||||
@@ -374,7 +374,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
|
||||
const receiver: o.Expression = this.visit(ast.receiver, _Mode.Expression);
|
||||
if (receiver === this._implicitReceiver) {
|
||||
const varExpr = this._getLocal(ast.name);
|
||||
if (isPresent(varExpr)) {
|
||||
if (varExpr) {
|
||||
throw new Error('Cannot assign to a reference or variable!');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
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 {isBlank, isPresent, stringify} from './facade/lang';
|
||||
import {CompilerInjectable} from './injectable';
|
||||
|
||||
@@ -14,8 +14,6 @@ import {CompilerInjectable} from './injectable';
|
||||
import {ReflectorReader, reflector} from './private_import_core';
|
||||
import {splitAtColon} from './util';
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Resolve a `Type` for {@link Directive}.
|
||||
*
|
||||
|
||||
@@ -128,7 +128,6 @@ class DirectiveWrapperBuilder implements ClassBuilder {
|
||||
new o.ClassMethod('ngOnDestroy', [], this.destroyStmts),
|
||||
];
|
||||
|
||||
|
||||
const fields: o.ClassField[] = [
|
||||
new o.ClassField(EVENT_HANDLER_FIELD_NAME, o.FUNCTION_TYPE, [o.StmtModifier.Private]),
|
||||
new o.ClassField(CONTEXT_FIELD_NAME, o.importType(this.dirMeta.type)),
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import * as chars from '../chars';
|
||||
import {NumberWrapper, isPresent} from '../facade/lang';
|
||||
import {NumberWrapper} from '../facade/lang';
|
||||
import {CompilerInjectable} from '../injectable';
|
||||
|
||||
export enum TokenType {
|
||||
@@ -241,7 +241,7 @@ class _Scanner {
|
||||
this.advance();
|
||||
str += two;
|
||||
}
|
||||
if (isPresent(threeCode) && this.peek == threeCode) {
|
||||
if (threeCode != null && this.peek == threeCode) {
|
||||
this.advance();
|
||||
str += three;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ export class Extractor {
|
||||
|
||||
extract(rootFiles: string[]): Promise<MessageBundle> {
|
||||
const programSymbols = extractProgramSymbols(this.staticSymbolResolver, rootFiles, this.host);
|
||||
const {ngModuleByPipeOrDirective, files, ngModules} =
|
||||
const {files, ngModules} =
|
||||
analyzeAndValidateNgModules(programSymbols, this.host, this.metadataResolver);
|
||||
return Promise
|
||||
.all(ngModules.map(
|
||||
|
||||
@@ -53,20 +53,22 @@ enum _VisitorMode {
|
||||
* @internal
|
||||
*/
|
||||
class _Visitor implements html.Visitor {
|
||||
private _depth: number;
|
||||
|
||||
// <el i18n>...</el>
|
||||
private _inI18nNode: boolean;
|
||||
private _depth: number;
|
||||
private _inImplicitNode: boolean;
|
||||
|
||||
// <!--i18n-->...<!--/i18n-->
|
||||
private _inI18nBlock: boolean;
|
||||
private _blockMeaningAndDesc: string;
|
||||
private _blockChildren: html.Node[];
|
||||
private _blockStartDepth: number;
|
||||
private _inI18nBlock: boolean;
|
||||
|
||||
// {<icu message>}
|
||||
private _inIcu: boolean;
|
||||
|
||||
// set to void 0 when not in a section
|
||||
private _msgCountAtSectionStart: number;
|
||||
private _errors: I18nError[];
|
||||
private _mode: _VisitorMode;
|
||||
@@ -208,50 +210,31 @@ class _Visitor implements html.Visitor {
|
||||
this._depth++;
|
||||
const wasInI18nNode = this._inI18nNode;
|
||||
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
|
||||
// message
|
||||
// Extract:
|
||||
// - top level nodes with the (implicit) "i18n" attribute if not already in a section
|
||||
// - ICU messages
|
||||
const i18nAttr = _getI18nAttr(el);
|
||||
const i18nMeta = i18nAttr ? i18nAttr.value : '';
|
||||
const isImplicit = this._implicitTags.some(tag => el.name === tag) && !this._inIcu &&
|
||||
!this._isInTranslatableSection;
|
||||
const isTopLevelImplicit = !wasInImplicitNode && isImplicit;
|
||||
this._inImplicitNode = this._inImplicitNode || isImplicit;
|
||||
this._inImplicitNode = wasInImplicitNode || isImplicit;
|
||||
|
||||
if (!this._isInTranslatableSection && !this._inIcu) {
|
||||
if (i18nAttr) {
|
||||
// explicit translation
|
||||
if (i18nAttr || isTopLevelImplicit) {
|
||||
this._inI18nNode = true;
|
||||
const message = this._addMessage(el.children, i18nAttr.value);
|
||||
childNodes = this._translateMessage(el, message);
|
||||
} else if (isTopLevelImplicit) {
|
||||
// implicit translation
|
||||
this._inI18nNode = true;
|
||||
const message = this._addMessage(el.children);
|
||||
childNodes = this._translateMessage(el, message);
|
||||
const message = this._addMessage(el.children, i18nMeta);
|
||||
translatedChildNodes = this._translateMessage(el, message);
|
||||
}
|
||||
|
||||
if (this._mode == _VisitorMode.Extract) {
|
||||
const isTranslatable = i18nAttr || isTopLevelImplicit;
|
||||
if (isTranslatable) {
|
||||
this._openTranslatableSection(el);
|
||||
}
|
||||
if (isTranslatable) this._openTranslatableSection(el);
|
||||
html.visitAll(this, el.children);
|
||||
if (isTranslatable) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
if (isTranslatable) this._closeTranslatableSection(el, el.children);
|
||||
}
|
||||
} else {
|
||||
if (i18nAttr || isTopLevelImplicit) {
|
||||
@@ -263,19 +246,18 @@ class _Visitor implements html.Visitor {
|
||||
// Descend into child nodes for extraction
|
||||
html.visitAll(this, el.children);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._mode == _VisitorMode.Merge) {
|
||||
// Translate attributes in ICU messages
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (this._mode === _VisitorMode.Merge) {
|
||||
const visitNodes = translatedChildNodes || el.children;
|
||||
visitNodes.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 later in this loop when the block closes (i.e. on `<!-- /i18n -->`)
|
||||
childNodes = childNodes.concat(visited);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this._visitAttributesOf(el);
|
||||
@@ -285,7 +267,6 @@ class _Visitor implements html.Visitor {
|
||||
this._inImplicitNode = wasInImplicitNode;
|
||||
|
||||
if (this._mode === _VisitorMode.Merge) {
|
||||
// There are no childNodes in translatable sections - those nodes will be replace anyway
|
||||
const translatedAttrs = this._translateAttributes(el);
|
||||
return new html.Element(
|
||||
el.name, translatedAttrs, childNodes, el.sourceSpan, el.startSourceSpan,
|
||||
@@ -432,7 +413,7 @@ class _Visitor implements html.Visitor {
|
||||
|
||||
/**
|
||||
* A translatable section could be:
|
||||
* - a translatable element,
|
||||
* - the content of translatable element,
|
||||
* - nodes between `<!-- i18n -->` and `<!-- /i18n -->` comments
|
||||
*/
|
||||
private get _isInTranslatableSection(): boolean {
|
||||
|
||||
@@ -111,8 +111,14 @@ export class PlaceholderRegistry {
|
||||
private _hashClosingTag(tag: string): string { return this._hashTag(`/${tag}`, {}, false); }
|
||||
|
||||
private _generateUniqueName(base: string): string {
|
||||
const next = this._placeHolderNameCounts[base];
|
||||
this._placeHolderNameCounts[base] = next ? next + 1 : 1;
|
||||
return next ? `${base}_${next}` : base;
|
||||
const seen = this._placeHolderNameCounts.hasOwnProperty(base);
|
||||
if (!seen) {
|
||||
this._placeHolderNameCounts[base] = 1;
|
||||
return base;
|
||||
}
|
||||
|
||||
const id = this._placeHolderNameCounts[base];
|
||||
this._placeHolderNameCounts[base] = id + 1;
|
||||
return `${base}_${id}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,26 @@
|
||||
|
||||
import * as i18n from '../i18n_ast';
|
||||
|
||||
export interface Serializer {
|
||||
write(messages: i18n.Message[]): string;
|
||||
export abstract class Serializer {
|
||||
abstract write(messages: i18n.Message[]): string;
|
||||
|
||||
load(content: string, url: string): {[msgId: string]: i18n.Node[]};
|
||||
abstract load(content: string, url: string): {[msgId: string]: i18n.Node[]};
|
||||
|
||||
digest(message: i18n.Message): string;
|
||||
abstract digest(message: i18n.Message): string;
|
||||
|
||||
// Creates a name mapper, see `PlaceholderMapper`
|
||||
// Returning `null` means that no name mapping is used.
|
||||
createNameMapper(message: i18n.Message): PlaceholderMapper { return null; }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `PlaceholderMapper` converts placeholder names from internal to serialized representation and
|
||||
* back.
|
||||
*
|
||||
* It should be used for serialization format that put constraints on the placeholder names.
|
||||
*/
|
||||
export interface PlaceholderMapper {
|
||||
toPublicName(internalName: string): string;
|
||||
|
||||
toInternalName(publicName: string): string;
|
||||
}
|
||||
@@ -27,7 +27,7 @@ const _UNIT_TAG = 'trans-unit';
|
||||
|
||||
// http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html
|
||||
// http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html
|
||||
export class Xliff implements Serializer {
|
||||
export class Xliff extends Serializer {
|
||||
write(messages: i18n.Message[]): string {
|
||||
const visitor = new _WriteVisitor();
|
||||
const visited: {[id: string]: boolean} = {};
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import {decimalDigest} from '../digest';
|
||||
import * as i18n from '../i18n_ast';
|
||||
|
||||
import {Serializer} from './serializer';
|
||||
import {PlaceholderMapper, Serializer} from './serializer';
|
||||
import * as xml from './xml_helper';
|
||||
|
||||
const _MESSAGES_TAG = 'messagebundle';
|
||||
@@ -37,7 +37,7 @@ const _DOCTYPE = `<!ELEMENT messagebundle (msg)*>
|
||||
|
||||
<!ELEMENT ex (#PCDATA)>`;
|
||||
|
||||
export class Xmb implements Serializer {
|
||||
export class Xmb extends Serializer {
|
||||
write(messages: i18n.Message[]): string {
|
||||
const exampleVisitor = new ExampleVisitor();
|
||||
const visitor = new _Visitor();
|
||||
@@ -51,6 +51,8 @@ export class Xmb implements Serializer {
|
||||
if (visited[id]) return;
|
||||
visited[id] = true;
|
||||
|
||||
const mapper = this.createNameMapper(message);
|
||||
|
||||
const attrs: {[k: string]: string} = {id};
|
||||
|
||||
if (message.description) {
|
||||
@@ -62,7 +64,8 @@ export class Xmb implements Serializer {
|
||||
}
|
||||
|
||||
rootNode.children.push(
|
||||
new xml.CR(2), new xml.Tag(_MESSAGE_TAG, attrs, visitor.serialize(message.nodes)));
|
||||
new xml.CR(2),
|
||||
new xml.Tag(_MESSAGE_TAG, attrs, visitor.serialize(message.nodes, {mapper})));
|
||||
});
|
||||
|
||||
rootNode.children.push(new xml.CR());
|
||||
@@ -82,22 +85,29 @@ export class Xmb implements Serializer {
|
||||
}
|
||||
|
||||
digest(message: i18n.Message): string { return digest(message); }
|
||||
|
||||
|
||||
createNameMapper(message: i18n.Message): PlaceholderMapper {
|
||||
return new XmbPlaceholderMapper(message);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor implements i18n.Visitor {
|
||||
visitText(text: i18n.Text, context?: any): xml.Node[] { return [new xml.Text(text.value)]; }
|
||||
visitText(text: i18n.Text, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
return [new xml.Text(text.value)];
|
||||
}
|
||||
|
||||
visitContainer(container: i18n.Container, context?: any): xml.Node[] {
|
||||
visitContainer(container: i18n.Container, ctx: any): xml.Node[] {
|
||||
const nodes: xml.Node[] = [];
|
||||
container.children.forEach((node: i18n.Node) => nodes.push(...node.visit(this)));
|
||||
container.children.forEach((node: i18n.Node) => nodes.push(...node.visit(this, ctx)));
|
||||
return nodes;
|
||||
}
|
||||
|
||||
visitIcu(icu: i18n.Icu, context?: any): xml.Node[] {
|
||||
visitIcu(icu: i18n.Icu, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
const nodes = [new xml.Text(`{${icu.expressionPlaceholder}, ${icu.type}, `)];
|
||||
|
||||
Object.keys(icu.cases).forEach((c: string) => {
|
||||
nodes.push(new xml.Text(`${c} {`), ...icu.cases[c].visit(this), new xml.Text(`} `));
|
||||
nodes.push(new xml.Text(`${c} {`), ...icu.cases[c].visit(this, ctx), new xml.Text(`} `));
|
||||
});
|
||||
|
||||
nodes.push(new xml.Text(`}`));
|
||||
@@ -105,30 +115,34 @@ class _Visitor implements i18n.Visitor {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
visitTagPlaceholder(ph: i18n.TagPlaceholder, context?: any): xml.Node[] {
|
||||
visitTagPlaceholder(ph: i18n.TagPlaceholder, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
const startEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`<${ph.tag}>`)]);
|
||||
const startTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name: ph.startName}, [startEx]);
|
||||
let name = ctx.mapper.toPublicName(ph.startName);
|
||||
const startTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name}, [startEx]);
|
||||
if (ph.isVoid) {
|
||||
// void tags have no children nor closing tags
|
||||
return [startTagPh];
|
||||
}
|
||||
|
||||
const closeEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`</${ph.tag}>`)]);
|
||||
const closeTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name: ph.closeName}, [closeEx]);
|
||||
name = ctx.mapper.toPublicName(ph.closeName);
|
||||
const closeTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name}, [closeEx]);
|
||||
|
||||
return [startTagPh, ...this.serialize(ph.children), closeTagPh];
|
||||
return [startTagPh, ...this.serialize(ph.children, ctx), closeTagPh];
|
||||
}
|
||||
|
||||
visitPlaceholder(ph: i18n.Placeholder, context?: any): xml.Node[] {
|
||||
return [new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name})];
|
||||
visitPlaceholder(ph: i18n.Placeholder, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
const name = ctx.mapper.toPublicName(ph.name);
|
||||
return [new xml.Tag(_PLACEHOLDER_TAG, {name})];
|
||||
}
|
||||
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): xml.Node[] {
|
||||
return [new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name})];
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
const name = ctx.mapper.toPublicName(ph.name);
|
||||
return [new xml.Tag(_PLACEHOLDER_TAG, {name})];
|
||||
}
|
||||
|
||||
serialize(nodes: i18n.Node[]): xml.Node[] {
|
||||
return [].concat(...nodes.map(node => node.visit(this)));
|
||||
serialize(nodes: i18n.Node[], ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
return [].concat(...nodes.map(node => node.visit(this, ctx)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,3 +172,69 @@ class ExampleVisitor implements xml.IVisitor {
|
||||
visitDeclaration(decl: xml.Declaration): void {}
|
||||
visitDoctype(doctype: xml.Doctype): void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* XMB/XTB placeholders can only contain A-Z, 0-9 and _
|
||||
*
|
||||
* Because such restrictions do not exist on placeholder names generated locally, the
|
||||
* `PlaceholderMapper` is used to convert internal names to XMB names when the XMB file is
|
||||
* serialized and back from XTB to internal names when an XTB is loaded.
|
||||
*/
|
||||
export class XmbPlaceholderMapper implements PlaceholderMapper, i18n.Visitor {
|
||||
private internalToXmb: {[k: string]: string} = {};
|
||||
private xmbToNextId: {[k: string]: number} = {};
|
||||
private xmbToInternal: {[k: string]: string} = {};
|
||||
|
||||
// create a mapping from the message
|
||||
constructor(message: i18n.Message) { message.nodes.forEach(node => node.visit(this)); }
|
||||
|
||||
toPublicName(internalName: string): string {
|
||||
return this.internalToXmb.hasOwnProperty(internalName) ? this.internalToXmb[internalName] :
|
||||
null;
|
||||
}
|
||||
|
||||
toInternalName(publicName: string): string {
|
||||
return this.xmbToInternal.hasOwnProperty(publicName) ? this.xmbToInternal[publicName] : null;
|
||||
}
|
||||
|
||||
visitText(text: i18n.Text, ctx?: any): any { return null; }
|
||||
|
||||
visitContainer(container: i18n.Container, ctx?: any): any {
|
||||
container.children.forEach(child => child.visit(this));
|
||||
}
|
||||
|
||||
visitIcu(icu: i18n.Icu, ctx?: any): any {
|
||||
Object.keys(icu.cases).forEach(k => { icu.cases[k].visit(this); });
|
||||
}
|
||||
|
||||
visitTagPlaceholder(ph: i18n.TagPlaceholder, ctx?: any): any {
|
||||
this.addPlaceholder(ph.startName);
|
||||
ph.children.forEach(child => child.visit(this));
|
||||
this.addPlaceholder(ph.closeName);
|
||||
}
|
||||
|
||||
visitPlaceholder(ph: i18n.Placeholder, ctx?: any): any { this.addPlaceholder(ph.name); }
|
||||
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, ctx?: any): any { this.addPlaceholder(ph.name); }
|
||||
|
||||
// XMB placeholders could only contains A-Z, 0-9 and _
|
||||
private addPlaceholder(internalName: string): void {
|
||||
if (!internalName || this.internalToXmb.hasOwnProperty(internalName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let xmbName = internalName.toUpperCase().replace(/[^A-Z0-9_]/g, '_');
|
||||
|
||||
if (this.xmbToInternal.hasOwnProperty(xmbName)) {
|
||||
// Create a new XMB when it has already been used
|
||||
const nextId = this.xmbToNextId[xmbName];
|
||||
this.xmbToNextId[xmbName] = nextId + 1;
|
||||
xmbName = `${xmbName}_${nextId}`;
|
||||
} else {
|
||||
this.xmbToNextId[xmbName] = 1;
|
||||
}
|
||||
|
||||
this.internalToXmb[internalName] = xmbName;
|
||||
this.xmbToInternal[xmbName] = internalName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,14 +11,14 @@ import {XmlParser} from '../../ml_parser/xml_parser';
|
||||
import * as i18n from '../i18n_ast';
|
||||
import {I18nError} from '../parse_util';
|
||||
|
||||
import {Serializer} from './serializer';
|
||||
import {digest} from './xmb';
|
||||
import {PlaceholderMapper, Serializer} from './serializer';
|
||||
import {XmbPlaceholderMapper, digest} from './xmb';
|
||||
|
||||
const _TRANSLATIONS_TAG = 'translationbundle';
|
||||
const _TRANSLATION_TAG = 'translation';
|
||||
const _PLACEHOLDER_TAG = 'ph';
|
||||
|
||||
export class Xtb implements Serializer {
|
||||
export class Xtb extends Serializer {
|
||||
write(messages: i18n.Message[]): string { throw new Error('Unsupported'); }
|
||||
|
||||
load(content: string, url: string): {[msgId: string]: i18n.Node[]} {
|
||||
@@ -43,6 +43,10 @@ export class Xtb implements Serializer {
|
||||
}
|
||||
|
||||
digest(message: i18n.Message): string { return digest(message); }
|
||||
|
||||
createNameMapper(message: i18n.Message): PlaceholderMapper {
|
||||
return new XmbPlaceholderMapper(message);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract messages as xml nodes from the xtb file
|
||||
|
||||
@@ -11,7 +11,7 @@ import {HtmlParser} from '../ml_parser/html_parser';
|
||||
|
||||
import * as i18n from './i18n_ast';
|
||||
import {I18nError} from './parse_util';
|
||||
import {Serializer} from './serializers/serializer';
|
||||
import {PlaceholderMapper, Serializer} from './serializers/serializer';
|
||||
|
||||
/**
|
||||
* A container for translated messages
|
||||
@@ -21,16 +21,20 @@ export class TranslationBundle {
|
||||
|
||||
constructor(
|
||||
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
|
||||
public digest: (m: i18n.Message) => string) {
|
||||
this._i18nToHtml = new I18nToHtmlVisitor(_i18nNodesByMsgId, digest);
|
||||
public digest: (m: i18n.Message) => string,
|
||||
public mapperFactory?: (m: i18n.Message) => PlaceholderMapper) {
|
||||
this._i18nToHtml = new I18nToHtmlVisitor(_i18nNodesByMsgId, digest, mapperFactory);
|
||||
}
|
||||
|
||||
// Creates a `TranslationBundle` by parsing the given `content` with the `serializer`.
|
||||
static load(content: string, url: string, serializer: Serializer): TranslationBundle {
|
||||
const i18nNodesByMsgId = serializer.load(content, url);
|
||||
const digestFn = (m: i18n.Message) => serializer.digest(m);
|
||||
return new TranslationBundle(i18nNodesByMsgId, digestFn);
|
||||
const mapperFactory = (m: i18n.Message) => serializer.createNameMapper(m);
|
||||
return new TranslationBundle(i18nNodesByMsgId, digestFn, mapperFactory);
|
||||
}
|
||||
|
||||
// Returns the translation as HTML nodes from the given source message.
|
||||
get(srcMsg: i18n.Message): html.Node[] {
|
||||
const html = this._i18nToHtml.convert(srcMsg);
|
||||
|
||||
@@ -46,15 +50,17 @@ export class TranslationBundle {
|
||||
|
||||
class I18nToHtmlVisitor implements i18n.Visitor {
|
||||
private _srcMsg: i18n.Message;
|
||||
private _srcMsgStack: i18n.Message[] = [];
|
||||
private _contextStack: {msg: i18n.Message, mapper: (name: string) => string}[] = [];
|
||||
private _errors: I18nError[] = [];
|
||||
private _mapper: (name: string) => string;
|
||||
|
||||
constructor(
|
||||
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
|
||||
private _digest: (m: i18n.Message) => string) {}
|
||||
private _digest: (m: i18n.Message) => string,
|
||||
private _mapperFactory: (m: i18n.Message) => PlaceholderMapper) {}
|
||||
|
||||
convert(srcMsg: i18n.Message): {nodes: html.Node[], errors: I18nError[]} {
|
||||
this._srcMsgStack.length = 0;
|
||||
this._contextStack.length = 0;
|
||||
this._errors.length = 0;
|
||||
// i18n to text
|
||||
const text = this._convertToText(srcMsg);
|
||||
@@ -88,7 +94,7 @@ class I18nToHtmlVisitor implements i18n.Visitor {
|
||||
}
|
||||
|
||||
visitPlaceholder(ph: i18n.Placeholder, context?: any): string {
|
||||
const phName = ph.name;
|
||||
const phName = this._mapper(ph.name);
|
||||
if (this._srcMsg.placeholders.hasOwnProperty(phName)) {
|
||||
return this._srcMsg.placeholders[phName];
|
||||
}
|
||||
@@ -105,14 +111,26 @@ class I18nToHtmlVisitor implements i18n.Visitor {
|
||||
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any { throw 'unreachable code'; }
|
||||
|
||||
/**
|
||||
* Convert a source message to a translated text string:
|
||||
* - text nodes are replaced with their translation,
|
||||
* - placeholders are replaced with their content,
|
||||
* - ICU nodes are converted to ICU expressions.
|
||||
*/
|
||||
private _convertToText(srcMsg: i18n.Message): string {
|
||||
const digest = this._digest(srcMsg);
|
||||
const mapper = this._mapperFactory ? this._mapperFactory(srcMsg) : null;
|
||||
|
||||
if (this._i18nNodesByMsgId.hasOwnProperty(digest)) {
|
||||
this._srcMsgStack.push(this._srcMsg);
|
||||
this._contextStack.push({msg: this._srcMsg, mapper: this._mapper});
|
||||
this._srcMsg = srcMsg;
|
||||
this._mapper = (name: string) => mapper ? mapper.toInternalName(name) : name;
|
||||
|
||||
const nodes = this._i18nNodesByMsgId[digest];
|
||||
const text = nodes.map(node => node.visit(this)).join('');
|
||||
this._srcMsg = this._srcMsgStack.pop();
|
||||
const context = this._contextStack.pop();
|
||||
this._srcMsg = context.msg;
|
||||
this._mapper = context.mapper;
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* 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 {CompileIdentifierMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName} from './compile_metadata';
|
||||
|
||||
@@ -283,7 +283,7 @@ class _TreeBuilder {
|
||||
const tagDef = this.getTagDefinition(el.name);
|
||||
const {parent, container} = this._getParentElementSkippingContainers();
|
||||
|
||||
if (isPresent(parent) && tagDef.requireExtraParent(parent.name)) {
|
||||
if (parent && tagDef.requireExtraParent(parent.name)) {
|
||||
const newParent = new html.Element(
|
||||
tagDef.parentToAdd, [], [], el.sourceSpan, el.startSourceSpan, el.endSourceSpan);
|
||||
this._insertBeforeContainer(parent, container, newParent);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenName, tokenReference} from './compile_metadata';
|
||||
import {createDiTokenExpression} from './compiler_util/identifier_util';
|
||||
import {isPresent} from './facade/lang';
|
||||
import {Identifiers, createIdentifier, createIdentifierToken, resolveIdentifier} from './identifiers';
|
||||
import {Identifiers, createIdentifier, resolveIdentifier} from './identifiers';
|
||||
import {CompilerInjectable} from './injectable';
|
||||
import {ClassBuilder, createClassStmt} from './output/class_builder';
|
||||
import * as o from './output/output_ast';
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import {NgModule, Type} from '@angular/core';
|
||||
|
||||
import {ListWrapper} from './facade/collection';
|
||||
import {isPresent, stringify} from './facade/lang';
|
||||
import {stringify} from './facade/lang';
|
||||
import {CompilerInjectable} from './injectable';
|
||||
import {ReflectorReader, reflector} from './private_import_core';
|
||||
|
||||
@@ -30,7 +30,7 @@ export class NgModuleResolver {
|
||||
const ngModuleMeta: NgModule =
|
||||
ListWrapper.findLast(this._reflector.annotations(type), _isNgModuleMetadata);
|
||||
|
||||
if (isPresent(ngModuleMeta)) {
|
||||
if (ngModuleMeta) {
|
||||
return ngModuleMeta;
|
||||
} else {
|
||||
if (throwIfNotFound) {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
*/
|
||||
|
||||
|
||||
import {CompileIdentifierMetadata} from '../compile_metadata';
|
||||
import {ValueTransformer, visitValue} from '../util';
|
||||
|
||||
import * as o from './output_ast';
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import {Pipe, Type, resolveForwardRef} from '@angular/core';
|
||||
|
||||
import {ListWrapper} from './facade/collection';
|
||||
import {isPresent, stringify} from './facade/lang';
|
||||
import {stringify} from './facade/lang';
|
||||
import {CompilerInjectable} from './injectable';
|
||||
import {ReflectorReader, reflector} from './private_import_core';
|
||||
|
||||
@@ -38,9 +38,9 @@ export class PipeResolver {
|
||||
*/
|
||||
resolve(type: Type<any>, throwIfNotFound = true): Pipe {
|
||||
const metas = this._reflector.annotations(resolveForwardRef(type));
|
||||
if (isPresent(metas)) {
|
||||
if (metas) {
|
||||
const annotation = ListWrapper.findLast(metas, _isPipeMetadata);
|
||||
if (isPresent(annotation)) {
|
||||
if (annotation) {
|
||||
return annotation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileDirectiveSummary, CompileNgModuleMetadata, CompileProviderMetadata, CompileQueryMetadata, CompileTokenMetadata, CompileTypeMetadata, tokenName, tokenReference} from './compile_metadata';
|
||||
import {isBlank, isPresent} from './facade/lang';
|
||||
import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers';
|
||||
import {Identifiers, resolveIdentifier} from './identifiers';
|
||||
import {ParseError, ParseSourceSpan} from './parse_util';
|
||||
import {AttrAst, DirectiveAst, ProviderAst, ProviderAstType, ReferenceAst} from './template_parser/template_ast';
|
||||
|
||||
@@ -114,7 +114,7 @@ export class ProviderElementContext {
|
||||
let queries: CompileQueryMetadata[];
|
||||
while (currentEl !== null) {
|
||||
queries = currentEl._contentQueries.get(tokenReference(token));
|
||||
if (isPresent(queries)) {
|
||||
if (queries) {
|
||||
result.push(...queries.filter((query) => query.descendants || distance <= 1));
|
||||
}
|
||||
if (currentEl._directiveAsts.length > 0) {
|
||||
@@ -123,7 +123,7 @@ export class ProviderElementContext {
|
||||
currentEl = currentEl._parent;
|
||||
}
|
||||
queries = this.viewContext.viewQueries.get(tokenReference(token));
|
||||
if (isPresent(queries)) {
|
||||
if (queries) {
|
||||
result.push(...queries);
|
||||
}
|
||||
return result;
|
||||
@@ -143,7 +143,7 @@ export class ProviderElementContext {
|
||||
return null;
|
||||
}
|
||||
let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
|
||||
if (isPresent(transformedProviderAst)) {
|
||||
if (transformedProviderAst) {
|
||||
return transformedProviderAst;
|
||||
}
|
||||
if (isPresent(this._seenProviders.get(tokenReference(token)))) {
|
||||
@@ -165,11 +165,11 @@ export class ProviderElementContext {
|
||||
transformedUseExisting = null;
|
||||
transformedUseValue = existingDiDep.value;
|
||||
}
|
||||
} else if (isPresent(provider.useFactory)) {
|
||||
} else if (provider.useFactory) {
|
||||
const deps = provider.deps || provider.useFactory.diDeps;
|
||||
transformedDeps =
|
||||
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;
|
||||
transformedDeps =
|
||||
deps.map((dep) => this._getDependency(resolvedProvider.providerType, dep, eager));
|
||||
@@ -235,7 +235,7 @@ export class ProviderElementContext {
|
||||
}
|
||||
} else {
|
||||
// check parent elements
|
||||
while (!result && isPresent(currElement._parent)) {
|
||||
while (!result && currElement._parent) {
|
||||
const prevElement = currElement;
|
||||
currElement = currElement._parent;
|
||||
if (prevElement._isViewRoot) {
|
||||
@@ -301,7 +301,7 @@ export class NgModuleProviderAnalyzer {
|
||||
return null;
|
||||
}
|
||||
let transformedProviderAst = this._transformedProviders.get(tokenReference(token));
|
||||
if (isPresent(transformedProviderAst)) {
|
||||
if (transformedProviderAst) {
|
||||
return transformedProviderAst;
|
||||
}
|
||||
if (isPresent(this._seenProviders.get(tokenReference(token)))) {
|
||||
@@ -324,11 +324,11 @@ export class NgModuleProviderAnalyzer {
|
||||
transformedUseExisting = null;
|
||||
transformedUseValue = existingDiDep.value;
|
||||
}
|
||||
} else if (isPresent(provider.useFactory)) {
|
||||
} else if (provider.useFactory) {
|
||||
const deps = provider.deps || provider.useFactory.diDeps;
|
||||
transformedDeps =
|
||||
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;
|
||||
transformedDeps =
|
||||
deps.map((dep) => this._getDependency(dep, eager, resolvedProvider.sourceSpan));
|
||||
@@ -454,7 +454,7 @@ function _resolveProviders(
|
||||
|
||||
function _getViewQueries(component: CompileDirectiveMetadata): Map<any, CompileQueryMetadata[]> {
|
||||
const viewQueries = new Map<any, CompileQueryMetadata[]>();
|
||||
if (isPresent(component.viewQueries)) {
|
||||
if (component.viewQueries) {
|
||||
component.viewQueries.forEach((query) => _addQueryToTokenMap(viewQueries, query));
|
||||
}
|
||||
return viewQueries;
|
||||
@@ -464,7 +464,7 @@ function _getContentQueries(directives: CompileDirectiveSummary[]):
|
||||
Map<any, CompileQueryMetadata[]> {
|
||||
const contentQueries = new Map<any, CompileQueryMetadata[]>();
|
||||
directives.forEach(directive => {
|
||||
if (isPresent(directive.queries)) {
|
||||
if (directive.queries) {
|
||||
directive.queries.forEach((query) => _addQueryToTokenMap(contentQueries, query));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,10 +9,11 @@
|
||||
import {getHtmlTagDefinition} from './ml_parser/html_tags';
|
||||
|
||||
const _SELECTOR_REGEXP = new RegExp(
|
||||
'(\\:not\\()|' + //":not("
|
||||
'([-\\w]+)|' + // "tag"
|
||||
'(?:\\.([-\\w]+))|' + // ".class"
|
||||
'(?:\\[([.-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
|
||||
'(\\:not\\()|' + //":not("
|
||||
'([-\\w]+)|' + // "tag"
|
||||
'(?:\\.([-\\w]+))|' + // ".class"
|
||||
// "-" should appear first in the regexp below as FF31 parses "[.-\w]" as a range
|
||||
'(?:\\[([-.\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
|
||||
'(\\))|' + // ")"
|
||||
'(\\s*,\\s*)', // ","
|
||||
'g');
|
||||
|
||||
@@ -11,7 +11,6 @@ import {SecurityContext} from '@angular/core';
|
||||
import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata';
|
||||
import {ASTWithSource, BindingPipe, EmptyExpr, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
|
||||
import {Parser} from '../expression_parser/parser';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||
import {mergeNsAndName} from '../ml_parser/tags';
|
||||
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
|
||||
@@ -111,14 +110,14 @@ export class BindingParser {
|
||||
}
|
||||
|
||||
parseInlineTemplateBinding(
|
||||
name: string, prefixToken: string, value: string, sourceSpan: ParseSourceSpan,
|
||||
prefixToken: string, value: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetProps: BoundProperty[], targetVars: VariableAst[]) {
|
||||
const bindings = this._parseTemplateBindings(prefixToken, value, sourceSpan);
|
||||
for (let i = 0; i < bindings.length; i++) {
|
||||
const binding = bindings[i];
|
||||
if (binding.keyIsVar) {
|
||||
targetVars.push(new VariableAst(binding.key, binding.name, sourceSpan));
|
||||
} else if (isPresent(binding.expression)) {
|
||||
} else if (binding.expression) {
|
||||
this._parsePropertyAst(
|
||||
binding.key, binding.expression, sourceSpan, targetMatchableAttrs, targetProps);
|
||||
} else {
|
||||
@@ -136,7 +135,7 @@ export class BindingParser {
|
||||
const bindingsResult = this._exprParser.parseTemplateBindings(prefixToken, value, sourceInfo);
|
||||
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
|
||||
bindingsResult.templateBindings.forEach((binding) => {
|
||||
if (isPresent(binding.expression)) {
|
||||
if (binding.expression) {
|
||||
this._checkPipes(binding.expression, sourceSpan);
|
||||
}
|
||||
});
|
||||
@@ -193,7 +192,7 @@ export class BindingParser {
|
||||
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
|
||||
targetProps: BoundProperty[]): boolean {
|
||||
const expr = this.parseInterpolation(value, sourceSpan);
|
||||
if (isPresent(expr)) {
|
||||
if (expr) {
|
||||
this._parsePropertyAst(name, expr, sourceSpan, targetMatchableAttrs, targetProps);
|
||||
return true;
|
||||
}
|
||||
@@ -374,7 +373,7 @@ export class BindingParser {
|
||||
}
|
||||
|
||||
private _checkPipes(ast: ASTWithSource, sourceSpan: ParseSourceSpan) {
|
||||
if (isPresent(ast)) {
|
||||
if (ast) {
|
||||
const collector = new PipeCollector();
|
||||
ast.visit(collector);
|
||||
collector.pipes.forEach((ast, pipeName) => {
|
||||
|
||||
@@ -276,7 +276,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
templateBindingsSource = attr.value;
|
||||
} else if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
|
||||
templateBindingsSource = attr.value;
|
||||
prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length);
|
||||
prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length) + ':';
|
||||
}
|
||||
|
||||
const hasTemplateBinding = isPresent(templateBindingsSource);
|
||||
@@ -288,7 +288,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
}
|
||||
hasInlineTemplates = true;
|
||||
this._bindingParser.parseInlineTemplateBinding(
|
||||
attr.name, prefixToken, templateBindingsSource, attr.sourceSpan, templateMatchableAttrs,
|
||||
prefixToken, templateBindingsSource, attr.sourceSpan, templateMatchableAttrs,
|
||||
templateElementOrDirectiveProps, templateElementVars);
|
||||
}
|
||||
|
||||
@@ -309,9 +309,11 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
const elementProps: BoundElementPropertyAst[] =
|
||||
this._createElementPropertyAsts(element.name, elementOrDirectiveProps, directiveAsts);
|
||||
const isViewRoot = parent.isTemplateElement || hasInlineTemplates;
|
||||
|
||||
const providerContext = new ProviderElementContext(
|
||||
this.providerViewContext, parent.providerContext, isViewRoot, directiveAsts, attrs,
|
||||
references, element.sourceSpan);
|
||||
|
||||
const children = html.visitAll(
|
||||
preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children,
|
||||
ElementContext.create(
|
||||
|
||||
@@ -19,7 +19,7 @@ import {ProviderAst, ProviderAstType, ReferenceAst, TemplateAst} from '../templa
|
||||
import {CompileMethod} from './compile_method';
|
||||
import {CompileQuery, addQueryToTokenMap, createQueryList} from './compile_query';
|
||||
import {CompileView, CompileViewRootNode} from './compile_view';
|
||||
import {InjectMethodVars, ViewProperties} from './constants';
|
||||
import {InjectMethodVars} from './constants';
|
||||
import {ComponentFactoryDependency, DirectiveWrapperDependency} from './deps';
|
||||
import {getPropertyInView, injectFromViewParentInjector} from './util';
|
||||
|
||||
@@ -195,7 +195,7 @@ export class CompileElement extends CompileNode {
|
||||
const propName =
|
||||
`_${tokenName(resolvedProvider.token)}_${this.nodeIndex}_${this.instances.size}`;
|
||||
const instance = createProviderProperty(
|
||||
propName, resolvedProvider, providerValueExpressions, resolvedProvider.multiProvider,
|
||||
propName, providerValueExpressions, resolvedProvider.multiProvider,
|
||||
resolvedProvider.eager, this);
|
||||
if (isDirectiveWrapper) {
|
||||
this.directiveWrapperInstance.set(tokenReference(resolvedProvider.token), instance);
|
||||
@@ -289,7 +289,7 @@ export class CompileElement extends CompileNode {
|
||||
CompileQuery {
|
||||
const propName =
|
||||
`_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);
|
||||
addQueryToTokenMap(this._queries, query);
|
||||
return query;
|
||||
@@ -369,8 +369,8 @@ function createInjectInternalCondition(
|
||||
}
|
||||
|
||||
function createProviderProperty(
|
||||
propName: string, provider: ProviderAst, providerValueExpressions: o.Expression[],
|
||||
isMulti: boolean, isEager: boolean, compileElement: CompileElement): o.Expression {
|
||||
propName: string, providerValueExpressions: o.Expression[], isMulti: boolean, isEager: boolean,
|
||||
compileElement: CompileElement): o.Expression {
|
||||
const view = compileElement.view;
|
||||
let resolvedProviderValueExpr: o.Expression;
|
||||
let type: o.Type;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {isPresent} from '../facade/lang';
|
||||
import * as o from '../output/output_ast';
|
||||
import {TemplateAst} from '../template_parser/template_ast';
|
||||
|
||||
@@ -34,7 +33,7 @@ export class CompileMethod {
|
||||
if (this._newState.nodeIndex !== this._currState.nodeIndex ||
|
||||
this._newState.sourceAst !== this._currState.sourceAst) {
|
||||
const expr = this._updateDebugContext(this._newState);
|
||||
if (isPresent(expr)) {
|
||||
if (expr) {
|
||||
this._bodyStatements.push(expr.toStmt());
|
||||
}
|
||||
}
|
||||
@@ -43,13 +42,12 @@ export class CompileMethod {
|
||||
private _updateDebugContext(newState: _DebugState): o.Expression {
|
||||
this._currState = this._newState = newState;
|
||||
if (this._debugEnabled) {
|
||||
const sourceLocation =
|
||||
isPresent(newState.sourceAst) ? newState.sourceAst.sourceSpan.start : null;
|
||||
const sourceLocation = newState.sourceAst ? newState.sourceAst.sourceSpan.start : null;
|
||||
|
||||
return o.THIS_EXPR.callMethod('debug', [
|
||||
o.literal(newState.nodeIndex),
|
||||
isPresent(sourceLocation) ? o.literal(sourceLocation.line) : o.NULL_EXPR,
|
||||
isPresent(sourceLocation) ? o.literal(sourceLocation.col) : o.NULL_EXPR
|
||||
sourceLocation ? o.literal(sourceLocation.line) : o.NULL_EXPR,
|
||||
sourceLocation ? o.literal(sourceLocation.col) : o.NULL_EXPR
|
||||
]);
|
||||
} else {
|
||||
return null;
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import {CompileQueryMetadata, tokenReference} from '../compile_metadata';
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {Identifiers, createIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
|
||||
@@ -33,7 +32,7 @@ export class CompileQuery {
|
||||
addValue(value: o.Expression, view: CompileView) {
|
||||
let currentView = view;
|
||||
const elPath: CompileElement[] = [];
|
||||
while (isPresent(currentView) && currentView !== this.view) {
|
||||
while (currentView && currentView !== this.view) {
|
||||
const parentEl = currentView.declarationElement;
|
||||
elPath.unshift(parentEl);
|
||||
currentView = parentEl.view;
|
||||
@@ -67,7 +66,7 @@ export class CompileQuery {
|
||||
generateStatements(targetStaticMethod: CompileMethod, targetDynamicMethod: CompileMethod) {
|
||||
const values = createQueryValues(this._values);
|
||||
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;
|
||||
updateStmts.push(
|
||||
this.ownerDirectiveExpression.prop(this.meta.propertyName).set(valueExpr).toStmt());
|
||||
@@ -110,9 +109,7 @@ function mapNestedViews(
|
||||
]);
|
||||
}
|
||||
|
||||
export function createQueryList(
|
||||
query: CompileQueryMetadata, directiveInstance: o.Expression, propertyName: string,
|
||||
compileView: CompileView): o.Expression {
|
||||
export function createQueryList(propertyName: string, compileView: CompileView): o.Expression {
|
||||
compileView.fields.push(new o.ClassField(
|
||||
propertyName, o.importType(createIdentifier(Identifiers.QueryList), [o.DYNAMIC_TYPE])));
|
||||
const expr = o.THIS_EXPR.prop(propertyName);
|
||||
|
||||
@@ -12,7 +12,6 @@ import {EventHandlerVars, NameResolver} from '../compiler_util/expression_conver
|
||||
import {createPureProxy} from '../compiler_util/identifier_util';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {Identifiers, createIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ViewType} from '../private_import_core';
|
||||
|
||||
@@ -119,7 +118,7 @@ export class CompileView implements NameResolver {
|
||||
const directiveInstance = o.THIS_EXPR.prop('context');
|
||||
this.component.viewQueries.forEach((queryMeta, 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);
|
||||
addQueryToTokenMap(viewQueries, query);
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core';
|
||||
import {createEnumExpression} from '../compiler_util/identifier_util';
|
||||
import {Identifiers} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ChangeDetectorStatus, ViewType} from '../private_import_core';
|
||||
import {ViewType} from '../private_import_core';
|
||||
|
||||
export class ViewTypeEnum {
|
||||
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 {
|
||||
static fromValue(value: ChangeDetectorStatusEnum): o.Expression {
|
||||
return createEnumExpression(Identifiers.ChangeDetectorStatus, value);
|
||||
|
||||
@@ -15,7 +15,6 @@ import {BoundEventAst, DirectiveAst} from '../template_parser/template_ast';
|
||||
|
||||
import {CompileElement} from './compile_element';
|
||||
import {CompileMethod} from './compile_method';
|
||||
import {ViewProperties} from './constants';
|
||||
import {getHandleEventMethodName} from './util';
|
||||
|
||||
export function bindOutputs(
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* 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 {CompileElement} from './compile_element';
|
||||
@@ -17,16 +17,18 @@ import {CompileQuery} from './compile_query';
|
||||
// as we create embedded views before the <template> elements themselves.
|
||||
export function bindQueryValues(ce: CompileElement) {
|
||||
const queriesWithReads: _QueryWithRead[] = [];
|
||||
|
||||
ce.getProviderTokens().forEach((token) => {
|
||||
const queriesForProvider = ce.getQueriesFor(token);
|
||||
queriesWithReads.push(...queriesForProvider.map(query => new _QueryWithRead(query, token)));
|
||||
});
|
||||
|
||||
Object.keys(ce.referenceTokens).forEach(varName => {
|
||||
const token = ce.referenceTokens[varName];
|
||||
const varToken = {value: varName};
|
||||
queriesWithReads.push(
|
||||
...ce.getQueriesFor(varToken).map(query => new _QueryWithRead(query, varToken)));
|
||||
});
|
||||
|
||||
queriesWithReads.forEach((queryWithRead) => {
|
||||
let value: o.Expression;
|
||||
if (queryWithRead.read.identifier) {
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
|
||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, CompileTokenMetadata, identifierName} from '../compile_metadata';
|
||||
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 {ViewType} from '../private_import_core';
|
||||
|
||||
@@ -23,7 +21,7 @@ export function getPropertyInView(
|
||||
} else {
|
||||
let viewProp: o.Expression = o.THIS_EXPR;
|
||||
let currView: CompileView = callingView;
|
||||
while (currView !== definedView && isPresent(currView.declarationElement.view)) {
|
||||
while (currView !== definedView && currView.declarationElement.view) {
|
||||
currView = currView.declarationElement.view;
|
||||
viewProp = viewProp.prop('parentView');
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import {isPresent} from '../facade/lang';
|
||||
import {Identifiers, createIdentifier, identifierToken} from '../identifiers';
|
||||
import {createClassStmt} from '../output/class_builder';
|
||||
import * as o from '../output/output_ast';
|
||||
import {ParseSourceSpan} from '../parse_util';
|
||||
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';
|
||||
|
||||
@@ -405,8 +404,9 @@ function createViewTopLevelStmts(view: CompileView, targetStatements: o.Statemen
|
||||
o.literal(view.component.template.ngContentSelectors.length),
|
||||
ViewEncapsulationEnum.fromValue(view.component.template.encapsulation),
|
||||
view.styles,
|
||||
o.literalMap(view.animations.map(
|
||||
(entry): [string, o.Expression] => [entry.name, entry.fnExp])),
|
||||
o.literalMap(
|
||||
view.animations.map((entry): [string, o.Expression] => [entry.name, entry.fnExp]),
|
||||
null, true),
|
||||
]))
|
||||
.toDeclStmt(o.importType(createIdentifier(Identifiers.RenderComponentType))));
|
||||
}
|
||||
@@ -695,7 +695,6 @@ function generateCreateEmbeddedViewsMethod(view: CompileView): o.ClassMethod {
|
||||
view.nodes.forEach((node) => {
|
||||
if (node instanceof CompileElement) {
|
||||
if (node.embeddedView) {
|
||||
const parentNodeIndex = node.isRootElement() ? null : node.parent.nodeIndex;
|
||||
stmts.push(new o.IfStmt(
|
||||
nodeIndexVar.equals(o.literal(node.nodeIndex)),
|
||||
[new o.ReturnStatement(node.embeddedView.classExpr.instantiate([
|
||||
|
||||
@@ -449,6 +449,40 @@ describe('StaticReflector', () => {
|
||||
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', () => {
|
||||
class ClassDecorator {
|
||||
constructor(public value: any) {}
|
||||
|
||||
@@ -182,6 +182,12 @@ const XTB = `
|
||||
<ph name="START_TAG_DIV_1"/><ph name="ICU"/><ph name="CLOSE_TAG_DIV"></ph>
|
||||
</translation>
|
||||
<translation id="1491627405349178954">ca <ph name="START_BOLD_TEXT"/>devrait<ph name="CLOSE_BOLD_TEXT"/> marcher</translation>
|
||||
<translation id="i18n16">avec un ID explicite</translation>
|
||||
<translation id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {un} =2 {deux} other {<ph
|
||||
name="START_BOLD_TEXT"><ex><b></ex></ph>beaucoup<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph>} }</translation>
|
||||
<translation id="4085484936881858615">{VAR_PLURAL, plural, =0 {Pas de réponse} =1 {une réponse} other {<ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph> réponse} }</translation>
|
||||
<translation id="4035252431381981115">FOO<ph name="START_LINK"><ex><a></ex></ph>BAR<ph name="CLOSE_LINK"><ex></a></ex></ph></translation>
|
||||
<translation id="5339604010413301604"><ph name="MAP_NAME"><ex>MAP_NAME</ex></ph></translation>
|
||||
</translationbundle>`;
|
||||
|
||||
// unused, for reference only
|
||||
@@ -210,5 +216,63 @@ const XMB = `
|
||||
<ph name="START_TAG_DIV_1"><ex><div></ex></ph><ph name="ICU"/><ph name="CLOSE_TAG_DIV"><ex></div></ex></ph>
|
||||
</msg>
|
||||
<msg id="1491627405349178954">it <ph name="START_BOLD_TEXT"><ex><b></ex></ph>should<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph> work</msg>
|
||||
</messagebundle>
|
||||
<msg id="i18n16">with an explicit ID</msg>
|
||||
<msg id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex><b></ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph>} }</msg>
|
||||
<msg id="4085484936881858615" desc="desc">{VAR_PLURAL, plural, =0 {Found no results} =1 {Found one result} other {Found <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph> results} }</msg>
|
||||
<msg id="4035252431381981115">foo<ph name="START_LINK"><ex><a></ex></ph>bar<ph name="CLOSE_LINK"><ex></a></ex></ph></msg>
|
||||
<msg id="5339604010413301604"><ph name="MAP_NAME"><ex>MAP_NAME</ex></ph></msg>;
|
||||
</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>
|
||||
|
||||
<div i18n>{{ 'test' //i18n(ph="map name") }}</div>
|
||||
`;
|
||||
|
||||
@@ -42,7 +42,7 @@ export function main(): void {
|
||||
});
|
||||
}
|
||||
|
||||
class _TestSerializer implements Serializer {
|
||||
class _TestSerializer extends Serializer {
|
||||
write(messages: i18n.Message[]): string {
|
||||
return messages.map(msg => `${serializeNodes(msg.nodes)} (${msg.meaning}|${msg.description})`)
|
||||
.join('//');
|
||||
|
||||
@@ -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', () => {
|
||||
expect(humanizeTplAst(parse('<div *ngIf="test">', [ngIf]))).toEqual([
|
||||
[EmbeddedTemplateAst], [DirectiveAst, ngIf],
|
||||
[BoundDirectivePropertyAst, 'ngIf', 'test'], [ElementAst, 'div']
|
||||
[EmbeddedTemplateAst],
|
||||
[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', () => {
|
||||
expect(humanizeTplAst(parse('<div *ngIf>', [ngIf]))).toEqual([
|
||||
[EmbeddedTemplateAst], [DirectiveAst, ngIf],
|
||||
[BoundDirectivePropertyAst, 'ngIf', 'null'], [ElementAst, 'div']
|
||||
[EmbeddedTemplateAst],
|
||||
[DirectiveAst, ngIf],
|
||||
[BoundDirectivePropertyAst, 'ngIf', 'null'],
|
||||
[ElementAst, 'div'],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,16 @@
|
||||
|
||||
import {AnimationStyles} from './animation_styles';
|
||||
|
||||
/**
|
||||
* `AnimationKeyframe` consists of a series of styles (contained within {@link AnimationStyles
|
||||
* `AnimationStyles`})
|
||||
* and an offset value indicating when those styles are applied within the `duration/delay/easing`
|
||||
* timings.
|
||||
* `AnimationKeyframe` is mostly an internal class which is designed to be used alongside {@link
|
||||
* Renderer#animate-anchor `Renderer.animate`}.
|
||||
*
|
||||
* @experimental Animation support is experimental
|
||||
*/
|
||||
export class AnimationKeyframe {
|
||||
constructor(public offset: number, public styles: AnimationStyles) {}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,20 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
// having an import prevents dgeni from truncating out
|
||||
// the class description in the docs. DO NOT REMOVE.
|
||||
import {isPresent} from '../facade/lang';
|
||||
|
||||
/**
|
||||
* `AnimationStyles` consists of a collection of key/value maps containing CSS-based style data
|
||||
* that can either be used as initial styling data or apart of a series of keyframes within an
|
||||
* animation.
|
||||
* This class is mostly internal, and it is designed to be used alongside
|
||||
* {@link AnimationKeyframe `AnimationKeyframe`} and {@link Renderer#animate-anchor
|
||||
* `Renderer.animate`}.
|
||||
*
|
||||
* @experimental Animation support is experimental
|
||||
*/
|
||||
export class AnimationStyles {
|
||||
constructor(public styles: {[key: string]: string | number}[]) {}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import {isPromise} from '../src/util/lang';
|
||||
|
||||
import {ApplicationInitStatus} from './application_init';
|
||||
import {APP_BOOTSTRAP_LISTENER, PLATFORM_INITIALIZER} from './application_tokens';
|
||||
import {ChangeDetectorRef} from './change_detection/change_detector_ref';
|
||||
import {Console} from './console';
|
||||
import {Injectable, Injector, OpaqueToken, Optional, Provider, ReflectiveInjector} from './di';
|
||||
import {CompilerFactory, CompilerOptions} from './linker/compiler';
|
||||
|
||||
@@ -35,4 +35,6 @@ export * from './core_private_export';
|
||||
export * from './animation/metadata';
|
||||
export {AnimationTransitionEvent} from './animation/animation_transition_event';
|
||||
export {AnimationPlayer} from './animation/animation_player';
|
||||
export {AnimationStyles} from './animation/animation_styles';
|
||||
export {AnimationKeyframe} from './animation/animation_keyframe';
|
||||
export {Sanitizer, SecurityContext} from './security';
|
||||
|
||||
@@ -13,7 +13,6 @@ import {Type} from '../type';
|
||||
|
||||
import {ElementRef} from './element_ref';
|
||||
import {AppView} from './view';
|
||||
import {ViewContainer} from './view_container';
|
||||
import {ViewRef} from './view_ref';
|
||||
import {ViewUtils} from './view_utils';
|
||||
|
||||
@@ -86,11 +85,6 @@ export class ComponentRef_<C> extends ComponentRef<C> {
|
||||
onDestroy(callback: Function): void { this.hostView.onDestroy(callback); }
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
const EMPTY_CONTEXT = new Object();
|
||||
|
||||
/**
|
||||
* @stable
|
||||
*/
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import {ElementRef} from './element_ref';
|
||||
import {AppView} from './view';
|
||||
import {ViewContainer} from './view_container';
|
||||
import {EmbeddedViewRef} from './view_ref';
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {ChangeDetectorRef, ChangeDetectorStatus} from '../change_detection/chang
|
||||
import {Injector, THROW_IF_NOT_FOUND} from '../di/injector';
|
||||
import {isPresent} from '../facade/lang';
|
||||
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 {ComponentRef} from './component_factory';
|
||||
|
||||
@@ -7,10 +7,8 @@
|
||||
*/
|
||||
|
||||
import {Injector} from '../di/injector';
|
||||
import {isPresent} from '../facade/lang';
|
||||
|
||||
import {ElementRef} from './element_ref';
|
||||
import {QueryList} from './query_list';
|
||||
import {AppView} from './view';
|
||||
import {ViewContainerRef_} from './view_container_ref';
|
||||
import {ViewType} from './view_type';
|
||||
|
||||
@@ -15,7 +15,6 @@ import {ViewEncapsulation} from '../metadata/view';
|
||||
import {RenderComponentType, RenderDebugInfo, Renderer, RootRenderer} from '../render/api';
|
||||
import {Sanitizer} from '../security';
|
||||
import {VERSION} from '../version';
|
||||
import {NgZone} from '../zone/ng_zone';
|
||||
|
||||
import {ExpressionChangedAfterItHasBeenCheckedError} from './errors';
|
||||
import {AppView} from './view';
|
||||
@@ -23,7 +22,6 @@ import {AppView} from './view';
|
||||
@Injectable()
|
||||
export class ViewUtils {
|
||||
sanitizer: Sanitizer;
|
||||
private _nextCompTypeId: number = 0;
|
||||
|
||||
constructor(
|
||||
private _renderer: RootRenderer, sanitizer: Sanitizer,
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* Determine if the argument is shaped like a Promise
|
||||
*/
|
||||
export function isPromise(obj: any): obj is Promise<any> {
|
||||
// allow any Promise/A+ compliant thenable.
|
||||
// It's up to the caller to ensure that obj.then conforms to the spec
|
||||
|
||||
@@ -138,7 +138,7 @@ export function main() {
|
||||
@Component({
|
||||
selector: 'child',
|
||||
template:
|
||||
'(<template [ngTemplateOutlet]="templateRef" [ngOutletContext]="templateCtx"></template>)'
|
||||
'(<ng-container [ngTemplateOutlet]="templateRef" [ngOutletContext]="templateCtx"></ng-container>)'
|
||||
})
|
||||
class Child {
|
||||
@Input()
|
||||
|
||||
@@ -23,8 +23,15 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureCompiler({useJit: useJit});
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [MyComp, NeedsContentChildren, NeedsViewChildren, TextDirective, Simple]});
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [
|
||||
MyComp,
|
||||
NeedsContentChildren,
|
||||
NeedsViewChildren,
|
||||
TextDirective,
|
||||
Simple,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should support the "i18n" attribute', () => {
|
||||
|
||||
@@ -421,7 +421,7 @@ export function main() {
|
||||
TestBed.overrideDirective(
|
||||
SimpleDirective, {set: {providers: [{provide: 'service', useValue: 'parentService'}]}});
|
||||
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)
|
||||
.toEqual('parentService');
|
||||
});
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convenience to throw an Error with 'unimplemented' as the message.
|
||||
*/
|
||||
export function unimplemented(): any {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ export class Request extends Body {
|
||||
case 'text/html':
|
||||
return ContentType.TEXT;
|
||||
case 'application/octet-stream':
|
||||
return ContentType.BLOB;
|
||||
return this._body instanceof ArrayBuffer ? ContentType.ARRAY_BUFFER : ContentType.BLOB;
|
||||
default:
|
||||
return this.detectContentTypeFromBody();
|
||||
}
|
||||
@@ -132,7 +132,7 @@ export class Request extends Body {
|
||||
return ContentType.BLOB;
|
||||
} else if (this._body instanceof ArrayBuffer) {
|
||||
return ContentType.ARRAY_BUFFER;
|
||||
} else if (this._body && typeof this._body == 'object') {
|
||||
} else if (this._body && typeof this._body === 'object') {
|
||||
return ContentType.JSON;
|
||||
} else {
|
||||
return ContentType.TEXT;
|
||||
@@ -167,4 +167,4 @@ const noop = function() {};
|
||||
const w = typeof window == 'object' ? window : noop;
|
||||
const FormData = (w as any /** TODO #9100 */)['FormData'] || 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;
|
||||
|
||||
@@ -36,7 +36,7 @@ import {Headers} from './headers';
|
||||
*/
|
||||
export class Response extends Body {
|
||||
/**
|
||||
* One of "basic", "cors", "default", "error, or "opaque".
|
||||
* One of "basic", "cors", "default", "error", or "opaque".
|
||||
*
|
||||
* Defaults to "default".
|
||||
*/
|
||||
|
||||
@@ -11,7 +11,7 @@ import {describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
import {RequestOptions} from '../src/base_request_options';
|
||||
import {ContentType} from '../src/enums';
|
||||
import {Headers} from '../src/headers';
|
||||
import {Request} from '../src/static_request';
|
||||
import {ArrayBuffer, Request} from '../src/static_request';
|
||||
|
||||
export function main() {
|
||||
describe('Request', () => {
|
||||
@@ -76,6 +76,17 @@ export function main() {
|
||||
|
||||
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', () => {
|
||||
|
||||
@@ -96,7 +96,9 @@ export class WebAnimationsPlayer implements AnimationPlayer {
|
||||
|
||||
/** @internal */
|
||||
_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; }
|
||||
|
||||
@@ -30,9 +30,14 @@ import {sanitizeUrl} from './url_sanitizer';
|
||||
const VALUES = '[-,."\'%_!# a-zA-Z0-9]+';
|
||||
const TRANSFORMATION_FNS = '(?:matrix|translate|scale|rotate|skew|perspective)(?:X|Y|3d)?';
|
||||
const COLOR_FNS = '(?:rgb|hsl)a?';
|
||||
const FN_ARGS = '\\([-0-9.%, a-zA-Z]+\\)';
|
||||
const SAFE_STYLE_VALUE =
|
||||
new RegExp(`^(${VALUES}|(?:${TRANSFORMATION_FNS}|${COLOR_FNS})${FN_ARGS})$`, 'g');
|
||||
const GRADIENTS = '(?:repeating-)?(?:linear|radial)-gradient';
|
||||
const CSS3_FNS = '(?:calc|attr)';
|
||||
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
|
||||
|
||||
@@ -39,6 +39,16 @@ export function main() {
|
||||
expectSanitize('translateX(12px, -5px)').toEqual('translateX(12px, -5px)');
|
||||
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', () => {
|
||||
expectSanitize('url(foo/bar.png)').toEqual('url(foo/bar.png)');
|
||||
expectSanitize('url( foo/bar.png\n )').toEqual('url( foo/bar.png\n )');
|
||||
|
||||
+3
-3
@@ -6,7 +6,7 @@
|
||||
* 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 {RootRenderer} from '@angular/core/src/render/api';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
@@ -152,7 +152,7 @@ export function main() {
|
||||
it('should update any template comment property/attributes', () => {
|
||||
|
||||
TestBed.overrideComponent(
|
||||
MyComp2, {set: {template: '<template [ngIf]="ctxBoolProp"></template>'}});
|
||||
MyComp2, {set: {template: '<ng-container *ngIf="ctxBoolProp"></ng-container>'}});
|
||||
const fixture = TestBed.createComponent(MyComp2);
|
||||
|
||||
(<MyComp2>fixture.componentInstance).ctxBoolProp = true;
|
||||
@@ -163,7 +163,7 @@ export function main() {
|
||||
|
||||
it('should add and remove fragments', () => {
|
||||
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 rootEl = getRenderElement(fixture.nativeElement);
|
||||
|
||||
@@ -71,4 +71,4 @@ Promise
|
||||
return Promise.all(
|
||||
allSpecFiles.map(function(moduleName) { return System.import(moduleName); }));
|
||||
})
|
||||
.then(__karma__.start, __karma__.error);
|
||||
.then(__karma__.start, (v) => console.error(v));
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* 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 {PRIMARY_OUTLET} from './shared';
|
||||
import {UrlSegment, UrlSegmentGroup} from './url_tree';
|
||||
@@ -310,7 +310,8 @@ export type ResolveData = {
|
||||
* See {@link Routes} for more details.
|
||||
* @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`.
|
||||
|
||||
@@ -133,7 +133,7 @@ export class RouterLink {
|
||||
*/
|
||||
@Directive({selector: 'a[routerLink]'})
|
||||
export class RouterLinkWithHref implements OnChanges, OnDestroy {
|
||||
@Input() target: string;
|
||||
@HostBinding('attr.target') @Input() target: string;
|
||||
@Input() queryParams: {[k: string]: any};
|
||||
@Input() fragment: string;
|
||||
@Input() preserveQueryParams: boolean;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* 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 {NavigationEnd, Router} from '../router';
|
||||
@@ -14,6 +14,7 @@ import {NavigationEnd, Router} from '../router';
|
||||
import {RouterLink, RouterLinkWithHref} from './router_link';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @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 subscription: Subscription;
|
||||
private active: boolean = 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 => {
|
||||
if (s instanceof NavigationEnd) {
|
||||
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 {
|
||||
this.links.changes.subscribe(s => this.update());
|
||||
this.linksWithHrefs.changes.subscribe(s => this.update());
|
||||
this.links.changes.subscribe(_ => this.update());
|
||||
this.linksWithHrefs.changes.subscribe(_ => this.update());
|
||||
this.update();
|
||||
}
|
||||
|
||||
@Input()
|
||||
set routerLinkActive(data: string[]|string) {
|
||||
if (Array.isArray(data)) {
|
||||
this.classes = <any>data;
|
||||
} else {
|
||||
this.classes = data.split(' ');
|
||||
}
|
||||
const classes = Array.isArray(data) ? data : data.split(' ');
|
||||
this.classes = classes.filter(c => !!c);
|
||||
}
|
||||
|
||||
ngOnChanges(changes: {}): any { this.update(); }
|
||||
ngOnDestroy(): any { this.subscription.unsubscribe(); }
|
||||
ngOnChanges(changes: SimpleChanges): void { this.update(); }
|
||||
ngOnDestroy(): void { this.subscription.unsubscribe(); }
|
||||
|
||||
private update(): void {
|
||||
if (!this.links || !this.linksWithHrefs || !this.router.navigated) return;
|
||||
const hasActiveLinks = this.hasActiveLinks();
|
||||
|
||||
const isActive = this.hasActiveLink();
|
||||
this.classes.forEach(c => {
|
||||
if (c) {
|
||||
this.renderer.setElementClass(this.element.nativeElement, c, isActive);
|
||||
}
|
||||
});
|
||||
// react only when status has changed to prevent unnecessary dom updates
|
||||
if (this.active !== hasActiveLinks) {
|
||||
this.active = hasActiveLinks;
|
||||
this.classes.forEach(
|
||||
c => this.renderer.setElementClass(this.element.nativeElement, c, hasActiveLinks));
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
private isLinkActive(router: Router): (link: (RouterLink|RouterLinkWithHref)) => boolean {
|
||||
@@ -136,7 +139,7 @@ export class RouterLinkActive implements OnChanges,
|
||||
router.isActive(link.urlTree, this.routerLinkActiveOptions.exact);
|
||||
}
|
||||
|
||||
private hasActiveLink(): boolean {
|
||||
private hasActiveLinks(): boolean {
|
||||
return this.links.some(this.isLinkActive(this.router)) ||
|
||||
this.linksWithHrefs.some(this.isLinkActive(this.router));
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ export interface CanActivateChild {
|
||||
* {
|
||||
* path: 'team/:id',
|
||||
* component: TeamCmp,
|
||||
* canActivate: ['canDeactivateTeam']
|
||||
* canDeactivate: ['canDeactivateTeam']
|
||||
* }
|
||||
* ])
|
||||
* ],
|
||||
|
||||
@@ -526,6 +526,7 @@ export class Router {
|
||||
*/
|
||||
navigate(commands: any[], extras: NavigationExtras = {skipLocationChange: false}):
|
||||
Promise<boolean> {
|
||||
validateCommands(commands);
|
||||
if (typeof extras.queryParams === 'object' && extras.queryParams !== null) {
|
||||
extras.queryParams = this.removeEmptyProps(extras.queryParams);
|
||||
}
|
||||
@@ -1237,3 +1238,12 @@ function getOutlet(outletMap: RouterOutletMap, route: ActivatedRoute): RouterOut
|
||||
}
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ function equalSegmentGroups(container: UrlSegmentGroup, containee: UrlSegmentGro
|
||||
|
||||
function containsQueryParams(
|
||||
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]);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgModuleFactory} from '@angular/core';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {fromPromise} from 'rxjs/observable/fromPromise';
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
return value;
|
||||
}
|
||||
@@ -136,4 +138,4 @@ export function wrapIntoObservable<T>(value: T | Promise<T>| Observable<T>): Obs
|
||||
}
|
||||
|
||||
return of (value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,6 +536,17 @@ describe('Integration', () => {
|
||||
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) => {
|
||||
const fixture = createRoot(router, RootCmp);
|
||||
|
||||
@@ -998,6 +1009,7 @@ describe('Integration', () => {
|
||||
|
||||
const native = fixture.nativeElement.querySelector('a');
|
||||
expect(native.getAttribute('href')).toEqual('/team/33/simple');
|
||||
expect(native.getAttribute('target')).toEqual('_self');
|
||||
native.click();
|
||||
advance(fixture);
|
||||
|
||||
@@ -2020,6 +2032,8 @@ describe('Integration', () => {
|
||||
@Component({
|
||||
template: `<a routerLink="/team" routerLinkActive #rla="routerLinkActive"></a>
|
||||
<p>{{rla.isActive}}</p>
|
||||
<span *ngIf="rla.isActive"></span>
|
||||
<span [ngClass]="{'highlight': rla.isActive}"></span>
|
||||
<router-outlet></router-outlet>`
|
||||
})
|
||||
class ComponentWithRouterLink {
|
||||
@@ -2039,15 +2053,15 @@ describe('Integration', () => {
|
||||
}
|
||||
]);
|
||||
|
||||
const f = TestBed.createComponent(ComponentWithRouterLink);
|
||||
const fixture = TestBed.createComponent(ComponentWithRouterLink);
|
||||
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');
|
||||
|
||||
router.navigateByUrl('/otherteam');
|
||||
advance(f);
|
||||
advance(fixture);
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
|
||||
@@ -102,8 +102,8 @@ describe('UrlTree', () => {
|
||||
});
|
||||
|
||||
it('should return true when container contains containees queryParams', () => {
|
||||
const t1 = serializer.parse('/one/two?test=1&page=5');
|
||||
const t2 = serializer.parse('/one/two?test=1');
|
||||
const t1 = serializer.parse('/one/two?test=1&u=5');
|
||||
const t2 = serializer.parse('/one/two?u=5');
|
||||
expect(containsTree(t1, t2, false)).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -150,10 +150,12 @@ export class UpgradeModule {
|
||||
*/
|
||||
bootstrap(
|
||||
element: Element, modules: string[] = [], config?: any /*angular.IAngularBootstrapConfig*/) {
|
||||
const INIT_MODULE_NAME = UPGRADE_MODULE_NAME + '.init';
|
||||
|
||||
// Create an ng1 module to bootstrap
|
||||
const upgradeModule =
|
||||
const initModule =
|
||||
angular
|
||||
.module(UPGRADE_MODULE_NAME, modules)
|
||||
.module(INIT_MODULE_NAME, [])
|
||||
|
||||
.value(INJECTOR_KEY, this.injector)
|
||||
|
||||
@@ -205,6 +207,8 @@ export class UpgradeModule {
|
||||
}
|
||||
]);
|
||||
|
||||
const upgradeModule = angular.module(UPGRADE_MODULE_NAME, [INIT_MODULE_NAME].concat(modules));
|
||||
|
||||
// Make sure resumeBootstrap() only exists if the current bootstrap is deferred
|
||||
const windowAngular = (window as any /** TODO #???? */)['angular'];
|
||||
windowAngular.resumeBootstrap = undefined;
|
||||
|
||||
@@ -577,10 +577,6 @@ export class UpgradeAdapter {
|
||||
})
|
||||
.then((ref: NgModuleRef<any>) => {
|
||||
this.moduleRef = ref;
|
||||
let subscription = this.ngZone.onMicrotaskEmpty.subscribe({
|
||||
next: (_: any) => this.ngZone.runOutsideAngular(() => rootScope.$evalAsync())
|
||||
});
|
||||
rootScope.$on('$destroy', () => { subscription.unsubscribe(); });
|
||||
this.ngZone.run(() => {
|
||||
if (rootScopePrototype) {
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgModule, OpaqueToken, destroyPlatform} from '@angular/core';
|
||||
import {Injector, NgModule, OpaqueToken, destroyPlatform} from '@angular/core';
|
||||
import {async} from '@angular/core/testing';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import * as angular from '@angular/upgrade/src/angular_js';
|
||||
import {$INJECTOR, INJECTOR_KEY} from '@angular/upgrade/src/aot/constants';
|
||||
import {UpgradeModule, downgradeInjectable} from '@angular/upgrade/static';
|
||||
|
||||
import {bootstrap, html} from '../test_helpers';
|
||||
@@ -76,5 +77,27 @@ export function main() {
|
||||
expect(ng2Injector.get(Ng1Service)).toBe('ng1 service value');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should initialize the upgraded injector before application run blocks are executed',
|
||||
async(() => {
|
||||
let runBlockTriggered = false;
|
||||
|
||||
const ng1Module = angular.module('ng1Module', []).run([
|
||||
INJECTOR_KEY,
|
||||
function(injector: Injector) {
|
||||
runBlockTriggered = true;
|
||||
expect(injector.get($INJECTOR)).toBeDefined();
|
||||
}
|
||||
]);
|
||||
|
||||
@NgModule({imports: [BrowserModule, UpgradeModule]})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, html('<div>'), ng1Module).then(() => {
|
||||
expect(runBlockTriggered).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* 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 {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
@@ -18,7 +18,89 @@ export function main() {
|
||||
beforeEach(() => 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', () => {
|
||||
let adapter: UpgradeAdapter;
|
||||
@@ -51,98 +133,18 @@ export function main() {
|
||||
}));
|
||||
|
||||
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(() => {
|
||||
adapter.bootstrap(html('<ng2></ng2>'), ['ng1']);
|
||||
flushMicrotasks();
|
||||
}).toThrowError();
|
||||
let args: any[] = consoleErrorSpy.calls.mostRecent().args;
|
||||
const args: any[] = consoleErrorSpy.calls.mostRecent().args;
|
||||
expect(consoleErrorSpy).toHaveBeenCalled();
|
||||
expect(args.length).toBeGreaterThan(0);
|
||||
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', () => {
|
||||
it('should interleave scope and component expressions', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
@@ -184,6 +186,87 @@ export function main() {
|
||||
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', () => {
|
||||
@@ -388,6 +471,31 @@ export function main() {
|
||||
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', () => {
|
||||
@@ -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', () => {
|
||||
it('should verify UpgradeAdapter example', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
|
||||
@@ -29,7 +29,7 @@ export function init(moduleRef: NgModuleRef<AppModule>) {
|
||||
}
|
||||
|
||||
function detectChanges() {
|
||||
for (var i = 0; i < 10; i++) {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
appRef.tick();
|
||||
}
|
||||
detectChangesRuns += 10;
|
||||
|
||||
+2637
-1185
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user