refactor: move angular source to /packages rather than modules/@angular

This commit is contained in:
Jason Aden
2017-03-02 10:48:42 -08:00
parent 5ad5301a3e
commit 3e51a19983
1051 changed files with 18 additions and 18 deletions

View File

@ -0,0 +1,20 @@
/**
* @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
*/
/**
* @module
* @description
* Entry point for all public APIs of the common package.
*/
export * from './location/index';
export {NgLocaleLocalization, NgLocalization} from './localization';
export {CommonModule} from './common_module';
export {NgClass, NgFor, NgForOf, NgIf, NgPlural, NgPluralCase, NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault, NgTemplateOutlet, NgComponentOutlet} from './directives/index';
export {AsyncPipe, DatePipe, I18nPluralPipe, I18nSelectPipe, JsonPipe, LowerCasePipe, CurrencyPipe, DecimalPipe, PercentPipe, SlicePipe, UpperCasePipe, TitleCasePipe} from './pipes/index';
export {PLATFORM_BROWSER_ID as ɵPLATFORM_BROWSER_ID, PLATFORM_SERVER_ID as ɵPLATFORM_SERVER_ID, PLATFORM_WORKER_APP_ID as ɵPLATFORM_WORKER_APP_ID, PLATFORM_WORKER_UI_ID as ɵPLATFORM_WORKER_UI_ID, isPlatformBrowser, isPlatformServer, isPlatformWorkerApp, isPlatformWorkerUi} from './platform_id';
export {VERSION} from './version';

View File

@ -0,0 +1,31 @@
/**
* @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 {NgModule} from '@angular/core';
import {COMMON_DEPRECATED_DIRECTIVES, COMMON_DIRECTIVES} from './directives/index';
import {NgLocaleLocalization, NgLocalization} from './localization';
import {COMMON_PIPES} from './pipes/index';
// Note: This does not contain the location providers,
// as they need some platform specific implementations to work.
/**
* The module that includes all the basic Angular directives like {@link NgIf}, {@link NgForOf}, ...
*
* @stable
*/
@NgModule({
declarations: [COMMON_DIRECTIVES, COMMON_PIPES],
exports: [COMMON_DIRECTIVES, COMMON_PIPES],
providers: [
{provide: NgLocalization, useClass: NgLocaleLocalization},
],
})
export class CommonModule {
}

View File

@ -0,0 +1,58 @@
/**
* @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 {Provider} from '@angular/core';
import {NgClass} from './ng_class';
import {NgComponentOutlet} from './ng_component_outlet';
import {NgFor, NgForOf} from './ng_for_of';
import {NgIf} from './ng_if';
import {NgPlural, NgPluralCase} from './ng_plural';
import {NgStyle} from './ng_style';
import {NgSwitch, NgSwitchCase, NgSwitchDefault} from './ng_switch';
import {NgTemplateOutlet} from './ng_template_outlet';
export {
NgClass,
NgComponentOutlet,
NgFor,
NgForOf,
NgIf,
NgPlural,
NgPluralCase,
NgStyle,
NgSwitch,
NgSwitchCase,
NgSwitchDefault,
NgTemplateOutlet
};
/**
* A collection of Angular directives that are likely to be used in each and every Angular
* application.
*/
export const COMMON_DIRECTIVES: Provider[] = [
NgClass,
NgComponentOutlet,
NgForOf,
NgIf,
NgTemplateOutlet,
NgStyle,
NgSwitch,
NgSwitchCase,
NgSwitchDefault,
NgPlural,
NgPluralCase,
];
/**
* A colletion of deprecated directives that are no longer part of the core module.
*/
export const COMMON_DEPRECATED_DIRECTIVES: Provider[] = [NgFor];

View File

@ -0,0 +1,142 @@
/**
* @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 {Directive, DoCheck, ElementRef, Input, IterableChanges, IterableDiffer, IterableDiffers, KeyValueChanges, KeyValueDiffer, KeyValueDiffers, Renderer, ɵisListLikeIterable as isListLikeIterable, ɵstringify as stringify} from '@angular/core';
/**
* @ngModule CommonModule
*
* @whatItDoes Adds and removes CSS classes on an HTML element.
*
* @howToUse
* ```
* <some-element [ngClass]="'first second'">...</some-element>
*
* <some-element [ngClass]="['first', 'second']">...</some-element>
*
* <some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element>
*
* <some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element>
*
* <some-element [ngClass]="{'class1 class2 class3' : true}">...</some-element>
* ```
*
* @description
*
* The CSS classes are updated as follows, depending on the type of the expression evaluation:
* - `string` - the CSS classes listed in the string (space delimited) are added,
* - `Array` - the CSS classes declared as Array elements are added,
* - `Object` - keys are CSS classes that get added when the expression given in the value
* evaluates to a truthy value, otherwise they are removed.
*
* @stable
*/
@Directive({selector: '[ngClass]'})
export class NgClass implements DoCheck {
private _iterableDiffer: IterableDiffer<string>;
private _keyValueDiffer: KeyValueDiffer<string, any>;
private _initialClasses: string[] = [];
private _rawClass: string[]|Set<string>|{[klass: string]: any};
constructor(
private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers,
private _ngEl: ElementRef, private _renderer: Renderer) {}
@Input('class')
set klass(v: string) {
this._applyInitialClasses(true);
this._initialClasses = typeof v === 'string' ? v.split(/\s+/) : [];
this._applyInitialClasses(false);
this._applyClasses(this._rawClass, false);
}
@Input()
set ngClass(v: string|string[]|Set<string>|{[klass: string]: any}) {
this._cleanupClasses(this._rawClass);
this._iterableDiffer = null;
this._keyValueDiffer = null;
this._rawClass = typeof v === 'string' ? v.split(/\s+/) : v;
if (this._rawClass) {
if (isListLikeIterable(this._rawClass)) {
this._iterableDiffer = this._iterableDiffers.find(this._rawClass).create();
} else {
this._keyValueDiffer = this._keyValueDiffers.find(this._rawClass).create();
}
}
}
ngDoCheck(): void {
if (this._iterableDiffer) {
const iterableChanges = this._iterableDiffer.diff(this._rawClass as string[]);
if (iterableChanges) {
this._applyIterableChanges(iterableChanges);
}
} else if (this._keyValueDiffer) {
const keyValueChanges = this._keyValueDiffer.diff(this._rawClass as{[k: string]: any});
if (keyValueChanges) {
this._applyKeyValueChanges(keyValueChanges);
}
}
}
private _cleanupClasses(rawClassVal: string[]|{[klass: string]: any}): void {
this._applyClasses(rawClassVal, true);
this._applyInitialClasses(false);
}
private _applyKeyValueChanges(changes: KeyValueChanges<string, any>): void {
changes.forEachAddedItem((record) => this._toggleClass(record.key, record.currentValue));
changes.forEachChangedItem((record) => this._toggleClass(record.key, record.currentValue));
changes.forEachRemovedItem((record) => {
if (record.previousValue) {
this._toggleClass(record.key, false);
}
});
}
private _applyIterableChanges(changes: IterableChanges<string>): void {
changes.forEachAddedItem((record) => {
if (typeof record.item === 'string') {
this._toggleClass(record.item, true);
} else {
throw new Error(
`NgClass can only toggle CSS classes expressed as strings, got ${stringify(record.item)}`);
}
});
changes.forEachRemovedItem((record) => this._toggleClass(record.item, false));
}
private _applyInitialClasses(isCleanup: boolean) {
this._initialClasses.forEach(klass => this._toggleClass(klass, !isCleanup));
}
private _applyClasses(
rawClassVal: string[]|Set<string>|{[klass: string]: any}, isCleanup: boolean) {
if (rawClassVal) {
if (Array.isArray(rawClassVal) || rawClassVal instanceof Set) {
(<any>rawClassVal).forEach((klass: string) => this._toggleClass(klass, !isCleanup));
} else {
Object.keys(rawClassVal).forEach(klass => {
if (rawClassVal[klass] != null) this._toggleClass(klass, !isCleanup);
});
}
}
}
private _toggleClass(klass: string, enabled: any): void {
klass = klass.trim();
if (klass) {
klass.split(/\s+/g).forEach(
klass => { this._renderer.setElementClass(this._ngEl.nativeElement, klass, !!enabled); });
}
}
}

View File

@ -0,0 +1,115 @@
/**
* @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 {ComponentFactoryResolver, ComponentRef, Directive, Injector, Input, NgModuleFactory, NgModuleRef, OnChanges, OnDestroy, Provider, SimpleChanges, Type, ViewContainerRef} from '@angular/core';
/**
* Instantiates a single {@link Component} type and inserts its Host View into current View.
* `NgComponentOutlet` provides a declarative approach for dynamic component creation.
*
* `NgComponentOutlet` requires a component type, if a falsy value is set the view will clear and
* any existing component will get destroyed.
*
* ### Fine tune control
*
* You can control the component creation process by using the following optional attributes:
*
* * `ngComponentOutletInjector`: Optional custom {@link Injector} that will be used as parent for
* the Component. Defaults to the injector of the current view container.
*
* * `ngComponentOutletProviders`: Optional injectable objects ({@link Provider}) that are visible
* to the component.
*
* * `ngComponentOutletContent`: Optional list of projectable nodes to insert into the content
* section of the component, if exists.
*
* * `ngComponentOutletNgModuleFactory`: Optional module factory to allow dynamically loading other
* module, then load a component from that module.
*
* ### Syntax
*
* Simple
* ```
* <ng-container *ngComponentOutlet="componentTypeExpression"></ng-container>
* ```
*
* Customized injector/content
* ```
* <ng-container *ngComponentOutlet="componentTypeExpression;
* injector: injectorExpression;
* content: contentNodesExpression;">
* </ng-container>
* ```
*
* Customized ngModuleFactory
* ```
* <ng-container *ngComponentOutlet="componentTypeExpression;
* ngModuleFactory: moduleFactory;">
* </ng-container>
* ```
* # Example
*
* {@example common/ngComponentOutlet/ts/module.ts region='SimpleExample'}
*
* A more complete example with additional options:
*
* {@example common/ngComponentOutlet/ts/module.ts region='CompleteExample'}
* A more complete example with ngModuleFactory:
*
* {@example common/ngComponentOutlet/ts/module.ts region='NgModuleFactoryExample'}
*
* @experimental
*/
@Directive({selector: '[ngComponentOutlet]'})
export class NgComponentOutlet implements OnChanges, OnDestroy {
@Input() ngComponentOutlet: Type<any>;
@Input() ngComponentOutletInjector: Injector;
@Input() ngComponentOutletContent: any[][];
@Input() ngComponentOutletNgModuleFactory: NgModuleFactory<any>;
private _componentRef: ComponentRef<any> = null;
private _moduleRef: NgModuleRef<any> = null;
constructor(private _viewContainerRef: ViewContainerRef) {}
ngOnChanges(changes: SimpleChanges) {
if (this._componentRef) {
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._componentRef.hostView));
}
this._viewContainerRef.clear();
this._componentRef = null;
if (this.ngComponentOutlet) {
let injector = this.ngComponentOutletInjector || this._viewContainerRef.parentInjector;
if ((changes as any).ngComponentOutletNgModuleFactory) {
if (this._moduleRef) this._moduleRef.destroy();
if (this.ngComponentOutletNgModuleFactory) {
this._moduleRef = this.ngComponentOutletNgModuleFactory.create(injector);
} else {
this._moduleRef = null;
}
}
if (this._moduleRef) {
injector = this._moduleRef.injector;
}
let componentFactory =
injector.get(ComponentFactoryResolver).resolveComponentFactory(this.ngComponentOutlet);
this._componentRef = this._viewContainerRef.createComponent(
componentFactory, this._viewContainerRef.length, injector, this.ngComponentOutletContent);
}
}
ngOnDestroy() {
if (this._moduleRef) this._moduleRef.destroy();
}
}

