Compare commits

...

48 Commits

Author SHA1 Message Date
Alex Rickabaugh 7ed39ebaaf docs(changelog): add changelog for 2.4.5 2017-01-25 13:48:29 -08:00
Alex Rickabaugh 091f0a5aaa chore(release): cut the 2.4.5 release 2017-01-25 13:48:21 -08:00
Alex Rickabaugh 315606e02c style(compiler): run format 2017-01-25 13:21:04 -08:00
Matias Niemelä 5ea373d184 docs(core): add docs for AnimationStyles and AnimationKeyframe (#14107) 2017-01-25 11:51:02 -08:00
Victor Berchet 6e36bb7b20 docs(compiler): add comment to warn about regexp changes (#14106)
ref #14082
2017-01-25 11:50:55 -08:00
Pete Bacon Darwin 3b2fb23805 fix(upgrade/static): ensure upgraded injector is initialized early enough (#14065)
This change ensures that the upgraded AngularJS injector is initialized
before the application run blocks are executed.

Closes #13811
2017-01-25 11:49:59 -08:00
Victor Berchet bd2eecb4de fix(compiler): fix regexp to support firefox 31 (#14082)
fixes #14029
closes #13900
2017-01-25 11:44:09 -08:00
Alex Rickabaugh 3d351a4f5f fixup: remove message.id check from this branch 2017-01-25 11:43:16 -08:00
Victor Berchet 5492fada21 fix(compiler): [i18n] XMB/XTB placeholder names can contain only A-Z, 0-9, _n
There are restrictions on the character set that can be used for xmb and xtb
placeholder names.

However because changing the placeholder names would change the message IDs it
is not possible to add those restrictions to the names used internally. Then we
have to map internal name to public names when generating an xmb file and back
when translating using an xtb file.

Note for implementors of `Serializer`:
- When writing a file, the implementor should take care of converting the
internal names to public names while visiting the message nodes - this is
required because the original nodes are needed to compute the message ID.
- When reading a file, the implementor does not need to take care of the mapping
back to internal names as this is handled in the `I18nToHtmlVisitor` used by the
`TranslationBundle`.

fixes b/34339636
2017-01-25 10:35:03 -08:00
Dzmitry Shylovich fd4f9acbcf fix(core): export animation classes required for Renderer impl (#14002)
Closes #14001
2017-01-25 10:32:16 -08:00
Matthew Hegarty 48528a86e1 docs(common): fix a typo on the DatePipe API docs (#14060) 2017-01-25 10:32:08 -08:00
Igor Minar 80364def27 ci: bump node and npm versions in circle.yaml to match travis 2017-01-25 10:31:50 -08:00
Jonathan Adamski 1803beb4d5 Fixed documentation reference to canActivate in canDeactivate (#14018)
Simple update to code sample which references canActivate: ['canDeactivateTeam'].
2017-01-25 10:31:42 -08:00
Alex Eagle 3bcba8a570 chore(docs): add missing comments (#14003)
This is a load-bearing change to avoid duplicate licenses in closure-compiled bundles.
See https://github.com/angular/tsickle/issues/332
2017-01-25 10:30:46 -08:00
Miško Hevery 84542d8ae7 docs(changelog): add changelog for 2.4.4 2017-01-18 18:35:54 -06:00
Miško Hevery 17cb3ec565 chore(release): cut the 2.4.4 release 2017-01-18 18:32:57 -06:00
Dzmitry Shylovich 015878afe6 fix(http): don't create a blob out of ArrayBuffer when type is application/octet-stream (#13992)
Closes #13973
2017-01-18 18:28:37 -06:00
Dzmitry Shylovich 2af58622c1 fix(router): enable loadChildren with function in aot (#13909)
Closes #11075
2017-01-18 18:28:02 -06:00
Tim Consolazio 7ffd10541d refactor(core): remove an unused import in application_ref (#13901) 2017-01-18 18:27:52 -06:00
Maarten Tibau 481b099d82 docs(CHANGELOG): added reference to closed issue in CHANGELOG for informational purposes (#13985) 2017-01-18 18:27:25 -06:00
Dzmitry Shylovich 49c4b0fa92 fix(router): routerLinkActive should not throw when not initialized (#13273)
Fixes #13270

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

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

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

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

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

Closes #10660, #12318, #12034

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

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

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

export function getNonLazyLoadedModule() { return NonLazyLoadedModule; }

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

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

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

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

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

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

Fixes angular/angular-cli#3663
2017-01-09 18:06:26 -05:00
Chuck Jazdzewski 5e9d3dba3a fix(compiler): avoid evaluating arguments to unknown decorators
Fixes #13605
2017-01-09 18:06:11 -05:00
Victor Berchet 16922655ca fix(Router): fix checking for object intersection 2017-01-09 18:06:03 -05:00
Victor Berchet 7dc12b93fe fix(Compiler): fix template binding parsing (*directive="-...")
fixes #13800
2017-01-09 16:50:14 -05:00
Dzmitry Shylovich 1c82b58185 fix(router): RouterLink mirrors input target as attribute
Closes #13837
2017-01-09 16:50:06 -05:00
Misko Hevery d6c414c08f fix: correctly show error when karma fails to load 2017-01-09 16:49:01 -05:00
Igor Minar d25d1730c7 chore(tsc-wrapped): bump version number to 0.5.1 2017-01-06 12:46:07 -08:00
106 changed files with 6396 additions and 3055 deletions
+32
View File
@@ -0,0 +1,32 @@
# Configuration for pullapprove.com
# See ownership spreadsheet:
# https://docs.google.com/spreadsheets/d/1-HIlzfbPYGsPr9KuYMe6bLfc4LXzPjpoALqtYRYTZB0/edit?pli=1#gid=0&vpid=A5
version: 2
group_defaults:
required: 1
reset_on_reopened:
enabled: true
approve_by_comment:
enabled: true
approve_regex: '^(Approved|:\+1:|LGTM)'
groups:
config:
conditions:
files:
- "*.yml"
- "*.json"
teams:
- repoowners
compiler:
conditions:
files:
- "tools/@angular/tsc-wrapped/*"
- "modules/@angular/compiler/*"
- "modules/@angular/compiler-cli/*"
teams:
- compiler-owners
- repoowners
+47 -1
View File
@@ -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
View File
@@ -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);
}
}
@@ -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 {
}
@@ -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'
+1 -1
View File
@@ -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"
},
+1 -24
View File
@@ -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;
}
+1 -1
View File
@@ -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));
}
});
+5 -4
View File
@@ -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>&lt;b&gt;</ex></ph>beaucoup<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</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>&lt;a&gt;</ex></ph>BAR<ph name="CLOSE_LINK"><ex>&lt;/a&gt;</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>&lt;div&gt;</ex></ph><ph name="ICU"/><ph name="CLOSE_TAG_DIV"><ex>&lt;/div&gt;</ex></ph>
</msg>
<msg id="1491627405349178954">it <ph name="START_BOLD_TEXT"><ex>&lt;b&gt;</ex></ph>should<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</ex></ph> work</msg>
</messagebundle>
<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>&lt;b&gt;</ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex>&lt;/b&gt;</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>&lt;a&gt;</ex></ph>bar<ph name="CLOSE_LINK"><ex>&lt;/a&gt;</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';
+2
View File
@@ -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';
+1 -1
View File
@@ -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,
+3
View File
@@ -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');
});
+3
View File
@@ -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');
}
+3 -3
View File
@@ -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;
+1 -1
View File
@@ -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 )');
@@ -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);
+1 -1
View File
@@ -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));
+3 -2
View File
@@ -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));
}
+1 -1
View File
@@ -216,7 +216,7 @@ export interface CanActivateChild {
* {
* path: 'team/:id',
* component: TeamCmp,
* canActivate: ['canDeactivateTeam']
* canDeactivate: ['canDeactivateTeam']
* }
* ])
* ],
+10
View File
@@ -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}`);
}
}
}
+1 -1
View File
@@ -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();
});
}));
});
}
+192 -109
View File
@@ -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));
+1 -1
View File
@@ -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
View File
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