Compare commits
81 Commits
2.3.1
...
4.0.0-beta
Author | SHA1 | Date | |
---|---|---|---|
fa9e21e83c | |||
b6078f5887 | |||
c65b4fa9dc | |||
169ed82900 | |||
fd8e15b15d | |||
aa40366a92 | |||
40d8d9c3e3 | |||
ee2ac025ef | |||
aa3769ba69 | |||
d4ddb6004e | |||
84400bcc86 | |||
42d9998cbb | |||
c18d2fe5e3 | |||
d91a86aac6 | |||
d6e5e9283c | |||
eab7e490c9 | |||
3e90605db9 | |||
79671a6f12 | |||
a659259962 | |||
b56474d067 | |||
8395f0e138 | |||
dd0519abad | |||
f238c8ac7a | |||
8c27c62fab | |||
5031adc7a3 | |||
821b8f09d6 | |||
2bf1bbc071 | |||
7b0a86718c | |||
3edca4d37e | |||
a0a05041ac | |||
7256d0ede5 | |||
d62d89319e | |||
f5f1d5f65c | |||
a8d237581d | |||
d036165a19 | |||
d17e690eb4 | |||
714f2af0dd | |||
2b90cd532f | |||
3a64ad895a | |||
9ec0a4e105 | |||
4b3d135193 | |||
1d0ed6f75f | |||
6f330a5fc9 | |||
e23076f767 | |||
7295a5e7f2 | |||
20bed46737 | |||
2a5012d515 | |||
fb38fba8f9 | |||
4c35be3e07 | |||
e9f307f948 | |||
2e500cc85b | |||
56dce0e26d | |||
8a8c53250e | |||
08ff2e5249 | |||
a006c1418a | |||
90c223591f | |||
aaf6e05f56 | |||
3bee521aa4 | |||
95f48292b1 | |||
04cfa1ebdf | |||
4022173d1e | |||
c8baf51f4f | |||
b4db73d0bf | |||
e15a3f273f | |||
213c713409 | |||
9a8423da36 | |||
f0b0762f4a | |||
b5c4bf1c59 | |||
56c361ff6a | |||
562f7a2f8b | |||
6dd5201765 | |||
72361fb68f | |||
5c6ec20c7e | |||
440ef02f29 | |||
4e3d58a792 | |||
61d7c1e0b3 | |||
bf93389615 | |||
4398056146 | |||
1b547886d0 | |||
9591a08dfb | |||
65965c27a8 |
@ -15,7 +15,7 @@
|
||||
* **language-service:** do not throw for invalid metadata ([#13261](https://github.com/angular/angular/issues/13261)) ([4a09c81](https://github.com/angular/angular/commit/4a09c81)), closes [#13255](https://github.com/angular/angular/issues/13255)
|
||||
* **language-service:** remove incompletely used parameter from `createLanguageServiceFromTypescript()` ([#13278](https://github.com/angular/angular/issues/13278)) ([25c2141](https://github.com/angular/angular/commit/25c2141)), closes [#13277](https://github.com/angular/angular/issues/13277)
|
||||
* **language-service:** update to use `CompilerHost` from compiler-cli ([#13189](https://github.com/angular/angular/issues/13189)) ([3ff6554](https://github.com/angular/angular/commit/3ff6554))
|
||||
* **router:** allow specifying a matcher wihtout specifying a path ([bbb7a39](https://github.com/angular/angular/commit/bbb7a39)), closes [#12972](https://github.com/angular/angular/issues/12972)
|
||||
* **router:** allow specifying a matcher without specifying a path ([bbb7a39](https://github.com/angular/angular/commit/bbb7a39)), closes [#12972](https://github.com/angular/angular/issues/12972)
|
||||
* **router:** fix replaceUrl on RouterLink directives ([349ad75](https://github.com/angular/angular/commit/349ad75))
|
||||
* **router:** fix skipLocationChanges on RouterLink directives ([f562cbf](https://github.com/angular/angular/commit/f562cbf)), closes [#13156](https://github.com/angular/angular/issues/13156)
|
||||
* **router:** make setUpLocationChangeListener idempotent ([25e5b2f](https://github.com/angular/angular/commit/25e5b2f))
|
||||
|
@ -24,8 +24,9 @@ with it.
|
||||
* `comp: forms`: `@kara`
|
||||
* `comp: http`: `@jeffbcross`
|
||||
* `comp: i18n`: `@vicb`
|
||||
* `comp: language service`: `@chuckjaz`
|
||||
* `comp: metadata-extractor`: `@chuckjaz`
|
||||
* `comp: router`: `@vsavkin`
|
||||
* `comp: router`: `@vicb`
|
||||
* `comp: testing`: `@juliemr`
|
||||
* `comp: upgrade`: `@mhevery`
|
||||
* `comp: web-worker`: `@vicb`
|
||||
|
@ -36,7 +36,7 @@ var CIconfiguration = {
|
||||
'iOS7': {unitTest: {target: 'BS', required: true}, e2e: {target: null, required: true}},
|
||||
'iOS8': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
||||
'iOS9': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
||||
'iOS10': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
|
||||
'iOS10': {unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
|
||||
'WindowsPhone': {unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}
|
||||
};
|
||||
|
||||
@ -44,10 +44,10 @@ var customLaunchers = {
|
||||
'DartiumWithWebPlatform':
|
||||
{base: 'Dartium', flags: ['--enable-experimental-web-platform-features']},
|
||||
'ChromeNoSandbox': {base: 'Chrome', flags: ['--no-sandbox']},
|
||||
'SL_CHROME': {base: 'SauceLabs', browserName: 'chrome', version: '52'},
|
||||
'SL_CHROME': {base: 'SauceLabs', browserName: 'chrome', version: '54'},
|
||||
'SL_CHROMEBETA': {base: 'SauceLabs', browserName: 'chrome', version: 'beta'},
|
||||
'SL_CHROMEDEV': {base: 'SauceLabs', browserName: 'chrome', version: 'dev'},
|
||||
'SL_FIREFOX': {base: 'SauceLabs', browserName: 'firefox', version: '46'},
|
||||
'SL_FIREFOX': {base: 'SauceLabs', browserName: 'firefox', version: '50'},
|
||||
'SL_FIREFOXBETA': {base: 'SauceLabs', browserName: 'firefox', version: 'beta'},
|
||||
'SL_FIREFOXDEV': {base: 'SauceLabs', browserName: 'firefox', version: 'dev'},
|
||||
'SL_SAFARI7': {base: 'SauceLabs', browserName: 'safari', platform: 'OS X 10.9', version: '7.0'},
|
||||
@ -96,7 +96,7 @@ var customLaunchers = {
|
||||
'BS_IE10': {
|
||||
base: 'BrowserStack',
|
||||
browser: 'ie',
|
||||
browser_version: '10.0',
|
||||
browser_version: '10.1',
|
||||
os: 'Windows',
|
||||
os_version: '8'
|
||||
},
|
||||
|
@ -48,13 +48,15 @@ module.exports = function(config) {
|
||||
|
||||
exclude: [
|
||||
'dist/all/@angular/**/e2e_test/**',
|
||||
'dist/all/@angular/router/**',
|
||||
'dist/all/@angular/**/*node_only_spec.js',
|
||||
'dist/all/@angular/benchpress/**',
|
||||
'dist/all/@angular/compiler-cli/**',
|
||||
'dist/all/@angular/compiler/test/aot/**',
|
||||
'dist/all/@angular/benchpress/**',
|
||||
'dist/all/@angular/examples/**/e2e_test/*',
|
||||
'dist/all/@angular/language-service/**',
|
||||
'dist/all/angular1_router.js',
|
||||
'dist/all/@angular/router/**',
|
||||
'dist/all/@angular/platform-browser/testing/e2e_util.js',
|
||||
'dist/all/angular1_router.js',
|
||||
'dist/examples/**/e2e_test/**',
|
||||
],
|
||||
|
||||
|
@ -12,9 +12,9 @@
|
||||
* Entry point for all public APIs of the common package.
|
||||
*/
|
||||
export * from './location/index';
|
||||
export {NgLocalization} from './localization';
|
||||
export {NgLocaleLocalization, NgLocalization} from './localization';
|
||||
export {CommonModule} from './common_module';
|
||||
export {NgClass, NgFor, NgIf, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet} from './directives/index';
|
||||
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe} from './pipes/index';
|
||||
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe} from './pipes/index';
|
||||
export {VERSION} from './version';
|
||||
export {Version} from '@angular/core';
|
||||
|
@ -6,46 +6,152 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
import {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
|
||||
|
||||
/**
|
||||
* Removes or recreates a portion of the DOM tree based on an {expression}.
|
||||
* Conditionally includes a template based on the value of an `expression`.
|
||||
*
|
||||
* If the expression assigned to `ngIf` evaluates to a falsy value then the element
|
||||
* is removed from the DOM, otherwise a clone of the element is reinserted into the DOM.
|
||||
* `ngIf` evaluates the `expression` and then renders the `then` or `else` template in its place
|
||||
* when expression is thruthy or falsy respectively. Typically the:
|
||||
* - `then` template is the inline template of `ngIf` unless bound to a different value.
|
||||
* - `else` template is blank unless its bound.
|
||||
*
|
||||
* ### Example ([live demo](http://plnkr.co/edit/fe0kgemFBtmQOY31b4tw?p=preview)):
|
||||
* # Most common usage
|
||||
*
|
||||
* The most common usage of the `ngIf` is to conditionally show the inline template as seen in this
|
||||
* example:
|
||||
* {@example common/ngIf/ts/module.ts region='NgIfSimple'}
|
||||
*
|
||||
* # Showing an alternative template using `else`
|
||||
*
|
||||
* If it is necessary to display a template when the `expression` is falsy use the `else` template
|
||||
* binding as shown. Note that the `else` binding points to a `<template>` labeled `#elseBlock`.
|
||||
* The template can be defined anywhere in the component view but is typically placed right after
|
||||
* `ngIf` for readability.
|
||||
*
|
||||
* {@example common/ngIf/ts/module.ts region='NgIfElse'}
|
||||
*
|
||||
* # Using non-inlined `then` template
|
||||
*
|
||||
* Usually the `then` template is the inlined template of the `ngIf`, but it can be changed using
|
||||
* a binding (just like `else`). Because `then` and `else` are bindings, the template references can
|
||||
* change at runtime as shown in thise example.
|
||||
*
|
||||
* {@example common/ngIf/ts/module.ts region='NgIfThenElse'}
|
||||
*
|
||||
* # Storing conditional result in a variable
|
||||
*
|
||||
* A common patter is that we need to show a set of properties from the same object. if the
|
||||
* object is undefined, then we have to use the safe-traversal-operator `?.` to guard against
|
||||
* dereferencing a `null` value. This is especially the case when waiting on async data such as
|
||||
* when using the `async` pipe as shown in folowing example:
|
||||
*
|
||||
* ```
|
||||
* <div *ngIf="errorCount > 0" class="error">
|
||||
* <!-- Error message displayed when the errorCount property in the current context is greater
|
||||
* than 0. -->
|
||||
* {{errorCount}} errors detected
|
||||
* </div>
|
||||
* Hello {{ (userStream|async)?.last }}, {{ (userStream|async)?.first }}!
|
||||
* ```
|
||||
*
|
||||
* There are several inefficiencies in the above example.
|
||||
* - We create multiple subscriptions on the `userStream`. One for each `async` pipe, or two
|
||||
* as shown in the example above.
|
||||
* - We can not display an alternative screen while waiting for the data to arrive asynchronously.
|
||||
* - We have to use the safe-traversal-operator `?.` to access properties, which is cumbersome.
|
||||
* - We have to place the `async` pipe in parenthesis.
|
||||
*
|
||||
* A better way to do this is to use `ngIf` and store the result of the condition in a local
|
||||
* variable as shown in the the example below:
|
||||
*
|
||||
* {@example common/ngIf/ts/module.ts region='NgIfLet'}
|
||||
*
|
||||
* Notice that:
|
||||
* - We use only one `async` pipe and hence only one subscription gets created.
|
||||
* - `ngIf` stores the result of the `userStream|async` in the local variable `user`.
|
||||
* - The local `user` can than be bound repeatedly in a more efficient way.
|
||||
* - No need to use the safe-traversal-operator `?.` to access properties as `ngIf` will only
|
||||
* display the data if `userStream` returns a value.
|
||||
* - We can display an alternative template while waiting for the data.
|
||||
*
|
||||
* ### Syntax
|
||||
*
|
||||
* Simple form:
|
||||
* - `<div *ngIf="condition">...</div>`
|
||||
* - `<div template="ngIf condition">...</div>`
|
||||
* - `<template [ngIf]="condition"><div>...</div></template>`
|
||||
* - `<ng-container [ngIf]="condition"><div>...</div></ng-container>`
|
||||
*
|
||||
* Form with an else block:
|
||||
* ```
|
||||
* <div *ngIf="condition; else elseBlock">...</div>
|
||||
* <template #elseBlock>...</template>
|
||||
* ```
|
||||
*
|
||||
* Form with a `then` and `else` block:
|
||||
* ```
|
||||
* <div *ngIf="condition; then thenBlock else elseBlock"></div>
|
||||
* <template #thenBlock>...</template>
|
||||
* <template #elseBlock>...</template>
|
||||
* ```
|
||||
*
|
||||
* Form with storing the value locally:
|
||||
* ```
|
||||
* <div *ngIf="condition; else elseBlock; let value">{{value}}</div>
|
||||
* <template #elseBlock>...</template>
|
||||
* ```
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Directive({selector: '[ngIf]'})
|
||||
export class NgIf {
|
||||
private _hasView = false;
|
||||
private _context: NgIfContext = new NgIfContext();
|
||||
private _thenTemplateRef: TemplateRef<NgIfContext> = null;
|
||||
private _elseTemplateRef: TemplateRef<NgIfContext> = null;
|
||||
private _thenViewRef: EmbeddedViewRef<NgIfContext> = null;
|
||||
private _elseViewRef: EmbeddedViewRef<NgIfContext> = null;
|
||||
|
||||
constructor(private _viewContainer: ViewContainerRef, private _template: TemplateRef<Object>) {}
|
||||
constructor(private _viewContainer: ViewContainerRef, templateRef: TemplateRef<NgIfContext>) {
|
||||
this._thenTemplateRef = templateRef;
|
||||
}
|
||||
|
||||
@Input()
|
||||
set ngIf(condition: any) {
|
||||
if (condition && !this._hasView) {
|
||||
this._hasView = true;
|
||||
this._viewContainer.createEmbeddedView(this._template);
|
||||
} else if (!condition && this._hasView) {
|
||||
this._hasView = false;
|
||||
this._viewContainer.clear();
|
||||
this._context.$implicit = condition;
|
||||
this._updateView();
|
||||
}
|
||||
|
||||
@Input()
|
||||
set ngIfThen(templateRef: TemplateRef<NgIfContext>) {
|
||||
this._thenTemplateRef = templateRef;
|
||||
this._thenViewRef = null; // clear previous view if any.
|
||||
this._updateView();
|
||||
}
|
||||
|
||||
@Input()
|
||||
set ngIfElse(templateRef: TemplateRef<NgIfContext>) {
|
||||
this._elseTemplateRef = templateRef;
|
||||
this._elseViewRef = null; // clear previous view if any.
|
||||
this._updateView();
|
||||
}
|
||||
|
||||
private _updateView() {
|
||||
if (this._context.$implicit) {
|
||||
if (!this._thenViewRef) {
|
||||
this._viewContainer.clear();
|
||||
this._elseViewRef = null;
|
||||
if (this._thenTemplateRef) {
|
||||
this._thenViewRef =
|
||||
this._viewContainer.createEmbeddedView(this._thenTemplateRef, this._context);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!this._elseViewRef) {
|
||||
this._viewContainer.clear();
|
||||
this._thenViewRef = null;
|
||||
if (this._elseTemplateRef) {
|
||||
this._elseViewRef =
|
||||
this._viewContainer.createEmbeddedView(this._elseTemplateRef, this._context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class NgIfContext { public $implicit: any = null; }
|
||||
|
@ -49,10 +49,10 @@ export function getPluralCategory(
|
||||
*/
|
||||
@Injectable()
|
||||
export class NgLocaleLocalization extends NgLocalization {
|
||||
constructor(@Inject(LOCALE_ID) private _locale: string) { super(); }
|
||||
constructor(@Inject(LOCALE_ID) protected locale: string) { super(); }
|
||||
|
||||
getPluralCategory(value: any): string {
|
||||
const plural = getPluralCase(this._locale, value);
|
||||
const plural = getPluralCase(this.locale, value);
|
||||
|
||||
switch (plural) {
|
||||
case Plural.Zero:
|
||||
@ -431,4 +431,4 @@ export function getPluralCase(locale: string, nLike: number | string): Plural {
|
||||
default:
|
||||
return Plural.Other;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
|
||||
|
||||
|
||||
/**
|
||||
* @whatItDoes Use URL hash for storing application location data.
|
||||
* @description
|
||||
* `HashLocationStrategy` is a {@link LocationStrategy} used to configure the
|
||||
* {@link Location} service to represent its state in the
|
||||
* [hash fragment](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax)
|
||||
@ -27,18 +29,7 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* import {Component, NgModule} from '@angular/core';
|
||||
* import {
|
||||
* LocationStrategy,
|
||||
* HashLocationStrategy
|
||||
* } from '@angular/common';
|
||||
*
|
||||
* @NgModule({
|
||||
* providers: [{provide: LocationStrategy, useClass: HashLocationStrategy}]
|
||||
* })
|
||||
* class AppModule {}
|
||||
* ```
|
||||
* {@example common/location/ts/hash_location_component.ts region='LocationComponent'}
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
|
@ -12,7 +12,8 @@ import {LocationStrategy} from './location_strategy';
|
||||
|
||||
|
||||
/**
|
||||
* `Location` is a service that applications can use to interact with a browser's URL.
|
||||
* @whatItDoes `Location` is a service that applications can use to interact with a browser's URL.
|
||||
* @description
|
||||
* Depending on which {@link LocationStrategy} is used, `Location` will either persist
|
||||
* to the URL's path or the URL's hash segment.
|
||||
*
|
||||
@ -28,19 +29,7 @@ import {LocationStrategy} from './location_strategy';
|
||||
* - `/my/app/user/123/` **is not** normalized
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```
|
||||
* import {Component} from '@angular/core';
|
||||
* import {Location} from '@angular/common';
|
||||
*
|
||||
* @Component({selector: 'app-component'})
|
||||
* class AppCmp {
|
||||
* constructor(location: Location) {
|
||||
* location.go('/foo');
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* {@example common/location/ts/path_location_component.ts region='LocationComponent'}
|
||||
* @stable
|
||||
*/
|
||||
@Injectable()
|
||||
|
@ -12,7 +12,7 @@ import {LocationChangeListener} from './platform_location';
|
||||
/**
|
||||
* `LocationStrategy` is responsible for representing and reading route state
|
||||
* from the browser's URL. Angular provides two strategies:
|
||||
* {@link HashLocationStrategy} and {@link PathLocationStrategy} (default).
|
||||
* {@link HashLocationStrategy} and {@link PathLocationStrategy}.
|
||||
*
|
||||
* This is used under the hood of the {@link Location} service.
|
||||
*
|
||||
|
@ -17,14 +17,13 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
|
||||
|
||||
|
||||
/**
|
||||
* @whatItDoes Use URL for storing application location data.
|
||||
* @description
|
||||
* `PathLocationStrategy` is a {@link LocationStrategy} used to configure the
|
||||
* {@link Location} service to represent its state in the
|
||||
* [path](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) of the
|
||||
* browser's URL.
|
||||
*
|
||||
* `PathLocationStrategy` is the default binding for {@link LocationStrategy}
|
||||
* provided in {@link ROUTER_PROVIDERS}.
|
||||
*
|
||||
* If you're using `PathLocationStrategy`, you must provide a {@link APP_BASE_HREF}
|
||||
* or add a base element to the document. This URL prefix that will be preserved
|
||||
* when generating and recognizing URLs.
|
||||
@ -37,6 +36,10 @@ import {LocationChangeListener, PlatformLocation} from './platform_location';
|
||||
* `location.go('/foo')`, the browser's URL will become
|
||||
* `example.com/my/app/foo`.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example common/location/ts/path_location_component.ts region='LocationComponent'}
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Injectable()
|
||||
|
61
modules/@angular/common/src/pipes/case_conversion_pipes.ts
Normal file
61
modules/@angular/common/src/pipes/case_conversion_pipes.ts
Normal file
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* @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 {Pipe, PipeTransform} from '@angular/core';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
/**
|
||||
* Transforms text to lowercase.
|
||||
*
|
||||
* {@example core/pipes/ts/lowerupper_pipe/lowerupper_pipe_example.ts region='LowerUpperPipe' }
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Pipe({name: 'lowercase'})
|
||||
export class LowerCasePipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
if (!value) return value;
|
||||
if (typeof value !== 'string') {
|
||||
throw new InvalidPipeArgumentError(LowerCasePipe, value);
|
||||
}
|
||||
return value.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms text to titlecase.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Pipe({name: 'titlecase'})
|
||||
export class TitleCasePipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
if (!value) return value;
|
||||
if (typeof value !== 'string') {
|
||||
throw new InvalidPipeArgumentError(TitleCasePipe, value);
|
||||
}
|
||||
|
||||
return value[0].toUpperCase() + value.substr(1).toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms text to uppercase.
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Pipe({name: 'uppercase'})
|
||||
export class UpperCasePipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
if (!value) return value;
|
||||
if (typeof value !== 'string') {
|
||||
throw new InvalidPipeArgumentError(UpperCasePipe, value);
|
||||
}
|
||||
return value.toUpperCase();
|
||||
}
|
||||
}
|
@ -7,11 +7,14 @@
|
||||
*/
|
||||
|
||||
import {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
|
||||
import {DateFormatter} from '../facade/intl';
|
||||
|
||||
import {NumberWrapper, isDate} from '../facade/lang';
|
||||
|
||||
import {DateFormatter} from './intl';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @ngModule CommonModule
|
||||
* @whatItDoes Formats a date according to locale rules.
|
||||
|
@ -12,14 +12,13 @@
|
||||
* This module provides a set of common Pipes.
|
||||
*/
|
||||
import {AsyncPipe} from './async_pipe';
|
||||
import {LowerCasePipe, TitleCasePipe, UpperCasePipe} from './case_conversion_pipes';
|
||||
import {DatePipe} from './date_pipe';
|
||||
import {I18nPluralPipe} from './i18n_plural_pipe';
|
||||
import {I18nSelectPipe} from './i18n_select_pipe';
|
||||
import {JsonPipe} from './json_pipe';
|
||||
import {LowerCasePipe} from './lowercase_pipe';
|
||||
import {CurrencyPipe, DecimalPipe, PercentPipe} from './number_pipe';
|
||||
import {SlicePipe} from './slice_pipe';
|
||||
import {UpperCasePipe} from './uppercase_pipe';
|
||||
|
||||
export {
|
||||
AsyncPipe,
|
||||
@ -32,9 +31,11 @@ export {
|
||||
LowerCasePipe,
|
||||
PercentPipe,
|
||||
SlicePipe,
|
||||
TitleCasePipe,
|
||||
UpperCasePipe
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A collection of Angular pipes that are likely to be used in each and every application.
|
||||
*/
|
||||
@ -46,6 +47,7 @@ export const COMMON_PIPES = [
|
||||
SlicePipe,
|
||||
DecimalPipe,
|
||||
PercentPipe,
|
||||
TitleCasePipe,
|
||||
CurrencyPipe,
|
||||
DatePipe,
|
||||
I18nPluralPipe,
|
||||
|
@ -1,37 +0,0 @@
|
||||
/**
|
||||
* @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 {Pipe, PipeTransform} from '@angular/core';
|
||||
import {isBlank} from '../facade/lang';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
|
||||
/**
|
||||
* @ngModule CommonModule
|
||||
* @whatItDoes Transforms string to lowercase.
|
||||
* @howToUse `expression | lowercase`
|
||||
* @description
|
||||
*
|
||||
* Converts value into a lowercase string using `String.prototype.toLowerCase()`.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example common/pipes/ts/lowerupper_pipe.ts region='LowerUpperPipe'}
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Pipe({name: 'lowercase'})
|
||||
export class LowerCasePipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
if (isBlank(value)) return value;
|
||||
if (typeof value !== 'string') {
|
||||
throw new InvalidPipeArgumentError(LowerCasePipe, value);
|
||||
}
|
||||
return value.toLowerCase();
|
||||
}
|
||||
}
|
@ -8,9 +8,9 @@
|
||||
|
||||
import {Inject, LOCALE_ID, Pipe, PipeTransform, Type} from '@angular/core';
|
||||
|
||||
import {NumberFormatStyle, NumberFormatter} from '../facade/intl';
|
||||
import {NumberWrapper, isBlank, isPresent} from '../facade/lang';
|
||||
|
||||
import {NumberFormatStyle, NumberFormatter} from './intl';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
const _NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(-(\d+))?)?$/;
|
||||
|
@ -1,36 +0,0 @@
|
||||
/**
|
||||
* @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 {Pipe, PipeTransform} from '@angular/core';
|
||||
import {isBlank} from '../facade/lang';
|
||||
import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
|
||||
/**
|
||||
* @ngModule CommonModule
|
||||
* @whatItDoes Transforms string to uppercase.
|
||||
* @howToUse `expression | uppercase`
|
||||
* @description
|
||||
*
|
||||
* Converts value into an uppercase string using `String.prototype.toUpperCase()`.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* {@example common/pipes/ts/lowerupper_pipe.ts region='LowerUpperPipe'}
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
@Pipe({name: 'uppercase'})
|
||||
export class UpperCasePipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
if (isBlank(value)) return value;
|
||||
if (typeof value !== 'string') {
|
||||
throw new InvalidPipeArgumentError(UpperCasePipe, value);
|
||||
}
|
||||
return value.toUpperCase();
|
||||
}
|
||||
}
|
@ -153,6 +153,78 @@ export function main() {
|
||||
expect(getDOM().hasClass(getDOM().querySelector(fixture.nativeElement, 'span'), 'foo'))
|
||||
.toBe(true);
|
||||
}));
|
||||
|
||||
describe('else', () => {
|
||||
it('should support else', async(() => {
|
||||
const template = '<div>' +
|
||||
'<span *ngIf="booleanCondition; else elseBlock">TRUE</span>' +
|
||||
'<template #elseBlock>FALSE</template>' +
|
||||
'</div>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('TRUE');
|
||||
|
||||
getComponent().booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('FALSE');
|
||||
}));
|
||||
|
||||
it('should support then and else', async(() => {
|
||||
const template = '<div>' +
|
||||
'<span *ngIf="booleanCondition; then thenBlock; else elseBlock">IGNORE</span>' +
|
||||
'<template #thenBlock>THEN</template>' +
|
||||
'<template #elseBlock>ELSE</template>' +
|
||||
'</div>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('THEN');
|
||||
|
||||
getComponent().booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('ELSE');
|
||||
}));
|
||||
|
||||
it('should support dynamic else', async(() => {
|
||||
const template = '<div>' +
|
||||
'<span *ngIf="booleanCondition; else nestedBooleanCondition ? b1 : b2">TRUE</span>' +
|
||||
'<template #b1>FALSE1</template>' +
|
||||
'<template #b2>FALSE2</template>' +
|
||||
'</div>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('TRUE');
|
||||
|
||||
getComponent().booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('FALSE1');
|
||||
|
||||
getComponent().nestedBooleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('FALSE2');
|
||||
}));
|
||||
|
||||
it('should support binding to variable', async(() => {
|
||||
const template = '<div>' +
|
||||
'<span *ngIf="booleanCondition; else elseBlock; let v">{{v}}</span>' +
|
||||
'<template #elseBlock let-v>{{v}}</template>' +
|
||||
'</div>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('true');
|
||||
|
||||
getComponent().booleanCondition = false;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement).toHaveText('false');
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @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 {LowerCasePipe, TitleCasePipe, UpperCasePipe} from '@angular/common';
|
||||
|
||||
export function main() {
|
||||
describe('LowerCasePipe', () => {
|
||||
let pipe: LowerCasePipe;
|
||||
|
||||
beforeEach(() => { pipe = new LowerCasePipe(); });
|
||||
|
||||
it('should return lowercase', () => { expect(pipe.transform('FOO')).toEqual('foo'); });
|
||||
|
||||
it('should lowercase when there is a new value', () => {
|
||||
expect(pipe.transform('FOO')).toEqual('foo');
|
||||
expect(pipe.transform('BAr')).toEqual('bar');
|
||||
});
|
||||
|
||||
it('should not support other objects',
|
||||
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
|
||||
});
|
||||
|
||||
describe('TitleCasePipe', () => {
|
||||
let pipe: TitleCasePipe;
|
||||
|
||||
beforeEach(() => { pipe = new TitleCasePipe(); });
|
||||
|
||||
it('should return titlecase', () => { expect(pipe.transform('foo')).toEqual('Foo'); });
|
||||
|
||||
it('should titlecase when there is a new value', () => {
|
||||
expect(pipe.transform('bar')).toEqual('Bar');
|
||||
expect(pipe.transform('foo')).toEqual('Foo');
|
||||
});
|
||||
|
||||
it('should not support other objects',
|
||||
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
|
||||
});
|
||||
|
||||
describe('UpperCasePipe', () => {
|
||||
let pipe: UpperCasePipe;
|
||||
|
||||
beforeEach(() => { pipe = new UpperCasePipe(); });
|
||||
|
||||
it('should return uppercase', () => { expect(pipe.transform('foo')).toEqual('FOO'); });
|
||||
|
||||
it('should uppercase when there is a new value', () => {
|
||||
expect(pipe.transform('foo')).toEqual('FOO');
|
||||
expect(pipe.transform('bar')).toEqual('BAR');
|
||||
});
|
||||
|
||||
it('should not support other objects',
|
||||
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
|
||||
});
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/**
|
||||
* @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 {LowerCasePipe} from '@angular/common';
|
||||
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
|
||||
export function main() {
|
||||
describe('LowerCasePipe', () => {
|
||||
let upper: string;
|
||||
let lower: string;
|
||||
let pipe: LowerCasePipe;
|
||||
|
||||
beforeEach(() => {
|
||||
lower = 'something';
|
||||
upper = 'SOMETHING';
|
||||
pipe = new LowerCasePipe();
|
||||
});
|
||||
|
||||
describe('transform', () => {
|
||||
it('should return lowercase', () => {
|
||||
const val = pipe.transform(upper);
|
||||
expect(val).toEqual(lower);
|
||||
});
|
||||
|
||||
it('should lowercase when there is a new value', () => {
|
||||
const val = pipe.transform(upper);
|
||||
expect(val).toEqual(lower);
|
||||
const val2 = pipe.transform('WAT');
|
||||
expect(val2).toEqual('wat');
|
||||
});
|
||||
|
||||
it('should not support other objects',
|
||||
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
|
||||
});
|
||||
|
||||
});
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
/**
|
||||
* @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 {UpperCasePipe} from '@angular/common';
|
||||
import {beforeEach, describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
|
||||
export function main() {
|
||||
describe('UpperCasePipe', () => {
|
||||
let upper: string;
|
||||
let lower: string;
|
||||
let pipe: UpperCasePipe;
|
||||
|
||||
beforeEach(() => {
|
||||
lower = 'something';
|
||||
upper = 'SOMETHING';
|
||||
pipe = new UpperCasePipe();
|
||||
});
|
||||
|
||||
describe('transform', () => {
|
||||
|
||||
it('should return uppercase', () => {
|
||||
const val = pipe.transform(lower);
|
||||
expect(val).toEqual(upper);
|
||||
});
|
||||
|
||||
it('should uppercase when there is a new value', () => {
|
||||
const val = pipe.transform(lower);
|
||||
expect(val).toEqual(upper);
|
||||
const val2 = pipe.transform('wat');
|
||||
expect(val2).toEqual('WAT');
|
||||
});
|
||||
|
||||
it('should not support other objects',
|
||||
() => { expect(() => pipe.transform(<any>{})).toThrowError(); });
|
||||
});
|
||||
|
||||
});
|
||||
}
|
@ -11,3 +11,7 @@ export {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter, NodeComp
|
||||
export {Extractor} from './src/extractor';
|
||||
export * from '@angular/tsc-wrapped';
|
||||
export {VERSION} from './src/version';
|
||||
|
||||
|
||||
// TODO(hansl): moving to Angular 4 need to update this API.
|
||||
export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api'
|
||||
|
@ -0,0 +1,3 @@
|
||||
:host {
|
||||
background-color: blue;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
<div>
|
||||
<h1>hello world</h1>
|
||||
<a [routerLink]="['lazy']">lazy</a>
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @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, ViewEncapsulation} from '@angular/core';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: 'app.component.html',
|
||||
styleUrls: ['app.component.css'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class AppComponent {
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @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 {BrowserModule} from '@angular/platform-browser';
|
||||
import {RouterModule} from '@angular/router';
|
||||
|
||||
import {AppComponent} from './app.component';
|
||||
|
||||
@Component({selector: 'home-view', template: 'home!'})
|
||||
export class HomeView {
|
||||
}
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent, HomeView],
|
||||
imports: [
|
||||
BrowserModule, RouterModule.forRoot([
|
||||
{path: 'lazy', loadChildren: './lazy.module#LazyModule'},
|
||||
{path: 'feature2', loadChildren: 'feature2/feature2.module#Feature2Module'},
|
||||
{path: '', component: HomeView}
|
||||
])
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @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: 'feature-component', template: 'foo.html'})
|
||||
export class FeatureComponent {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [FeatureComponent],
|
||||
imports: [RouterModule.forChild([{path: '', component: FeatureComponent}])]
|
||||
})
|
||||
export class FeatureModule {
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @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!'})
|
||||
export class LazyFeatureComponent {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild([
|
||||
{path: '', component: LazyFeatureComponent, pathMatch: 'full'},
|
||||
{path: 'feature', loadChildren: './feature.module#FeatureModule'}
|
||||
])],
|
||||
declarations: [LazyFeatureComponent]
|
||||
})
|
||||
export class LazyFeatureModule {
|
||||
}
|
@ -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: 'feature-component', template: 'foo.html'})
|
||||
export class FeatureComponent {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [FeatureComponent],
|
||||
imports: [RouterModule.forChild([
|
||||
{path: '', component: FeatureComponent},
|
||||
])]
|
||||
})
|
||||
export default class DefaultModule {
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @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: 'feature-component', template: 'foo.html'})
|
||||
export class FeatureComponent {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [FeatureComponent],
|
||||
imports: [RouterModule.forChild([
|
||||
{path: '', component: FeatureComponent}, {path: 'd', loadChildren: './default.module'} {
|
||||
path: 'e',
|
||||
loadChildren: 'feature/feature.module#FeatureModule'
|
||||
}
|
||||
])]
|
||||
})
|
||||
export class Feature2Module {
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @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-comp', template: 'lazy!'})
|
||||
export class LazyComponent {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild([
|
||||
{path: '', component: LazyComponent, pathMatch: 'full'},
|
||||
{path: 'feature', loadChildren: './feature/feature.module#FeatureModule'},
|
||||
{path: 'lazy-feature', loadChildren: './feature/lazy-feature.module#LazyFeatureModule'}
|
||||
])],
|
||||
declarations: [LazyComponent]
|
||||
})
|
||||
export class LazyModule {
|
||||
}
|
||||
|
||||
export class SecondModule {}
|
@ -0,0 +1,19 @@
|
||||
{
|
||||
"angularCompilerOptions": {
|
||||
// For TypeScript 1.8, we have to lay out generated files
|
||||
// in the same source directory with your code.
|
||||
"genDir": ".",
|
||||
"debug": true
|
||||
},
|
||||
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"experimentalDecorators": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "",
|
||||
"declaration": true,
|
||||
"lib": ["es6", "dom"],
|
||||
"baseUrl": "."
|
||||
}
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
/* tslint:disable:no-console */
|
||||
|
||||
// Must be imported first, because angular2 decorators throws on load.
|
||||
import 'reflect-metadata';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
import * as assert from 'assert';
|
||||
import {tsc} from '@angular/tsc-wrapped/src/tsc';
|
||||
import {AngularCompilerOptions, CodeGenerator, CompilerHostContext, NodeCompilerHostContext, __NGTOOLS_PRIVATE_API_2} from '@angular/compiler-cli';
|
||||
|
||||
const glob = require('glob');
|
||||
|
||||
|
||||
/**
|
||||
* Main method.
|
||||
* Standalone program that executes codegen using the ngtools API and tests that files were
|
||||
* properly read and wrote.
|
||||
*/
|
||||
function main() {
|
||||
console.log(`testing ngtools API...`);
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => codeGenTest())
|
||||
.then(() => lazyRoutesTest())
|
||||
.then(() => {
|
||||
console.log('All done!');
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err.stack);
|
||||
console.error('Test failed');
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function codeGenTest() {
|
||||
const basePath = path.join(__dirname, '../ngtools_src');
|
||||
const project = path.join(basePath, 'tsconfig-build.json');
|
||||
const readResources: string[] = [];
|
||||
const wroteFiles: string[] = [];
|
||||
|
||||
const config = tsc.readConfiguration(project, basePath);
|
||||
const hostContext = new NodeCompilerHostContext();
|
||||
const delegateHost = ts.createCompilerHost(config.parsed.options, true);
|
||||
const host: ts.CompilerHost = Object.assign({}, delegateHost, {
|
||||
writeFile: (fileName: string, ...rest: any[]) => {
|
||||
wroteFiles.push(fileName);
|
||||
return delegateHost.writeFile.call(delegateHost, fileName, ...rest);
|
||||
}
|
||||
});
|
||||
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
|
||||
|
||||
config.ngOptions.basePath = basePath;
|
||||
|
||||
console.log(`>>> running codegen for ${project}`);
|
||||
return __NGTOOLS_PRIVATE_API_2
|
||||
.codeGen({
|
||||
basePath,
|
||||
compilerOptions: config.parsed.options, program, host,
|
||||
|
||||
angularCompilerOptions: config.ngOptions,
|
||||
|
||||
// i18n options.
|
||||
i18nFormat: null,
|
||||
i18nFile: null,
|
||||
locale: null,
|
||||
|
||||
readResource: (fileName: string) => {
|
||||
readResources.push(fileName);
|
||||
return hostContext.readResource(fileName);
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
console.log(`>>> codegen done, asserting read and wrote files`);
|
||||
|
||||
// Assert for each file that it has been read and each `ts` has a written file associated.
|
||||
const allFiles = glob.sync(path.join(basePath, '**/*'), {nodir: true});
|
||||
|
||||
allFiles.forEach((fileName: string) => {
|
||||
// Skip tsconfig.
|
||||
if (fileName.match(/tsconfig-build.json$/)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Assert that file was read.
|
||||
if (fileName.match(/\.module\.ts$/)) {
|
||||
const factory = fileName.replace(/\.module\.ts$/, '.module.ngfactory.ts');
|
||||
assert(wroteFiles.indexOf(factory) != -1, `Expected file "${factory}" to be written.`);
|
||||
} else if (fileName.match(/\.css$/) || fileName.match(/\.html$/)) {
|
||||
assert(
|
||||
readResources.indexOf(fileName) != -1,
|
||||
`Expected resource "${fileName}" to be read.`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`done, no errors.`);
|
||||
})
|
||||
.catch((e: any) => {
|
||||
console.error(e.stack);
|
||||
console.error('Compilation failed');
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function lazyRoutesTest() {
|
||||
const basePath = path.join(__dirname, '../ngtools_src');
|
||||
const project = path.join(basePath, 'tsconfig-build.json');
|
||||
|
||||
const config = tsc.readConfiguration(project, basePath);
|
||||
const host = ts.createCompilerHost(config.parsed.options, true);
|
||||
const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host);
|
||||
|
||||
config.ngOptions.basePath = basePath;
|
||||
|
||||
const lazyRoutes = __NGTOOLS_PRIVATE_API_2.listLazyRoutes({
|
||||
program,
|
||||
host,
|
||||
angularCompilerOptions: config.ngOptions,
|
||||
entryModule: 'app.module#AppModule'
|
||||
});
|
||||
|
||||
const expectations: {[route: string]: string} = {
|
||||
'./lazy.module#LazyModule': 'lazy.module.ts',
|
||||
'./feature/feature.module#FeatureModule': 'feature/feature.module.ts',
|
||||
'./feature/lazy-feature.module#LazyFeatureModule': 'feature/lazy-feature.module.ts',
|
||||
'feature2/feature2.module#Feature2Module': 'feature2/feature2.module.ts',
|
||||
'./default.module': 'feature2/default.module.ts',
|
||||
'feature/feature.module#FeatureModule': 'feature/feature.module.ts'
|
||||
};
|
||||
|
||||
Object.keys(lazyRoutes).forEach((route: string) => {
|
||||
assert(route in expectations, `Found a route that was not expected: "${route}".`);
|
||||
assert(
|
||||
lazyRoutes[route] == path.join(basePath, expectations[route]),
|
||||
`Route "${route}" does not point to the expected absolute path ` +
|
||||
`"${path.join(basePath, expectations[route])}". It points to "${lazyRoutes[route]}"`);
|
||||
});
|
||||
|
||||
// Verify that all expectations were met.
|
||||
assert.deepEqual(
|
||||
Object.keys(lazyRoutes), Object.keys(expectations), `Expected routes listed to be: \n` +
|
||||
` ${JSON.stringify(Object.keys(expectations))}\n` +
|
||||
`Actual:\n` +
|
||||
` ${JSON.stringify(Object.keys(lazyRoutes))}\n`);
|
||||
}
|
||||
|
||||
main();
|
@ -21,6 +21,7 @@
|
||||
"src/module",
|
||||
"src/bootstrap",
|
||||
"test/all_spec",
|
||||
"test/test_ngtools_api",
|
||||
"test/test_summaries",
|
||||
"benchmarks/src/tree/ng2/index_aot.ts",
|
||||
"benchmarks/src/tree/ng2_switch/index_aot.ts",
|
||||
|
@ -21,9 +21,9 @@ import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './
|
||||
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
||||
import {Console} from './private_import_core';
|
||||
|
||||
const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
|
||||
const GENERATED_FILES = /\.ngfactory\.ts$|\.ngstyle\.ts$/;
|
||||
const GENERATED_META_FILES = /\.json$/;
|
||||
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
|
||||
const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.ngstyle\.ts$/;
|
||||
|
||||
const PREAMBLE = `/**
|
||||
* @fileoverview This file is generated by the Angular 2 template compiler.
|
||||
|
@ -15,7 +15,7 @@ import * as ts from 'typescript';
|
||||
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
|
||||
const DTS = /\.d\.ts$/;
|
||||
const NODE_MODULES = '/node_modules/';
|
||||
const IS_GENERATED = /\.(ngfactory|css(\.shim)?)$/;
|
||||
const IS_GENERATED = /\.(ngfactory|ngstyle)$/;
|
||||
|
||||
export interface CompilerHostContext extends ts.ModuleResolutionHost {
|
||||
readResource(fileName: string): Promise<string>;
|
||||
@ -177,28 +177,31 @@ export class CompilerHost implements AotCompilerHost {
|
||||
(Array.isArray(metadataOrMetadatas) ? metadataOrMetadatas : [metadataOrMetadatas]) :
|
||||
[];
|
||||
const v1Metadata = metadatas.find((m: any) => m['version'] === 1);
|
||||
let v2Metadata = metadatas.find((m: any) => m['version'] === 2);
|
||||
if (!v2Metadata && v1Metadata) {
|
||||
// patch up v1 to v2 by merging the metadata with metadata collected from the d.ts file
|
||||
let v3Metadata = metadatas.find((m: any) => m['version'] === 3);
|
||||
if (!v3Metadata && v1Metadata) {
|
||||
// patch up v1 to v3 by merging the metadata with metadata collected from the d.ts file
|
||||
// as the only difference between the versions is whether all exports are contained in
|
||||
// the metadata and the `extends` clause.
|
||||
v2Metadata = {'__symbolic': 'module', 'version': 2, 'metadata': {}};
|
||||
v3Metadata = {'__symbolic': 'module', 'version': 3, 'metadata': {}};
|
||||
if (v1Metadata.exports) {
|
||||
v2Metadata.exports = v1Metadata.exports;
|
||||
v3Metadata.exports = v1Metadata.exports;
|
||||
}
|
||||
for (let prop in v1Metadata.metadata) {
|
||||
v2Metadata.metadata[prop] = v1Metadata.metadata[prop];
|
||||
v3Metadata.metadata[prop] = v1Metadata.metadata[prop];
|
||||
}
|
||||
const sourceText = this.context.readFile(dtsFilePath);
|
||||
|
||||
const exports = this.metadataCollector.getMetadata(this.getSourceFile(dtsFilePath));
|
||||
if (exports) {
|
||||
for (let prop in exports.metadata) {
|
||||
if (!v2Metadata.metadata[prop]) {
|
||||
v2Metadata.metadata[prop] = exports.metadata[prop];
|
||||
if (!v3Metadata.metadata[prop]) {
|
||||
v3Metadata.metadata[prop] = exports.metadata[prop];
|
||||
}
|
||||
}
|
||||
if (exports.exports) {
|
||||
v3Metadata.exports = exports.exports;
|
||||
}
|
||||
}
|
||||
metadatas.push(v2Metadata);
|
||||
metadatas.push(v3Metadata);
|
||||
}
|
||||
this.resolverCache.set(filePath, metadatas);
|
||||
return metadatas;
|
||||
|
124
modules/@angular/compiler-cli/src/ngtools_api.ts
Normal file
124
modules/@angular/compiler-cli/src/ngtools_api.ts
Normal file
@ -0,0 +1,124 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a private API for the ngtools toolkit.
|
||||
*
|
||||
* This API should be stable for NG 2. It can be removed in NG 4..., but should be replaced by
|
||||
* something else.
|
||||
*/
|
||||
|
||||
import {AotCompilerHost, StaticReflector} from '@angular/compiler';
|
||||
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {CodeGenerator} from './codegen';
|
||||
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
|
||||
import {listLazyRoutesOfModule} from './ngtools_impl';
|
||||
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
|
||||
|
||||
|
||||
export interface NgTools_InternalApi_NG2_CodeGen_Options {
|
||||
basePath: string;
|
||||
compilerOptions: ts.CompilerOptions;
|
||||
program: ts.Program;
|
||||
host: ts.CompilerHost;
|
||||
|
||||
angularCompilerOptions: AngularCompilerOptions;
|
||||
|
||||
// i18n options.
|
||||
i18nFormat: string;
|
||||
i18nFile: string;
|
||||
locale: string;
|
||||
|
||||
readResource: (fileName: string) => Promise<string>;
|
||||
|
||||
// Every new property under this line should be optional.
|
||||
}
|
||||
|
||||
export interface NgTools_InternalApi_NG2_ListLazyRoutes_Options {
|
||||
program: ts.Program;
|
||||
host: ts.CompilerHost;
|
||||
angularCompilerOptions: AngularCompilerOptions;
|
||||
entryModule: string;
|
||||
|
||||
// Every new property under this line should be optional.
|
||||
}
|
||||
|
||||
|
||||
export interface NgTools_InternalApi_NG_2_LazyRouteMap { [route: string]: string; }
|
||||
|
||||
|
||||
/**
|
||||
* A ModuleResolutionHostAdapter that overrides the readResource() method with the one
|
||||
* passed in the interface.
|
||||
*/
|
||||
class CustomLoaderModuleResolutionHostAdapter extends ModuleResolutionHostAdapter {
|
||||
constructor(
|
||||
private _readResource: (path: string) => Promise<string>, host: ts.ModuleResolutionHost) {
|
||||
super(host);
|
||||
}
|
||||
|
||||
readResource(path: string) { return this._readResource(path); }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @private
|
||||
*/
|
||||
export class NgTools_InternalApi_NG_2 {
|
||||
/**
|
||||
* @internal
|
||||
* @private
|
||||
*/
|
||||
static codeGen(options: NgTools_InternalApi_NG2_CodeGen_Options): Promise<void> {
|
||||
const hostContext: CompilerHostContext =
|
||||
new CustomLoaderModuleResolutionHostAdapter(options.readResource, options.host);
|
||||
const cliOptions: NgcCliOptions = {
|
||||
i18nFormat: options.i18nFormat,
|
||||
i18nFile: options.i18nFile,
|
||||
locale: options.locale,
|
||||
basePath: options.basePath
|
||||
};
|
||||
|
||||
// Create the Code Generator.
|
||||
const codeGenerator = CodeGenerator.create(
|
||||
options.angularCompilerOptions, cliOptions, options.program, options.host, hostContext);
|
||||
|
||||
return codeGenerator.codegen();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @private
|
||||
*/
|
||||
static listLazyRoutes(options: NgTools_InternalApi_NG2_ListLazyRoutes_Options):
|
||||
NgTools_InternalApi_NG_2_LazyRouteMap {
|
||||
const angularCompilerOptions = options.angularCompilerOptions;
|
||||
const program = options.program;
|
||||
|
||||
const moduleResolutionHost = new ModuleResolutionHostAdapter(options.host);
|
||||
const usePathMapping =
|
||||
!!angularCompilerOptions.rootDirs && angularCompilerOptions.rootDirs.length > 0;
|
||||
const ngCompilerHost: AotCompilerHost = usePathMapping ?
|
||||
new PathMappedCompilerHost(program, angularCompilerOptions, moduleResolutionHost) :
|
||||
new CompilerHost(program, angularCompilerOptions, moduleResolutionHost);
|
||||
|
||||
const staticReflector = new StaticReflector(ngCompilerHost);
|
||||
const routeMap = listLazyRoutesOfModule(options.entryModule, ngCompilerHost, staticReflector);
|
||||
|
||||
return Object.keys(routeMap).reduce(
|
||||
(acc: NgTools_InternalApi_NG_2_LazyRouteMap, route: string) => {
|
||||
acc[route] = routeMap[route].absoluteFilePath;
|
||||
return acc;
|
||||
},
|
||||
{});
|
||||
}
|
||||
}
|
205
modules/@angular/compiler-cli/src/ngtools_impl.ts
Normal file
205
modules/@angular/compiler-cli/src/ngtools_impl.ts
Normal file
@ -0,0 +1,205 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a private API for the ngtools toolkit.
|
||||
*
|
||||
* This API should be stable for NG 2. It can be removed in NG 4..., but should be replaced by
|
||||
* something else.
|
||||
*/
|
||||
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';
|
||||
const ROUTER_ROUTES_SYMBOL_NAME = 'ROUTES';
|
||||
|
||||
|
||||
// LazyRoute information between the extractors.
|
||||
export interface LazyRoute {
|
||||
routeDef: RouteDef;
|
||||
absoluteFilePath: string;
|
||||
}
|
||||
export type LazyRouteMap = {
|
||||
[route: string]: LazyRoute
|
||||
};
|
||||
|
||||
// A route definition. Normally the short form 'path/to/module#ModuleClassName' is used by
|
||||
// the user, and this is a helper class to extract information from it.
|
||||
export class RouteDef {
|
||||
private constructor(public readonly path: string, public readonly className: string = null) {}
|
||||
|
||||
toString() {
|
||||
return (this.className === null || this.className == 'default') ?
|
||||
this.path :
|
||||
`${this.path}#${this.className}`;
|
||||
}
|
||||
|
||||
static fromString(entry: string): RouteDef {
|
||||
const split = entry.split('#');
|
||||
return new RouteDef(split[0], split[1] || null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {LazyRouteMap}
|
||||
* @private
|
||||
*/
|
||||
export function listLazyRoutesOfModule(
|
||||
entryModule: string, host: AotCompilerHost, reflector: StaticReflector): LazyRouteMap {
|
||||
const entryRouteDef = RouteDef.fromString(entryModule);
|
||||
const containingFile = _resolveModule(entryRouteDef.path, entryRouteDef.path, host);
|
||||
const modulePath = `./${containingFile.replace(/^(.*)\//, '')}`;
|
||||
const className = entryRouteDef.className;
|
||||
|
||||
// List loadChildren of this single module.
|
||||
const staticSymbol = 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 = {};
|
||||
|
||||
lazyRoutes.forEach((lazyRoute: LazyRoute) => {
|
||||
const route: string = lazyRoute.routeDef.toString();
|
||||
_assertRoute(routes, lazyRoute);
|
||||
routes[route] = lazyRoute;
|
||||
|
||||
const lazyModuleSymbol = reflector.findDeclaration(
|
||||
lazyRoute.absoluteFilePath, lazyRoute.routeDef.className || 'default');
|
||||
const subRoutes = _extractLazyRoutesFromStaticModule(lazyModuleSymbol, reflector, host, ROUTES);
|
||||
|
||||
// Populate the map using the routes we just found.
|
||||
subRoutes.forEach(subRoute => {
|
||||
_assertRoute(routes, subRoute);
|
||||
routes[subRoute.routeDef.toString()] = subRoute;
|
||||
});
|
||||
});
|
||||
|
||||
return routes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Try to resolve a module, and returns its absolute path.
|
||||
* @private
|
||||
*/
|
||||
function _resolveModule(modulePath: string, containingFile: string, host: AotCompilerHost) {
|
||||
const result = host.moduleNameToFileName(modulePath, containingFile);
|
||||
if (!result) {
|
||||
throw new Error(`Could not resolve "${modulePath}" from "${containingFile}".`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Throw an exception if a route is in a route map, but does not point to the same module.
|
||||
* @private
|
||||
*/
|
||||
function _assertRoute(map: LazyRouteMap, route: LazyRoute) {
|
||||
const r = route.routeDef.toString();
|
||||
if (map[r] && map[r].absoluteFilePath != route.absoluteFilePath) {
|
||||
throw new Error(
|
||||
`Duplicated path in loadChildren detected: "${r}" is used in 2 loadChildren, ` +
|
||||
`but they point to different modules "(${map[r].absoluteFilePath} and ` +
|
||||
`"${route.absoluteFilePath}"). Webpack cannot distinguish on context and would fail to ` +
|
||||
'load the proper one.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract all the LazyRoutes from a module. This extracts all `loadChildren` keys from this
|
||||
* module and all statically referred modules.
|
||||
* @private
|
||||
*/
|
||||
function _extractLazyRoutesFromStaticModule(
|
||||
staticSymbol: StaticSymbol, reflector: StaticReflector, host: AotCompilerHost,
|
||||
ROUTES: StaticSymbol): LazyRoute[] {
|
||||
const moduleMetadata = _getNgModuleMetadata(staticSymbol, reflector);
|
||||
const allRoutes: any =
|
||||
(moduleMetadata.imports || [])
|
||||
.filter(i => 'providers' in i)
|
||||
.reduce((mem: Route[], m: any) => {
|
||||
return mem.concat(_collectRoutes(m.providers || [], reflector, ROUTES));
|
||||
}, _collectRoutes(moduleMetadata.providers || [], reflector, ROUTES));
|
||||
|
||||
const lazyRoutes: LazyRoute[] =
|
||||
_collectLoadChildren(allRoutes).reduce((acc: LazyRoute[], route: string) => {
|
||||
const routeDef = RouteDef.fromString(route);
|
||||
const absoluteFilePath = _resolveModule(routeDef.path, staticSymbol.filePath, host);
|
||||
acc.push({routeDef, absoluteFilePath});
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const importedSymbols = ((moduleMetadata.imports || []) as any[])
|
||||
.filter(i => i instanceof StaticSymbol) as StaticSymbol[];
|
||||
|
||||
return importedSymbols
|
||||
.reduce(
|
||||
(acc: LazyRoute[], i: StaticSymbol) => {
|
||||
return acc.concat(_extractLazyRoutesFromStaticModule(i, reflector, host, ROUTES));
|
||||
},
|
||||
[])
|
||||
.concat(lazyRoutes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the NgModule Metadata of a symbol.
|
||||
* @private
|
||||
*/
|
||||
function _getNgModuleMetadata(staticSymbol: StaticSymbol, reflector: StaticReflector): NgModule {
|
||||
const ngModules = reflector.annotations(staticSymbol).filter((s: any) => s instanceof NgModule);
|
||||
if (ngModules.length === 0) {
|
||||
throw new Error(`${staticSymbol.name} is not an NgModule`);
|
||||
}
|
||||
return ngModules[0];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the routes from the provider list.
|
||||
* @private
|
||||
*/
|
||||
function _collectRoutes(
|
||||
providers: any[], reflector: StaticReflector, ROUTES: StaticSymbol): Route[] {
|
||||
return providers.reduce((routeList: Route[], p: any) => {
|
||||
if (p.provide === ROUTES) {
|
||||
return routeList.concat(p.useValue);
|
||||
} else if (Array.isArray(p)) {
|
||||
return routeList.concat(_collectRoutes(p, reflector, ROUTES));
|
||||
} else {
|
||||
return routeList;
|
||||
}
|
||||
}, []);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the loadChildren values of a list of Route.
|
||||
* @private
|
||||
*/
|
||||
function _collectLoadChildren(routes: Route[]): string[] {
|
||||
return routes.reduce((m, r) => {
|
||||
if (r.loadChildren) {
|
||||
return m.concat(r.loadChildren);
|
||||
} else if (Array.isArray(r)) {
|
||||
return m.concat(_collectLoadChildren(r));
|
||||
} else if (r.children) {
|
||||
return m.concat(_collectLoadChildren(r.children));
|
||||
} else {
|
||||
return m;
|
||||
}
|
||||
}, []);
|
||||
}
|
@ -66,11 +66,14 @@ describe('CompilerHost', () => {
|
||||
'/tmp/project/src/my.other.ngfactory.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||
.toEqual('./my.other.ngfactory');
|
||||
expect(hostNestedGenDir.fileNameToModuleName(
|
||||
'/tmp/project/src/my.other.css.ts', '/tmp/project/src/a/my.ngfactory.ts'))
|
||||
.toEqual('../my.other.css');
|
||||
'/tmp/project/src/my.other.css.ngstyle.ts', '/tmp/project/src/a/my.ngfactory.ts'))
|
||||
.toEqual('../my.other.css.ngstyle');
|
||||
expect(hostNestedGenDir.fileNameToModuleName(
|
||||
'/tmp/project/src/a/my.other.css.shim.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||
.toEqual('./a/my.other.css.shim');
|
||||
'/tmp/project/src/a/my.other.shim.ngstyle.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||
.toEqual('./a/my.other.shim.ngstyle');
|
||||
expect(hostNestedGenDir.fileNameToModuleName(
|
||||
'/tmp/project/src/my.other.sass.ngstyle.ts', '/tmp/project/src/a/my.ngfactory.ts'))
|
||||
.toEqual('../my.other.sass.ngstyle');
|
||||
});
|
||||
|
||||
it('should import application from factory', () => {
|
||||
@ -83,6 +86,12 @@ describe('CompilerHost', () => {
|
||||
expect(hostNestedGenDir.fileNameToModuleName(
|
||||
'/tmp/project/src/a/my.other.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||
.toEqual('../a/my.other');
|
||||
expect(hostNestedGenDir.fileNameToModuleName(
|
||||
'/tmp/project/src/a/my.other.css.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||
.toEqual('../a/my.other.css');
|
||||
expect(hostNestedGenDir.fileNameToModuleName(
|
||||
'/tmp/project/src/a/my.other.css.shim.ts', '/tmp/project/src/my.ngfactory.ts'))
|
||||
.toEqual('../a/my.other.css.shim');
|
||||
});
|
||||
});
|
||||
|
||||
@ -157,16 +166,18 @@ describe('CompilerHost', () => {
|
||||
expect(hostNestedGenDir.getMetadataFor('node_modules/@angular/missing.d.ts')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should add missing v2 metadata from v1 metadata and .d.ts files', () => {
|
||||
it('should add missing v3 metadata from v1 metadata and .d.ts files', () => {
|
||||
expect(hostNestedGenDir.getMetadataFor('metadata_versions/v1.d.ts')).toEqual([
|
||||
{__symbolic: 'module', version: 1, metadata: {foo: {__symbolic: 'class'}}}, {
|
||||
__symbolic: 'module',
|
||||
version: 2,
|
||||
version: 3,
|
||||
metadata: {
|
||||
foo: {__symbolic: 'class'},
|
||||
Bar: {__symbolic: 'class', members: {ngOnInit: [{__symbolic: 'method'}]}},
|
||||
BarChild: {__symbolic: 'class', extends: {__symbolic: 'reference', name: 'Bar'}}
|
||||
}
|
||||
BarChild: {__symbolic: 'class', extends: {__symbolic: 'reference', name: 'Bar'}},
|
||||
ReExport: {__symbolic: 'reference', module: './lib/utils2', name: 'ReExport'},
|
||||
},
|
||||
exports: [{from: './lib/utils2', export: ['Export']}],
|
||||
}
|
||||
]);
|
||||
});
|
||||
@ -202,6 +213,11 @@ const FILES: Entry = {
|
||||
},
|
||||
'metadata_versions': {
|
||||
'v1.d.ts': `
|
||||
import {ReExport} from './lib/utils2';
|
||||
export {ReExport};
|
||||
|
||||
export {Export} from './lib/utils2';
|
||||
|
||||
export declare class Bar {
|
||||
ngOnInit() {}
|
||||
}
|
||||
|
@ -199,8 +199,9 @@ class _AnimationBuilder implements AnimationAstVisitor {
|
||||
.set(_ANIMATION_FACTORY_VIEW_CONTEXT.callMethod(
|
||||
'getAnimationPlayers',
|
||||
[
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR, o.literal(this.animationName),
|
||||
_ANIMATION_FACTORY_ELEMENT_VAR,
|
||||
_ANIMATION_NEXT_STATE_VAR.equals(o.literal(EMPTY_STATE))
|
||||
.conditional(o.NULL_EXPR, o.literal(this.animationName))
|
||||
]))
|
||||
.toDeclStmt());
|
||||
|
||||
|
@ -174,6 +174,11 @@ function _normalizeStyleMetadata(
|
||||
entry: CompileAnimationStyleMetadata, stateStyles: {[key: string]: AnimationStylesAst},
|
||||
schema: ElementSchemaRegistry, errors: AnimationParseError[],
|
||||
permitStateReferences: boolean): {[key: string]: string | number}[] {
|
||||
const offset = entry.offset;
|
||||
if (offset > 1 || offset < 0) {
|
||||
errors.push(new AnimationParseError(`Offset values for animations must be between 0 and 1`));
|
||||
}
|
||||
|
||||
const normalizedStyles: {[key: string]: string | number}[] = [];
|
||||
entry.styles.forEach(styleEntry => {
|
||||
if (typeof styleEntry === 'string') {
|
||||
|
@ -272,7 +272,7 @@ function _componentFactoryName(comp: CompileIdentifierMetadata): string {
|
||||
}
|
||||
|
||||
function _stylesModuleUrl(stylesheetUrl: string, shim: boolean, suffix: string): string {
|
||||
return shim ? `${stylesheetUrl}.shim${suffix}` : `${stylesheetUrl}${suffix}`;
|
||||
return `${stylesheetUrl}${shim ? '.shim' : ''}.ngstyle${suffix}`;
|
||||
}
|
||||
|
||||
function _assertComponent(meta: CompileDirectiveMetadata) {
|
||||
|
@ -10,7 +10,7 @@ import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, Ho
|
||||
import {ReflectorReader} from '../private_import_core';
|
||||
import {StaticSymbol} from './static_symbol';
|
||||
|
||||
const SUPPORTED_SCHEMA_VERSION = 2;
|
||||
const SUPPORTED_SCHEMA_VERSION = 3;
|
||||
const ANGULAR_IMPORT_LOCATIONS = {
|
||||
coreDecorators: '@angular/core/src/metadata',
|
||||
diDecorators: '@angular/core/src/di/metadata',
|
||||
@ -20,6 +20,8 @@ const ANGULAR_IMPORT_LOCATIONS = {
|
||||
provider: '@angular/core/src/di/provider'
|
||||
};
|
||||
|
||||
const HIDDEN_KEY = /^\$.*\$$/;
|
||||
|
||||
/**
|
||||
* The host of the StaticReflector disconnects the implementation from TypeScript / other language
|
||||
* services and from underlying file systems.
|
||||
@ -638,6 +640,9 @@ export class StaticReflector implements ReflectorReader {
|
||||
return simplifyInContext(selectContext, selectTarget[member], depth + 1);
|
||||
return null;
|
||||
case 'reference':
|
||||
if (!expression['name']) {
|
||||
return context;
|
||||
}
|
||||
if (!expression.module) {
|
||||
const name: string = expression['name'];
|
||||
const localValue = scope.resolve(name);
|
||||
@ -749,10 +754,10 @@ export class StaticReflector implements ReflectorReader {
|
||||
{__symbolic: 'module', version: SUPPORTED_SCHEMA_VERSION, module: module, metadata: {}};
|
||||
}
|
||||
if (moduleMetadata['version'] != SUPPORTED_SCHEMA_VERSION) {
|
||||
this.reportError(
|
||||
new Error(
|
||||
`Metadata version mismatch for module ${module}, found version ${moduleMetadata['version']}, expected ${SUPPORTED_SCHEMA_VERSION}`),
|
||||
null);
|
||||
const errorMessage = moduleMetadata['version'] == 2 ?
|
||||
`Unsupported metadata version ${moduleMetadata['version']} for module ${module}. This module should be compiled with a newer version of ngc` :
|
||||
`Metadata version mismatch for module ${module}, found version ${moduleMetadata['version']}, expected ${SUPPORTED_SCHEMA_VERSION}`;
|
||||
this.reportError(new Error(errorMessage), null);
|
||||
}
|
||||
this.metadataCache.set(module, moduleMetadata);
|
||||
}
|
||||
@ -806,7 +811,11 @@ function mapStringMap(input: {[key: string]: any}, transform: (value: any, key:
|
||||
Object.keys(input).forEach((key) => {
|
||||
const value = transform(input[key], key);
|
||||
if (!shouldIgnore(value)) {
|
||||
result[key] = value;
|
||||
if (HIDDEN_KEY.test(key)) {
|
||||
Object.defineProperty(result, key, {enumerable: false, configurable: true, value: value});
|
||||
} else {
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
|
@ -91,8 +91,8 @@ function sanitizedValue(
|
||||
|
||||
export function triggerAnimation(
|
||||
view: o.Expression, componentView: o.Expression, boundProp: BoundElementPropertyAst,
|
||||
eventListener: o.Expression, renderElement: o.Expression, renderValue: o.Expression,
|
||||
lastRenderValue: o.Expression) {
|
||||
boundOutputs: BoundEventAst[], eventListener: o.Expression, renderElement: o.Expression,
|
||||
renderValue: o.Expression, lastRenderValue: o.Expression) {
|
||||
const detachStmts: o.Statement[] = [];
|
||||
const updateStmts: o.Statement[] = [];
|
||||
|
||||
@ -121,23 +121,32 @@ export function triggerAnimation(
|
||||
.set(animationFnExpr.callFn([view, renderElement, lastRenderValue, emptyStateValue]))
|
||||
.toDeclStmt());
|
||||
|
||||
const registerStmts = [
|
||||
animationTransitionVar
|
||||
.callMethod(
|
||||
'onStart',
|
||||
[eventListener.callMethod(
|
||||
o.BuiltinMethod.Bind,
|
||||
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'start'))])])
|
||||
.toStmt(),
|
||||
animationTransitionVar
|
||||
.callMethod(
|
||||
'onDone',
|
||||
[eventListener.callMethod(
|
||||
o.BuiltinMethod.Bind,
|
||||
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'done'))])])
|
||||
.toStmt(),
|
||||
const registerStmts: o.Statement[] = [];
|
||||
const animationStartMethodExists = boundOutputs.find(
|
||||
event => event.isAnimation && event.name == animationName && event.phase == 'start');
|
||||
if (animationStartMethodExists) {
|
||||
registerStmts.push(
|
||||
animationTransitionVar
|
||||
.callMethod(
|
||||
'onStart',
|
||||
[eventListener.callMethod(
|
||||
o.BuiltinMethod.Bind,
|
||||
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'start'))])])
|
||||
.toStmt());
|
||||
}
|
||||
|
||||
];
|
||||
const animationDoneMethodExists = boundOutputs.find(
|
||||
event => event.isAnimation && event.name == animationName && event.phase == 'done');
|
||||
if (animationDoneMethodExists) {
|
||||
registerStmts.push(
|
||||
animationTransitionVar
|
||||
.callMethod(
|
||||
'onDone',
|
||||
[eventListener.callMethod(
|
||||
o.BuiltinMethod.Bind,
|
||||
[view, o.literal(BoundEventAst.calcFullName(animationName, null, 'done'))])])
|
||||
.toStmt());
|
||||
}
|
||||
|
||||
updateStmts.push(...registerStmts);
|
||||
detachStmts.push(...registerStmts);
|
||||
|
@ -75,9 +75,8 @@ export class DirectiveResolver {
|
||||
outputs.push(propName);
|
||||
}
|
||||
}
|
||||
const hostBinding =
|
||||
ListWrapper.findLast(propertyMetadata[propName], (a) => a instanceof HostBinding);
|
||||
if (hostBinding) {
|
||||
const hostBindings = propertyMetadata[propName].filter(a => a && a instanceof HostBinding);
|
||||
hostBindings.forEach(hostBinding => {
|
||||
if (hostBinding.hostPropertyName) {
|
||||
const startWith = hostBinding.hostPropertyName[0];
|
||||
if (startWith === '(') {
|
||||
@ -90,13 +89,12 @@ export class DirectiveResolver {
|
||||
} else {
|
||||
host[`[${propName}]`] = propName;
|
||||
}
|
||||
}
|
||||
const hostListener =
|
||||
ListWrapper.findLast(propertyMetadata[propName], (a) => a instanceof HostListener);
|
||||
if (hostListener) {
|
||||
});
|
||||
const hostListeners = propertyMetadata[propName].filter(a => a && a instanceof HostListener);
|
||||
hostListeners.forEach(hostListener => {
|
||||
const args = hostListener.args || [];
|
||||
host[`(${hostListener.eventName})`] = `${propName}(${args.join(',')})`;
|
||||
}
|
||||
});
|
||||
const query = ListWrapper.findLast(propertyMetadata[propName], (a) => a instanceof Query);
|
||||
if (query) {
|
||||
queries[propName] = query;
|
||||
|
@ -70,7 +70,7 @@ export class DirectiveWrapperCompiler {
|
||||
addCheckInputMethod(inputFieldName, builder);
|
||||
});
|
||||
addNgDoCheckMethod(builder);
|
||||
addCheckHostMethod(hostParseResult.hostProps, builder);
|
||||
addCheckHostMethod(hostParseResult.hostProps, hostParseResult.hostListeners, builder);
|
||||
addHandleEventMethod(hostParseResult.hostListeners, builder);
|
||||
addSubscribeMethod(dirMeta, builder);
|
||||
|
||||
@ -235,7 +235,8 @@ function addCheckInputMethod(input: string, builder: DirectiveWrapperBuilder) {
|
||||
}
|
||||
|
||||
function addCheckHostMethod(
|
||||
hostProps: BoundElementPropertyAst[], builder: DirectiveWrapperBuilder) {
|
||||
hostProps: BoundElementPropertyAst[], hostEvents: BoundEventAst[],
|
||||
builder: DirectiveWrapperBuilder) {
|
||||
const stmts: o.Statement[] = [];
|
||||
const methodParams: o.FnParam[] = [
|
||||
new o.FnParam(
|
||||
@ -262,7 +263,7 @@ function addCheckHostMethod(
|
||||
let checkBindingStmts: o.Statement[];
|
||||
if (hostProp.isAnimation) {
|
||||
const {updateStmts, detachStmts} = triggerAnimation(
|
||||
VIEW_VAR, COMPONENT_VIEW_VAR, hostProp,
|
||||
VIEW_VAR, COMPONENT_VIEW_VAR, hostProp, hostEvents,
|
||||
o.THIS_EXPR.prop(EVENT_HANDLER_FIELD_NAME)
|
||||
.or(o.importExpr(createIdentifier(Identifiers.noop))),
|
||||
RENDER_EL_VAR, evalResult.currValExpr, field.expression);
|
||||
|
@ -344,7 +344,7 @@ export class _ParseAST {
|
||||
while (this.optionalCharacter(chars.$COLON)) {
|
||||
args.push(this.parseExpression());
|
||||
}
|
||||
result = new BindingPipe(this.span(result.span.start - this.offset), result, name, args);
|
||||
result = new BindingPipe(this.span(result.span.start), result, name, args);
|
||||
} while (this.optionalOperator('|'));
|
||||
}
|
||||
|
||||
|
@ -9,13 +9,13 @@
|
||||
import * as i18n from './i18n_ast';
|
||||
|
||||
export function digest(message: i18n.Message): string {
|
||||
return sha1(serializeNodes(message.nodes).join('') + `[${message.meaning}]`);
|
||||
return message.id || sha1(serializeNodes(message.nodes).join('') + `[${message.meaning}]`);
|
||||
}
|
||||
|
||||
export function decimalDigest(message: i18n.Message): string {
|
||||
const visitor = new _SerializerIgnoreIcuExpVisitor();
|
||||
const parts = message.nodes.map(a => a.visit(visitor, null));
|
||||
return computeMsgId(parts.join(''), message.meaning);
|
||||
return message.id || computeMsgId(parts.join(''), message.meaning);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -18,6 +18,8 @@ import {TranslationBundle} from './translation_bundle';
|
||||
const _I18N_ATTR = 'i18n';
|
||||
const _I18N_ATTR_PREFIX = 'i18n-';
|
||||
const _I18N_COMMENT_PREFIX_REGEXP = /^i18n:?/;
|
||||
const MEANING_SEPARATOR = '|';
|
||||
const ID_SEPARATOR = '@@';
|
||||
|
||||
/**
|
||||
* Extract translatable messages from an html AST
|
||||
@ -77,7 +79,7 @@ class _Visitor implements html.Visitor {
|
||||
// _VisitorMode.Merge only
|
||||
private _translations: TranslationBundle;
|
||||
private _createI18nMessage:
|
||||
(msg: html.Node[], meaning: string, description: string) => i18n.Message;
|
||||
(msg: html.Node[], meaning: string, description: string, id: string) => i18n.Message;
|
||||
|
||||
|
||||
constructor(private _implicitTags: string[], private _implicitAttrs: {[k: string]: string[]}) {}
|
||||
@ -330,15 +332,15 @@ class _Visitor implements html.Visitor {
|
||||
}
|
||||
|
||||
// add a translatable message
|
||||
private _addMessage(ast: html.Node[], meaningAndDesc?: string): i18n.Message {
|
||||
private _addMessage(ast: html.Node[], msgMeta?: string): i18n.Message {
|
||||
if (ast.length == 0 ||
|
||||
ast.length == 1 && ast[0] instanceof html.Attribute && !(<html.Attribute>ast[0]).value) {
|
||||
// Do not create empty messages
|
||||
return;
|
||||
}
|
||||
|
||||
const [meaning, description] = _splitMeaningAndDesc(meaningAndDesc);
|
||||
const message = this._createI18nMessage(ast, meaning, description);
|
||||
const {meaning, description, id} = _parseMessageMeta(msgMeta);
|
||||
const message = this._createI18nMessage(ast, meaning, description, id);
|
||||
this._messages.push(message);
|
||||
return message;
|
||||
}
|
||||
@ -368,7 +370,7 @@ class _Visitor implements html.Visitor {
|
||||
attributes.forEach(attr => {
|
||||
if (attr.name.startsWith(_I18N_ATTR_PREFIX)) {
|
||||
i18nAttributeMeanings[attr.name.slice(_I18N_ATTR_PREFIX.length)] =
|
||||
_splitMeaningAndDesc(attr.value)[0];
|
||||
_parseMessageMeta(attr.value).meaning;
|
||||
}
|
||||
});
|
||||
|
||||
@ -382,7 +384,7 @@ class _Visitor implements html.Visitor {
|
||||
|
||||
if (attr.value && attr.value != '' && i18nAttributeMeanings.hasOwnProperty(attr.name)) {
|
||||
const meaning = i18nAttributeMeanings[attr.name];
|
||||
const message: i18n.Message = this._createI18nMessage([attr], meaning, '');
|
||||
const message: i18n.Message = this._createI18nMessage([attr], meaning, '', '');
|
||||
const nodes = this._translations.get(message);
|
||||
if (nodes) {
|
||||
if (nodes[0] instanceof html.Text) {
|
||||
@ -496,8 +498,16 @@ function _getI18nAttr(p: html.Element): html.Attribute {
|
||||
return p.attrs.find(attr => attr.name === _I18N_ATTR) || null;
|
||||
}
|
||||
|
||||
function _splitMeaningAndDesc(i18n: string): [string, string] {
|
||||
if (!i18n) return ['', ''];
|
||||
const pipeIndex = i18n.indexOf('|');
|
||||
return pipeIndex == -1 ? ['', i18n] : [i18n.slice(0, pipeIndex), i18n.slice(pipeIndex + 1)];
|
||||
function _parseMessageMeta(i18n: string): {meaning: string, description: string, id: string} {
|
||||
if (!i18n) return {meaning: '', description: '', id: ''};
|
||||
|
||||
const idIndex = i18n.indexOf(ID_SEPARATOR);
|
||||
const descIndex = i18n.indexOf(MEANING_SEPARATOR);
|
||||
const [meaningAndDesc, id] =
|
||||
(idIndex > -1) ? [i18n.slice(0, idIndex), i18n.slice(idIndex + 2)] : [i18n, ''];
|
||||
const [meaning, description] = (descIndex > -1) ?
|
||||
[meaningAndDesc.slice(0, descIndex), meaningAndDesc.slice(descIndex + 1)] :
|
||||
['', meaningAndDesc];
|
||||
|
||||
return {meaning, description, id};
|
||||
}
|
||||
|
@ -15,11 +15,12 @@ export class Message {
|
||||
* @param placeholderToMessage maps placeholder names to messages (used for nested ICU messages)
|
||||
* @param meaning
|
||||
* @param description
|
||||
* @param id
|
||||
*/
|
||||
constructor(
|
||||
public nodes: Node[], public placeholders: {[phName: string]: string},
|
||||
public placeholderToMessage: {[phName: string]: Message}, public meaning: string,
|
||||
public description: string) {}
|
||||
public description: string, public id: string) {}
|
||||
}
|
||||
|
||||
export interface Node {
|
||||
|
@ -22,11 +22,11 @@ const _expParser = new ExpressionParser(new ExpressionLexer());
|
||||
* Returns a function converting html nodes to an i18n Message given an interpolationConfig
|
||||
*/
|
||||
export function createI18nMessageFactory(interpolationConfig: InterpolationConfig): (
|
||||
nodes: html.Node[], meaning: string, description: string) => i18n.Message {
|
||||
nodes: html.Node[], meaning: string, description: string, id: string) => i18n.Message {
|
||||
const visitor = new _I18nVisitor(_expParser, interpolationConfig);
|
||||
|
||||
return (nodes: html.Node[], meaning: string, description: string) =>
|
||||
visitor.toI18nMessage(nodes, meaning, description);
|
||||
return (nodes: html.Node[], meaning: string, description: string, id: string) =>
|
||||
visitor.toI18nMessage(nodes, meaning, description, id);
|
||||
}
|
||||
|
||||
class _I18nVisitor implements html.Visitor {
|
||||
@ -40,7 +40,8 @@ class _I18nVisitor implements html.Visitor {
|
||||
private _expressionParser: ExpressionParser,
|
||||
private _interpolationConfig: InterpolationConfig) {}
|
||||
|
||||
public toI18nMessage(nodes: html.Node[], meaning: string, description: string): i18n.Message {
|
||||
public toI18nMessage(nodes: html.Node[], meaning: string, description: string, id: string):
|
||||
i18n.Message {
|
||||
this._isIcu = nodes.length == 1 && nodes[0] instanceof html.Expansion;
|
||||
this._icuDepth = 0;
|
||||
this._placeholderRegistry = new PlaceholderRegistry();
|
||||
@ -50,7 +51,7 @@ class _I18nVisitor implements html.Visitor {
|
||||
const i18nodes: i18n.Node[] = html.visitAll(this, nodes, {});
|
||||
|
||||
return new i18n.Message(
|
||||
i18nodes, this._placeholderToContent, this._placeholderToMessage, meaning, description);
|
||||
i18nodes, this._placeholderToContent, this._placeholderToMessage, meaning, description, id);
|
||||
}
|
||||
|
||||
visitElement(el: html.Element, context: any): i18n.Node {
|
||||
@ -115,7 +116,7 @@ class _I18nVisitor implements html.Visitor {
|
||||
// TODO(vicb): add a html.Node -> i18n.Message cache to avoid having to re-create the msg
|
||||
const phName = this._placeholderRegistry.getPlaceholderName('ICU', icu.sourceSpan.toString());
|
||||
const visitor = new _I18nVisitor(this._expressionParser, this._interpolationConfig);
|
||||
this._placeholderToMessage[phName] = visitor.toI18nMessage([icu], '', '');
|
||||
this._placeholderToMessage[phName] = visitor.toI18nMessage([icu], '', '', '');
|
||||
return new i18n.IcuPlaceholder(i18nIcu, phName, icu.sourceSpan);
|
||||
}
|
||||
|
||||
|
@ -70,6 +70,14 @@ export class JitCompiler implements Compiler {
|
||||
return this._compileModuleAndAllComponents(moduleType, false).asyncResult;
|
||||
}
|
||||
|
||||
getNgContentSelectors(component: Type<any>): string[] {
|
||||
const template = this._compiledTemplateCache.get(component);
|
||||
if (!template) {
|
||||
throw new Error(`The component ${stringify(component)} is not yet compiled!`);
|
||||
}
|
||||
return template.compMeta.template.ngContentSelectors;
|
||||
}
|
||||
|
||||
private _compileModuleAndComponents<T>(moduleType: Type<T>, isSync: boolean):
|
||||
SyncAsyncResult<NgModuleFactory<T>> {
|
||||
const loadingPromise = this._loadModules(moduleType, isSync);
|
||||
@ -213,9 +221,8 @@ export class JitCompiler implements Compiler {
|
||||
const compMeta = this._metadataResolver.getDirectiveMetadata(compType);
|
||||
assertComponent(compMeta);
|
||||
|
||||
class HostClass {
|
||||
static overriddenName = `${identifierName(compMeta.type)}_Host`;
|
||||
}
|
||||
const HostClass = function HostClass() {};
|
||||
(<any>HostClass).overriddenName = `${identifierName(compMeta.type)}_Host`;
|
||||
|
||||
const hostMeta = createHostComponentMeta(HostClass, compMeta);
|
||||
compiledTemplate = new CompiledTemplate(
|
||||
@ -342,7 +349,8 @@ export class JitCompiler implements Compiler {
|
||||
if (!this._compilerConfig.useJit) {
|
||||
return interpretStatements(result.statements, result.stylesVar);
|
||||
} else {
|
||||
return jitStatements(`/${result.meta.moduleUrl}.css.js`, result.statements, result.stylesVar);
|
||||
return jitStatements(
|
||||
`/${result.meta.moduleUrl}.ngstyle.js`, result.statements, result.stylesVar);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -408,6 +416,11 @@ class ModuleBoundCompiler implements Compiler {
|
||||
return this._delegate.compileModuleAndAllComponentsAsync(moduleType);
|
||||
}
|
||||
|
||||
getNgContentSelectors(component: Type<any>): string[] {
|
||||
return this._delegate.getNgContentSelectors(component);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clears all caches
|
||||
*/
|
||||
|
@ -367,8 +367,8 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex
|
||||
ctx.print(`{`, useNewLine);
|
||||
ctx.incIndent();
|
||||
this.visitAllObjects(entry => {
|
||||
ctx.print(`${escapeIdentifier(entry[0], this._escapeDollarInStrings, false)}: `);
|
||||
entry[1].visitExpression(this, ctx);
|
||||
ctx.print(`${escapeIdentifier(entry.key, this._escapeDollarInStrings, entry.quoted)}: `);
|
||||
entry.value.visitExpression(this, ctx);
|
||||
}, ast.entries, ctx, ',', useNewLine);
|
||||
ctx.decIndent();
|
||||
ctx.print(`}`, useNewLine);
|
||||
|
@ -413,10 +413,13 @@ export class LiteralArrayExpr extends Expression {
|
||||
}
|
||||
}
|
||||
|
||||
export class LiteralMapEntry {
|
||||
constructor(public key: string, public value: Expression, public quoted: boolean = false) {}
|
||||
}
|
||||
|
||||
export class LiteralMapExpr extends Expression {
|
||||
public valueType: Type = null;
|
||||
constructor(public entries: [string, Expression][], type: MapType = null) {
|
||||
constructor(public entries: LiteralMapEntry[], type: MapType = null) {
|
||||
super(type);
|
||||
if (isPresent(type)) {
|
||||
this.valueType = type.valueType;
|
||||
@ -677,7 +680,8 @@ export class ExpressionTransformer implements StatementVisitor, ExpressionVisito
|
||||
|
||||
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any {
|
||||
const entries = ast.entries.map(
|
||||
(entry): [string, Expression] => [entry[0], entry[1].visitExpression(this, context), ]);
|
||||
(entry): LiteralMapEntry => new LiteralMapEntry(
|
||||
entry.key, entry.value.visitExpression(this, context), entry.quoted));
|
||||
return new LiteralMapExpr(entries);
|
||||
}
|
||||
visitAllExpressions(exprs: Expression[], context: any): Expression[] {
|
||||
@ -791,7 +795,7 @@ export class RecursiveExpressionVisitor implements StatementVisitor, ExpressionV
|
||||
return ast;
|
||||
}
|
||||
visitLiteralMapExpr(ast: LiteralMapExpr, context: any): any {
|
||||
ast.entries.forEach((entry) => (<Expression>entry[1]).visitExpression(this, context));
|
||||
ast.entries.forEach((entry) => entry.value.visitExpression(this, context));
|
||||
return ast;
|
||||
}
|
||||
visitAllExpressions(exprs: Expression[], context: any): void {
|
||||
@ -891,7 +895,7 @@ export function literalArr(values: Expression[], type: Type = null): LiteralArra
|
||||
}
|
||||
|
||||
export function literalMap(values: [string, Expression][], type: MapType = null): LiteralMapExpr {
|
||||
return new LiteralMapExpr(values, type);
|
||||
return new LiteralMapExpr(values.map(entry => new LiteralMapEntry(entry[0], entry[1])), type);
|
||||
}
|
||||
|
||||
export function not(expr: Expression): NotExpr {
|
||||
|
@ -301,8 +301,7 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor {
|
||||
visitLiteralMapExpr(ast: o.LiteralMapExpr, ctx: _ExecutionContext): any {
|
||||
const result = {};
|
||||
ast.entries.forEach(
|
||||
(entry) => (result as any)[<string>entry[0]] =
|
||||
(<o.Expression>entry[1]).visitExpression(this, ctx));
|
||||
(entry) => (result as any)[entry.key] = entry.value.visitExpression(this, ctx));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,8 @@ import {ValueTransformer, visitValue} from '../util';
|
||||
|
||||
import * as o from './output_ast';
|
||||
|
||||
export const QUOTED_KEYS = '$quoted$';
|
||||
|
||||
export function convertValueToOutputAst(value: any, type: o.Type = null): o.Expression {
|
||||
return visitValue(value, new _ValueOutputAstTransformer(), type);
|
||||
}
|
||||
@ -22,9 +24,13 @@ class _ValueOutputAstTransformer implements ValueTransformer {
|
||||
}
|
||||
|
||||
visitStringMap(map: {[key: string]: any}, type: o.MapType): o.Expression {
|
||||
const entries: [string, o.Expression][] = [];
|
||||
Object.keys(map).forEach(key => { entries.push([key, visitValue(map[key], this, null)]); });
|
||||
return o.literalMap(entries, type);
|
||||
const entries: o.LiteralMapEntry[] = [];
|
||||
const quotedSet = new Set<string>(map && map[QUOTED_KEYS]);
|
||||
Object.keys(map).forEach(key => {
|
||||
entries.push(
|
||||
new o.LiteralMapEntry(key, visitValue(map[key], this, null), quotedSet.has(key)));
|
||||
});
|
||||
return new o.LiteralMapExpr(entries, type);
|
||||
}
|
||||
|
||||
visitPrimitive(value: any, type: o.Type): o.Expression { return o.literal(value, type); }
|
||||
|
@ -5,6 +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 * as chars from './chars';
|
||||
import {isPresent} from './facade/lang';
|
||||
|
||||
export class ParseLocation {
|
||||
@ -15,6 +16,38 @@ export class ParseLocation {
|
||||
toString(): string {
|
||||
return isPresent(this.offset) ? `${this.file.url}@${this.line}:${this.col}` : this.file.url;
|
||||
}
|
||||
|
||||
moveBy(delta: number): ParseLocation {
|
||||
const source = this.file.content;
|
||||
const len = source.length;
|
||||
let offset = this.offset;
|
||||
let line = this.line;
|
||||
let col = this.col;
|
||||
while (offset > 0 && delta < 0) {
|
||||
offset--;
|
||||
delta++;
|
||||
const ch = source.charCodeAt(offset);
|
||||
if (ch == chars.$LF) {
|
||||
line--;
|
||||
const priorLine = source.substr(0, offset - 1).lastIndexOf(String.fromCharCode(chars.$LF));
|
||||
col = priorLine > 0 ? offset - priorLine : offset;
|
||||
} else {
|
||||
col--;
|
||||
}
|
||||
}
|
||||
while (offset < len && delta > 0) {
|
||||
const ch = source.charCodeAt(offset);
|
||||
offset++;
|
||||
delta--;
|
||||
if (ch == chars.$LF) {
|
||||
line++;
|
||||
col = 0;
|
||||
} else {
|
||||
col++;
|
||||
}
|
||||
}
|
||||
return new ParseLocation(this.file, offset, line, col);
|
||||
}
|
||||
}
|
||||
|
||||
export class ParseSourceFile {
|
||||
|
@ -297,12 +297,12 @@ export class SelectorMatcher {
|
||||
return false;
|
||||
}
|
||||
|
||||
let selectables = map.get(name);
|
||||
const starSelectables = map.get('*');
|
||||
let selectables: SelectorContext[] = map.get(name) || [];
|
||||
const starSelectables: SelectorContext[] = map.get('*');
|
||||
if (starSelectables) {
|
||||
selectables = selectables.concat(starSelectables);
|
||||
}
|
||||
if (!selectables) {
|
||||
if (selectables.length === 0) {
|
||||
return false;
|
||||
}
|
||||
let selectable: SelectorContext;
|
||||
|
@ -9,13 +9,12 @@
|
||||
import {SecurityContext} from '@angular/core';
|
||||
|
||||
import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata';
|
||||
import {AST, ASTWithSource, BindingPipe, EmptyExpr, Interpolation, LiteralPrimitive, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
|
||||
import {ASTWithSource, BindingPipe, EmptyExpr, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
|
||||
import {Parser} from '../expression_parser/parser';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||
import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||
import {mergeNsAndName} from '../ml_parser/tags';
|
||||
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
|
||||
import {view_utils} from '../private_import_core';
|
||||
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
||||
import {CssSelector} from '../selector';
|
||||
import {splitAtColon, splitAtPeriod} from '../util';
|
||||
@ -246,18 +245,12 @@ export class BindingParser {
|
||||
|
||||
let unit: string = null;
|
||||
let bindingType: PropertyBindingType;
|
||||
let boundPropertyName: string;
|
||||
let boundPropertyName: string = null;
|
||||
const parts = boundProp.name.split(PROPERTY_PARTS_SEPARATOR);
|
||||
let securityContexts: SecurityContext[];
|
||||
|
||||
if (parts.length === 1) {
|
||||
const partValue = parts[0];
|
||||
boundPropertyName = this._schemaRegistry.getMappedPropName(partValue);
|
||||
securityContexts = calcPossibleSecurityContexts(
|
||||
this._schemaRegistry, elementSelector, boundPropertyName, false);
|
||||
bindingType = PropertyBindingType.Property;
|
||||
this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, false);
|
||||
} else {
|
||||
// Check check for special cases (prefix style, attr, class)
|
||||
if (parts.length > 1) {
|
||||
if (parts[0] == ATTRIBUTE_PREFIX) {
|
||||
boundPropertyName = parts[1];
|
||||
this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, true);
|
||||
@ -281,12 +274,18 @@ export class BindingParser {
|
||||
boundPropertyName = parts[1];
|
||||
bindingType = PropertyBindingType.Style;
|
||||
securityContexts = [SecurityContext.STYLE];
|
||||
} else {
|
||||
this._reportError(`Invalid property name '${boundProp.name}'`, boundProp.sourceSpan);
|
||||
bindingType = null;
|
||||
securityContexts = [];
|
||||
}
|
||||
}
|
||||
|
||||
// If not a special case, use the full property name
|
||||
if (boundPropertyName === null) {
|
||||
boundPropertyName = this._schemaRegistry.getMappedPropName(boundProp.name);
|
||||
securityContexts = calcPossibleSecurityContexts(
|
||||
this._schemaRegistry, elementSelector, boundPropertyName, false);
|
||||
bindingType = PropertyBindingType.Property;
|
||||
this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, false);
|
||||
}
|
||||
|
||||
return new BoundElementPropertyAst(
|
||||
boundPropertyName, bindingType, securityContexts.length === 1 ? securityContexts[0] : null,
|
||||
securityContexts.length > 1, boundProp.expression, unit, boundProp.sourceSpan);
|
||||
@ -378,9 +377,12 @@ export class BindingParser {
|
||||
if (isPresent(ast)) {
|
||||
const collector = new PipeCollector();
|
||||
ast.visit(collector);
|
||||
collector.pipes.forEach((pipeName) => {
|
||||
collector.pipes.forEach((ast, pipeName) => {
|
||||
if (!this.pipesByName.has(pipeName)) {
|
||||
this._reportError(`The pipe '${pipeName}' could not be found`, sourceSpan);
|
||||
this._reportError(
|
||||
`The pipe '${pipeName}' could not be found`,
|
||||
new ParseSourceSpan(
|
||||
sourceSpan.start.moveBy(ast.span.start), sourceSpan.start.moveBy(ast.span.end)));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -403,9 +405,9 @@ export class BindingParser {
|
||||
}
|
||||
|
||||
export class PipeCollector extends RecursiveAstVisitor {
|
||||
pipes = new Set<string>();
|
||||
pipes = new Map<string, BindingPipe>();
|
||||
visitPipe(ast: BindingPipe, context: any): any {
|
||||
this.pipes.add(ast.name);
|
||||
this.pipes.set(ast.name, ast);
|
||||
ast.exp.visit(this);
|
||||
this.visitAll(ast.args, context);
|
||||
return null;
|
||||
|
@ -6,10 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Inject, Injectable, OpaqueToken, Optional, SchemaMetadata, SecurityContext} from '@angular/core';
|
||||
import {Inject, Injectable, OpaqueToken, Optional, SchemaMetadata} from '@angular/core';
|
||||
|
||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileTemplateMetadata, CompileTemplateSummary, CompileTokenMetadata, CompileTypeMetadata, identifierName} from '../compile_metadata';
|
||||
import {AST, ASTWithSource, BindingPipe, EmptyExpr, Interpolation, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
|
||||
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileTemplateSummary, CompileTokenMetadata, CompileTypeMetadata, identifierName} from '../compile_metadata';
|
||||
import {Parser} from '../expression_parser/parser';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {I18NHtmlParser} from '../i18n/i18n_html_parser';
|
||||
@ -18,9 +17,9 @@ import * as html from '../ml_parser/ast';
|
||||
import {ParseTreeResult} from '../ml_parser/html_parser';
|
||||
import {expandNodes} from '../ml_parser/icu_ast_expander';
|
||||
import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||
import {mergeNsAndName, splitNsName} from '../ml_parser/tags';
|
||||
import {splitNsName} from '../ml_parser/tags';
|
||||
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
|
||||
import {Console, view_utils} from '../private_import_core';
|
||||
import {Console} from '../private_import_core';
|
||||
import {ProviderElementContext, ProviderViewContext} from '../provider_analyzer';
|
||||
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
||||
import {CssSelector, SelectorMatcher} from '../selector';
|
||||
@ -30,8 +29,6 @@ import {BindingParser, BoundProperty} from './binding_parser';
|
||||
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from './template_ast';
|
||||
import {PreparsedElementType, preparseElement} from './template_preparser';
|
||||
|
||||
|
||||
|
||||
// Group 1 = "bind-"
|
||||
// Group 2 = "let-"
|
||||
// Group 3 = "ref-/#"
|
||||
@ -685,7 +682,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
}
|
||||
elementProps.forEach(prop => {
|
||||
this._reportError(
|
||||
`Property binding ${prop.name} not used by any directive on an embedded template. Make sure that the property name is spelled correctly and all directives are listed in the "directives" section.`,
|
||||
`Property binding ${prop.name} not used by any directive on an embedded template. Make sure that the property name is spelled correctly and all directives are listed in the "@NgModule.declarations".`,
|
||||
sourceSpan);
|
||||
});
|
||||
}
|
||||
@ -704,7 +701,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
events.forEach(event => {
|
||||
if (isPresent(event.target) || !allDirectiveEvents.has(event.name)) {
|
||||
this._reportError(
|
||||
`Event binding ${event.fullName} not emitted by any directive on an embedded template. Make sure that the event name is spelled correctly and all directives are listed in the "directives" section.`,
|
||||
`Event binding ${event.fullName} not emitted by any directive on an embedded template. Make sure that the event name is spelled correctly and all directives are listed in the "@NgModule.declarations".`,
|
||||
event.sourceSpan);
|
||||
}
|
||||
});
|
||||
@ -813,7 +810,8 @@ class ElementContext {
|
||||
}
|
||||
}
|
||||
|
||||
function createElementCssSelector(elementName: string, matchableAttrs: string[][]): CssSelector {
|
||||
export function createElementCssSelector(
|
||||
elementName: string, matchableAttrs: string[][]): CssSelector {
|
||||
const cssSelector = new CssSelector();
|
||||
const elNameNoNs = splitNsName(elementName)[1];
|
||||
|
||||
|
@ -17,7 +17,7 @@ import {Identifiers, createIdentifier} from '../identifiers';
|
||||
import * as o from '../output/output_ast';
|
||||
import {isDefaultChangeDetectionStrategy} from '../private_import_core';
|
||||
import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
||||
import {BoundElementPropertyAst, BoundTextAst, DirectiveAst, PropertyBindingType} from '../template_parser/template_ast';
|
||||
import {BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, PropertyBindingType} from '../template_parser/template_ast';
|
||||
import {CompileElement, CompileNode} from './compile_element';
|
||||
import {CompileView} from './compile_view';
|
||||
import {DetectChangesVars} from './constants';
|
||||
@ -41,7 +41,8 @@ export function bindRenderText(
|
||||
}
|
||||
|
||||
export function bindRenderInputs(
|
||||
boundProps: BoundElementPropertyAst[], hasEvents: boolean, compileElement: CompileElement) {
|
||||
boundProps: BoundElementPropertyAst[], boundOutputs: BoundEventAst[], hasEvents: boolean,
|
||||
compileElement: CompileElement) {
|
||||
const view = compileElement.view;
|
||||
const renderNode = compileElement.renderNode;
|
||||
|
||||
@ -67,7 +68,7 @@ export function bindRenderInputs(
|
||||
case PropertyBindingType.Animation:
|
||||
compileMethod = view.animationBindingsMethod;
|
||||
const {updateStmts, detachStmts} = triggerAnimation(
|
||||
o.THIS_EXPR, o.THIS_EXPR, boundProp,
|
||||
o.THIS_EXPR, o.THIS_EXPR, boundProp, boundOutputs,
|
||||
(hasEvents ? o.THIS_EXPR.prop(getHandleEventMethodName(compileElement.nodeIndex)) :
|
||||
o.importExpr(createIdentifier(Identifiers.noop)))
|
||||
.callMethod(o.BuiltinMethod.Bind, [o.THIS_EXPR]),
|
||||
|
@ -44,7 +44,7 @@ class ViewBinderVisitor implements TemplateAstVisitor {
|
||||
visitElement(ast: ElementAst, parent: CompileElement): any {
|
||||
const compileElement = <CompileElement>this.view.nodes[this._nodeIndex++];
|
||||
const hasEvents = bindOutputs(ast.outputs, ast.directives, compileElement, true);
|
||||
bindRenderInputs(ast.inputs, hasEvents, compileElement);
|
||||
bindRenderInputs(ast.inputs, ast.outputs, hasEvents, compileElement);
|
||||
ast.directives.forEach((directiveAst, dirIndex) => {
|
||||
const directiveWrapperInstance =
|
||||
compileElement.directiveWrapperInstance.get(directiveAst.directive.type.reference);
|
||||
|
@ -576,7 +576,8 @@ function generateDetectChangesMethod(view: CompileView): o.Statement[] {
|
||||
}
|
||||
stmts.push(...view.detectChangesRenderPropertiesMethod.finish());
|
||||
view.viewChildren.forEach((viewChild) => {
|
||||
stmts.push(viewChild.callMethod('detectChanges', [DetectChangesVars.throwOnChange]).toStmt());
|
||||
stmts.push(
|
||||
viewChild.callMethod('internalDetectChanges', [DetectChangesVars.throwOnChange]).toStmt());
|
||||
});
|
||||
const afterViewStmts =
|
||||
view.updateViewQueriesMethod.finish().concat(view.afterViewLifecycleCallbacksMethod.finish());
|
||||
|
@ -80,7 +80,13 @@ describe('StaticReflector', () => {
|
||||
it('should throw an exception for unsupported metadata versions', () => {
|
||||
expect(() => reflector.findDeclaration('src/version-error', 'e'))
|
||||
.toThrow(new Error(
|
||||
'Metadata version mismatch for module /tmp/src/version-error.d.ts, found version 100, expected 2'));
|
||||
'Metadata version mismatch for module /tmp/src/version-error.d.ts, found version 100, expected 3'));
|
||||
});
|
||||
|
||||
it('should throw an exception for version 2 metadata', () => {
|
||||
expect(() => reflector.findDeclaration('src/version-2-error', 'e'))
|
||||
.toThrowError(
|
||||
'Unsupported metadata version 2 for module /tmp/src/version-2-error.d.ts. This module should be compiled with a newer version of ngc');
|
||||
});
|
||||
|
||||
it('should get and empty annotation list for an unknown class', () => {
|
||||
@ -307,6 +313,12 @@ describe('StaticReflector', () => {
|
||||
.toEqual('s');
|
||||
});
|
||||
|
||||
it('should not simplify a module reference without a name', () => {
|
||||
const staticSymbol = new StaticSymbol('/src/cases', '');
|
||||
expect(simplify(staticSymbol, ({__symbolic: 'reference', module: './extern', name: ''})))
|
||||
.toEqual(staticSymbol);
|
||||
});
|
||||
|
||||
it('should simplify a non existing reference as a static symbol', () => {
|
||||
expect(simplify(
|
||||
new StaticSymbol('/src/cases', ''),
|
||||
@ -378,7 +390,7 @@ describe('StaticReflector', () => {
|
||||
const metadata = reflector.getModuleMetadata('/tmp/src/custom-decorator-reference.ts');
|
||||
expect(metadata).toEqual({
|
||||
__symbolic: 'module',
|
||||
version: 2,
|
||||
version: 3,
|
||||
metadata: {
|
||||
Foo: {
|
||||
__symbolic: 'class',
|
||||
@ -769,7 +781,7 @@ export class MockStaticReflectorHost implements StaticReflectorHost {
|
||||
const DEFAULT_TEST_DATA: {[key: string]: any} = {
|
||||
'/tmp/@angular/common/src/forms-deprecated/directives.d.ts': [{
|
||||
'__symbolic': 'module',
|
||||
'version': 2,
|
||||
'version': 3,
|
||||
'metadata': {
|
||||
'FORM_DIRECTIVES': [
|
||||
{
|
||||
@ -782,7 +794,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
|
||||
}],
|
||||
'/tmp/@angular/common/src/directives/ng_for.d.ts': {
|
||||
'__symbolic': 'module',
|
||||
'version': 2,
|
||||
'version': 3,
|
||||
'metadata': {
|
||||
'NgFor': {
|
||||
'__symbolic': 'class',
|
||||
@ -835,16 +847,16 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
|
||||
}
|
||||
},
|
||||
'/tmp/@angular/core/src/linker/view_container_ref.d.ts':
|
||||
{version: 2, 'metadata': {'ViewContainerRef': {'__symbolic': 'class'}}},
|
||||
{version: 3, 'metadata': {'ViewContainerRef': {'__symbolic': 'class'}}},
|
||||
'/tmp/@angular/core/src/linker/template_ref.d.ts':
|
||||
{version: 2, 'module': './template_ref', 'metadata': {'TemplateRef': {'__symbolic': 'class'}}},
|
||||
{version: 3, 'module': './template_ref', 'metadata': {'TemplateRef': {'__symbolic': 'class'}}},
|
||||
'/tmp/@angular/core/src/change_detection/differs/iterable_differs.d.ts':
|
||||
{version: 2, 'metadata': {'IterableDiffers': {'__symbolic': 'class'}}},
|
||||
{version: 3, 'metadata': {'IterableDiffers': {'__symbolic': 'class'}}},
|
||||
'/tmp/@angular/core/src/change_detection/change_detector_ref.d.ts':
|
||||
{version: 2, 'metadata': {'ChangeDetectorRef': {'__symbolic': 'class'}}},
|
||||
{version: 3, 'metadata': {'ChangeDetectorRef': {'__symbolic': 'class'}}},
|
||||
'/tmp/src/app/hero-detail.component.d.ts': {
|
||||
'__symbolic': 'module',
|
||||
'version': 2,
|
||||
'version': 3,
|
||||
'metadata': {
|
||||
'HeroDetailComponent': {
|
||||
'__symbolic': 'class',
|
||||
@ -995,11 +1007,12 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
|
||||
}
|
||||
}
|
||||
},
|
||||
'/src/extern.d.ts': {'__symbolic': 'module', 'version': 2, metadata: {s: 's'}},
|
||||
'/src/extern.d.ts': {'__symbolic': 'module', 'version': 3, metadata: {s: 's'}},
|
||||
'/tmp/src/version-error.d.ts': {'__symbolic': 'module', 'version': 100, metadata: {e: 's'}},
|
||||
'/tmp/src/version-2-error.d.ts': {'__symbolic': 'module', 'version': 2, metadata: {e: 's'}},
|
||||
'/tmp/src/error-reporting.d.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 2,
|
||||
version: 3,
|
||||
metadata: {
|
||||
SomeClass: {
|
||||
__symbolic: 'class',
|
||||
@ -1029,7 +1042,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
|
||||
},
|
||||
'/tmp/src/error-references.d.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 2,
|
||||
version: 3,
|
||||
metadata: {
|
||||
Link1: {
|
||||
__symbolic: 'reference',
|
||||
@ -1051,7 +1064,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
|
||||
},
|
||||
'/tmp/src/function-declaration.d.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 2,
|
||||
version: 3,
|
||||
metadata: {
|
||||
one: {
|
||||
__symbolic: 'function',
|
||||
@ -1080,7 +1093,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
|
||||
},
|
||||
'/tmp/src/function-reference.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 2,
|
||||
version: 3,
|
||||
metadata: {
|
||||
one: {
|
||||
__symbolic: 'call',
|
||||
@ -1122,7 +1135,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
|
||||
},
|
||||
'/tmp/src/function-recursive.d.ts': {
|
||||
__symbolic: 'modules',
|
||||
version: 2,
|
||||
version: 3,
|
||||
metadata: {
|
||||
recursive: {
|
||||
__symbolic: 'function',
|
||||
@ -1182,7 +1195,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
|
||||
},
|
||||
'/tmp/src/spread.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 2,
|
||||
version: 3,
|
||||
metadata: {
|
||||
spread: [0, {__symbolic: 'spread', expression: [1, 2, 3, 4]}, 5]
|
||||
}
|
||||
@ -1332,7 +1345,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
|
||||
`,
|
||||
'/tmp/src/reexport/reexport.d.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 2,
|
||||
version: 3,
|
||||
metadata: {},
|
||||
exports: [
|
||||
{from: './src/origin1', export: ['One', 'Two', {name: 'Three', as: 'Four'}]},
|
||||
@ -1341,7 +1354,7 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
|
||||
},
|
||||
'/tmp/src/reexport/src/origin1.d.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 2,
|
||||
version: 3,
|
||||
metadata: {
|
||||
One: {__symbolic: 'class'},
|
||||
Two: {__symbolic: 'class'},
|
||||
@ -1350,26 +1363,26 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = {
|
||||
},
|
||||
'/tmp/src/reexport/src/origin5.d.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 2,
|
||||
version: 3,
|
||||
metadata: {
|
||||
Five: {__symbolic: 'class'},
|
||||
},
|
||||
},
|
||||
'/tmp/src/reexport/src/origin30.d.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 2,
|
||||
version: 3,
|
||||
metadata: {
|
||||
Thirty: {__symbolic: 'class'},
|
||||
},
|
||||
},
|
||||
'/tmp/src/reexport/src/originNone.d.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 2,
|
||||
version: 3,
|
||||
metadata: {},
|
||||
},
|
||||
'/tmp/src/reexport/src/reexport2.d.ts': {
|
||||
__symbolic: 'module',
|
||||
version: 2,
|
||||
version: 3,
|
||||
metadata: {},
|
||||
exports: [{from: './originNone'}, {from: './origin30'}]
|
||||
}
|
||||
|
@ -309,7 +309,8 @@ export function main() {
|
||||
}
|
||||
|
||||
const directiveMetadata = resolver.resolve(Child);
|
||||
expect(directiveMetadata.host).toEqual({'[p1]': 'p1', '[p22]': 'p2', '[p3]': 'p3'});
|
||||
expect(directiveMetadata.host)
|
||||
.toEqual({'[p1]': 'p1', '[p21]': 'p2', '[p22]': 'p2', '[p3]': 'p3'});
|
||||
});
|
||||
|
||||
it('should support inheriting host listeners', () => {
|
||||
@ -329,7 +330,37 @@ export function main() {
|
||||
}
|
||||
|
||||
const directiveMetadata = resolver.resolve(Child);
|
||||
expect(directiveMetadata.host).toEqual({'(p1)': 'p1()', '(p22)': 'p2()', '(p3)': 'p3()'});
|
||||
expect(directiveMetadata.host)
|
||||
.toEqual({'(p1)': 'p1()', '(p21)': 'p2()', '(p22)': 'p2()', '(p3)': 'p3()'});
|
||||
});
|
||||
|
||||
it('should combine host bindings and listeners during inheritance', () => {
|
||||
@Directive({selector: 'p'})
|
||||
class Parent {
|
||||
@HostListener('p11') @HostListener('p12')
|
||||
p1() {}
|
||||
|
||||
@HostBinding('p21') @HostBinding('p22')
|
||||
p2: any;
|
||||
}
|
||||
|
||||
class Child extends Parent {
|
||||
@HostListener('c1')
|
||||
p1() {}
|
||||
|
||||
@HostBinding('c2')
|
||||
p2: any;
|
||||
}
|
||||
|
||||
const directiveMetadata = resolver.resolve(Child);
|
||||
expect(directiveMetadata.host).toEqual({
|
||||
'(p11)': 'p1()',
|
||||
'(p12)': 'p1()',
|
||||
'(c1)': 'p1()',
|
||||
'[p21]': 'p2',
|
||||
'[p22]': 'p2',
|
||||
'[c2]': 'p2'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -6,10 +6,23 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {computeMsgId, sha1} from '../../src/i18n/digest';
|
||||
import {computeMsgId, digest, sha1} from '../../src/i18n/digest';
|
||||
|
||||
export function main(): void {
|
||||
describe('digest', () => {
|
||||
describe('digest', () => {
|
||||
it('must return the ID if it\'s explicit', () => {
|
||||
expect(digest({
|
||||
id: 'i',
|
||||
nodes: [],
|
||||
placeholders: {},
|
||||
placeholderToMessage: {},
|
||||
meaning: '',
|
||||
description: '',
|
||||
})).toEqual('i');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sha1', () => {
|
||||
it('should work on empty strings',
|
||||
() => { expect(sha1('')).toEqual('da39a3ee5e6b4b0d3255bfef95601890afd80709'); });
|
||||
|
@ -20,7 +20,10 @@ export function main() {
|
||||
describe('elements', () => {
|
||||
it('should extract from elements', () => {
|
||||
expect(extract('<div i18n="m|d|e">text<span>nested</span></div>')).toEqual([
|
||||
[['text', '<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], 'm', 'd|e'],
|
||||
[
|
||||
['text', '<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], 'm', 'd|e',
|
||||
''
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -29,11 +32,45 @@ export function main() {
|
||||
extract(
|
||||
'<div i18n="m1|d1"><span i18n-title="m2|d2" title="single child">nested</span></div>'))
|
||||
.toEqual([
|
||||
[['<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], 'm1', 'd1'],
|
||||
[['single child'], 'm2', 'd2'],
|
||||
[['<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], 'm1', 'd1', ''],
|
||||
[['single child'], 'm2', 'd2', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract from attributes with id', () => {
|
||||
expect(
|
||||
extract(
|
||||
'<div i18n="m1|d1@@i1"><span i18n-title="m2|d2@@i2" title="single child">nested</span></div>'))
|
||||
.toEqual([
|
||||
[
|
||||
['<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], 'm1', 'd1',
|
||||
'i1'
|
||||
],
|
||||
[['single child'], 'm2', 'd2', 'i2'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract from attributes without meaning and with id', () => {
|
||||
expect(
|
||||
extract(
|
||||
'<div i18n="d1@@i1"><span i18n-title="d2@@i2" title="single child">nested</span></div>'))
|
||||
.toEqual([
|
||||
[['<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], '', 'd1', 'i1'],
|
||||
[['single child'], '', 'd2', 'i2'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract from attributes with id only', () => {
|
||||
expect(
|
||||
extract(
|
||||
'<div i18n="@@i1"><span i18n-title="@@i2" title="single child">nested</span></div>'))
|
||||
.toEqual([
|
||||
[['<ph tag name="START_TAG_SPAN">nested</ph name="CLOSE_TAG_SPAN">'], '', '', 'i1'],
|
||||
[['single child'], '', '', 'i2'],
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
it('should extract from ICU messages', () => {
|
||||
expect(
|
||||
extract(
|
||||
@ -43,10 +80,10 @@ export function main() {
|
||||
[
|
||||
'{count, plural, =0 {[<ph tag name="START_PARAGRAPH"></ph name="CLOSE_PARAGRAPH">]}}'
|
||||
],
|
||||
'm', 'd'
|
||||
'm', 'd', ''
|
||||
],
|
||||
[['title'], '', ''],
|
||||
[['desc'], '', ''],
|
||||
[['title'], '', '', ''],
|
||||
[['desc'], '', '', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -55,7 +92,7 @@ export function main() {
|
||||
|
||||
it('should ignore implicit elements in translatable elements', () => {
|
||||
expect(extract('<div i18n="m|d"><p></p></div>', ['p'])).toEqual([
|
||||
[['<ph tag name="START_PARAGRAPH"></ph name="CLOSE_PARAGRAPH">'], 'm', 'd']
|
||||
[['<ph tag name="START_PARAGRAPH"></ph name="CLOSE_PARAGRAPH">'], 'm', 'd', '']
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -64,17 +101,19 @@ export function main() {
|
||||
it('should extract from blocks', () => {
|
||||
expect(extract(`<!-- i18n: meaning1|desc1 -->message1<!-- /i18n -->
|
||||
<!-- i18n: desc2 -->message2<!-- /i18n -->
|
||||
<!-- i18n -->message3<!-- /i18n -->`))
|
||||
<!-- i18n -->message3<!-- /i18n -->
|
||||
<!-- i18n: meaning4|desc4@@id4 -->message4<!-- /i18n -->
|
||||
<!-- i18n: @@id5 -->message5<!-- /i18n -->`))
|
||||
.toEqual([
|
||||
[['message1'], 'meaning1', 'desc1'],
|
||||
[['message2'], '', 'desc2'],
|
||||
[['message3'], '', ''],
|
||||
[['message1'], 'meaning1', 'desc1', ''], [['message2'], '', 'desc2', ''],
|
||||
[['message3'], '', '', ''], [['message4'], 'meaning4', 'desc4', 'id4'],
|
||||
[['message5'], '', '', 'id5']
|
||||
]);
|
||||
});
|
||||
|
||||
it('should ignore implicit elements in blocks', () => {
|
||||
expect(extract('<!-- i18n:m|d --><p></p><!-- /i18n -->', ['p'])).toEqual([
|
||||
[['<ph tag name="START_PARAGRAPH"></ph name="CLOSE_PARAGRAPH">'], 'm', 'd']
|
||||
[['<ph tag name="START_PARAGRAPH"></ph name="CLOSE_PARAGRAPH">'], 'm', 'd', '']
|
||||
]);
|
||||
});
|
||||
|
||||
@ -88,7 +127,7 @@ export function main() {
|
||||
[
|
||||
'{count, plural, =0 {[<ph tag name="START_TAG_SPAN">html</ph name="CLOSE_TAG_SPAN">]}}'
|
||||
],
|
||||
'', ''
|
||||
'', '', ''
|
||||
],
|
||||
[
|
||||
[
|
||||
@ -98,15 +137,15 @@ export function main() {
|
||||
' name="START_TAG_SPAN">html</ph name="CLOSE_TAG_SPAN">]}}</ph>',
|
||||
'[<ph name="INTERPOLATION">interp</ph>]'
|
||||
],
|
||||
'', ''
|
||||
'', '', ''
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should ignore other comments', () => {
|
||||
expect(extract(`<!-- i18n: meaning1|desc1 --><!-- other -->message1<!-- /i18n -->`))
|
||||
expect(extract(`<!-- i18n: meaning1|desc1@@id1 --><!-- other -->message1<!-- /i18n -->`))
|
||||
.toEqual([
|
||||
[['message1'], 'meaning1', 'desc1'],
|
||||
[['message1'], 'meaning1', 'desc1', 'id1'],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -118,34 +157,37 @@ export function main() {
|
||||
it('should extract ICU messages from translatable elements', () => {
|
||||
// single message when ICU is the only children
|
||||
expect(extract('<div i18n="m|d">{count, plural, =0 {text}}</div>')).toEqual([
|
||||
[['{count, plural, =0 {[text]}}'], 'm', 'd'],
|
||||
[['{count, plural, =0 {[text]}}'], 'm', 'd', ''],
|
||||
]);
|
||||
|
||||
// single message when ICU is the only (implicit) children
|
||||
expect(extract('<div>{count, plural, =0 {text}}</div>', ['div'])).toEqual([
|
||||
[['{count, plural, =0 {[text]}}'], '', ''],
|
||||
[['{count, plural, =0 {[text]}}'], '', '', ''],
|
||||
]);
|
||||
|
||||
// one message for the element content and one message for the ICU
|
||||
expect(extract('<div i18n="m|d">before{count, plural, =0 {text}}after</div>')).toEqual([
|
||||
[['before', '<ph icu name="ICU">{count, plural, =0 {[text]}}</ph>', 'after'], 'm', 'd'],
|
||||
[['{count, plural, =0 {[text]}}'], '', ''],
|
||||
expect(extract('<div i18n="m|d@@i">before{count, plural, =0 {text}}after</div>')).toEqual([
|
||||
[
|
||||
['before', '<ph icu name="ICU">{count, plural, =0 {[text]}}</ph>', 'after'], 'm', 'd',
|
||||
'i'
|
||||
],
|
||||
[['{count, plural, =0 {[text]}}'], '', '', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract ICU messages from translatable block', () => {
|
||||
// single message when ICU is the only children
|
||||
expect(extract('<!-- i18n:m|d -->{count, plural, =0 {text}}<!-- /i18n -->')).toEqual([
|
||||
[['{count, plural, =0 {[text]}}'], 'm', 'd'],
|
||||
[['{count, plural, =0 {[text]}}'], 'm', 'd', ''],
|
||||
]);
|
||||
|
||||
// one message for the block content and one message for the ICU
|
||||
expect(extract('<!-- i18n:m|d -->before{count, plural, =0 {text}}after<!-- /i18n -->'))
|
||||
.toEqual([
|
||||
[['{count, plural, =0 {[text]}}'], '', ''],
|
||||
[['{count, plural, =0 {[text]}}'], '', '', ''],
|
||||
[
|
||||
['before', '<ph icu name="ICU">{count, plural, =0 {[text]}}</ph>', 'after'], 'm',
|
||||
'd'
|
||||
'd', ''
|
||||
],
|
||||
]);
|
||||
});
|
||||
@ -156,20 +198,20 @@ export function main() {
|
||||
it('should ignore nested ICU messages', () => {
|
||||
expect(extract('<div i18n="m|d">{count, plural, =0 { {sex, select, male {m}} }}</div>'))
|
||||
.toEqual([
|
||||
[['{count, plural, =0 {[{sex, select, male {[m]}}, ]}}'], 'm', 'd'],
|
||||
[['{count, plural, =0 {[{sex, select, male {[m]}}, ]}}'], 'm', 'd', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should ignore implicit elements in non translatable ICU messages', () => {
|
||||
expect(
|
||||
extract(
|
||||
'<div i18n="m|d">{count, plural, =0 { {sex, select, male {<p>ignore</p>}} }}</div>',
|
||||
['p']))
|
||||
expect(extract(
|
||||
'<div i18n="m|d@@i">{count, plural, =0 { {sex, select, male {<p>ignore</p>}}' +
|
||||
' }}</div>',
|
||||
['p']))
|
||||
.toEqual([[
|
||||
[
|
||||
'{count, plural, =0 {[{sex, select, male {[<ph tag name="START_PARAGRAPH">ignore</ph name="CLOSE_PARAGRAPH">]}}, ]}}'
|
||||
],
|
||||
'm', 'd'
|
||||
'm', 'd', 'i'
|
||||
]]);
|
||||
});
|
||||
|
||||
@ -181,46 +223,45 @@ export function main() {
|
||||
|
||||
describe('attributes', () => {
|
||||
it('should extract from attributes outside of translatable sections', () => {
|
||||
expect(extract('<div i18n-title="m|d" title="msg"></div>')).toEqual([
|
||||
[['msg'], 'm', 'd'],
|
||||
expect(extract('<div i18n-title="m|d@@i" title="msg"></div>')).toEqual([
|
||||
[['msg'], 'm', 'd', 'i'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract from attributes in translatable elements', () => {
|
||||
expect(extract('<div i18n><p><b i18n-title="m|d" title="msg"></b></p></div>')).toEqual([
|
||||
expect(extract('<div i18n><p><b i18n-title="m|d@@i" title="msg"></b></p></div>')).toEqual([
|
||||
[
|
||||
['<ph tag name="START_PARAGRAPH"><ph tag name="START_BOLD_TEXT"></ph' +
|
||||
' name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">'],
|
||||
'', ''
|
||||
'', '', ''
|
||||
],
|
||||
[['msg'], 'm', 'd'],
|
||||
[['msg'], 'm', 'd', 'i'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract from attributes in translatable blocks', () => {
|
||||
expect(extract('<!-- i18n --><p><b i18n-title="m|d" title="msg"></b></p><!-- /i18n -->'))
|
||||
.toEqual([
|
||||
[['msg'], 'm', 'd'],
|
||||
[['msg'], 'm', 'd', ''],
|
||||
[
|
||||
['<ph tag name="START_PARAGRAPH"><ph tag name="START_BOLD_TEXT"></ph' +
|
||||
' name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">'],
|
||||
'', ''
|
||||
'', '', ''
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should extract from attributes in translatable ICUs', () => {
|
||||
expect(
|
||||
extract(
|
||||
'<!-- i18n -->{count, plural, =0 {<p><b i18n-title="m|d" title="msg"></b></p>}}<!-- /i18n -->'))
|
||||
expect(extract(`<!-- i18n -->{count, plural, =0 {<p><b i18n-title="m|d@@i"
|
||||
title="msg"></b></p>}}<!-- /i18n -->`))
|
||||
.toEqual([
|
||||
[['msg'], 'm', 'd'],
|
||||
[['msg'], 'm', 'd', 'i'],
|
||||
[
|
||||
[
|
||||
'{count, plural, =0 {[<ph tag name="START_PARAGRAPH"><ph tag' +
|
||||
' name="START_BOLD_TEXT"></ph name="CLOSE_BOLD_TEXT"></ph name="CLOSE_PARAGRAPH">]}}'
|
||||
],
|
||||
'', ''
|
||||
'', '', ''
|
||||
],
|
||||
]);
|
||||
});
|
||||
@ -228,7 +269,7 @@ export function main() {
|
||||
it('should extract from attributes in non translatable ICUs', () => {
|
||||
expect(extract('{count, plural, =0 {<p><b i18n-title="m|d" title="msg"></b></p>}}'))
|
||||
.toEqual([
|
||||
[['msg'], 'm', 'd'],
|
||||
[['msg'], 'm', 'd', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -239,7 +280,7 @@ export function main() {
|
||||
describe('implicit elements', () => {
|
||||
it('should extract from implicit elements', () => {
|
||||
expect(extract('<b>bold</b><i>italic</i>', ['b'])).toEqual([
|
||||
[['bold'], '', ''],
|
||||
[['bold'], '', '', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -251,7 +292,7 @@ export function main() {
|
||||
}).not.toThrow();
|
||||
|
||||
expect(result).toEqual([
|
||||
[['outer', '<ph tag name="START_TAG_DIV">inner</ph name="CLOSE_TAG_DIV">'], '', ''],
|
||||
[['outer', '<ph tag name="START_TAG_DIV">inner</ph name="CLOSE_TAG_DIV">'], '', '', ''],
|
||||
]);
|
||||
});
|
||||
|
||||
@ -261,7 +302,7 @@ export function main() {
|
||||
it('should extract implicit attributes', () => {
|
||||
expect(extract('<b title="bb">bold</b><i title="ii">italic</i>', [], {'b': ['title']}))
|
||||
.toEqual([
|
||||
[['bb'], '', ''],
|
||||
[['bb'], '', '', ''],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -433,7 +474,7 @@ function extract(
|
||||
// clang-format off
|
||||
// https://github.com/angular/clang-format/issues/35
|
||||
return result.messages.map(
|
||||
message => [serializeI18nNodes(message.nodes), message.meaning, message.description, ]) as [string[], string, string][];
|
||||
message => [serializeI18nNodes(message.nodes), message.meaning, message.description, message.id]) as [string[], string, string][];
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
|
@ -59,14 +59,17 @@ export function main() {
|
||||
tb.detectChanges();
|
||||
expect(el.query(By.css('#i18n-7')).nativeElement).toHaveText('un');
|
||||
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('un');
|
||||
expect(el.query(By.css('#i18n-17')).nativeElement).toHaveText('un');
|
||||
cmp.count = 2;
|
||||
tb.detectChanges();
|
||||
expect(el.query(By.css('#i18n-7')).nativeElement).toHaveText('deux');
|
||||
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('deux');
|
||||
expect(el.query(By.css('#i18n-17')).nativeElement).toHaveText('deux');
|
||||
cmp.count = 3;
|
||||
tb.detectChanges();
|
||||
expect(el.query(By.css('#i18n-7')).nativeElement).toHaveText('beaucoup');
|
||||
expect(el.query(By.css('#i18n-14')).nativeElement).toHaveText('beaucoup');
|
||||
expect(el.query(By.css('#i18n-17')).nativeElement).toHaveText('beaucoup');
|
||||
|
||||
cmp.sex = 'm';
|
||||
cmp.sexB = 'f';
|
||||
@ -90,8 +93,8 @@ export function main() {
|
||||
.toEqual('<h1 id="i18n-12">Balises dans les commentaires html</h1>');
|
||||
expectHtml(el, '#i18n-13')
|
||||
.toBe('<div id="i18n-13" title="dans une section traductible"></div>');
|
||||
|
||||
expectHtml(el, '#i18n-15').toMatch(/ca <b>devrait<\/b> marcher/);
|
||||
expectHtml(el, '#i18n-16').toMatch(/avec un ID explicite/);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -141,6 +144,8 @@ function expectHtml(el: DebugElement, cssSelector: string): any {
|
||||
<!-- /i18n -->
|
||||
|
||||
<div id="i18n-15"><ng-container i18n>it <b>should</b> work</ng-container></div>
|
||||
<div id="i18n-16" i18n="@@i18n16">with an explicit ID</div>
|
||||
<div id="i18n-17" i18n="@@i18n17">{count, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>}}</div>
|
||||
`
|
||||
})
|
||||
class I18nComponent {
|
||||
@ -182,6 +187,9 @@ const XTB = `
|
||||
<ph name="START_TAG_DIV_1"/><ph name="ICU"/><ph name="CLOSE_TAG_DIV"></ph>
|
||||
</translation>
|
||||
<translation id="1491627405349178954">ca <ph name="START_BOLD_TEXT"/>devrait<ph name="CLOSE_BOLD_TEXT"/> marcher</translation>
|
||||
<translation id="i18n16">avec un ID explicite</translation>
|
||||
<translation id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {un} =2 {deux} other {<ph
|
||||
name="START_BOLD_TEXT"><ex><b></ex></ph>beaucoup<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph>} }</translation>
|
||||
</translationbundle>`;
|
||||
|
||||
// unused, for reference only
|
||||
@ -210,5 +218,7 @@ const XMB = `
|
||||
<ph name="START_TAG_DIV_1"><ex><div></ex></ph><ph name="ICU"/><ph name="CLOSE_TAG_DIV"><ex></div></ex></ph>
|
||||
</msg>
|
||||
<msg id="1491627405349178954">it <ph name="START_BOLD_TEXT"><ex><b></ex></ph>should<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph> work</msg>
|
||||
<msg id="i18n16">with an explicit ID</msg>
|
||||
<msg id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex><b></ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph>} }</msg>
|
||||
</messagebundle>
|
||||
`;
|
||||
|
@ -18,6 +18,8 @@ const HTML = `
|
||||
<p i18n-title title="translatable attribute">not translatable</p>
|
||||
<p i18n>translatable element <b>with placeholders</b> {{ interpolation}}</p>
|
||||
<p i18n="m|d">foo</p>
|
||||
<p i18n="m|d@@i">foo</p>
|
||||
<p i18n="@@bar">foo</p>
|
||||
<p i18n="ph names"><br><img><div></div></p>
|
||||
`;
|
||||
|
||||
@ -39,6 +41,16 @@ const WRITE_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<note priority="1" from="description">d</note>
|
||||
<note priority="1" from="meaning">m</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="i" datatype="html">
|
||||
<source>foo</source>
|
||||
<target/>
|
||||
<note priority="1" from="description">d</note>
|
||||
<note priority="1" from="meaning">m</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="bar" datatype="html">
|
||||
<source>foo</source>
|
||||
<target/>
|
||||
</trans-unit>
|
||||
<trans-unit id="d7fa2d59aaedcaa5309f13028c59af8c85b8c49d" datatype="html">
|
||||
<source><x id="LINE_BREAK" ctype="lb"/><x id="TAG_IMG" ctype="image"/><x id="START_TAG_DIV" ctype="x-div"/><x id="CLOSE_TAG_DIV" ctype="x-div"/></source>
|
||||
<target/>
|
||||
@ -67,6 +79,16 @@ const LOAD_XLIFF = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<note priority="1" from="description">d</note>
|
||||
<note priority="1" from="meaning">m</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="i" datatype="html">
|
||||
<source>foo</source>
|
||||
<target>toto</target>
|
||||
<note priority="1" from="description">d</note>
|
||||
<note priority="1" from="meaning">m</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="bar" datatype="html">
|
||||
<source>foo</source>
|
||||
<target>tata</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="d7fa2d59aaedcaa5309f13028c59af8c85b8c49d" datatype="html">
|
||||
<source><x id="LINE_BREAK" ctype="lb"/><x id="TAG_IMG" ctype="image"/><x id="START_TAG_DIV" ctype="x-div"/><x id="CLOSE_TAG_DIV" ctype="x-div"/></source>
|
||||
<target><x id="START_TAG_DIV" ctype="x-div"/><x id="CLOSE_TAG_DIV" ctype="x-div"/><x id="TAG_IMG" ctype="image"/><x id="LINE_BREAK" ctype="lb"/></target>
|
||||
@ -107,6 +129,8 @@ export function main(): void {
|
||||
'ec1d033f2436133c14ab038286c4f5df4697484a':
|
||||
'<ph name="INTERPOLATION"/> footnemele elbatalsnart <ph name="START_BOLD_TEXT"/>sredlohecalp htiw<ph name="CLOSE_BOLD_TEXT"/>',
|
||||
'db3e0a6a5a96481f60aec61d98c3eecddef5ac23': 'oof',
|
||||
'i': 'toto',
|
||||
'bar': 'tata',
|
||||
'd7fa2d59aaedcaa5309f13028c59af8c85b8c49d':
|
||||
'<ph name="START_TAG_DIV"/><ph name="CLOSE_TAG_DIV"/><ph name="TAG_IMG"/><ph name="LINE_BREAK"/>',
|
||||
});
|
||||
|
@ -18,6 +18,9 @@ export function main(): void {
|
||||
<p i18n>translatable element <b>with placeholders</b> {{ interpolation}}</p>
|
||||
<!-- i18n -->{ count, plural, =0 {<p>test</p>}}<!-- /i18n -->
|
||||
<p i18n="m|d">foo</p>
|
||||
<p i18n="m|d@@i">foo</p>
|
||||
<p i18n="@@bar">foo</p>
|
||||
<p i18n="@@baz">{ count, plural, =0 { { sex, select, other {<p>deeply nested</p>}} }}</p>
|
||||
<p i18n>{ count, plural, =0 { { sex, select, other {<p>deeply nested</p>}} }}</p>`;
|
||||
|
||||
const XMB = `<?xml version="1.0" encoding="UTF-8" ?>
|
||||
@ -46,6 +49,9 @@ export function main(): void {
|
||||
<msg id="7056919470098446707">translatable element <ph name="START_BOLD_TEXT"><ex><b></ex></ph>with placeholders<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph> <ph name="INTERPOLATION"/></msg>
|
||||
<msg id="2981514368455622387">{VAR_PLURAL, plural, =0 {<ph name="START_PARAGRAPH"><ex><p></ex></ph>test<ph name="CLOSE_PARAGRAPH"><ex></p></ex></ph>} }</msg>
|
||||
<msg id="7999024498831672133" desc="d" meaning="m">foo</msg>
|
||||
<msg id="i" desc="d" meaning="m">foo</msg>
|
||||
<msg id="bar">foo</msg>
|
||||
<msg id="baz">{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<ph name="START_PARAGRAPH"><ex><p></ex></ph>deeply nested<ph name="CLOSE_PARAGRAPH"><ex></p></ex></ph>} } } }</msg>
|
||||
<msg id="2015957479576096115">{VAR_PLURAL, plural, =0 {{VAR_SELECT, select, other {<ph name="START_PARAGRAPH"><ex><p></ex></ph>deeply nested<ph name="CLOSE_PARAGRAPH"><ex></p></ex></ph>} } } }</msg>
|
||||
</messagebundle>
|
||||
`;
|
||||
|
@ -21,7 +21,7 @@ export function main(): void {
|
||||
it('should translate a plain message', () => {
|
||||
const msgMap = {foo: [new i18n.Text('bar', null)]};
|
||||
const tb = new TranslationBundle(msgMap, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
expect(serializeNodes(tb.get(msg))).toEqual(['bar']);
|
||||
});
|
||||
|
||||
@ -36,7 +36,7 @@ export function main(): void {
|
||||
ph1: '*phContent*',
|
||||
};
|
||||
const tb = new TranslationBundle(msgMap, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], phMap, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], phMap, {}, 'm', 'd', 'i');
|
||||
expect(serializeNodes(tb.get(msg))).toEqual(['bar*phContent*']);
|
||||
});
|
||||
|
||||
@ -51,8 +51,8 @@ export function main(): void {
|
||||
new i18n.Text('*refMsg*', null),
|
||||
],
|
||||
};
|
||||
const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], {}, {ph1: refMsg}, 'm', 'd');
|
||||
const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
const msg = new i18n.Message([srcNode], {}, {ph1: refMsg}, 'm', 'd', 'i');
|
||||
let count = 0;
|
||||
const digest = (_: any) => count++ ? 'ref' : 'foo';
|
||||
const tb = new TranslationBundle(msgMap, digest);
|
||||
@ -69,13 +69,13 @@ export function main(): void {
|
||||
]
|
||||
};
|
||||
const tb = new TranslationBundle(msgMap, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
expect(() => tb.get(msg)).toThrowError(/Unknown placeholder/);
|
||||
});
|
||||
|
||||
it('should report missing translation', () => {
|
||||
const tb = new TranslationBundle({}, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
expect(() => tb.get(msg)).toThrowError(/Missing translation for message foo/);
|
||||
});
|
||||
|
||||
@ -83,8 +83,8 @@ export function main(): void {
|
||||
const msgMap = {
|
||||
foo: [new i18n.Placeholder('', 'ph1', span)],
|
||||
};
|
||||
const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], {}, {ph1: refMsg}, 'm', 'd');
|
||||
const refMsg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i');
|
||||
const msg = new i18n.Message([srcNode], {}, {ph1: refMsg}, 'm', 'd', 'i');
|
||||
let count = 0;
|
||||
const digest = (_: any) => count++ ? 'ref' : 'foo';
|
||||
const tb = new TranslationBundle(msgMap, digest);
|
||||
@ -102,7 +102,7 @@ export function main(): void {
|
||||
ph1: '</b>',
|
||||
};
|
||||
const tb = new TranslationBundle(msgMap, (_) => 'foo');
|
||||
const msg = new i18n.Message([srcNode], phMap, {}, 'm', 'd');
|
||||
const msg = new i18n.Message([srcNode], phMap, {}, 'm', 'd', 'i');
|
||||
expect(() => tb.get(msg)).toThrowError(/Unexpected closing tag "b"/);
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* @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 {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
import {ImportResolver} from '@angular/compiler/src/output/path_util';
|
||||
import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter';
|
||||
import {convertValueToOutputAst} from '@angular/compiler/src/output/value_util';
|
||||
import {MetadataCollector, isClassMetadata, isMetadataSymbolicCallExpression} from '@angular/tsc-wrapped';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
describe('TypeScriptEmitter (node only)', () => {
|
||||
it('should quote identifiers quoted in the source', () => {
|
||||
const sourceText = `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
providers: [{ provide: 'SomeToken', useValue: {a: 1, 'b': 2, c: 3, 'd': 4}}]
|
||||
})
|
||||
export class MyComponent {}
|
||||
`;
|
||||
const source = ts.createSourceFile('test.ts', sourceText, ts.ScriptTarget.Latest);
|
||||
const collector = new MetadataCollector({quotedNames: true});
|
||||
const stubHost = new StubReflectorHost();
|
||||
const reflector = new StaticReflector(stubHost);
|
||||
|
||||
// Get the metadata from the above source
|
||||
const metadata = collector.getMetadata(source);
|
||||
const componentMetadata = metadata.metadata['MyComponent'];
|
||||
|
||||
// Get the first argument of the decorator call which is passed to @Component
|
||||
expect(isClassMetadata(componentMetadata)).toBeTruthy();
|
||||
if (!isClassMetadata(componentMetadata)) return;
|
||||
const decorators = componentMetadata.decorators;
|
||||
const firstDecorator = decorators[0];
|
||||
expect(isMetadataSymbolicCallExpression(firstDecorator)).toBeTruthy();
|
||||
if (!isMetadataSymbolicCallExpression(firstDecorator)) return;
|
||||
const firstArgument = firstDecorator.arguments[0];
|
||||
|
||||
// Simplify this value using the StaticReflector
|
||||
const context = reflector.getStaticSymbol('none', 'none');
|
||||
const argumentValue = reflector.simplify(context, firstArgument);
|
||||
|
||||
// Convert the value to an output AST
|
||||
const outputAst = convertValueToOutputAst(argumentValue);
|
||||
const statement = outputAst.toStmt();
|
||||
|
||||
// Convert the value to text using the typescript emitter
|
||||
const emitter = new TypeScriptEmitter(new StubImportResolver());
|
||||
const text = emitter.emitStatements('module', [statement], []);
|
||||
|
||||
// Expect the keys for 'b' and 'd' to be quoted but 'a' and 'c' not to be.
|
||||
expect(text).toContain('\'b\': 2');
|
||||
expect(text).toContain('\'d\': 4');
|
||||
expect(text).not.toContain('\'a\'');
|
||||
expect(text).not.toContain('\'c\'');
|
||||
});
|
||||
});
|
||||
|
||||
class StubReflectorHost implements StaticReflectorHost {
|
||||
getMetadataFor(modulePath: string): {[key: string]: any}[] { return []; }
|
||||
moduleNameToFileName(moduleName: string, containingFile: string): string { return ''; }
|
||||
}
|
||||
|
||||
class StubImportResolver extends ImportResolver {
|
||||
fileNameToModuleName(importedFilePath: string, containingFilePath: string): string { return ''; }
|
||||
}
|
@ -102,6 +102,17 @@ export function main() {
|
||||
expect(matched).toEqual([s1[0], 1, s2[0], 2]);
|
||||
});
|
||||
|
||||
it('should support "." in attribute names', () => {
|
||||
matcher.addSelectables(s1 = CssSelector.parse('[foo.bar]'), 1);
|
||||
|
||||
expect(matcher.match(CssSelector.parse('[barfoo]')[0], selectableCollector)).toEqual(false);
|
||||
expect(matched).toEqual([]);
|
||||
|
||||
reset();
|
||||
expect(matcher.match(CssSelector.parse('[foo.bar]')[0], selectableCollector)).toEqual(true);
|
||||
expect(matched).toEqual([s1[0], 1]);
|
||||
});
|
||||
|
||||
it('should select by attr name only once if the value is from the DOM', () => {
|
||||
matcher.addSelectables(s1 = CssSelector.parse('[some-decor]'), 1);
|
||||
|
||||
@ -223,6 +234,11 @@ export function main() {
|
||||
expect(matched).toEqual([s1[0], 1, s2[0], 2, s3[0], 3, s4[0], 4]);
|
||||
});
|
||||
|
||||
it('should match * with :not selector', () => {
|
||||
matcher.addSelectables(CssSelector.parse(':not([a])'), 1);
|
||||
expect(matcher.match(CssSelector.parse('div')[0], () => {})).toEqual(true);
|
||||
});
|
||||
|
||||
it('should match with multiple :not selectors', () => {
|
||||
matcher.addSelectables(s1 = CssSelector.parse('div:not([a]):not([b])'), 1);
|
||||
expect(matcher.match(CssSelector.parse('div[a]')[0], selectableCollector)).toBe(false);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -70,6 +70,10 @@ export class TestingCompilerImpl implements TestingCompiler {
|
||||
return this._compiler.compileModuleAndAllComponentsAsync(moduleType);
|
||||
}
|
||||
|
||||
getNgContentSelectors(component: Type<any>): string[] {
|
||||
return this._compiler.getNgContentSelectors(component);
|
||||
}
|
||||
|
||||
overrideModule(ngModule: Type<any>, override: MetadataOverride<NgModule>): void {
|
||||
const oldMetadata = this._moduleResolver.resolve(ngModule, false);
|
||||
this._moduleResolver.setNgModule(
|
||||
|
@ -5,30 +5,47 @@
|
||||
* 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 {Injectable} from '../di/metadata';
|
||||
import {NgZone} from '../zone/ng_zone';
|
||||
import {AnimationPlayer} from './animation_player';
|
||||
|
||||
let _queuedAnimations: AnimationPlayer[] = [];
|
||||
@Injectable()
|
||||
export class AnimationQueue {
|
||||
public entries: AnimationPlayer[] = [];
|
||||
|
||||
/** @internal */
|
||||
export function queueAnimation(player: AnimationPlayer) {
|
||||
_queuedAnimations.push(player);
|
||||
}
|
||||
constructor(private _zone: NgZone) {}
|
||||
|
||||
/** @internal */
|
||||
export function triggerQueuedAnimations() {
|
||||
// this code is wrapped into a single promise such that the
|
||||
// onStart and onDone player callbacks are triggered outside
|
||||
// of the digest cycle of animations
|
||||
if (_queuedAnimations.length) {
|
||||
Promise.resolve(null).then(_triggerAnimations);
|
||||
enqueue(player: AnimationPlayer) { this.entries.push(player); }
|
||||
|
||||
flush() {
|
||||
// given that each animation player may set aside
|
||||
// microtasks and rely on DOM-based events, this
|
||||
// will cause Angular to run change detection after
|
||||
// each request. This sidesteps the issue. If a user
|
||||
// hooks into an animation via (@anim.start) or (@anim.done)
|
||||
// then those methods will automatically trigger change
|
||||
// detection by wrapping themselves inside of a zone
|
||||
if (this.entries.length) {
|
||||
this._zone.runOutsideAngular(() => {
|
||||
// this code is wrapped into a single promise such that the
|
||||
// onStart and onDone player callbacks are triggered outside
|
||||
// of the digest cycle of animations
|
||||
Promise.resolve(null).then(() => this._triggerAnimations());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _triggerAnimations() {
|
||||
NgZone.assertNotInAngularZone();
|
||||
|
||||
while (this.entries.length) {
|
||||
const player = this.entries.shift();
|
||||
// in the event that an animation throws an error then we do
|
||||
// not want to re-run animations on any previous animations
|
||||
// if they have already been kicked off beforehand
|
||||
if (!player.hasStarted()) {
|
||||
player.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _triggerAnimations() {
|
||||
for (let i = 0; i < _queuedAnimations.length; i++) {
|
||||
const player = _queuedAnimations[i];
|
||||
player.play();
|
||||
}
|
||||
_queuedAnimations = [];
|
||||
}
|
||||
|
@ -23,12 +23,14 @@ export class AnimationTransition {
|
||||
}
|
||||
|
||||
onStart(callback: (event: AnimationTransitionEvent) => any): void {
|
||||
const event = this._createEvent('start');
|
||||
this._player.onStart(() => callback(event));
|
||||
const fn =
|
||||
<() => void>Zone.current.wrap(() => callback(this._createEvent('start')), 'player.onStart');
|
||||
this._player.onStart(fn);
|
||||
}
|
||||
|
||||
onDone(callback: (event: AnimationTransitionEvent) => any): void {
|
||||
const event = this._createEvent('done');
|
||||
this._player.onDone(() => callback(event));
|
||||
const fn =
|
||||
<() => void>Zone.current.wrap(() => callback(this._createEvent('done')), 'player.onDone');
|
||||
this._player.onDone(fn);
|
||||
}
|
||||
}
|
||||
|
@ -44,16 +44,18 @@ export class ViewAnimationMap {
|
||||
|
||||
getAllPlayers(): AnimationPlayer[] { return this._allPlayers; }
|
||||
|
||||
remove(element: any, animationName: string): void {
|
||||
remove(element: any, animationName: string, targetPlayer: AnimationPlayer = null): void {
|
||||
const playersByAnimation = this._map.get(element);
|
||||
if (playersByAnimation) {
|
||||
const player = playersByAnimation[animationName];
|
||||
delete playersByAnimation[animationName];
|
||||
const index = this._allPlayers.indexOf(player);
|
||||
this._allPlayers.splice(index, 1);
|
||||
if (!targetPlayer || player === targetPlayer) {
|
||||
delete playersByAnimation[animationName];
|
||||
const index = this._allPlayers.indexOf(player);
|
||||
this._allPlayers.splice(index, 1);
|
||||
|
||||
if (Object.keys(playersByAnimation).length === 0) {
|
||||
this._map.delete(element);
|
||||
if (Object.keys(playersByAnimation).length === 0) {
|
||||
this._map.delete(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AnimationQueue} from './animation/animation_queue';
|
||||
import {ApplicationInitStatus} from './application_init';
|
||||
import {ApplicationRef, ApplicationRef_} from './application_ref';
|
||||
import {APP_ID_RANDOM_PROVIDER} from './application_tokens';
|
||||
@ -37,6 +38,7 @@ export function _keyValueDiffersFactory() {
|
||||
Compiler,
|
||||
APP_ID_RANDOM_PROVIDER,
|
||||
ViewUtils,
|
||||
AnimationQueue,
|
||||
{provide: IterableDiffers, useFactory: _iterableDiffersFactory},
|
||||
{provide: KeyValueDiffers, useFactory: _keyValueDiffersFactory},
|
||||
{provide: LOCALE_ID, useValue: 'en-US'},
|
||||
|
@ -7,14 +7,15 @@
|
||||
*/
|
||||
import {AnimationGroupPlayer} from '../animation/animation_group_player';
|
||||
import {AnimationPlayer} from '../animation/animation_player';
|
||||
import {queueAnimation as queueAnimationGlobally} from '../animation/animation_queue';
|
||||
import {AnimationQueue} from '../animation/animation_queue';
|
||||
import {AnimationSequencePlayer} from '../animation/animation_sequence_player';
|
||||
import {ViewAnimationMap} from '../animation/view_animation_map';
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
|
||||
export class AnimationViewContext {
|
||||
private _players = new ViewAnimationMap();
|
||||
|
||||
constructor(private _animationQueue: AnimationQueue) {}
|
||||
|
||||
onAllActiveAnimationsDone(callback: () => any): void {
|
||||
const activeAnimationPlayers = this._players.getAllPlayers();
|
||||
// we check for the length to avoid having GroupAnimationPlayer
|
||||
@ -27,21 +28,21 @@ export class AnimationViewContext {
|
||||
}
|
||||
|
||||
queueAnimation(element: any, animationName: string, player: AnimationPlayer): void {
|
||||
queueAnimationGlobally(player);
|
||||
this._animationQueue.enqueue(player);
|
||||
this._players.set(element, animationName, player);
|
||||
player.onDone(() => this._players.remove(element, animationName, player));
|
||||
}
|
||||
|
||||
getAnimationPlayers(element: any, animationName: string, removeAllAnimations: boolean = false):
|
||||
AnimationPlayer[] {
|
||||
getAnimationPlayers(element: any, animationName: string = null): AnimationPlayer[] {
|
||||
const players: AnimationPlayer[] = [];
|
||||
if (removeAllAnimations) {
|
||||
this._players.findAllPlayersByElement(element).forEach(
|
||||
player => { _recursePlayers(player, players); });
|
||||
} else {
|
||||
if (animationName) {
|
||||
const currentPlayer = this._players.find(element, animationName);
|
||||
if (currentPlayer) {
|
||||
_recursePlayers(currentPlayer, players);
|
||||
}
|
||||
} else {
|
||||
this._players.findAllPlayersByElement(element).forEach(
|
||||
player => _recursePlayers(player, players));
|
||||
}
|
||||
return players;
|
||||
}
|
||||
|
@ -82,6 +82,14 @@ export class Compiler {
|
||||
throw _throwError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes the CSS-style selectors that have been used in `ngContent` directives within
|
||||
* the template of the given component.
|
||||
* This is used by the `upgrade` library to compile the appropriate transclude content
|
||||
* in the Angular 1 wrapper component.
|
||||
*/
|
||||
getNgContentSelectors(component: Type<any>): string[] { throw _throwError(); }
|
||||
|
||||
/**
|
||||
* Clears all caches.
|
||||
*/
|
||||
|
@ -9,8 +9,6 @@
|
||||
import {Injector, THROW_IF_NOT_FOUND} from '../di/injector';
|
||||
import {AppView} from './view';
|
||||
|
||||
const _UNDEFINED = new Object();
|
||||
|
||||
export class ElementInjector extends Injector {
|
||||
constructor(private _view: AppView<any>, private _nodeIndex: number) { super(); }
|
||||
|
||||
|
@ -9,7 +9,6 @@
|
||||
import {ApplicationRef} from '../application_ref';
|
||||
import {ChangeDetectorRef, ChangeDetectorStatus} from '../change_detection/change_detection';
|
||||
import {Injector, THROW_IF_NOT_FOUND} from '../di/injector';
|
||||
import {ListWrapper} from '../facade/collection';
|
||||
import {isPresent} from '../facade/lang';
|
||||
import {WtfScopeFn, wtfCreateScope, wtfLeave} from '../profile/profile';
|
||||
import {DirectRenderer, RenderComponentType, RenderDebugInfo, Renderer} from '../render/api';
|
||||
@ -64,7 +63,7 @@ export abstract class AppView<T> {
|
||||
public viewUtils: ViewUtils, public parentView: AppView<any>, public parentIndex: number,
|
||||
public parentElement: any, public cdMode: ChangeDetectorStatus,
|
||||
public declaredViewContainer: ViewContainer = null) {
|
||||
this.ref = new ViewRef_(this);
|
||||
this.ref = new ViewRef_(this, viewUtils.animationQueue);
|
||||
if (type === ViewType.COMPONENT || type === ViewType.HOST) {
|
||||
this.renderer = viewUtils.renderComponent(componentType);
|
||||
} else {
|
||||
@ -75,7 +74,7 @@ export abstract class AppView<T> {
|
||||
|
||||
get animationContext(): AnimationViewContext {
|
||||
if (!this._animationContext) {
|
||||
this._animationContext = new AnimationViewContext();
|
||||
this._animationContext = new AnimationViewContext(this.viewUtils.animationQueue);
|
||||
}
|
||||
return this._animationContext;
|
||||
}
|
||||
@ -192,7 +191,8 @@ export abstract class AppView<T> {
|
||||
} else {
|
||||
this._renderDetach();
|
||||
}
|
||||
if (this.declaredViewContainer && this.declaredViewContainer !== this.viewContainer) {
|
||||
if (this.declaredViewContainer && this.declaredViewContainer !== this.viewContainer &&
|
||||
this.declaredViewContainer.projectedViews) {
|
||||
const projectedViews = this.declaredViewContainer.projectedViews;
|
||||
const index = projectedViews.indexOf(this);
|
||||
// perf: pop is faster than splice!
|
||||
@ -312,11 +312,16 @@ export abstract class AppView<T> {
|
||||
*/
|
||||
dirtyParentQueriesInternal(): void {}
|
||||
|
||||
internalDetectChanges(throwOnChange: boolean): void {
|
||||
if (this.cdMode !== ChangeDetectorStatus.Detached) {
|
||||
this.detectChanges(throwOnChange);
|
||||
}
|
||||
}
|
||||
|
||||
detectChanges(throwOnChange: boolean): void {
|
||||
const s = _scope_check(this.clazz);
|
||||
if (this.cdMode === ChangeDetectorStatus.Checked ||
|
||||
this.cdMode === ChangeDetectorStatus.Errored ||
|
||||
this.cdMode === ChangeDetectorStatus.Detached)
|
||||
this.cdMode === ChangeDetectorStatus.Errored)
|
||||
return;
|
||||
if (this.cdMode === ChangeDetectorStatus.Destroyed) {
|
||||
this.throwDestroyedError('detectChanges');
|
||||
|
@ -6,14 +6,12 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {triggerQueuedAnimations} from '../animation/animation_queue';
|
||||
import {AnimationQueue} from '../animation/animation_queue';
|
||||
import {ChangeDetectorRef} from '../change_detection/change_detector_ref';
|
||||
import {ChangeDetectorStatus} from '../change_detection/constants';
|
||||
import {unimplemented} from '../facade/errors';
|
||||
|
||||
import {AppView} from './view';
|
||||
|
||||
|
||||
/**
|
||||
* @stable
|
||||
*/
|
||||
@ -92,7 +90,7 @@ export class ViewRef_<C> implements EmbeddedViewRef<C>, ChangeDetectorRef {
|
||||
/** @internal */
|
||||
_originalMode: ChangeDetectorStatus;
|
||||
|
||||
constructor(private _view: AppView<C>) {
|
||||
constructor(private _view: AppView<C>, public animationQueue: AnimationQueue) {
|
||||
this._view = _view;
|
||||
this._originalMode = this._view.cdMode;
|
||||
}
|
||||
@ -109,7 +107,7 @@ export class ViewRef_<C> implements EmbeddedViewRef<C>, ChangeDetectorRef {
|
||||
detach(): void { this._view.cdMode = ChangeDetectorStatus.Detached; }
|
||||
detectChanges(): void {
|
||||
this._view.detectChanges(false);
|
||||
triggerQueuedAnimations();
|
||||
this.animationQueue.flush();
|
||||
}
|
||||
checkNoChanges(): void { this._view.detectChanges(true); }
|
||||
reattach(): void {
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AnimationQueue} from '../animation/animation_queue';
|
||||
import {SimpleChange, devModeEqual} from '../change_detection/change_detection';
|
||||
import {UNINITIALIZED} from '../change_detection/change_detection_util';
|
||||
import {Inject, Injectable} from '../di';
|
||||
@ -14,17 +15,21 @@ 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';
|
||||
import {ViewContainer} from './view_container';
|
||||
|
||||
@Injectable()
|
||||
export class ViewUtils {
|
||||
sanitizer: Sanitizer;
|
||||
private _nextCompTypeId: number = 0;
|
||||
|
||||
constructor(private _renderer: RootRenderer, sanitizer: Sanitizer) { this.sanitizer = sanitizer; }
|
||||
constructor(
|
||||
private _renderer: RootRenderer, sanitizer: Sanitizer,
|
||||
public animationQueue: AnimationQueue) {
|
||||
this.sanitizer = sanitizer;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
renderComponent(renderComponentType: RenderComponentType): Renderer {
|
||||
|
@ -29,7 +29,7 @@ export enum LifecycleHooks {
|
||||
*/
|
||||
export interface SimpleChanges { [propName: string]: SimpleChange; }
|
||||
|
||||
export var LIFECYCLE_HOOKS_VALUES = [
|
||||
export const LIFECYCLE_HOOKS_VALUES = [
|
||||
LifecycleHooks.OnInit, LifecycleHooks.OnDestroy, LifecycleHooks.DoCheck, LifecycleHooks.OnChanges,
|
||||
LifecycleHooks.AfterContentInit, LifecycleHooks.AfterContentChecked, LifecycleHooks.AfterViewInit,
|
||||
LifecycleHooks.AfterViewChecked
|
||||
@ -92,7 +92,7 @@ export abstract class OnInit { abstract ngOnInit(): void; }
|
||||
export abstract class DoCheck { abstract ngDoCheck(): void; }
|
||||
|
||||
/**
|
||||
* @whatItDoes Lifecycle hook that is called when a directive or pipe is destroyed.
|
||||
* @whatItDoes Lifecycle hook that is called when a directive, pipe or service is destroyed.
|
||||
* @howToUse
|
||||
* {@example core/ts/metadata/lifecycle_hooks_spec.ts region='OnDestroy'}
|
||||
*
|
||||
|
@ -156,6 +156,60 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
expect(kf[1]).toEqual([1, {'backgroundColor': 'blue'}]);
|
||||
}));
|
||||
|
||||
it('should throw an error when a provided offset for an animation step if an offset value is greater than 1',
|
||||
fakeAsync(() => {
|
||||
TestBed.overrideComponent(DummyIfCmp, {
|
||||
set: {
|
||||
template: `
|
||||
<div *ngIf="exp" [@tooBig]="exp"></div>
|
||||
`,
|
||||
animations: [trigger(
|
||||
'tooBig',
|
||||
[transition(
|
||||
'* => *', [animate('444ms', style({'opacity': '1', offset: 1.1}))])])]
|
||||
}
|
||||
});
|
||||
|
||||
let message = '';
|
||||
try {
|
||||
const fixture = TestBed.createComponent(DummyIfCmp);
|
||||
} catch (e) {
|
||||
message = e.message;
|
||||
}
|
||||
|
||||
const lines = message.split(/\n+/);
|
||||
expect(lines[1]).toMatch(
|
||||
/Unable to parse the animation sequence for "tooBig" on the DummyIfCmp component due to the following errors:/);
|
||||
expect(lines[2]).toMatch(/Offset values for animations must be between 0 and 1/);
|
||||
}));
|
||||
|
||||
it('should throw an error when a provided offset for an animation step if an offset value is less than 0',
|
||||
fakeAsync(() => {
|
||||
TestBed.overrideComponent(DummyIfCmp, {
|
||||
set: {
|
||||
template: `
|
||||
<div *ngIf="exp" [@tooSmall]="exp"></div>
|
||||
`,
|
||||
animations: [trigger(
|
||||
'tooSmall',
|
||||
[transition(
|
||||
'* => *', [animate('444ms', style({'opacity': '0', offset: -1}))])])]
|
||||
}
|
||||
});
|
||||
|
||||
let message = '';
|
||||
try {
|
||||
const fixture = TestBed.createComponent(DummyIfCmp);
|
||||
} catch (e) {
|
||||
message = e.message;
|
||||
}
|
||||
|
||||
const lines = message.split(/\n+/);
|
||||
expect(lines[1]).toMatch(
|
||||
/Unable to parse the animation sequence for "tooSmall" on the DummyIfCmp component due to the following errors:/);
|
||||
expect(lines[2]).toMatch(/Offset values for animations must be between 0 and 1/);
|
||||
}));
|
||||
|
||||
describe('animation aliases', () => {
|
||||
it('should animate the ":enter" animation alias as "void => *"', fakeAsync(() => {
|
||||
TestBed.overrideComponent(DummyIfCmp, {
|
||||
@ -1493,6 +1547,36 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
expect(message).toMatch(/Couldn't find an animation entry for "something"/);
|
||||
});
|
||||
|
||||
it('should throw an error if an animation output is referenced that is not bound to as a property on the same element',
|
||||
() => {
|
||||
TestBed.overrideComponent(DummyLoadingCmp, {
|
||||
set: {
|
||||
template: `
|
||||
<if-cmp (@trigger.done)="callback($event)"></if-cmp>
|
||||
`
|
||||
}
|
||||
});
|
||||
TestBed.overrideComponent(DummyIfCmp, {
|
||||
set: {
|
||||
template: `
|
||||
<div [@trigger]="exp"></div>
|
||||
`,
|
||||
animations: [trigger('trigger', [transition('one => two', [animate(1000)])])]
|
||||
}
|
||||
});
|
||||
|
||||
let message = '';
|
||||
try {
|
||||
const fixture = TestBed.createComponent(DummyIfCmp);
|
||||
fixture.detectChanges();
|
||||
} catch (e) {
|
||||
message = e.message;
|
||||
}
|
||||
|
||||
expect(message).toMatch(
|
||||
/Unable to listen on \(@trigger.done\) because the animation trigger \[@trigger\] isn't being used on the same element/);
|
||||
});
|
||||
|
||||
it('should throw an error if an animation output is referenced that is not bound to as a property on the same element',
|
||||
() => {
|
||||
TestBed.overrideComponent(DummyIfCmp, {
|
||||
@ -2121,6 +2205,43 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
expect(kf[1]).toEqual([1, {'height': '333px', 'opacity': AUTO_STYLE, 'width': '200px'}]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not use the previous animation\'s styling if the previous animation has already finished',
|
||||
fakeAsync(() => {
|
||||
TestBed.overrideComponent(DummyIfCmp, {
|
||||
set: {
|
||||
template: `
|
||||
<div [@myAnimation]="exp"></div>
|
||||
`,
|
||||
animations: [trigger(
|
||||
'myAnimation',
|
||||
[
|
||||
state('a', style({color: 'red'})), state('b', style({color: 'red'})),
|
||||
transition('* => *', animate(1000))
|
||||
])]
|
||||
}
|
||||
});
|
||||
|
||||
const driver = TestBed.get(AnimationDriver) as MockAnimationDriver;
|
||||
const fixture = TestBed.createComponent(DummyIfCmp);
|
||||
const cmp = fixture.componentInstance;
|
||||
|
||||
cmp.exp = 'a';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
const animation1 = driver.log.shift();
|
||||
expect(animation1['previousStyles']).toEqual({});
|
||||
|
||||
animation1['player'].finish();
|
||||
|
||||
cmp.exp = 'b';
|
||||
fixture.detectChanges();
|
||||
flushMicrotasks();
|
||||
|
||||
const animation2 = driver.log.shift();
|
||||
expect(animation2['previousStyles']).toEqual({});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('full animation integration tests', () => {
|
||||
@ -2140,6 +2261,23 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
];
|
||||
});
|
||||
|
||||
function assertStatus(value: string) {
|
||||
const text = getDOM().getText(el);
|
||||
const regexp = new RegExp(`Animation Status: ${value}`);
|
||||
expect(text).toMatch(regexp);
|
||||
}
|
||||
|
||||
function assertTime(value: number) {
|
||||
const text = getDOM().getText(el);
|
||||
const regexp = new RegExp(`Animation Time: ${value}`);
|
||||
expect(text).toMatch(regexp);
|
||||
}
|
||||
|
||||
function finishAnimation(player: WebAnimationsPlayer, cb: () => any) {
|
||||
getDOM().dispatchEvent(player.domPlayer, getDOM().createEvent('finish'));
|
||||
Promise.resolve(null).then(cb);
|
||||
}
|
||||
|
||||
afterEach(() => { destroyPlatform(); });
|
||||
|
||||
it('should automatically run change detection when the animation done callback code updates any bindings',
|
||||
@ -2150,24 +2288,82 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
appRef.components.find(cmp => cmp.componentType === AnimationAppCmp).instance;
|
||||
const driver: ExtendedWebAnimationsDriver = ref.injector.get(AnimationDriver);
|
||||
const zone: NgZone = ref.injector.get(NgZone);
|
||||
let text = '';
|
||||
zone.run(() => {
|
||||
text = getDOM().getText(el);
|
||||
expect(text).toMatch(/Animation Status: pending/);
|
||||
expect(text).toMatch(/Animation Time: 0/);
|
||||
assertStatus('pending');
|
||||
assertTime(0);
|
||||
appCmp.animationStatus = 'on';
|
||||
setTimeout(() => {
|
||||
text = getDOM().getText(el);
|
||||
expect(text).toMatch(/Animation Status: started/);
|
||||
expect(text).toMatch(/Animation Time: 555/);
|
||||
const player = driver.players.pop().domPlayer;
|
||||
getDOM().dispatchEvent(player, getDOM().createEvent('finish'));
|
||||
zone.runOutsideAngular(() => {
|
||||
setTimeout(() => {
|
||||
text = getDOM().getText(el);
|
||||
expect(text).toMatch(/Animation Status: done/);
|
||||
expect(text).toMatch(/Animation Time: 555/);
|
||||
asyncDone();
|
||||
assertStatus('started');
|
||||
assertTime(555);
|
||||
const player = driver.players.pop();
|
||||
finishAnimation(player, () => {
|
||||
assertStatus('done');
|
||||
assertTime(555);
|
||||
asyncDone();
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not run change detection for an animation that contains multiple steps until a callback is fired',
|
||||
(asyncDone: Function) => {
|
||||
bootstrap(AnimationAppCmp, testProviders).then(ref => {
|
||||
const appRef = <ApplicationRef>ref.injector.get(ApplicationRef);
|
||||
const appCmpDetails: any =
|
||||
appRef.components.find(cmp => cmp.componentType === AnimationAppCmp);
|
||||
const appCD = appCmpDetails.changeDetectorRef;
|
||||
const appCmp: AnimationAppCmp = appCmpDetails.instance;
|
||||
const driver: ExtendedWebAnimationsDriver = ref.injector.get(AnimationDriver);
|
||||
const zone: NgZone = ref.injector.get(NgZone);
|
||||
|
||||
let player: WebAnimationsPlayer;
|
||||
let onDoneCalls: string[] = [];
|
||||
function onDoneFn(value: string) {
|
||||
return () => {
|
||||
NgZone.assertNotInAngularZone();
|
||||
onDoneCalls.push(value);
|
||||
appCmp.status = value;
|
||||
};
|
||||
};
|
||||
|
||||
zone.run(() => {
|
||||
assertStatus('pending');
|
||||
appCmp.animationWithSteps = 'on';
|
||||
|
||||
setTimeout(() => {
|
||||
expect(driver.players.length).toEqual(3);
|
||||
assertStatus('started');
|
||||
|
||||
zone.runOutsideAngular(() => {
|
||||
setTimeout(() => {
|
||||
assertStatus('started');
|
||||
player = driver.players.shift();
|
||||
player.onDone(onDoneFn('1'));
|
||||
|
||||
// step 1 => 2
|
||||
finishAnimation(player, () => {
|
||||
assertStatus('started');
|
||||
player = driver.players.shift();
|
||||
player.onDone(onDoneFn('2'));
|
||||
|
||||
// step 2 => 3
|
||||
finishAnimation(player, () => {
|
||||
assertStatus('started');
|
||||
player = driver.players.shift();
|
||||
player.onDone(onDoneFn('3'));
|
||||
|
||||
// step 3 => done
|
||||
finishAnimation(player, () => {
|
||||
assertStatus('done');
|
||||
asyncDone();
|
||||
});
|
||||
});
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
@ -2258,7 +2454,17 @@ class _NaiveElementSchema extends DomElementSchemaRegistry {
|
||||
|
||||
@Component({
|
||||
selector: 'animation-app',
|
||||
animations: [trigger('animationStatus', [transition('off => on', animate(555))])],
|
||||
animations: [
|
||||
trigger('animationStatus', [transition('off => on', animate(555))]),
|
||||
trigger(
|
||||
'animationWithSteps',
|
||||
[transition(
|
||||
'* => on',
|
||||
[
|
||||
style({height: '0px'}), animate(100, style({height: '100px'})),
|
||||
animate(100, style({height: '200px'})), animate(100, style({height: '300px'}))
|
||||
])])
|
||||
],
|
||||
template: `
|
||||
Animation Time: {{ time }}
|
||||
Animation Status: {{ status }}
|
||||
@ -2272,7 +2478,7 @@ class AnimationAppCmp {
|
||||
animationStatus = 'off';
|
||||
|
||||
@HostListener('@animationStatus.start', ['$event'])
|
||||
onStart(event: AnimationTransitionEvent) {
|
||||
onAnimationStartDone(event: AnimationTransitionEvent) {
|
||||
if (event.toState == 'on') {
|
||||
this.time = event.totalTime;
|
||||
this.status = 'started';
|
||||
@ -2280,7 +2486,26 @@ class AnimationAppCmp {
|
||||
}
|
||||
|
||||
@HostListener('@animationStatus.done', ['$event'])
|
||||
onDone(event: AnimationTransitionEvent) {
|
||||
onAnimationStatusDone(event: AnimationTransitionEvent) {
|
||||
if (event.toState == 'on') {
|
||||
this.time = event.totalTime;
|
||||
this.status = 'done';
|
||||
}
|
||||
}
|
||||
|
||||
@HostBinding('@animationWithSteps')
|
||||
animationWithSteps = 'off';
|
||||
|
||||
@HostListener('@animationWithSteps.start', ['$event'])
|
||||
onAnimationWithStepsStart(event: AnimationTransitionEvent) {
|
||||
if (event.toState == 'on') {
|
||||
this.time = event.totalTime;
|
||||
this.status = 'started';
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('@animationWithSteps.done', ['$event'])
|
||||
onAnimationWithStepsDone(event: AnimationTransitionEvent) {
|
||||
if (event.toState == 'on') {
|
||||
this.time = event.totalTime;
|
||||
this.status = 'done';
|
||||
|
143
modules/@angular/core/test/animation/animation_queue_spec.ts
Normal file
143
modules/@angular/core/test/animation/animation_queue_spec.ts
Normal file
@ -0,0 +1,143 @@
|
||||
/**
|
||||
* @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 {AnimationQueue} from '@angular/core/src/animation/animation_queue';
|
||||
|
||||
import {NgZone} from '../../src/zone/ng_zone';
|
||||
import {TestBed, fakeAsync, flushMicrotasks} from '../../testing';
|
||||
import {MockAnimationPlayer} from '../../testing/mock_animation_player';
|
||||
import {beforeEach, describe, expect, it} from '../../testing/testing_internal';
|
||||
|
||||
export function main() {
|
||||
describe('AnimationQueue', function() {
|
||||
beforeEach(() => { TestBed.configureTestingModule({declarations: [], imports: []}); });
|
||||
|
||||
it('should queue animation players and run when flushed, but only as the next scheduled microtask',
|
||||
fakeAsync(() => {
|
||||
const zone = TestBed.get(NgZone);
|
||||
const queue = new AnimationQueue(zone);
|
||||
|
||||
const log: string[] = [];
|
||||
const p1 = new MockAnimationPlayer();
|
||||
const p2 = new MockAnimationPlayer();
|
||||
const p3 = new MockAnimationPlayer();
|
||||
|
||||
p1.onStart(() => log.push('1'));
|
||||
p2.onStart(() => log.push('2'));
|
||||
p3.onStart(() => log.push('3'));
|
||||
|
||||
queue.enqueue(p1);
|
||||
queue.enqueue(p2);
|
||||
queue.enqueue(p3);
|
||||
expect(log).toEqual([]);
|
||||
|
||||
queue.flush();
|
||||
expect(log).toEqual([]);
|
||||
|
||||
flushMicrotasks();
|
||||
expect(log).toEqual(['1', '2', '3']);
|
||||
}));
|
||||
|
||||
it('should always run each of the animation players outside of the angular zone on start',
|
||||
fakeAsync(() => {
|
||||
const zone = TestBed.get(NgZone);
|
||||
const queue = new AnimationQueue(zone);
|
||||
|
||||
const player = new MockAnimationPlayer();
|
||||
let eventHasRun = false;
|
||||
player.onStart(() => {
|
||||
NgZone.assertNotInAngularZone();
|
||||
eventHasRun = true;
|
||||
});
|
||||
|
||||
zone.run(() => {
|
||||
NgZone.assertInAngularZone();
|
||||
queue.enqueue(player);
|
||||
queue.flush();
|
||||
flushMicrotasks();
|
||||
});
|
||||
|
||||
expect(eventHasRun).toBe(true);
|
||||
}));
|
||||
|
||||
it('should always run each of the animation players outside of the angular zone on done',
|
||||
fakeAsync(() => {
|
||||
const zone = TestBed.get(NgZone);
|
||||
const queue = new AnimationQueue(zone);
|
||||
|
||||
const player = new MockAnimationPlayer();
|
||||
let eventHasRun = false;
|
||||
player.onDone(() => {
|
||||
NgZone.assertNotInAngularZone();
|
||||
eventHasRun = true;
|
||||
});
|
||||
|
||||
zone.run(() => {
|
||||
NgZone.assertInAngularZone();
|
||||
queue.enqueue(player);
|
||||
queue.flush();
|
||||
flushMicrotasks();
|
||||
});
|
||||
|
||||
expect(eventHasRun).toBe(false);
|
||||
player.finish();
|
||||
expect(eventHasRun).toBe(true);
|
||||
}));
|
||||
|
||||
it('should not run animations again incase an animation midway fails', fakeAsync(() => {
|
||||
const zone = TestBed.get(NgZone);
|
||||
const queue = new AnimationQueue(zone);
|
||||
|
||||
const log: string[] = [];
|
||||
const p1 = new PlayerThatFails(false);
|
||||
const p2 = new PlayerThatFails(true);
|
||||
const p3 = new PlayerThatFails(false);
|
||||
|
||||
p1.onStart(() => log.push('1'));
|
||||
p2.onStart(() => log.push('2'));
|
||||
p3.onStart(() => log.push('3'));
|
||||
|
||||
queue.enqueue(p1);
|
||||
queue.enqueue(p2);
|
||||
queue.enqueue(p3);
|
||||
|
||||
queue.flush();
|
||||
|
||||
expect(() => flushMicrotasks()).toThrowError();
|
||||
|
||||
expect(log).toEqual(['1', '2']);
|
||||
|
||||
// let's reset this so that it gets triggered again
|
||||
p2.reset();
|
||||
p2.onStart(() => log.push('2'));
|
||||
|
||||
queue.flush();
|
||||
|
||||
expect(() => flushMicrotasks()).not.toThrowError();
|
||||
|
||||
expect(log).toEqual(['1', '2', '3']);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
class PlayerThatFails extends MockAnimationPlayer {
|
||||
private _animationStarted = false;
|
||||
|
||||
constructor(public doFail: boolean) { super(); }
|
||||
|
||||
play() {
|
||||
super.play();
|
||||
this._animationStarted = true;
|
||||
if (this.doFail) {
|
||||
throw new Error('Oh nooooo');
|
||||
}
|
||||
}
|
||||
|
||||
reset() { this._animationStarted = false; }
|
||||
|
||||
hasStarted() { return this._animationStarted; }
|
||||
}
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, CompilerFactory, Component, NgModule, PlatformRef, Type, ViewChild, ViewContainerRef} from '@angular/core';
|
||||
import {APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, CompilerFactory, Component, NgModule, PlatformRef, TemplateRef, Type, ViewChild, ViewContainerRef} from '@angular/core';
|
||||
import {ApplicationRef, ApplicationRef_} from '@angular/core/src/application_ref';
|
||||
import {ErrorHandler} from '@angular/core/src/error_handler';
|
||||
import {ComponentRef} from '@angular/core/src/linker/component_factory';
|
||||
@ -275,9 +275,15 @@ export function main() {
|
||||
vc: ViewContainerRef;
|
||||
}
|
||||
|
||||
@Component({template: '<template #t>Dynamic content</template>'})
|
||||
class EmbeddedViewComp {
|
||||
@ViewChild(TemplateRef)
|
||||
tplRef: TemplateRef<Object>;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [MyComp, ContainerComp],
|
||||
declarations: [MyComp, ContainerComp, EmbeddedViewComp],
|
||||
providers: [{provide: ComponentFixtureNoNgZone, useValue: true}]
|
||||
});
|
||||
});
|
||||
@ -321,6 +327,17 @@ export function main() {
|
||||
expect(appRef.viewCount).toBe(0);
|
||||
});
|
||||
|
||||
it('should detach attached embedded views if they are destroyed', () => {
|
||||
const comp = TestBed.createComponent(EmbeddedViewComp);
|
||||
const appRef: ApplicationRef = TestBed.get(ApplicationRef);
|
||||
const embeddedViewRef = comp.componentInstance.tplRef.createEmbeddedView({});
|
||||
|
||||
appRef.attachView(embeddedViewRef);
|
||||
embeddedViewRef.destroy();
|
||||
|
||||
expect(appRef.viewCount).toBe(0);
|
||||
});
|
||||
|
||||
it('should not allow to attach a view to both, a view container and the ApplicationRef',
|
||||
() => {
|
||||
const comp = TestBed.createComponent(MyComp);
|
||||
|
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @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 {el} from '@angular/platform-browser/testing/browser_util';
|
||||
|
||||
import {NoOpAnimationPlayer} from '../../src/animation/animation_player';
|
||||
import {AnimationQueue} from '../../src/animation/animation_queue';
|
||||
import {AnimationViewContext} from '../../src/linker/animation_view_context';
|
||||
import {TestBed, fakeAsync, flushMicrotasks} from '../../testing';
|
||||
import {describe, expect, iit, it} from '../../testing/testing_internal';
|
||||
|
||||
export function main() {
|
||||
describe('AnimationViewContext', function() {
|
||||
let elm: any;
|
||||
beforeEach(() => { elm = el('<div></div>'); });
|
||||
|
||||
function getPlayers(vc: any) { return vc.getAnimationPlayers(elm); }
|
||||
|
||||
it('should remove the player from the registry once the animation is complete',
|
||||
fakeAsync(() => {
|
||||
const player = new NoOpAnimationPlayer();
|
||||
const animationQueue = TestBed.get(AnimationQueue) as AnimationQueue;
|
||||
const vc = new AnimationViewContext(animationQueue);
|
||||
|
||||
expect(getPlayers(vc).length).toEqual(0);
|
||||
vc.queueAnimation(elm, 'someAnimation', player);
|
||||
expect(getPlayers(vc).length).toEqual(1);
|
||||
player.finish();
|
||||
expect(getPlayers(vc).length).toEqual(0);
|
||||
}));
|
||||
|
||||
it('should not remove a follow-up player from the registry if another player is queued',
|
||||
fakeAsync(() => {
|
||||
const player1 = new NoOpAnimationPlayer();
|
||||
const player2 = new NoOpAnimationPlayer();
|
||||
const animationQueue = TestBed.get(AnimationQueue) as AnimationQueue;
|
||||
const vc = new AnimationViewContext(animationQueue);
|
||||
|
||||
vc.queueAnimation(elm, 'someAnimation', player1);
|
||||
expect(getPlayers(vc).length).toBe(1);
|
||||
expect(getPlayers(vc)[0]).toBe(player1);
|
||||
|
||||
vc.queueAnimation(elm, 'someAnimation', player2);
|
||||
expect(getPlayers(vc).length).toBe(1);
|
||||
expect(getPlayers(vc)[0]).toBe(player2);
|
||||
|
||||
player1.finish();
|
||||
|
||||
expect(getPlayers(vc).length).toBe(1);
|
||||
expect(getPlayers(vc)[0]).toBe(player2);
|
||||
|
||||
player2.finish();
|
||||
|
||||
expect(getPlayers(vc).length).toBe(0);
|
||||
}));
|
||||
});
|
||||
}
|
@ -82,6 +82,7 @@ export function main() {
|
||||
AnotherComponent,
|
||||
TestLocals,
|
||||
CompWithRef,
|
||||
WrapCompWithRef,
|
||||
EmitterDirective,
|
||||
PushComp,
|
||||
OnDestroyDirective,
|
||||
@ -1133,6 +1134,23 @@ export function main() {
|
||||
expect(renderLog.log).toEqual([]);
|
||||
}));
|
||||
|
||||
it('Detached view can be checked locally', fakeAsync(() => {
|
||||
const ctx = createCompFixture('<wrap-comp-with-ref></wrap-comp-with-ref>');
|
||||
const cmp: CompWithRef = queryDirs(ctx.debugElement, CompWithRef)[0];
|
||||
cmp.value = 'hello';
|
||||
cmp.changeDetectorRef.detach();
|
||||
expect(renderLog.log).toEqual([]);
|
||||
|
||||
ctx.detectChanges();
|
||||
|
||||
expect(renderLog.log).toEqual([]);
|
||||
|
||||
cmp.changeDetectorRef.detectChanges();
|
||||
|
||||
expect(renderLog.log).toEqual(['{{hello}}']);
|
||||
}));
|
||||
|
||||
|
||||
it('Reattaches', fakeAsync(() => {
|
||||
const ctx = createCompFixture('<comp-with-ref></comp-with-ref>');
|
||||
const cmp: CompWithRef = queryDirs(ctx.debugElement, CompWithRef)[0];
|
||||
@ -1346,6 +1364,11 @@ class CompWithRef {
|
||||
noop() {}
|
||||
}
|
||||
|
||||
@Component({selector: 'wrap-comp-with-ref', template: '<comp-with-ref></comp-with-ref>'})
|
||||
class WrapCompWithRef {
|
||||
constructor(public changeDetectorRef: ChangeDetectorRef) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'push-cmp',
|
||||
template: '<div (event)="noop()" emitterDirective></div>{{value}}{{renderIncrement}}',
|
||||
|
@ -8,7 +8,6 @@
|
||||
|
||||
import {Component, Injector, OpaqueToken, Pipe, PipeTransform, Provider} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {beforeEach, describe, it} from '@angular/core/testing/testing_internal';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
@ -139,6 +138,15 @@ function declareTests({useJit}: {useJit: boolean}) {
|
||||
expect(injector.get(token1)).toEqual(tokenValue1);
|
||||
expect(injector.get(token2)).toEqual(tokenValue2);
|
||||
});
|
||||
|
||||
it('should support providers that have a `name` property with a number value', () => {
|
||||
class TestClass {
|
||||
constructor(public name: number) {}
|
||||
}
|
||||
const data = [new TestClass(1), new TestClass(2)];
|
||||
const injector = createInjector([{provide: 'someToken', useValue: data}]);
|
||||
expect(injector.get('someToken')).toEqual(data);
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow logging a previous elements class binding via interpolation', () => {
|
||||
|
@ -6,7 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CompilerOptions, Component, Directive, Injector, ModuleWithComponentFactories, NgModule, NgModuleRef, NgZone, OpaqueToken, Pipe, PlatformRef, Provider, SchemaMetadata, Type} from '@angular/core';
|
||||
import {CompilerOptions, Component, Directive, Injector, ModuleWithComponentFactories, NgModule, NgModuleRef, NgZone, OpaqueToken, Pipe, PlatformRef, Provider, ReflectiveInjector, SchemaMetadata, Type} from '@angular/core';
|
||||
|
||||
import {AsyncTestCompleter} from './async_test_completer';
|
||||
import {ComponentFixture} from './component_fixture';
|
||||
import {stringify} from './facade/lang';
|
||||
@ -134,6 +135,11 @@ export class TestBed implements Injector {
|
||||
return TestBed;
|
||||
}
|
||||
|
||||
static overrideTemplate(component: Type<any>, template: string): typeof TestBed {
|
||||
getTestBed().overrideComponent(component, {set: {template, templateUrl: null}});
|
||||
return TestBed;
|
||||
}
|
||||
|
||||
static get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND) {
|
||||
return getTestBed().get(token, notFoundValue);
|
||||
}
|
||||
@ -268,8 +274,10 @@ export class TestBed implements Injector {
|
||||
}
|
||||
}
|
||||
}
|
||||
this._moduleRef =
|
||||
this._moduleWithComponentFactories.ngModuleFactory.create(this.platform.injector);
|
||||
const ngZone = new NgZone({enableLongStackTrace: true});
|
||||
const ngZoneInjector = ReflectiveInjector.resolveAndCreate(
|
||||
[{provide: NgZone, useValue: ngZone}], this.platform.injector);
|
||||
this._moduleRef = this._moduleWithComponentFactories.ngModuleFactory.create(ngZoneInjector);
|
||||
this._instantiated = true;
|
||||
}
|
||||
|
||||
|
@ -3,3 +3,32 @@
|
||||
This folder contains small example apps that get in-lined into our API docs.
|
||||
Each example contains tests for application behavior (as opposed to testing Angular's
|
||||
behavior) just like an Angular application developer would write.
|
||||
|
||||
# Running the examples
|
||||
|
||||
```
|
||||
# # execute the following command only when framework code changes
|
||||
./build.sh
|
||||
|
||||
# run when test change
|
||||
./modules/@angular/examples/build.sh
|
||||
|
||||
# start server
|
||||
$(npm bin)/gulp serve-examples
|
||||
```
|
||||
|
||||
navigate to [http://localhost:8001](http://localhost:8001)
|
||||
|
||||
# Running the tests
|
||||
|
||||
```
|
||||
# run only when framework code changes
|
||||
./build.sh
|
||||
|
||||
# run to compile tests and run them
|
||||
./modules/@angular/examples/test.sh
|
||||
```
|
||||
|
||||
NOTE: sometimes the http server does not exit properly and it retains the `8001` port.
|
||||
in such a case you can use `lsof -i:8001` to see which process it is and then use `kill`
|
||||
to remove it. (Or in single command: `lsof -i:8001 -t | xargs kill`)
|
@ -1,11 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="text/javascript" src="bootstrap.js"></script>
|
||||
<script type="text/javascript">
|
||||
System.import('main-dynamic').catch(console.error.bind(console));
|
||||
</script>
|
||||
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" />
|
||||
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" />
|
||||
<script type="text/javascript" src="bootstrap.js"></script>
|
||||
<script type="text/javascript">
|
||||
System.import('main-dynamic').catch(console.error.bind(console));
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* @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 {browser, by, element, protractor} from 'protractor';
|
||||
|
||||
import {verifyNoBrowserErrors} from '../../../../_common/e2e_util';
|
||||
|
||||
|
||||
function waitForElement(selector: string) {
|
||||
const EC = (<any>protractor).ExpectedConditions;
|
||||
// Waits for the element with id 'abc' to be present on the dom.
|
||||
browser.wait(EC.presenceOf($(selector)), 20000);
|
||||
}
|
||||
|
||||
describe('Location', () => {
|
||||
afterEach(verifyNoBrowserErrors);
|
||||
|
||||
it('should verify paths', () => {
|
||||
browser.get('/common/location/ts/#/bar/baz');
|
||||
waitForElement('hash-location');
|
||||
expect(element.all(by.css('path-location code')).get(0).getText())
|
||||
.toEqual('/common/location/ts');
|
||||
expect(element.all(by.css('hash-location code')).get(0).getText()).toEqual('/bar/baz');
|
||||
});
|
||||
});
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
// #docregion LocationComponent
|
||||
import {HashLocationStrategy, Location, LocationStrategy} from '@angular/common';
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'hash-location',
|
||||
providers: [Location, {provide: LocationStrategy, useClass: HashLocationStrategy}],
|
||||
template: `
|
||||
<h1>HashLocationStrategy</h1>
|
||||
Current URL is: <code>{{location.path()}}</code><br>
|
||||
Normalize: <code>/foo/bar/</code> is: <code>{{location.normalize('foo/bar')}}</code><br>
|
||||
`
|
||||
})
|
||||
export class HashLocationComponent {
|
||||
location: Location;
|
||||
constructor(location: Location) { this.location = location; }
|
||||
}
|
||||
// #enddocregion
|
30
modules/@angular/examples/common/location/ts/module.ts
Normal file
30
modules/@angular/examples/common/location/ts/module.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* @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 {APP_BASE_HREF} from '@angular/common';
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
|
||||
import {HashLocationComponent} from './hash_location_component';
|
||||
import {PathLocationComponent} from './path_location_component';
|
||||
|
||||
@Component({
|
||||
selector: 'example-app',
|
||||
template: `<hash-location></hash-location><path-location></path-location>`
|
||||
})
|
||||
export class ExampleAppComponent {
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
declarations: [ExampleAppComponent, PathLocationComponent, HashLocationComponent],
|
||||
providers: [{provide: APP_BASE_HREF, useValue: '/'}],
|
||||
imports: [BrowserModule],
|
||||
bootstrap: [ExampleAppComponent]
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
// #docregion LocationComponent
|
||||
import {Location, LocationStrategy, PathLocationStrategy} from '@angular/common';
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'path-location',
|
||||
providers: [Location, {provide: LocationStrategy, useClass: PathLocationStrategy}],
|
||||
template: `
|
||||
<h1>PathLocationStrategy</h1>
|
||||
Current URL is: <code>{{location.path()}}</code><br>
|
||||
Normalize: <code>/foo/bar/</code> is: <code>{{location.normalize('foo/bar')}}</code><br>
|
||||
`
|
||||
})
|
||||
export class PathLocationComponent {
|
||||
location: Location;
|
||||
constructor(location: Location) { this.location = location; }
|
||||
}
|
||||
// #enddocregion
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user