View File

@ -0,0 +1,199 @@
/**
* @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 {ChangeDetectorRef, Directive, DoCheck, EmbeddedViewRef, Input, IterableChangeRecord, IterableChanges, IterableDiffer, IterableDiffers, NgIterable, OnChanges, SimpleChanges, TemplateRef, TrackByFunction, ViewContainerRef, forwardRef, isDevMode} from '@angular/core';
export class NgForOfRow<T> {
constructor(public $implicit: T, public index: number, public count: number) {}
get first(): boolean { return this.index === 0; }
get last(): boolean { return this.index === this.count - 1; }
get even(): boolean { return this.index % 2 === 0; }
get odd(): boolean { return !this.even; }
}
/**
* The `NgForOf` directive instantiates a template once per item from an iterable. The context
* for each instantiated template inherits from the outer context with the given loop variable
* set to the current item from the iterable.
*
* ### Local Variables
*
* `NgForOf` provides several exported values that can be aliased to local variables:
*
* * `index` will be set to the current loop iteration for each template context.
* * `first` will be set to a boolean value indicating whether the item is the first one in the
* iteration.
* * `last` will be set to a boolean value indicating whether the item is the last one in the
* iteration.
* * `even` will be set to a boolean value indicating whether this item has an even index.
* * `odd` will be set to a boolean value indicating whether this item has an odd index.
*
* ### Change Propagation
*
* When the contents of the iterator changes, `NgForOf` makes the corresponding changes to the DOM:
*
* * When an item is added, a new instance of the template is added to the DOM.
* * When an item is removed, its template instance is removed from the DOM.
* * When items are reordered, their respective templates are reordered in the DOM.
* * Otherwise, the DOM element for that item will remain the same.
*
* Angular uses object identity to track insertions and deletions within the iterator and reproduce
* those changes in the DOM. This has important implications for animations and any stateful
* controls (such as `<input>` elements which accept user input) that are present. Inserted rows can
* be animated in, deleted rows can be animated out, and unchanged rows retain any unsaved state
* such as user input.
*
* It is possible for the identities of elements in the iterator to change while the data does not.
* This can happen, for example, if the iterator produced from an RPC to the server, and that
* RPC is re-run. Even if the data hasn't changed, the second response will produce objects with
* different identities, and Angular will tear down the entire DOM and rebuild it (as if all old
* elements were deleted and all new elements inserted). This is an expensive operation and should
* be avoided if possible.
*
* To customize the default tracking algorithm, `NgForOf` supports `trackBy` option.
* `trackBy` takes a function which has two arguments: `index` and `item`.
* If `trackBy` is given, Angular tracks changes by the return value of the function.
*
* ### Syntax
*
* - `<li *ngFor="let item of items; let i = index; trackBy: trackByFn">...</li>`
* - `<li template="ngFor let item of items; let i = index; trackBy: trackByFn">...</li>`
*
* With `<ng-template>` element:
*
* ```
* <ng-template ngFor let-item [ngForOf]="items" let-i="index" [ngForTrackBy]="trackByFn">
* <li>...</li>
* </ng-template>
* ```
*
* ### Example
*
* See a [live demo](http://plnkr.co/edit/KVuXxDp0qinGDyo307QW?p=preview) for a more detailed
* example.
*
* @stable
*/
@Directive({selector: '[ngFor][ngForOf]'})
export class NgForOf<T> implements DoCheck, OnChanges {
@Input() ngForOf: NgIterable<T>;
@Input()
set ngForTrackBy(fn: TrackByFunction<T>) {
if (isDevMode() && fn != null && typeof fn !== 'function') {
// TODO(vicb): use a log service once there is a public one available
if (<any>console && <any>console.warn) {
console.warn(
`trackBy must be a function, but received ${JSON.stringify(fn)}. ` +
`See https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html#!#change-propagation for more information.`);
}
}
this._trackByFn = fn;
}
get ngForTrackBy(): TrackByFunction<T> { return this._trackByFn; }
private _differ: IterableDiffer<T> = null;
private _trackByFn: TrackByFunction<T>;
constructor(
private _viewContainer: ViewContainerRef, private _template: TemplateRef<NgForOfRow<T>>,
private _differs: IterableDiffers) {}
@Input()
set ngForTemplate(value: TemplateRef<NgForOfRow<T>>) {
// TODO(TS2.1): make TemplateRef<Partial<NgForRowOf<T>>> once we move to TS v2.1
// The current type is too restrictive; a template that just uses index, for example,
// should be acceptable.
if (value) {
this._template = value;
}
}
ngOnChanges(changes: SimpleChanges): void {
if ('ngForOf' in changes) {
// React on ngForOf changes only once all inputs have been initialized
const value = changes['ngForOf'].currentValue;
if (!this._differ && value) {
try {
this._differ = this._differs.find(value).create(this.ngForTrackBy);
} catch (e) {
throw new Error(
`Cannot find a differ supporting object '${value}' of type '${getTypeNameForDebugging(value)}'. NgFor only supports binding to Iterables such as Arrays.`);
}
}
}
}
ngDoCheck(): void {
if (this._differ) {
const changes = this._differ.diff(this.ngForOf);
if (changes) this._applyChanges(changes);
}
}
private _applyChanges(changes: IterableChanges<T>) {
const insertTuples: RecordViewTuple<T>[] = [];
changes.forEachOperation(
(item: IterableChangeRecord<any>, adjustedPreviousIndex: number, currentIndex: number) => {
if (item.previousIndex == null) {
const view = this._viewContainer.createEmbeddedView(
this._template, new NgForOfRow(null, null, null), currentIndex);
const tuple = new RecordViewTuple(item, view);
insertTuples.push(tuple);
} else if (currentIndex == null) {
this._viewContainer.remove(adjustedPreviousIndex);
} else {
const view = this._viewContainer.get(adjustedPreviousIndex);
this._viewContainer.move(view, currentIndex);
const tuple = new RecordViewTuple(item, <EmbeddedViewRef<NgForOfRow<T>>>view);
insertTuples.push(tuple);
}
});
for (let i = 0; i < insertTuples.length; i++) {
this._perViewChange(insertTuples[i].view, insertTuples[i].record);
}
for (let i = 0, ilen = this._viewContainer.length; i < ilen; i++) {
const viewRef = <EmbeddedViewRef<NgForOfRow<T>>>this._viewContainer.get(i);
viewRef.context.index = i;
viewRef.context.count = ilen;
}
changes.forEachIdentityChange((record: any) => {
const viewRef = <EmbeddedViewRef<NgForOfRow<T>>>this._viewContainer.get(record.currentIndex);
viewRef.context.$implicit = record.item;
});
}
private _perViewChange(view: EmbeddedViewRef<NgForOfRow<T>>, record: IterableChangeRecord<any>) {
view.context.$implicit = record.item;
}
}
class RecordViewTuple<T> {
constructor(public record: any, public view: EmbeddedViewRef<NgForOfRow<T>>) {}
}
/**
* @deprecated from v4.0.0 - Use NgForOf<any> instead.
*/
export type NgFor = NgForOf<any>;
/**
* @deprecated from v4.0.0 - Use NgForOf instead.
*/
export const NgFor = NgForOf;
export function getTypeNameForDebugging(type: any): string {
return type['name'] || typeof type;
}

View File

@ -0,0 +1,157 @@
/**
* @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 {Directive, EmbeddedViewRef, Input, TemplateRef, ViewContainerRef} from '@angular/core';
/**
* Conditionally includes a template based on the value of an `expression`.
*
* `ngIf` evaluates the `expression` and then renders the `then` or `else` template in its place
* when expression is truthy or falsy respectively. Typically the:
* - `then` template is the inline template of `ngIf` unless bound to a different value.
* - `else` template is blank unless it is bound.
*
* # Most common usage
*
* The most common usage of the `ngIf` directive 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 `<ng-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 this example.
*
* {@example common/ngIf/ts/module.ts region='NgIfThenElse'}
*
* # Storing conditional result in a variable
*
* A common pattern 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:
*
* ```
* Hello {{ (userStream|async)?.last }}, {{ (userStream|async)?.first }}!
* ```
*
* There are several inefficiencies in the above example:
* - We create multiple subscriptions on `userStream`. One for each `async` pipe, or two in the
* example above.
* - We cannot 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 then 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>`
* - `<ng-template [ngIf]="condition"><div>...</div></ng-template>`
*
* Form with an else block:
* ```
* <div *ngIf="condition; else elseBlock">...</div>
* <ng-template #elseBlock>...</ng-template>
* ```
*
* Form with a `then` and `else` block:
* ```
* <div *ngIf="condition; then thenBlock else elseBlock"></div>
* <ng-template #thenBlock>...</ng-template>
* <ng-template #elseBlock>...</ng-template>
* ```
*
* Form with storing the value locally:
* ```
* <div *ngIf="condition; else elseBlock; let value">{{value}}</div>
* <ng-template #elseBlock>...</ng-template>
* ```
*
* @stable
*/
@Directive({selector: '[ngIf]'})
export class NgIf {
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, templateRef: TemplateRef<NgIfContext>) {
this._thenTemplateRef = templateRef;
}
@Input()
set ngIf(condition: any) {
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; }

View File

@ -0,0 +1,109 @@
/**
* @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 {Attribute, Directive, Host, Input, TemplateRef, ViewContainerRef} from '@angular/core';
import {NgLocalization, getPluralCategory} from '../localization';
import {SwitchView} from './ng_switch';
/**
* @ngModule CommonModule
*
* @whatItDoes Adds / removes DOM sub-trees based on a numeric value. Tailored for pluralization.
*
* @howToUse
* ```
* <some-element [ngPlural]="value">
* <ng-template ngPluralCase="=0">there is nothing</ng-template>
* <ng-template ngPluralCase="=1">there is one</ng-template>
* <ng-template ngPluralCase="few">there are a few</ng-template>
* </some-element>
* ```
*
* @description
*
* Displays DOM sub-trees that match the switch expression value, or failing that, DOM sub-trees
* that match the switch expression's pluralization category.
*
* To use this directive you must provide a container element that sets the `[ngPlural]` attribute
* to a switch expression. Inner elements with a `[ngPluralCase]` will display based on their
* expression:
* - if `[ngPluralCase]` is set to a value starting with `=`, it will only display if the value
* matches the switch expression exactly,
* - otherwise, the view will be treated as a "category match", and will only display if exact
* value matches aren't found and the value maps to its category for the defined locale.
*
* See http://cldr.unicode.org/index/cldr-spec/plural-rules
*
* @experimental
*/
@Directive({selector: '[ngPlural]'})
export class NgPlural {
private _switchValue: number;
private _activeView: SwitchView;
private _caseViews: {[k: string]: SwitchView} = {};
constructor(private _localization: NgLocalization) {}
@Input()
set ngPlural(value: number) {
this._switchValue = value;
this._updateView();
}
addCase(value: string, switchView: SwitchView): void { this._caseViews[value] = switchView; }
private _updateView(): void {
this._clearViews();
const cases = Object.keys(this._caseViews);
const key = getPluralCategory(this._switchValue, cases, this._localization);
this._activateView(this._caseViews[key]);
}
private _clearViews() {
if (this._activeView) this._activeView.destroy();
}
private _activateView(view: SwitchView) {
if (view) {
this._activeView = view;
this._activeView.create();
}
}
}
/**
* @ngModule CommonModule
*
* @whatItDoes Creates a view that will be added/removed from the parent {@link NgPlural} when the
* given expression matches the plural expression according to CLDR rules.
*
* @howToUse
* ```
* <some-element [ngPlural]="value">
* <ng-template ngPluralCase="=0">...</ng-template>
* <ng-template ngPluralCase="other">...</ng-template>
* </some-element>
*```
*
* See {@link NgPlural} for more details and example.
*
* @experimental
*/
@Directive({selector: '[ngPluralCase]'})
export class NgPluralCase {
constructor(
@Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>,
viewContainer: ViewContainerRef, @Host() ngPlural: NgPlural) {
const isANumber: boolean = !isNaN(Number(value));
ngPlural.addCase(isANumber ? `=${value}` : value, new SwitchView(viewContainer, template));
}
}

