Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plural): add plural and select pipes #7268

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 3 additions & 29 deletions modules/angular2/src/common/pipes.ts
Expand Up @@ -3,15 +3,6 @@
* @description
* This module provides a set of common Pipes.
*/
import {AsyncPipe} from './pipes/async_pipe';
import {UpperCasePipe} from './pipes/uppercase_pipe';
import {LowerCasePipe} from './pipes/lowercase_pipe';
import {JsonPipe} from './pipes/json_pipe';
import {SlicePipe} from './pipes/slice_pipe';
import {DatePipe} from './pipes/date_pipe';
import {DecimalPipe, PercentPipe, CurrencyPipe} from './pipes/number_pipe';
import {ReplacePipe} from './pipes/replace_pipe';
import {CONST_EXPR} from 'angular2/src/facade/lang';

export {AsyncPipe} from './pipes/async_pipe';
export {DatePipe} from './pipes/date_pipe';
Expand All @@ -21,23 +12,6 @@ export {LowerCasePipe} from './pipes/lowercase_pipe';
export {NumberPipe, DecimalPipe, PercentPipe, CurrencyPipe} from './pipes/number_pipe';
export {UpperCasePipe} from './pipes/uppercase_pipe';
export {ReplacePipe} from './pipes/replace_pipe';

/**
* A collection of Angular core pipes that are likely to be used in each and every
* application.
*
* This collection can be used to quickly enumerate all the built-in pipes in the `pipes`
* property of the `@Component` or `@View` decorators.
*/
export const COMMON_PIPES = CONST_EXPR([
AsyncPipe,
UpperCasePipe,
LowerCasePipe,
JsonPipe,
SlicePipe,
DecimalPipe,
PercentPipe,
CurrencyPipe,
DatePipe,
ReplacePipe
]);
export {I18nPluralPipe} from './pipes/i18n_plural_pipe';
export {I18nSelectPipe} from './pipes/i18n_select_pipe';
export {COMMON_PIPES} from './pipes/common_pipes';
6 changes: 5 additions & 1 deletion modules/angular2/src/common/pipes/common_pipes.ts
Expand Up @@ -11,6 +11,8 @@ import {SlicePipe} from './slice_pipe';
import {DatePipe} from './date_pipe';
import {DecimalPipe, PercentPipe, CurrencyPipe} from './number_pipe';
import {ReplacePipe} from './replace_pipe';
import {I18nPluralPipe} from './i18n_plural_pipe';
import {I18nSelectPipe} from './i18n_select_pipe';
import {CONST_EXPR} from 'angular2/src/facade/lang';

/**
Expand All @@ -30,5 +32,7 @@ export const COMMON_PIPES = CONST_EXPR([
PercentPipe,
CurrencyPipe,
DatePipe,
ReplacePipe
ReplacePipe,
I18nPluralPipe,
I18nSelectPipe
]);
62 changes: 62 additions & 0 deletions modules/angular2/src/common/pipes/i18n_plural_pipe.ts
@@ -0,0 +1,62 @@
import {
CONST,
isStringMap,
StringWrapper,
isPresent,
RegExpWrapper
} from 'angular2/src/facade/lang';
import {Injectable, PipeTransform, Pipe} from 'angular2/core';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';

var interpolationExp: RegExp = RegExpWrapper.create('#');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it a const

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(we discussed and decided to leave as is)


/**
*
* Maps a value to a string that pluralizes the value properly.
*
* ## Usage
*
* expression | i18nPlural:mapping
*
* where `expression` is a number and `mapping` is an object that indicates the proper text for
* when the `expression` evaluates to 0, 1, or some other number. You can interpolate the actual
* value into the text using the `#` sign.
*
* ## Example
*
* ```
* <div>
* {{ messages.length | i18nPlural: messageMapping }}
* </div>
*
* class MyApp {
* messages: any[];
* messageMapping: any = {
* '=0': 'No messages.',
* '=1': 'One message.',
* 'other': '# messages.'
* }
* ...
* }
* ```
*
*/
@CONST()
@Pipe({name: 'i18nPlural', pure: true})
@Injectable()
export class I18nPluralPipe implements PipeTransform {
transform(value: number, args: any[] = null): string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use {}[] or Object[]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to do so, but the compiler didn't like it

var key: string;
var valueStr: string;
var pluralMap: {[count: string]: string} = args[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if you are aware, but it will be compiled into:

Map<String,Object> pluralMap = args[0];

This will throw in the checked mode in Dart. So the next if statement will never execute.

Is it your intent?


if (!isStringMap(pluralMap)) {
throw new InvalidPipeArgumentException(I18nPluralPipe, pluralMap);
}

key = value === 0 || value === 1 ? `=${value}` : 'other';
valueStr = isPresent(value) ? value.toString() : '';

return StringWrapper.replaceAll(pluralMap[key], interpolationExp, valueStr);
}
}
47 changes: 47 additions & 0 deletions modules/angular2/src/common/pipes/i18n_select_pipe.ts
@@ -0,0 +1,47 @@
import {CONST, isStringMap} from 'angular2/src/facade/lang';
import {StringMapWrapper} from 'angular2/src/facade/collection';
import {Injectable, PipeTransform, Pipe} from 'angular2/core';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';