View File

@ -0,0 +1,70 @@
/**
* @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 {Directive, DoCheck, ElementRef, Input, KeyValueChanges, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
/**
* @ngModule CommonModule
*
* @whatItDoes Update an HTML element styles.
*
* @howToUse
* ```
* <some-element [ngStyle]="{'font-style': styleExp}">...</some-element>
*
* <some-element [ngStyle]="{'max-width.px': widthExp}">...</some-element>
*
* <some-element [ngStyle]="objExp">...</some-element>
* ```
*
* @description
*
* The styles are updated according to the value of the expression evaluation:
* - keys are style names with an optional `.<unit>` suffix (ie 'top.px', 'font-style.em'),
* - values are the values assigned to those properties (expressed in the given unit).
*
* @stable
*/
@Directive({selector: '[ngStyle]'})
export class NgStyle implements DoCheck {
private _ngStyle: {[key: string]: string};
private _differ: KeyValueDiffer<string, string|number>;
constructor(
private _differs: KeyValueDiffers, private _ngEl: ElementRef, private _renderer: Renderer) {}
@Input()
set ngStyle(v: {[key: string]: string}) {
this._ngStyle = v;
if (!this._differ && v) {
this._differ = this._differs.find(v).create();
}
}
ngDoCheck() {
if (this._differ) {
const changes = this._differ.diff(this._ngStyle);
if (changes) {
this._applyChanges(changes);
}
}
}
private _applyChanges(changes: KeyValueChanges<string, string|number>): void {
changes.forEachRemovedItem((record) => this._setStyle(record.key, null));
changes.forEachAddedItem((record) => this._setStyle(record.key, record.currentValue));
changes.forEachChangedItem((record) => this._setStyle(record.key, record.currentValue));
}
private _setStyle(nameAndUnit: string, value: string|number): void {
const [name, unit] = nameAndUnit.split('.');
value = value != null && unit ? `${value}${unit}` : value;
this._renderer.setElementStyle(this._ngEl.nativeElement, name, value as string);
}
}

View File

@ -0,0 +1,200 @@
/**
* @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 {Directive, DoCheck, Host, Input, TemplateRef, ViewContainerRef} from '@angular/core';
export class SwitchView {
private _created = false;
constructor(
private _viewContainerRef: ViewContainerRef, private _templateRef: TemplateRef<Object>) {}
create(): void {
this._created = true;
this._viewContainerRef.createEmbeddedView(this._templateRef);
}
destroy(): void {
this._created = false;
this._viewContainerRef.clear();
}
enforceState(created: boolean) {
if (created && !this._created) {
this.create();
} else if (!created && this._created) {
this.destroy();
}
}
}
/**
* @ngModule CommonModule
*
* @whatItDoes Adds / removes DOM sub-trees when the nest match expressions matches the switch
* expression.
*
* @howToUse
* ```
* <container-element [ngSwitch]="switch_expression">
* <some-element *ngSwitchCase="match_expression_1">...</some-element>
* <some-element *ngSwitchCase="match_expression_2">...</some-element>
* <some-other-element *ngSwitchCase="match_expression_3">...</some-other-element>
* <ng-container *ngSwitchCase="match_expression_3">
* <!-- use a ng-container to group multiple root nodes -->
* <inner-element></inner-element>
* <inner-other-element></inner-other-element>
* </ng-container>
* <some-element *ngSwitchDefault>...</some-element>
* </container-element>
* ```
* @description
*
* `NgSwitch` stamps out nested views when their match expression value matches the value of the
* switch expression.
*
* In other words:
* - you define a container element (where you place the directive with a switch expression on the
* `[ngSwitch]="..."` attribute)
* - you define inner views inside the `NgSwitch` and place a `*ngSwitchCase` attribute on the view
* root elements.
*
* Elements within `NgSwitch` but outside of a `NgSwitchCase` or `NgSwitchDefault` directives will
* be preserved at the location.
*
* The `ngSwitchCase` directive informs the parent `NgSwitch` of which view to display when the
* expression is evaluated.
* When no matching expression is found on a `ngSwitchCase` view, the `ngSwitchDefault` view is
* stamped out.
*
* @stable
*/
@Directive({selector: '[ngSwitch]'})
export class NgSwitch {
private _defaultViews: SwitchView[];
private _defaultUsed = false;
private _caseCount = 0;
private _lastCaseCheckIndex = 0;
private _lastCasesMatched = false;
private _ngSwitch: any;
@Input()
set ngSwitch(newValue: any) {
this._ngSwitch = newValue;
if (this._caseCount === 0) {
this._updateDefaultCases(true);
}
}
/** @internal */
_addCase(): number { return this._caseCount++; }
/** @internal */
_addDefault(view: SwitchView) {
if (!this._defaultViews) {
this._defaultViews = [];
}
this._defaultViews.push(view);
}
/** @internal */
_matchCase(value: any): boolean {
const matched = value == this._ngSwitch;
this._lastCasesMatched = this._lastCasesMatched || matched;
this._lastCaseCheckIndex++;
if (this._lastCaseCheckIndex === this._caseCount) {
this._updateDefaultCases(!this._lastCasesMatched);
this._lastCaseCheckIndex = 0;
this._lastCasesMatched = false;
}
return matched;
}
private _updateDefaultCases(useDefault: boolean) {
if (this._defaultViews && useDefault !== this._defaultUsed) {
this._defaultUsed = useDefault;
for (let i = 0; i < this._defaultViews.length; i++) {
const defaultView = this._defaultViews[i];
defaultView.enforceState(useDefault);
}
}
}
}
/**
* @ngModule CommonModule
*
* @whatItDoes Creates a view that will be added/removed from the parent {@link NgSwitch} when the
* given expression evaluate to respectively the same/different value as the switch
* expression.
*
* @howToUse
* ```
* <container-element [ngSwitch]="switch_expression">
* <some-element *ngSwitchCase="match_expression_1">...</some-element>
* </container-element>
*```
* @description
*
* Insert the sub-tree when the expression evaluates to the same value as the enclosing switch
* expression.
*
* If multiple match expressions match the switch expression value, all of them are displayed.
*
* See {@link NgSwitch} for more details and example.
*
* @stable
*/
@Directive({selector: '[ngSwitchCase]'})
export class NgSwitchCase implements DoCheck {
private _view: SwitchView;
@Input()
ngSwitchCase: any;
constructor(
viewContainer: ViewContainerRef, templateRef: TemplateRef<Object>,
@Host() private ngSwitch: NgSwitch) {
ngSwitch._addCase();
this._view = new SwitchView(viewContainer, templateRef);
}
ngDoCheck() { this._view.enforceState(this.ngSwitch._matchCase(this.ngSwitchCase)); }
}
/**
* @ngModule CommonModule
* @whatItDoes Creates a view that is added to the parent {@link NgSwitch} when no case expressions
* match the
* switch expression.
*
* @howToUse
* ```
* <container-element [ngSwitch]="switch_expression">
* <some-element *ngSwitchCase="match_expression_1">...</some-element>
* <some-other-element *ngSwitchDefault>...</some-other-element>
* </container-element>
* ```
*
* @description
*
* Insert the sub-tree when no case expressions evaluate to the same value as the enclosing switch
* expression.
*
* See {@link NgSwitch} for more details and example.
*
* @stable
*/
@Directive({selector: '[ngSwitchDefault]'})
export class NgSwitchDefault {
constructor(
viewContainer: ViewContainerRef, templateRef: TemplateRef<Object>,
@Host() ngSwitch: NgSwitch) {
ngSwitch._addDefault(new SwitchView(viewContainer, templateRef));
}
}

View 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 {Directive, EmbeddedViewRef, Input, OnChanges, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
/**
* @ngModule CommonModule
*
* @whatItDoes Inserts an embedded view from a prepared `TemplateRef`
*
* @howToUse
* ```
* <ng-container *ngTemplateOutlet="templateRefExp; context: contextExp"></ng-container>
* ```
*
* @description
*
* You can attach a context object to the `EmbeddedViewRef` by setting `[ngTemplateOutletContext]`.
* `[ngTemplateOutletContext]` should be an object, the object's keys will be available for binding
* by the local template `let` declarations.
*
* Note: using the key `$implicit` in the context object will set it's value as default.
*
* # Example
*
* {@example common/ngTemplateOutlet/ts/module.ts region='NgTemplateOutlet'}
*
* @experimental
*/
@Directive({selector: '[ngTemplateOutlet]'})
export class NgTemplateOutlet implements OnChanges {
private _viewRef: EmbeddedViewRef<any>;
@Input() public ngTemplateOutletContext: Object;
@Input() public ngTemplateOutlet: TemplateRef<any>;
constructor(private _viewContainerRef: ViewContainerRef) {}
/**
* @deprecated v4.0.0 - Renamed to ngTemplateOutletContext.
*/
@Input()
set ngOutletContext(context: Object) { this.ngTemplateOutletContext = context; }
ngOnChanges(changes: SimpleChanges) {
if (this._viewRef) {
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._viewRef));
}
if (this.ngTemplateOutlet) {
this._viewRef = this._viewContainerRef.createEmbeddedView(
this.ngTemplateOutlet, this.ngTemplateOutletContext);
}
}
}

View File

@ -0,0 +1,434 @@
/**
* @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 {Inject, Injectable, LOCALE_ID} from '@angular/core';
/**
* @experimental
*/
export abstract class NgLocalization { abstract getPluralCategory(value: any): string; }
/**
* Returns the plural category for a given value.
* - "=value" when the case exists,
* - the plural category otherwise
*
* @internal
*/
export function getPluralCategory(
value: number, cases: string[], ngLocalization: NgLocalization): string {
let key = `=${value}`;
if (cases.indexOf(key) > -1) {
return key;
}
key = ngLocalization.getPluralCategory(value);
if (cases.indexOf(key) > -1) {
return key;
}
if (cases.indexOf('other') > -1) {
return 'other';
}
throw new Error(`No plural message found for value "${value}"`);
}
/**
* Returns the plural case based on the locale
*
* @experimental
*/
@Injectable()
export class NgLocaleLocalization extends NgLocalization {
constructor(@Inject(LOCALE_ID) protected locale: string) { super(); }
getPluralCategory(value: any): string {
const plural = getPluralCase(this.locale, value);
switch (plural) {
case Plural.Zero:
return 'zero';
case Plural.One:
return 'one';
case Plural.Two:
return 'two';
case Plural.Few:
return 'few';
case Plural.Many:
return 'many';
default:
return 'other';
}
}
}
// This is generated code DO NOT MODIFY
// see angular2/script/cldr/gen_plural_rules.js
/** @experimental */
export enum Plural {
Zero,
One,
Two,
Few,
Many,
Other,
}
/**
* Returns the plural case based on the locale
*
* @experimental
*/
export function getPluralCase(locale: string, nLike: number | string): Plural {
// TODO(vicb): lazy compute
if (typeof nLike === 'string') {
nLike = parseInt(<string>nLike, 10);
}
const n: number = nLike as number;
const nDecimal = n.toString().replace(/^[^.]*\.?/, '');
const i = Math.floor(Math.abs(n));
const v = nDecimal.length;
const f = parseInt(nDecimal, 10);
const t = parseInt(n.toString().replace(/^[^.]*\.?|0+$/g, ''), 10) || 0;
const lang = locale.split('-')[0].toLowerCase();
switch (lang) {
case 'af':
case 'asa':
case 'az':
case 'bem':
case 'bez':
case 'bg':
case 'brx':
case 'ce':
case 'cgg':
case 'chr':
case 'ckb':
case 'ee':
case 'el':
case 'eo':
case 'es':
case 'eu':
case 'fo':
case 'fur':
case 'gsw':
case 'ha':
case 'haw':
case 'hu':
case 'jgo':
case 'jmc':
case 'ka':
case 'kk':
case 'kkj':
case 'kl':
case 'ks':
case 'ksb':
case 'ky':
case 'lb':
case 'lg':
case 'mas':
case 'mgo':
case 'ml':
case 'mn':
case 'nb':
case 'nd':
case 'ne':
case 'nn':
case 'nnh':
case 'nyn':
case 'om':
case 'or':
case 'os':
case 'ps':
case 'rm':
case 'rof':
case 'rwk':
case 'saq':
case 'seh':
case 'sn':
case 'so':
case 'sq':
case 'ta':
case 'te':
case 'teo':
case 'tk':
case 'tr':
case 'ug':
case 'uz':
case 'vo':
case 'vun':
case 'wae':
case 'xog':
if (n === 1) return Plural.One;
return Plural.Other;
case 'agq':
case 'bas':
case 'cu':
case 'dav':
case 'dje':
case 'dua':
case 'dyo':
case 'ebu':
case 'ewo':
case 'guz':
case 'kam':
case 'khq':
case 'ki':
case 'kln':
case 'kok':
case 'ksf':
case 'lrc':
case 'lu':
case 'luo':
case 'luy':
case 'mer':
case 'mfe':
case 'mgh':
case 'mua':
case 'mzn':
case 'nmg':
case 'nus':
case 'qu':
case 'rn':
case 'rw':
case 'sbp':
case 'twq':
case 'vai':
case 'yav':
case 'yue':
case 'zgh':
case 'ak':
case 'ln':
case 'mg':
case 'pa':
case 'ti':
if (n === Math.floor(n) && n >= 0 && n <= 1) return Plural.One;
return Plural.Other;
case 'am':
case 'as':
case 'bn':
case 'fa':
case 'gu':
case 'hi':
case 'kn':
case 'mr':
case 'zu':
if (i === 0 || n === 1) return Plural.One;
return Plural.Other;
case 'ar':
if (n === 0) return Plural.Zero;
if (n === 1) return Plural.One;
if (n === 2) return Plural.Two;
if (n % 100 === Math.floor(n % 100) && n % 100 >= 3 && n % 100 <= 10) return Plural.Few;
if (n % 100 === Math.floor(n % 100) && n % 100 >= 11 && n % 100 <= 99) return Plural.Many;
return Plural.Other;
case 'ast':
case 'ca':
case 'de':
case 'en':
case 'et':
case 'fi':
case 'fy':
case 'gl':
case 'it':
case 'nl':
case 'sv':
case 'sw':
case 'ur':
case 'yi':
if (i === 1 && v === 0) return Plural.One;
return Plural.Other;
case 'be':
if (n % 10 === 1 && !(n % 100 === 11)) return Plural.One;
if (n % 10 === Math.floor(n % 10) && n % 10 >= 2 && n % 10 <= 4 &&
!(n % 100 >= 12 && n % 100 <= 14))
return Plural.Few;
if (n % 10 === 0 || n % 10 === Math.floor(n % 10) && n % 10 >= 5 && n % 10 <= 9 ||
n % 100 === Math.floor(n % 100) && n % 100 >= 11 && n % 100 <= 14)
return Plural.Many;
return Plural.Other;
case 'br':
if (n % 10 === 1 && !(n % 100 === 11 || n % 100 === 71 || n % 100 === 91)) return Plural.One;
if (n % 10 === 2 && !(n % 100 === 12 || n % 100 === 72 || n % 100 === 92)) return Plural.Two;
if (n % 10 === Math.floor(n % 10) && (n % 10 >= 3 && n % 10 <= 4 || n % 10 === 9) &&
!(n % 100 >= 10 && n % 100 <= 19 || n % 100 >= 70 && n % 100 <= 79 ||
n % 100 >= 90 && n % 100 <= 99))
return Plural.Few;
if (!(n === 0) && n % 1e6 === 0) return Plural.Many;
return Plural.Other;
case 'bs':
case 'hr':
case 'sr':
if (v === 0 && i % 10 === 1 && !(i % 100 === 11) || f % 10 === 1 && !(f % 100 === 11))
return Plural.One;
if (v === 0 && i % 10 === Math.floor(i % 10) && i % 10 >= 2 && i % 10 <= 4 &&
!(i % 100 >= 12 && i % 100 <= 14) ||
f % 10 === Math.floor(f % 10) && f % 10 >= 2 && f % 10 <= 4 &&
!(f % 100 >= 12 && f % 100 <= 14))
return Plural.Few;
return Plural.Other;
case 'cs':
case 'sk':
if (i === 1 && v === 0) return Plural.One;
if (i === Math.floor(i) && i >= 2 && i <= 4 && v === 0) return Plural.Few;
if (!(v === 0)) return Plural.Many;
return Plural.Other;
case 'cy':
if (n === 0) return Plural.Zero;
if (n === 1) return Plural.One;
if (n === 2) return Plural.Two;
if (n === 3) return Plural.Few;
if (n === 6) return Plural.Many;
return Plural.Other;
case 'da':
if (n === 1 || !(t === 0) && (i === 0 || i === 1)) return Plural.One;
return Plural.Other;
case 'dsb':
case 'hsb':
if (v === 0 && i % 100 === 1 || f % 100 === 1) return Plural.One;
if (v === 0 && i % 100 === 2 || f % 100 === 2) return Plural.Two;
if (v === 0 && i % 100 === Math.floor(i % 100) && i % 100 >= 3 && i % 100 <= 4 ||
f % 100 === Math.floor(f % 100) && f % 100 >= 3 && f % 100 <= 4)
return Plural.Few;
return Plural.Other;
case 'ff':
case 'fr':
case 'hy':
case 'kab':
if (i === 0 || i === 1) return Plural.One;
return Plural.Other;
case 'fil':
if (v === 0 && (i === 1 || i === 2 || i === 3) ||
v === 0 && !(i % 10 === 4 || i % 10 === 6 || i % 10 === 9) ||
!(v === 0) && !(f % 10 === 4 || f % 10 === 6 || f % 10 === 9))
return Plural.One;
return Plural.Other;
case 'ga':
if (n === 1) return Plural.One;
if (n === 2) return Plural.Two;
if (n === Math.floor(n) && n >= 3 && n <= 6) return Plural.Few;
if (n === Math.floor(n) && n >= 7 && n <= 10) return Plural.Many;
return Plural.Other;
case 'gd':
if (n === 1 || n === 11) return Plural.One;
if (n === 2 || n === 12) return Plural.Two;
if (n === Math.floor(n) && (n >= 3 && n <= 10 || n >= 13 && n <= 19)) return Plural.Few;
return Plural.Other;
case 'gv':
if (v === 0 && i % 10 === 1) return Plural.One;
if (v === 0 && i % 10 === 2) return Plural.Two;
if (v === 0 &&
(i % 100 === 0 || i % 100 === 20 || i % 100 === 40 || i % 100 === 60 || i % 100 === 80))
return Plural.Few;
if (!(v === 0)) return Plural.Many;
return Plural.Other;
case 'he':
if (i === 1 && v === 0) return Plural.One;
if (i === 2 && v === 0) return Plural.Two;
if (v === 0 && !(n >= 0 && n <= 10) && n % 10 === 0) return Plural.Many;
return Plural.Other;
case 'is':
if (t === 0 && i % 10 === 1 && !(i % 100 === 11) || !(t === 0)) return Plural.One;
return Plural.Other;
case 'ksh':
if (n === 0) return Plural.Zero;
if (n === 1) return Plural.One;
return Plural.Other;
case 'kw':
case 'naq':
case 'se':
case 'smn':
if (n === 1) return Plural.One;
if (n === 2) return Plural.Two;
return Plural.Other;
case 'lag':
if (n === 0) return Plural.Zero;
if ((i === 0 || i === 1) && !(n === 0)) return Plural.One;
return Plural.Other;
case 'lt':
if (n % 10 === 1 && !(n % 100 >= 11 && n % 100 <= 19)) return Plural.One;
if (n % 10 === Math.floor(n % 10) && n % 10 >= 2 && n % 10 <= 9 &&
!(n % 100 >= 11 && n % 100 <= 19))
return Plural.Few;
if (!(f === 0)) return Plural.Many;
return Plural.Other;
case 'lv':
case 'prg':
if (n % 10 === 0 || n % 100 === Math.floor(n % 100) && n % 100 >= 11 && n % 100 <= 19 ||
v === 2 && f % 100 === Math.floor(f % 100) && f % 100 >= 11 && f % 100 <= 19)
return Plural.Zero;
if (n % 10 === 1 && !(n % 100 === 11) || v === 2 && f % 10 === 1 && !(f % 100 === 11) ||
!(v === 2) && f % 10 === 1)
return Plural.One;
return Plural.Other;
case 'mk':
if (v === 0 && i % 10 === 1 || f % 10 === 1) return Plural.One;
return Plural.Other;
case 'mt':
if (n === 1) return Plural.One;
if (n === 0 || n % 100 === Math.floor(n % 100) && n % 100 >= 2 && n % 100 <= 10)
return Plural.Few;
if (n % 100 === Math.floor(n % 100) && n % 100 >= 11 && n % 100 <= 19) return Plural.Many;
return Plural.Other;
case 'pl':
if (i === 1 && v === 0) return Plural.One;
if (v === 0 && i % 10 === Math.floor(i % 10) && i % 10 >= 2 && i % 10 <= 4 &&
!(i % 100 >= 12 && i % 100 <= 14))
return Plural.Few;
if (v === 0 && !(i === 1) && i % 10 === Math.floor(i % 10) && i % 10 >= 0 && i % 10 <= 1 ||
v === 0 && i % 10 === Math.floor(i % 10) && i % 10 >= 5 && i % 10 <= 9 ||
v === 0 && i % 100 === Math.floor(i % 100) && i % 100 >= 12 && i % 100 <= 14)
return Plural.Many;
return Plural.Other;
case 'pt':
if (n === Math.floor(n) && n >= 0 && n <= 2 && !(n === 2)) return Plural.One;
return Plural.Other;
case 'ro':
if (i === 1 && v === 0) return Plural.One;
if (!(v === 0) || n === 0 ||
!(n === 1) && n % 100 === Math.floor(n % 100) && n % 100 >= 1 && n % 100 <= 19)
return Plural.Few;
return Plural.Other;
case 'ru':
case 'uk':
if (v === 0 && i % 10 === 1 && !(i % 100 === 11)) return Plural.One;
if (v === 0 && i % 10 === Math.floor(i % 10) && i % 10 >= 2 && i % 10 <= 4 &&
!(i % 100 >= 12 && i % 100 <= 14))
return Plural.Few;
if (v === 0 && i % 10 === 0 ||
v === 0 && i % 10 === Math.floor(i % 10) && i % 10 >= 5 && i % 10 <= 9 ||
v === 0 && i % 100 === Math.floor(i % 100) && i % 100 >= 11 && i % 100 <= 14)
return Plural.Many;
return Plural.Other;
case 'shi':
if (i === 0 || n === 1) return Plural.One;
if (n === Math.floor(n) && n >= 2 && n <= 10) return Plural.Few;
return Plural.Other;
case 'si':
if (n === 0 || n === 1 || i === 0 && f === 1) return Plural.One;
return Plural.Other;
case 'sl':
if (v === 0 && i % 100 === 1) return Plural.One;
if (v === 0 && i % 100 === 2) return Plural.Two;
if (v === 0 && i % 100 === Math.floor(i % 100) && i % 100 >= 3 && i % 100 <= 4 || !(v === 0))
return Plural.Few;
return Plural.Other;
case 'tzm':
if (n === Math.floor(n) && n >= 0 && n <= 1 || n === Math.floor(n) && n >= 11 && n <= 99)
return Plural.One;
return Plural.Other;
default:
return Plural.Other;
}
}