/**
*
* Generic selector that displays the string that matches the current value.
*
* ## Usage
*
* expression | i18nSelect:mapping
*
* where `mapping` is an object that indicates the text that should be displayed
* for different values of the provided `expression`.
*
* ## Example
*
* ```
* <div>
* {{ gender | i18nSelect: inviteMap }}
* </div>
*
* class MyApp {
* gender: string = 'male';
* inviteMap: any = {
* 'male': 'Invite her.',
* 'female': 'Invite him.',
* 'other': 'Invite them.'
* }
* ...
* }
* ```
*/
@CONST()
@Pipe({name: 'i18nSelect', pure: true})
@Injectable()
export class I18nSelectPipe implements PipeTransform {
transform(value: string, args: any[] = null): string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use {}[] or Object[]

var mapping: {[key: string]: string} = args[0];
if (!isStringMap(mapping)) {
throw new InvalidPipeArgumentException(I18nSelectPipe, mapping);
}

return StringMapWrapper.contains(mapping, value) ? mapping[value] : mapping['other'];
}
}
59 changes: 59 additions & 0 deletions modules/angular2/test/common/pipes/i18n_plural_pipe_spec.ts
@@ -0,0 +1,59 @@
import {
ddescribe,
describe,
it,
iit,
xit,
expect,
beforeEach,
afterEach
} from 'angular2/testing_internal';

import {I18nPluralPipe} from 'angular2/common';
import {PipeResolver} from 'angular2/src/core/linker/pipe_resolver';

export function main() {
describe("I18nPluralPipe", () => {
var pipe;
var mapping = {'=0': 'No messages.', '=1': 'One message.', 'other': 'There are some messages.'};
var interpolatedMapping =
{'=0': 'No messages.', '=1': 'One message.', 'other': 'There are # messages, that is #.'};

beforeEach(() => { pipe = new I18nPluralPipe(); });

it('should be marked as pure',
() => { expect(new PipeResolver().resolve(I18nPluralPipe).pure).toEqual(true); });

describe("transform", () => {
it("should return 0 text if value is 0", () => {
var val = pipe.transform(0, [mapping]);
expect(val).toEqual('No messages.');
});

it("should return 1 text if value is 1", () => {
var val = pipe.transform(1, [mapping]);
expect(val).toEqual('One message.');
});

it("should return other text if value is anything other than 0 or 1", () => {
var val = pipe.transform(6, [mapping]);
expect(val).toEqual('There are some messages.');
});

it("should interpolate the value into the text where indicated", () => {
var val = pipe.transform(6, [interpolatedMapping]);
expect(val).toEqual('There are 6 messages, that is 6.');
});

it("should use 'other' if value is undefined", () => {
var messageLength;
var val = pipe.transform(messageLength, [interpolatedMapping]);
expect(val).toEqual('There are messages, that is .');
});

it("should not support bad arguments",
() => { expect(() => pipe.transform(0, ['hey'])).toThrowError(); });
});

});
}
52 changes: 52 additions & 0 deletions modules/angular2/test/common/pipes/i18n_select_pipe_spec.ts
@@ -0,0 +1,52 @@
import {
ddescribe,
describe,
it,
iit,
xit,
expect,
beforeEach,
afterEach
} from 'angular2/testing_internal';

import {I18nSelectPipe} from 'angular2/common';
import {PipeResolver} from 'angular2/src/core/linker/pipe_resolver';

export function main() {
describe("I18nSelectPipe", () => {
var pipe;
var mapping = {'male': 'Invite him.', 'female': 'Invite her.', 'other': 'Invite them.'};

beforeEach(() => { pipe = new I18nSelectPipe(); });

it('should be marked as pure',
() => { expect(new PipeResolver().resolve(I18nSelectPipe).pure).toEqual(true); });

describe("transform", () => {
it("should return male text if value is male", () => {
var val = pipe.transform('male', [mapping]);
expect(val).toEqual('Invite him.');
});

it("should return female text if value is female", () => {
var val = pipe.transform('female', [mapping]);
expect(val).toEqual('Invite her.');
});

it("should return other text if value is anything other than male or female", () => {
var val = pipe.transform('Anything else', [mapping]);
expect(val).toEqual('Invite them.');
});

it("should use 'other' if value is undefined", () => {
var gender;
var val = pipe.transform(gender, [mapping]);
expect(val).toEqual('Invite them.');
});

it("should not support bad arguments",
() => { expect(() => pipe.transform('male', ['hey'])).toThrowError(); });
});

});
}
4 changes: 4 additions & 0 deletions modules/angular2/test/public_api_spec.ts
Expand Up @@ -208,6 +208,10 @@ var NG_COMMON = [
'FormBuilder.array()',
'FormBuilder.control()',
'FormBuilder.group()',
'I18nPluralPipe',
'I18nPluralPipe.transform()',
'I18nSelectPipe',
'I18nSelectPipe.transform()',
'JsonPipe',
'JsonPipe.transform()',
'LowerCasePipe',
Expand Down
4 changes: 4 additions & 0 deletions tools/public_api_guard/public_api_spec.ts
Expand Up @@ -642,6 +642,10 @@ const COMMON = [
'FormBuilder.array(controlsConfig:any[], validator:Function, asyncValidator:Function):ControlArray',
'FormBuilder.control(value:Object, validator:Function, asyncValidator:Function):Control',
'FormBuilder.group(controlsConfig:{[key:string]:any}, extra:{[key:string]:any}):ControlGroup',
'I18nPluralPipe',
'I18nPluralPipe.transform(value:number, args:any[]):string',
'I18nSelectPipe',
'I18nSelectPipe.transform(value:string, args:any[]):string',
'JsonPipe',
'JsonPipe.transform(value:any, args:any[]):string',
'LowerCasePipe',
Expand Down