View File

@ -0,0 +1,87 @@
/**
* @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 {Inject, Injectable, Optional} from '@angular/core';
import {Location} from './location';
import {APP_BASE_HREF, LocationStrategy} from './location_strategy';
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)
* of the browser's URL.
*
* For instance, if you call `location.go('/foo')`, the browser's URL will become
* `example.com#/foo`.
*
* ### Example
*
* {@example common/location/ts/hash_location_component.ts region='LocationComponent'}
*
* @stable
*/
@Injectable()
export class HashLocationStrategy extends LocationStrategy {
private _baseHref: string = '';
constructor(
private _platformLocation: PlatformLocation,
@Optional() @Inject(APP_BASE_HREF) _baseHref?: string) {
super();
if (_baseHref != null) {
this._baseHref = _baseHref;
}
}
onPopState(fn: LocationChangeListener): void {
this._platformLocation.onPopState(fn);
this._platformLocation.onHashChange(fn);
}
getBaseHref(): string { return this._baseHref; }
path(includeHash: boolean = false): string {
// the hash value is always prefixed with a `#`
// and if it is empty then it will stay empty
let path = this._platformLocation.hash;
if (path == null) path = '#';
return path.length > 0 ? path.substring(1) : path;
}
prepareExternalUrl(internal: string): string {
const url = Location.joinWithSlash(this._baseHref, internal);
return url.length > 0 ? ('#' + url) : url;
}
pushState(state: any, title: string, path: string, queryParams: string) {
let url = this.prepareExternalUrl(path + Location.normalizeQueryParams(queryParams));
if (url.length == 0) {
url = this._platformLocation.pathname;
}
this._platformLocation.pushState(state, title, url);
}
replaceState(state: any, title: string, path: string, queryParams: string) {
let url = this.prepareExternalUrl(path + Location.normalizeQueryParams(queryParams));
if (url.length == 0) {
url = this._platformLocation.pathname;
}
this._platformLocation.replaceState(state, title, url);
}
forward(): void { this._platformLocation.forward(); }
back(): void { this._platformLocation.back(); }
}

View File

@ -0,0 +1,13 @@
/**
* @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
*/
export * from './platform_location';
export * from './location_strategy';
export * from './hash_location_strategy';
export * from './path_location_strategy';
export * from './location';

View File

@ -0,0 +1,182 @@
/**
* @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 {EventEmitter, Injectable} from '@angular/core';
import {LocationStrategy} from './location_strategy';
/** @experimental */
export interface PopStateEvent {
pop?: boolean;
type?: string;
url?: string;
}
/**
* @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.
*
* Note: it's better to use {@link Router#navigate} service to trigger route changes. Use
* `Location` only if you need to interact with or create normalized URLs outside of
* routing.
*
* `Location` is responsible for normalizing the URL against the application's base href.
* A normalized URL is absolute from the URL host, includes the application's base href, and has no
* trailing slash:
* - `/my/app/user/123` is normalized
* - `my/app/user/123` **is not** normalized
* - `/my/app/user/123/` **is not** normalized
*
* ### Example
* {@example common/location/ts/path_location_component.ts region='LocationComponent'}
* @stable
*/
@Injectable()
export class Location {
/** @internal */
_subject: EventEmitter<any> = new EventEmitter();
/** @internal */
_baseHref: string;
/** @internal */
_platformStrategy: LocationStrategy;
constructor(platformStrategy: LocationStrategy) {
this._platformStrategy = platformStrategy;
const browserBaseHref = this._platformStrategy.getBaseHref();
this._baseHref = Location.stripTrailingSlash(_stripIndexHtml(browserBaseHref));
this._platformStrategy.onPopState((ev) => {
this._subject.emit({
'url': this.path(true),
'pop': true,
'type': ev.type,
});
});
}
/**
* Returns the normalized URL path.
*/
// TODO: vsavkin. Remove the boolean flag and always include hash once the deprecated router is
// removed.
path(includeHash: boolean = false): string {
return this.normalize(this._platformStrategy.path(includeHash));
}
/**
* Normalizes the given path and compares to the current normalized path.
*/
isCurrentPathEqualTo(path: string, query: string = ''): boolean {
return this.path() == this.normalize(path + Location.normalizeQueryParams(query));
}
/**
* Given a string representing a URL, returns the normalized URL path without leading or
* trailing slashes.
*/
normalize(url: string): string {
return Location.stripTrailingSlash(_stripBaseHref(this._baseHref, _stripIndexHtml(url)));
}
/**
* Given a string representing a URL, returns the platform-specific external URL path.
* If the given URL doesn't begin with a leading slash (`'/'`), this method adds one
* before normalizing. This method will also add a hash if `HashLocationStrategy` is
* used, or the `APP_BASE_HREF` if the `PathLocationStrategy` is in use.
*/
prepareExternalUrl(url: string): string {
if (url && url[0] !== '/') {
url = '/' + url;
}
return this._platformStrategy.prepareExternalUrl(url);
}
// TODO: rename this method to pushState
/**
* Changes the browsers URL to the normalized version of the given URL, and pushes a
* new item onto the platform's history.
*/
go(path: string, query: string = ''): void {
this._platformStrategy.pushState(null, '', path, query);
}
/**
* Changes the browsers URL to the normalized version of the given URL, and replaces
* the top item on the platform's history stack.
*/
replaceState(path: string, query: string = ''): void {
this._platformStrategy.replaceState(null, '', path, query);
}
/**
* Navigates forward in the platform's history.
*/
forward(): void { this._platformStrategy.forward(); }
/**
* Navigates back in the platform's history.
*/
back(): void { this._platformStrategy.back(); }
/**
* Subscribe to the platform's `popState` events.
*/
subscribe(
onNext: (value: PopStateEvent) => void, onThrow: (exception: any) => void = null,
onReturn: () => void = null): Object {
return this._subject.subscribe({next: onNext, error: onThrow, complete: onReturn});
}
/**
* Given a string of url parameters, prepend with '?' if needed, otherwise return parameters as
* is.
*/
public static normalizeQueryParams(params: string): string {
return params && params[0] !== '?' ? '?' + params : params;
}
/**
* Given 2 parts of a url, join them with a slash if needed.
*/
public static joinWithSlash(start: string, end: string): string {
if (start.length == 0) {
return end;
}
if (end.length == 0) {
return start;
}
let slashes = 0;
if (start.endsWith('/')) {
slashes++;
}
if (end.startsWith('/')) {
slashes++;
}
if (slashes == 2) {
return start + end.substring(1);
}
if (slashes == 1) {
return start + end;
}
return start + '/' + end;
}
/**
* If url has a trailing slash, remove it, otherwise return url as is.
*/
public static stripTrailingSlash(url: string): string { return url.replace(/\/$/, ''); }
}
function _stripBaseHref(baseHref: string, url: string): string {
return baseHref && url.startsWith(baseHref) ? url.substring(baseHref.length) : url;
}
function _stripIndexHtml(url: string): string {
return url.replace(/\/index.html$/, '');
}

View File

@ -0,0 +1,64 @@
/**
* @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 {InjectionToken} from '@angular/core';
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}.
*
* This is used under the hood of the {@link Location} service.
*
* Applications should use the {@link Router} or {@link Location} services to
* interact with application route state.
*
* For instance, {@link HashLocationStrategy} produces URLs like
* `http://example.com#/foo`, and {@link PathLocationStrategy} produces
* `http://example.com/foo` as an equivalent URL.
*
* See these two classes for more.
*
* @stable
*/
export abstract class LocationStrategy {
abstract path(includeHash?: boolean): string;
abstract prepareExternalUrl(internal: string): string;
abstract pushState(state: any, title: string, url: string, queryParams: string): void;
abstract replaceState(state: any, title: string, url: string, queryParams: string): void;
abstract forward(): void;
abstract back(): void;
abstract onPopState(fn: LocationChangeListener): void;
abstract getBaseHref(): string;
}
/**
* The `APP_BASE_HREF` token represents the base href to be used with the
* {@link PathLocationStrategy}.
*
* If you're using {@link PathLocationStrategy}, you must provide a provider to a string
* representing the URL prefix that should be preserved when generating and recognizing
* URLs.
*
* ### Example
*
* ```typescript
* import {Component, NgModule} from '@angular/core';
* import {APP_BASE_HREF} from '@angular/common';
*
* @NgModule({
* providers: [{provide: APP_BASE_HREF, useValue: '/my/app'}]
* })
* class AppModule {}
* ```
*
* @stable
*/
export const APP_BASE_HREF = new InjectionToken<string>('appBaseHref');

View File

@ -0,0 +1,96 @@
/**
* @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 {Inject, Injectable, Optional} from '@angular/core';
import {Location} from './location';
import {APP_BASE_HREF, LocationStrategy} from './location_strategy';
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.
*
* 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.
*
* For instance, if you provide an `APP_BASE_HREF` of `'/my/app'` and call
* `location.go('/foo')`, the browser's URL will become
* `example.com/my/app/foo`.
*
* Similarly, if you add `<base href='/my/app'/>` to the document and call
* `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()
export class PathLocationStrategy extends LocationStrategy {
private _baseHref: string;
constructor(
private _platformLocation: PlatformLocation,
@Optional() @Inject(APP_BASE_HREF) href?: string) {
super();
if (href == null) {
href = this._platformLocation.getBaseHrefFromDOM();
}
if (href == null) {
throw new Error(
`No base href set. Please provide a value for the APP_BASE_HREF token or add a base element to the document.`);
}
this._baseHref = href;
}
onPopState(fn: LocationChangeListener): void {
this._platformLocation.onPopState(fn);
this._platformLocation.onHashChange(fn);
}
getBaseHref(): string { return this._baseHref; }
prepareExternalUrl(internal: string): string {
return Location.joinWithSlash(this._baseHref, internal);
}
path(includeHash: boolean = false): string {
const pathname = this._platformLocation.pathname +
Location.normalizeQueryParams(this._platformLocation.search);
const hash = this._platformLocation.hash;
return hash && includeHash ? `${pathname}${hash}` : pathname;
}
pushState(state: any, title: string, url: string, queryParams: string) {
const externalUrl = this.prepareExternalUrl(url + Location.normalizeQueryParams(queryParams));
this._platformLocation.pushState(state, title, externalUrl);
}
replaceState(state: any, title: string, url: string, queryParams: string) {
const externalUrl = this.prepareExternalUrl(url + Location.normalizeQueryParams(queryParams));
this._platformLocation.replaceState(state, title, externalUrl);
}
forward(): void { this._platformLocation.forward(); }
back(): void { this._platformLocation.back(); }
}

View File

@ -0,0 +1,70 @@
/**
* @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 {InjectionToken} from '@angular/core';
/**
* This class should not be used directly by an application developer. Instead, use
* {@link Location}.
*
* `PlatformLocation` encapsulates all calls to DOM apis, which allows the Router to be platform
* agnostic.
* This means that we can have different implementation of `PlatformLocation` for the different
* platforms
* that angular supports. For example, the default `PlatformLocation` is {@link
* BrowserPlatformLocation},
* however when you run your app in a WebWorker you use {@link WebWorkerPlatformLocation}.
*
* The `PlatformLocation` class is used directly by all implementations of {@link LocationStrategy}
* when
* they need to interact with the DOM apis like pushState, popState, etc...
*
* {@link LocationStrategy} in turn is used by the {@link Location} service which is used directly
* by
* the {@link Router} in order to navigate between routes. Since all interactions between {@link
* Router} /
* {@link Location} / {@link LocationStrategy} and DOM apis flow through the `PlatformLocation`
* class
* they are all platform independent.
*
* @stable
*/
export abstract class PlatformLocation {
abstract getBaseHrefFromDOM(): string;
abstract onPopState(fn: LocationChangeListener): void;
abstract onHashChange(fn: LocationChangeListener): void;
get pathname(): string { return null; }
get search(): string { return null; }
get hash(): string { return null; }
abstract replaceState(state: any, title: string, url: string): void;
abstract pushState(state: any, title: string, url: string): void;
abstract forward(): void;
abstract back(): void;
}
/**
* @whatItDoes indicates when a location is initialized
* @experimental
*/
export const LOCATION_INITIALIZED = new InjectionToken<Promise<any>>('Location Initialized');
/**
* A serializable version of the event from onPopState or onHashChange
*
* @experimental
*/
export interface LocationChangeEvent { type: string; }
/**
* @experimental
*/
export interface LocationChangeListener { (e: LocationChangeEvent): any; }

View 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 {ChangeDetectorRef, EventEmitter, OnDestroy, Pipe, PipeTransform, WrappedValue, ɵisObservable, ɵisPromise} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
interface SubscriptionStrategy {
createSubscription(async: any, updateLatestValue: any): any;
dispose(subscription: any): void;
onDestroy(subscription: any): void;
}
class ObservableStrategy implements SubscriptionStrategy {
createSubscription(async: any, updateLatestValue: any): any {
return async.subscribe({next: updateLatestValue, error: (e: any) => { throw e; }});
}
dispose(subscription: any): void { subscription.unsubscribe(); }
onDestroy(subscription: any): void { subscription.unsubscribe(); }
}
class PromiseStrategy implements SubscriptionStrategy {
createSubscription(async: Promise<any>, updateLatestValue: (v: any) => any): any {
return async.then(updateLatestValue, e => { throw e; });
}
dispose(subscription: any): void {}
onDestroy(subscription: any): void {}
}
const _promiseStrategy = new PromiseStrategy();
const _observableStrategy = new ObservableStrategy();
/**
* @ngModule CommonModule
* @whatItDoes Unwraps a value from an asynchronous primitive.
* @howToUse `observable_or_promise_expression | async`
* @description
* The `async` pipe subscribes to an `Observable` or `Promise` and returns the latest value it has
* emitted. When a new value is emitted, the `async` pipe marks the component to be checked for
* changes. When the component gets destroyed, the `async` pipe unsubscribes automatically to avoid
* potential memory leaks.
*
*
* ## Examples
*
* This example binds a `Promise` to the view. Clicking the `Resolve` button resolves the
* promise.
*
* {@example common/pipes/ts/async_pipe.ts region='AsyncPipePromise'}
*
* It's also possible to use `async` with Observables. The example below binds the `time` Observable
* to the view. The Observable continuously updates the view with the current time.
*
* {@example common/pipes/ts/async_pipe.ts region='AsyncPipeObservable'}
*
* @stable
*/
@Pipe({name: 'async', pure: false})
export class AsyncPipe implements OnDestroy, PipeTransform {
private _latestValue: Object = null;
private _latestReturnedValue: Object = null;
private _subscription: Object = null;
private _obj: Observable<any>|Promise<any>|EventEmitter<any> = null;
private _strategy: SubscriptionStrategy = null;
constructor(private _ref: ChangeDetectorRef) {}
ngOnDestroy(): void {
if (this._subscription) {
this._dispose();
}
}
transform<T>(obj: Observable<T>): T|null;
transform<T>(obj: Promise<T>): T|null;
transform<T>(obj: EventEmitter<T>): T|null;
transform(obj: Observable<any>|Promise<any>|EventEmitter<any>): any {
if (!this._obj) {
if (obj) {
this._subscribe(obj);
}
this._latestReturnedValue = this._latestValue;
return this._latestValue;
}
if (obj !== this._obj) {
this._dispose();
return this.transform(obj as any);
}
if (this._latestValue === this._latestReturnedValue) {
return this._latestReturnedValue;
}
this._latestReturnedValue = this._latestValue;
return WrappedValue.wrap(this._latestValue);
}
private _subscribe(obj: Observable<any>|Promise<any>|EventEmitter<any>): void {
this._obj = obj;
this._strategy = this._selectStrategy(obj);
this._subscription = this._strategy.createSubscription(
obj, (value: Object) => this._updateLatestValue(obj, value));
}
private _selectStrategy(obj: Observable<any>|Promise<any>|EventEmitter<any>): any {
if (ɵisPromise(obj)) {
return _promiseStrategy;
}
if (ɵisObservable(obj)) {
return _observableStrategy;
}
throw invalidPipeArgumentError(AsyncPipe, obj);
}
private _dispose(): void {
this._strategy.dispose(this._subscription);
this._latestValue = null;
this._latestReturnedValue = null;
this._subscription = null;
this._obj = null;
}
private _updateLatestValue(async: any, value: Object): void {
if (async === this._obj) {
this._latestValue = value;
this._ref.markForCheck();
}
}
}

View File

@ -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 {Pipe, PipeTransform} from '@angular/core';
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
/**
* Transforms text to lowercase.
*
* {@example common/pipes/ts/lowerupper_pipe.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 invalidPipeArgumentError(LowerCasePipe, value);
}
return value.toLowerCase();
}
}
/**
* Helper method to transform a single word to titlecase.
*
* @stable
*/
function titleCaseWord(word: string) {
if (!word) return word;
return word[0].toUpperCase() + word.substr(1).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 invalidPipeArgumentError(TitleCasePipe, value);
}
return value.split(/\b/g).map(word => titleCaseWord(word)).join('');
}
}
/**
* 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 invalidPipeArgumentError(UpperCasePipe, value);
}
return value.toUpperCase();
}
}

View File

@ -0,0 +1,175 @@
/**
* @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 {Inject, LOCALE_ID, Pipe, PipeTransform} from '@angular/core';
import {DateFormatter} from './intl';
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
import {isNumeric} from './number_pipe';
const ISO8601_DATE_REGEX =
/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
// 1 2 3 4 5 6 7 8 9 10 11
/**
* @ngModule CommonModule
* @whatItDoes Formats a date according to locale rules.
* @howToUse `date_expression | date[:format]`
* @description
*
* Where:
* - `expression` is a date object or a number (milliseconds since UTC epoch) or an ISO string
* (https://www.w3.org/TR/NOTE-datetime).
* - `format` indicates which date/time components to include. The format can be predefined as
* shown below or custom as shown in the table.
* - `'medium'`: equivalent to `'yMMMdjms'` (e.g. `Sep 3, 2010, 12:05:08 PM` for `en-US`)
* - `'short'`: equivalent to `'yMdjm'` (e.g. `9/3/2010, 12:05 PM` for `en-US`)
* - `'fullDate'`: equivalent to `'yMMMMEEEEd'` (e.g. `Friday, September 3, 2010` for `en-US`)
* - `'longDate'`: equivalent to `'yMMMMd'` (e.g. `September 3, 2010` for `en-US`)
* - `'mediumDate'`: equivalent to `'yMMMd'` (e.g. `Sep 3, 2010` for `en-US`)
* - `'shortDate'`: equivalent to `'yMd'` (e.g. `9/3/2010` for `en-US`)
* - `'mediumTime'`: equivalent to `'jms'` (e.g. `12:05:08 PM` for `en-US`)
* - `'shortTime'`: equivalent to `'jm'` (e.g. `12:05 PM` for `en-US`)
*
*
* | Component | Symbol | Narrow | Short Form | Long Form | Numeric | 2-digit |
* |-----------|:------:|--------|--------------|-------------------|-----------|-----------|
* | era | G | G (A) | GGG (AD) | GGGG (Anno Domini)| - | - |
* | year | y | - | - | - | y (2015) | yy (15) |
* | month | M | L (S) | MMM (Sep) | MMMM (September) | M (9) | MM (09) |
* | day | d | - | - | - | d (3) | dd (03) |
* | weekday | E | E (S) | EEE (Sun) | EEEE (Sunday) | - | - |
* | hour | j | - | - | - | j (13) | jj (13) |
* | hour12 | h | - | - | - | h (1 PM) | hh (01 PM)|
* | hour24 | H | - | - | - | H (13) | HH (13) |
* | minute | m | - | - | - | m (5) | mm (05) |
* | second | s | - | - | - | s (9) | ss (09) |
* | timezone | z | - | - | z (Pacific Standard Time)| - | - |
* | timezone | Z | - | Z (GMT-8:00) | - | - | - |
* | timezone | a | - | a (PM) | - | - | - |
*
* In javascript, only the components specified will be respected (not the ordering,
* punctuations, ...) and details of the formatting will be dependent on the locale.
*
* Timezone of the formatted text will be the local system timezone of the end-user's machine.
*
* When the expression is a ISO string without time (e.g. 2016-09-19) the time zone offset is not
* applied and the formatted text will have the same day, month and year of the expression.
*
* WARNINGS:
* - this pipe is marked as pure hence it will not be re-evaluated when the input is mutated.
* Instead users should treat the date as an immutable object and change the reference when the
* pipe needs to re-run (this is to avoid reformatting the date on every change detection run
* which would be an expensive operation).
* - this pipe uses the Internationalization API. Therefore it is only reliable in Chrome and Opera
* browsers.
*
* ### Examples
*
* Assuming `dateObj` is (year: 2015, month: 6, day: 15, hour: 21, minute: 43, second: 11)
* in the _local_ time and locale is 'en-US':
*
* ```
* {{ dateObj | date }} // output is 'Jun 15, 2015'
* {{ dateObj | date:'medium' }} // output is 'Jun 15, 2015, 9:43:11 PM'
* {{ dateObj | date:'shortTime' }} // output is '9:43 PM'
* {{ dateObj | date:'mmss' }} // output is '43:11'
* ```
*
* {@example common/pipes/ts/date_pipe.ts region='DatePipe'}
*
* @stable
*/
@Pipe({name: 'date', pure: true})
export class DatePipe implements PipeTransform {
/** @internal */
static _ALIASES: {[key: string]: string} = {
'medium': 'yMMMdjms',
'short': 'yMdjm',
'fullDate': 'yMMMMEEEEd',
'longDate': 'yMMMMd',
'mediumDate': 'yMMMd',
'shortDate': 'yMd',
'mediumTime': 'jms',
'shortTime': 'jm'
};
constructor(@Inject(LOCALE_ID) private _locale: string) {}
transform(value: any, pattern: string = 'mediumDate'): string {
let date: Date;
if (isBlank(value) || value !== value) return null;
if (typeof value === 'string') {
value = value.trim();
}
if (isDate(value)) {
date = value;
} else if (isNumeric(value)) {
date = new Date(parseFloat(value));
} else if (typeof value === 'string' && /^(\d{4}-\d{1,2}-\d{1,2})$/.test(value)) {
/**
* For ISO Strings without time the day, month and year must be extracted from the ISO String
* before Date creation to avoid time offset and errors in the new Date.
* If we only replace '-' with ',' in the ISO String ("2015,01,01"), and try to create a new
* date, some browsers (e.g. IE 9) will throw an invalid Date error
* If we leave the '-' ("2015-01-01") and try to create a new Date("2015-01-01") the timeoffset
* is applied
* Note: ISO months are 0 for January, 1 for February, ...
*/
const [y, m, d] = value.split('-').map((val: string) => parseInt(val, 10));
date = new Date(y, m - 1, d);
} else {
date = new Date(value);
}
if (!isDate(date)) {
let match: RegExpMatchArray;
if ((typeof value === 'string') && (match = value.match(ISO8601_DATE_REGEX))) {
date = isoStringToDate(match);
} else {
throw invalidPipeArgumentError(DatePipe, value);
}
}
return DateFormatter.format(date, this._locale, DatePipe._ALIASES[pattern] || pattern);
}
}
function isBlank(obj: any): boolean {
return obj == null || obj === '';
}
function isDate(obj: any): obj is Date {
return obj instanceof Date && !isNaN(obj.valueOf());
}
function isoStringToDate(match: RegExpMatchArray): Date {
const date = new Date(0);
let tzHour = 0;
let tzMin = 0;
const dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear;
const timeSetter = match[8] ? date.setUTCHours : date.setHours;
if (match[9]) {
tzHour = toInt(match[9] + match[10]);
tzMin = toInt(match[9] + match[11]);
}
dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
const h = toInt(match[4] || '0') - tzHour;
const m = toInt(match[5] || '0') - tzMin;
const s = toInt(match[6] || '0');
const ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
timeSetter.call(date, h, m, s, ms);
return date;
}
function toInt(str: string): number {
return parseInt(str, 10);
}

View File

@ -0,0 +1,47 @@
/**
* @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 {NgLocalization, getPluralCategory} from '../localization';
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
const _INTERPOLATION_REGEXP: RegExp = /#/g;
/**
* @ngModule CommonModule
* @whatItDoes Maps a value to a string that pluralizes the value according to locale rules.
* @howToUse `expression | i18nPlural:mapping`
* @description
*
* Where:
* - `expression` is a number.
* - `mapping` is an object that mimics the ICU format, see
* http://userguide.icu-project.org/formatparse/messages
*
* ## Example
*
* {@example common/pipes/ts/i18n_pipe.ts region='I18nPluralPipeComponent'}
*
* @experimental
*/
@Pipe({name: 'i18nPlural', pure: true})
export class I18nPluralPipe implements PipeTransform {
constructor(private _localization: NgLocalization) {}
transform(value: number, pluralMap: {[count: string]: string}): string {
if (value == null) return '';
if (typeof pluralMap !== 'object' || pluralMap === null) {
throw invalidPipeArgumentError(I18nPluralPipe, pluralMap);
}
const key = getPluralCategory(value, Object.keys(pluralMap), this._localization);
return pluralMap[key].replace(_INTERPOLATION_REGEXP, value.toString());
}
}

View File

@ -0,0 +1,48 @@
/**
* @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';
/**
* @ngModule CommonModule
* @whatItDoes Generic selector that displays the string that matches the current value.
* @howToUse `expression | i18nSelect:mapping`
* @description
*
* Where `mapping` is an object that indicates the text that should be displayed
* for different values of the provided `expression`.
* If none of the keys of the mapping match the value of the `expression`, then the content
* of the `other` key is returned when present, otherwise an empty string is returned.
*
* ## Example
*
* {@example common/pipes/ts/i18n_pipe.ts region='I18nSelectPipeComponent'}
*
* @experimental
*/
@Pipe({name: 'i18nSelect', pure: true})
export class I18nSelectPipe implements PipeTransform {
transform(value: string, mapping: {[key: string]: string}): string {
if (value == null) return '';
if (typeof mapping !== 'object' || typeof value !== 'string') {
throw invalidPipeArgumentError(I18nSelectPipe, mapping);
}
if (mapping.hasOwnProperty(value)) {
return mapping[value];
}
if (mapping.hasOwnProperty('other')) {
return mapping['other'];
}
return '';
}
}

View File

@ -0,0 +1,55 @@
/**
* @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
*/
/**
* @module
* @description
* 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 {CurrencyPipe, DecimalPipe, PercentPipe} from './number_pipe';
import {SlicePipe} from './slice_pipe';
export {
AsyncPipe,
CurrencyPipe,
DatePipe,
DecimalPipe,
I18nPluralPipe,
I18nSelectPipe,
JsonPipe,
LowerCasePipe,
PercentPipe,
SlicePipe,
TitleCasePipe,
UpperCasePipe
};
/**
* A collection of Angular pipes that are likely to be used in each and every application.
*/
export const COMMON_PIPES = [
AsyncPipe,
UpperCasePipe,
LowerCasePipe,
JsonPipe,
SlicePipe,
DecimalPipe,
PercentPipe,
TitleCasePipe,
CurrencyPipe,
DatePipe,
I18nPluralPipe,
I18nSelectPipe,
];

View File

@ -0,0 +1,226 @@
/**
* @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
*/
export enum NumberFormatStyle {
Decimal,
Percent,
Currency,
}
export class NumberFormatter {
static format(
num: number, locale: string, style: NumberFormatStyle,
{minimumIntegerDigits, minimumFractionDigits, maximumFractionDigits, currency,
currencyAsSymbol = false}: {
minimumIntegerDigits?: number,
minimumFractionDigits?: number,
maximumFractionDigits?: number,
currency?: string,
currencyAsSymbol?: boolean
} = {}): string {
const options: Intl.NumberFormatOptions = {
minimumIntegerDigits,
minimumFractionDigits,
maximumFractionDigits,
style: NumberFormatStyle[style].toLowerCase()
};
if (style == NumberFormatStyle.Currency) {
options.currency = currency;
options.currencyDisplay = currencyAsSymbol ? 'symbol' : 'code';
}
return new Intl.NumberFormat(locale, options).format(num);
}
}
type DateFormatterFn = (date: Date, locale: string) => string;
const DATE_FORMATS_SPLIT =
/((?:[^yMLdHhmsazZEwGjJ']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|J+|j+|m+|s+|a|z|Z|G+|w+))(.*)/;
const PATTERN_ALIASES: {[format: string]: DateFormatterFn} = {
// Keys are quoted so they do not get renamed during closure compilation.
'yMMMdjms': datePartGetterFactory(combine([
digitCondition('year', 1),
nameCondition('month', 3),
digitCondition('day', 1),
digitCondition('hour', 1),
digitCondition('minute', 1),
digitCondition('second', 1),
])),
'yMdjm': datePartGetterFactory(combine([
digitCondition('year', 1), digitCondition('month', 1), digitCondition('day', 1),
digitCondition('hour', 1), digitCondition('minute', 1)
])),
'yMMMMEEEEd': datePartGetterFactory(combine([
digitCondition('year', 1), nameCondition('month', 4), nameCondition('weekday', 4),
digitCondition('day', 1)
])),
'yMMMMd': datePartGetterFactory(
combine([digitCondition('year', 1), nameCondition('month', 4), digitCondition('day', 1)])),
'yMMMd': datePartGetterFactory(
combine([digitCondition('year', 1), nameCondition('month', 3), digitCondition('day', 1)])),
'yMd': datePartGetterFactory(
combine([digitCondition('year', 1), digitCondition('month', 1), digitCondition('day', 1)])),
'jms': datePartGetterFactory(combine(
[digitCondition('hour', 1), digitCondition('second', 1), digitCondition('minute', 1)])),
'jm': datePartGetterFactory(combine([digitCondition('hour', 1), digitCondition('minute', 1)]))
};
const DATE_FORMATS: {[format: string]: DateFormatterFn} = {
// Keys are quoted so they do not get renamed.
'yyyy': datePartGetterFactory(digitCondition('year', 4)),
'yy': datePartGetterFactory(digitCondition('year', 2)),
'y': datePartGetterFactory(digitCondition('year', 1)),
'MMMM': datePartGetterFactory(nameCondition('month', 4)),
'MMM': datePartGetterFactory(nameCondition('month', 3)),
'MM': datePartGetterFactory(digitCondition('month', 2)),
'M': datePartGetterFactory(digitCondition('month', 1)),
'LLLL': datePartGetterFactory(nameCondition('month', 4)),
'L': datePartGetterFactory(nameCondition('month', 1)),
'dd': datePartGetterFactory(digitCondition('day', 2)),
'd': datePartGetterFactory(digitCondition('day', 1)),
'HH': digitModifier(
hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 2), false)))),
'H': hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), false))),
'hh': digitModifier(
hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 2), true)))),
'h': hourExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), true))),
'jj': datePartGetterFactory(digitCondition('hour', 2)),
'j': datePartGetterFactory(digitCondition('hour', 1)),
'mm': digitModifier(datePartGetterFactory(digitCondition('minute', 2))),
'm': datePartGetterFactory(digitCondition('minute', 1)),
'ss': digitModifier(datePartGetterFactory(digitCondition('second', 2))),
's': datePartGetterFactory(digitCondition('second', 1)),
// while ISO 8601 requires fractions to be prefixed with `.` or `,`
// we can be just safely rely on using `sss` since we currently don't support single or two digit
// fractions
'sss': datePartGetterFactory(digitCondition('second', 3)),
'EEEE': datePartGetterFactory(nameCondition('weekday', 4)),
'EEE': datePartGetterFactory(nameCondition('weekday', 3)),
'EE': datePartGetterFactory(nameCondition('weekday', 2)),
'E': datePartGetterFactory(nameCondition('weekday', 1)),
'a': hourClockExtractor(datePartGetterFactory(hour12Modify(digitCondition('hour', 1), true))),
'Z': timeZoneGetter('short'),
'z': timeZoneGetter('long'),
'ww': datePartGetterFactory({}), // Week of year, padded (00-53). Week 01 is the week with the
// first Thursday of the year. not support ?
'w':
datePartGetterFactory({}), // Week of year (0-53). Week 1 is the week with the first Thursday
// of the year not support ?
'G': datePartGetterFactory(nameCondition('era', 1)),
'GG': datePartGetterFactory(nameCondition('era', 2)),
'GGG': datePartGetterFactory(nameCondition('era', 3)),
'GGGG': datePartGetterFactory(nameCondition('era', 4))
};
function digitModifier(inner: DateFormatterFn): DateFormatterFn {
return function(date: Date, locale: string): string {
const result = inner(date, locale);
return result.length == 1 ? '0' + result : result;
};
}
function hourClockExtractor(inner: DateFormatterFn): DateFormatterFn {
return function(date: Date, locale: string): string { return inner(date, locale).split(' ')[1]; };
}
function hourExtractor(inner: DateFormatterFn): DateFormatterFn {
return function(date: Date, locale: string): string { return inner(date, locale).split(' ')[0]; };
}
function intlDateFormat(date: Date, locale: string, options: Intl.DateTimeFormatOptions): string {
return new Intl.DateTimeFormat(locale, options).format(date).replace(/[\u200e\u200f]/g, '');
}
function timeZoneGetter(timezone: string): DateFormatterFn {
// To workaround `Intl` API restriction for single timezone let format with 24 hours
const options = {hour: '2-digit', hour12: false, timeZoneName: timezone};
return function(date: Date, locale: string): string {
const result = intlDateFormat(date, locale, options);
// Then extract first 3 letters that related to hours
return result ? result.substring(3) : '';
};
}
function hour12Modify(
options: Intl.DateTimeFormatOptions, value: boolean): Intl.DateTimeFormatOptions {
options.hour12 = value;
return options;
}
function digitCondition(prop: string, len: number): Intl.DateTimeFormatOptions {
const result: {[k: string]: string} = {};
result[prop] = len === 2 ? '2-digit' : 'numeric';
return result;
}
function nameCondition(prop: string, len: number): Intl.DateTimeFormatOptions {
const result: {[k: string]: string} = {};
if (len < 4) {
result[prop] = len > 1 ? 'short' : 'narrow';
} else {
result[prop] = 'long';
}
return result;
}
function combine(options: Intl.DateTimeFormatOptions[]): Intl.DateTimeFormatOptions {
return (<any>Object).assign({}, ...options);
}
function datePartGetterFactory(ret: Intl.DateTimeFormatOptions): DateFormatterFn {
return (date: Date, locale: string): string => intlDateFormat(date, locale, ret);
}
const DATE_FORMATTER_CACHE = new Map<string, string[]>();
function dateFormatter(format: string, date: Date, locale: string): string {
const fn = PATTERN_ALIASES[format];
if (fn) return fn(date, locale);
const cacheKey = format;
let parts = DATE_FORMATTER_CACHE.get(cacheKey);
if (!parts) {
parts = [];
let match: RegExpExecArray;
DATE_FORMATS_SPLIT.exec(format);
while (format) {
match = DATE_FORMATS_SPLIT.exec(format);
if (match) {
parts = parts.concat(match.slice(1));
format = parts.pop();
} else {
parts.push(format);
format = null;
}
}
DATE_FORMATTER_CACHE.set(cacheKey, parts);
}
return parts.reduce((text, part) => {
const fn = DATE_FORMATS[part];
return text + (fn ? fn(date, locale) : partToTime(part));
}, '');
}
function partToTime(part: string): string {
return part === '\'\'' ? '\'' : part.replace(/(^'|'$)/g, '').replace(/''/g, '\'');
}
export class DateFormatter {
static format(date: Date, locale: string, pattern: string): string {
return dateFormatter(pattern, date, locale);
}
}

View File

@ -0,0 +1,13 @@
/**
* @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 {Type, ɵstringify as stringify} from '@angular/core';
export function invalidPipeArgumentError(type: Type<any>, value: Object) {
return Error(`InvalidPipeArgument: '${value}' for pipe '${stringify(type)}'`);
}

View File

@ -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 {Pipe, PipeTransform} from '@angular/core';
/**
* @ngModule CommonModule
* @whatItDoes Converts value into JSON string.
* @howToUse `expression | json`
* @description
*
* Converts value into string using `JSON.stringify`. Useful for debugging.
*
* ### Example
* {@example common/pipes/ts/json_pipe.ts region='JsonPipe'}
*
* @stable
*/
@Pipe({name: 'json', pure: false})
export class JsonPipe implements PipeTransform {
transform(value: any): string { return JSON.stringify(value, null, 2); }
}

View File

@ -0,0 +1,173 @@
/**
* @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 {Inject, LOCALE_ID, Pipe, PipeTransform, Type} from '@angular/core';
import {NumberFormatStyle, NumberFormatter} from './intl';
import {invalidPipeArgumentError} from './invalid_pipe_argument_error';
const _NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(-(\d+))?)?$/;
function formatNumber(
pipe: Type<any>, locale: string, value: number | string, style: NumberFormatStyle,
digits: string, currency: string = null, currencyAsSymbol: boolean = false): string {
if (value == null) return null;
// Convert strings to numbers
value = typeof value === 'string' && isNumeric(value) ? +value : value;
if (typeof value !== 'number') {
throw invalidPipeArgumentError(pipe, value);
}
let minInt: number;
let minFraction: number;
let maxFraction: number;
if (style !== NumberFormatStyle.Currency) {
// rely on Intl default for currency
minInt = 1;
minFraction = 0;
maxFraction = 3;
}
if (digits) {
const parts = digits.match(_NUMBER_FORMAT_REGEXP);
if (parts === null) {
throw new Error(`${digits} is not a valid digit info for number pipes`);
}
if (parts[1] != null) { // min integer digits
minInt = parseIntAutoRadix(parts[1]);
}
if (parts[3] != null) { // min fraction digits
minFraction = parseIntAutoRadix(parts[3]);
}
if (parts[5] != null) { // max fraction digits
maxFraction = parseIntAutoRadix(parts[5]);
}
}
return NumberFormatter.format(value as number, locale, style, {
minimumIntegerDigits: minInt,
minimumFractionDigits: minFraction,
maximumFractionDigits: maxFraction,
currency: currency,
currencyAsSymbol: currencyAsSymbol,
});
}
/**
* @ngModule CommonModule
* @whatItDoes Formats a number according to locale rules.
* @howToUse `number_expression | number[:digitInfo]`
*
* Formats a number as text. Group sizing and separator and other locale-specific
* configurations are based on the active locale.
*
* where `expression` is a number:
* - `digitInfo` is a `string` which has a following format: <br>
* <code>{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}</code>
* - `minIntegerDigits` is the minimum number of integer digits to use. Defaults to `1`.
* - `minFractionDigits` is the minimum number of digits after fraction. Defaults to `0`.
* - `maxFractionDigits` is the maximum number of digits after fraction. Defaults to `3`.
*
* For more information on the acceptable range for each of these numbers and other
* details see your native internationalization library.
*
* WARNING: this pipe uses the Internationalization API which is not yet available in all browsers
* and may require a polyfill. See {@linkDocs guide/browser-support} for details.
*
* ### Example
*
* {@example common/pipes/ts/number_pipe.ts region='NumberPipe'}
*
* @stable
*/
@Pipe({name: 'number'})
export class DecimalPipe implements PipeTransform {
constructor(@Inject(LOCALE_ID) private _locale: string) {}
transform(value: any, digits: string = null): string {
return formatNumber(DecimalPipe, this._locale, value, NumberFormatStyle.Decimal, digits);
}
}
/**
* @ngModule CommonModule
* @whatItDoes Formats a number as a percentage according to locale rules.
* @howToUse `number_expression | percent[:digitInfo]`
*
* @description
*
* Formats a number as percentage.
*
* - `digitInfo` See {@link DecimalPipe} for detailed description.
*
* WARNING: this pipe uses the Internationalization API which is not yet available in all browsers
* and may require a polyfill. See {@linkDocs guide/browser-support} for details.
*
* ### Example
*
* {@example common/pipes/ts/number_pipe.ts region='PercentPipe'}
*
* @stable
*/
@Pipe({name: 'percent'})
export class PercentPipe implements PipeTransform {
constructor(@Inject(LOCALE_ID) private _locale: string) {}
transform(value: any, digits: string = null): string {
return formatNumber(PercentPipe, this._locale, value, NumberFormatStyle.Percent, digits);
}
}
/**
* @ngModule CommonModule
* @whatItDoes Formats a number as currency using locale rules.
* @howToUse `number_expression | currency[:currencyCode[:symbolDisplay[:digitInfo]]]`
* @description
*
* Use `currency` to format a number as currency.
*
* - `currencyCode` is the [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currency code, such
* as `USD` for the US dollar and `EUR` for the euro.
* - `symbolDisplay` is a boolean indicating whether to use the currency symbol or code.
* - `true`: use symbol (e.g. `$`).
* - `false`(default): use code (e.g. `USD`).
* - `digitInfo` See {@link DecimalPipe} for detailed description.
*
* WARNING: this pipe uses the Internationalization API which is not yet available in all browsers
* and may require a polyfill. See {@linkDocs guide/browser-support} for details.
*
* ### Example
*
* {@example common/pipes/ts/number_pipe.ts region='CurrencyPipe'}
*
* @stable
*/
@Pipe({name: 'currency'})
export class CurrencyPipe implements PipeTransform {
constructor(@Inject(LOCALE_ID) private _locale: string) {}
transform(
value: any, currencyCode: string = 'USD', symbolDisplay: boolean = false,
digits: string = null): string {
return formatNumber(
CurrencyPipe, this._locale, value, NumberFormatStyle.Currency, digits, currencyCode,
symbolDisplay);
}
}
function parseIntAutoRadix(text: string): number {
const result: number = parseInt(text);
if (isNaN(result)) {
throw new Error('Invalid integer literal when parsing ' + text);
}
return result;
}
export function isNumeric(value: any): boolean {
return !isNaN(value - parseFloat(value));
}

View File

@ -0,0 +1,70 @@
/**
* @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';
/**
* @ngModule CommonModule
* @whatItDoes Creates a new List or String containing a subset (slice) of the elements.
* @howToUse `array_or_string_expression | slice:start[:end]`
* @description
*
* Where the input expression is a `List` or `String`, and:
* - `start`: The starting index of the subset to return.
* - **a positive integer**: return the item at `start` index and all items after
* in the list or string expression.
* - **a negative integer**: return the item at `start` index from the end and all items after
* in the list or string expression.
* - **if positive and greater than the size of the expression**: return an empty list or string.
* - **if negative and greater than the size of the expression**: return entire list or string.
* - `end`: The ending index of the subset to return.
* - **omitted**: return all items until the end.
* - **if positive**: return all items before `end` index of the list or string.
* - **if negative**: return all items before `end` index from the end of the list or string.
*
* All behavior is based on the expected behavior of the JavaScript API `Array.prototype.slice()`
* and `String.prototype.slice()`.
*
* When operating on a [List], the returned list is always a copy even when all
* the elements are being returned.
*
* When operating on a blank value, the pipe returns the blank value.
*
* ## List Example
*
* This `ngFor` example:
*
* {@example common/pipes/ts/slice_pipe.ts region='SlicePipe_list'}
*
* produces the following:
*
* <li>b</li>
* <li>c</li>
*
* ## String Examples
*
* {@example common/pipes/ts/slice_pipe.ts region='SlicePipe_string'}
*
* @stable
*/
@Pipe({name: 'slice', pure: false})
export class SlicePipe implements PipeTransform {
transform(value: any, start: number, end?: number): any {
if (value == null) return value;
if (!this.supports(value)) {
throw invalidPipeArgumentError(SlicePipe, value);
}
return value.slice(start, end);
}
private supports(obj: any): boolean { return typeof obj === 'string' || Array.isArray(obj); }
}

View File

@ -0,0 +1,44 @@
/**
* @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
*/
export const PLATFORM_BROWSER_ID = 'browser';
export const PLATFORM_SERVER_ID = 'server';
export const PLATFORM_WORKER_APP_ID = 'browserWorkerApp';
export const PLATFORM_WORKER_UI_ID = 'browserWorkerUi';
/**
* Returns whether a platform id represents a browser platform.
* @experimental
*/
export function isPlatformBrowser(platformId: Object): boolean {
return platformId === PLATFORM_BROWSER_ID;
}
/**
* Returns whether a platform id represents a server platform.
* @experimental
*/
export function isPlatformServer(platformId: Object): boolean {
return platformId === PLATFORM_SERVER_ID;
}
/**
* Returns whether a platform id represents a web worker app platform.
* @experimental
*/
export function isPlatformWorkerApp(platformId: Object): boolean {
return platformId === PLATFORM_WORKER_APP_ID;
}
/**
* Returns whether a platform id represents a web worker UI platform.
* @experimental
*/
export function isPlatformWorkerUi(platformId: Object): boolean {
return platformId === PLATFORM_WORKER_UI_ID;
}

View File

@ -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
*/
/**
* @module
* @description
* Entry point for all public APIs of the common package.
*/
import {Version} from '@angular/core';
/**
* @stable
*/
export const VERSION = new Version('0.0.0-PLACEHOLDER');