Skip to content

Commit 8eb567e

Browse files
committed
fix(jsverse#632): locale pipes optimizations
1 parent 6daa2ea commit 8eb567e

File tree

5 files changed

+284
-238
lines changed

5 files changed

+284
-238
lines changed
Lines changed: 64 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,64 @@
1-
import {
2-
ChangeDetectorRef,
3-
inject,
4-
Injectable,
5-
OnDestroy,
6-
} from '@angular/core';
7-
import { Subscription } from 'rxjs';
8-
9-
import { Locale } from '../../lib/transloco-locale.types';
10-
import { TranslocoLocaleService } from '../transloco-locale.service';
11-
12-
type Deps = [TranslocoLocaleService, ChangeDetectorRef];
13-
@Injectable()
14-
export abstract class BaseLocalePipe implements OnDestroy {
15-
protected localeService = inject(TranslocoLocaleService);
16-
protected cdr = inject(ChangeDetectorRef);
17-
18-
private localeChangeSub: Subscription | null =
19-
this.localeService.localeChanges$.subscribe(() => this.cdr.markForCheck());
20-
21-
protected getLocale(locale?: Locale): Locale {
22-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
23-
return locale || this.localeService.getLocale()!;
24-
}
25-
26-
ngOnDestroy(): void {
27-
this.localeChangeSub?.unsubscribe();
28-
// Caretaker note: it's important to clean up references to subscriptions since they save the `next`
29-
// callback within its `destination` property, preventing classes from being GC'd.
30-
this.localeChangeSub = null;
31-
}
32-
}
1+
import {
2+
ChangeDetectorRef,
3+
inject,
4+
Injectable,
5+
OnDestroy,
6+
} from '@angular/core';
7+
import { Subscription } from 'rxjs';
8+
9+
import { Locale } from '../../lib/transloco-locale.types';
10+
import { TranslocoLocaleService } from '../transloco-locale.service';
11+
12+
type Deps = [TranslocoLocaleService, ChangeDetectorRef];
13+
@Injectable()
14+
export abstract class BaseLocalePipe<VALUE = unknown, ARGS extends unknown[] = []> implements OnDestroy {
15+
protected localeService = inject(TranslocoLocaleService);
16+
protected cdr = inject(ChangeDetectorRef);
17+
18+
private localeChangeSub: Subscription | null =
19+
this.localeService.localeChanges$.subscribe(() => this.invalidate());
20+
21+
protected lastValue?: VALUE;
22+
protected lastArgs?: string;
23+
24+
protected lastResult = '';
25+
26+
protected getLocale(locale?: Locale): Locale {
27+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
28+
return locale || this.localeService.getLocale()!;
29+
}
30+
31+
transform(value: VALUE, ...args: ARGS): string {
32+
if (this.isSameValue(value) && this.isSameArgs(...args)) {
33+
return this.lastResult;
34+
}
35+
this.lastResult = this.doTransform(value, ...args);
36+
this.lastValue = value;
37+
this.lastArgs = JSON.stringify(args);
38+
return this.lastResult;
39+
}
40+
41+
protected abstract doTransform(value: VALUE, ...args: ARGS): string;
42+
43+
protected isSameValue(value: VALUE): boolean {
44+
return this.lastValue === value;
45+
}
46+
47+
protected isSameArgs(...args: ARGS): boolean {
48+
return JSON.stringify(args) === this.lastArgs;
49+
}
50+
51+
invalidate() {
52+
this.lastValue = undefined;
53+
this.lastArgs = undefined;
54+
this.lastResult = '';
55+
this.cdr.markForCheck();
56+
}
57+
58+
ngOnDestroy(): void {
59+
this.localeChangeSub?.unsubscribe();
60+
// Caretaker note: it's important to clean up references to subscriptions since they save the `next`
61+
// callback within its `destination` property, preventing classes from being GC'd.
62+
this.localeChangeSub = null;
63+
}
64+
}
Lines changed: 66 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,66 @@
1-
import { inject, Pipe, PipeTransform } from '@angular/core';
2-
import { isNil } from '@ngneat/transloco';
3-
4-
import { getDefaultOptions } from '../shared';
5-
import { TRANSLOCO_LOCALE_CONFIG } from '../transloco-locale.config';
6-
import {
7-
Currency,
8-
Locale,
9-
LocaleConfig,
10-
NumberFormatOptions,
11-
} from '../transloco-locale.types';
12-
13-
import { BaseLocalePipe } from './base-locale.pipe';
14-
15-
@Pipe({
16-
name: 'translocoCurrency',
17-
pure: false,
18-
standalone: true,
19-
})
20-
export class TranslocoCurrencyPipe
21-
extends BaseLocalePipe
22-
implements PipeTransform
23-
{
24-
private localeConfig: LocaleConfig = inject(TRANSLOCO_LOCALE_CONFIG);
25-
26-
/**
27-
* Transform a given number into the locale's currency format.
28-
*
29-
* @example
30-
*
31-
* 1000000 | translocoCurrency: 'symbol' : {} : USD // $1,000,000.00
32-
* 1000000 | translocoCurrency: 'name' : {} : USD // 1,000,000.00 US dollars
33-
* 1000000 | translocoCurrency: 'symbol' : {minimumFractionDigits: 0 } : USD // $1,000,000
34-
* 1000000 | translocoCurrency: 'symbol' : {minimumFractionDigits: 0 } : CAD // CA$1,000,000
35-
* 1000000 | translocoCurrency: 'narrowSymbol' : {minimumFractionDigits: 0 } : CAD // $1,000,000
36-
*
37-
*/
38-
transform(
39-
value: number | string,
40-
display: 'code' | 'symbol' | 'narrowSymbol' | 'name' = 'symbol',
41-
numberFormatOptions: NumberFormatOptions = {},
42-
currencyCode?: Currency,
43-
locale?: Locale
44-
): string {
45-
if (isNil(value)) return '';
46-
locale = this.getLocale(locale);
47-
48-
const options = {
49-
...getDefaultOptions(locale, 'currency', this.localeConfig),
50-
...numberFormatOptions,
51-
currencyDisplay: display,
52-
currency: currencyCode || this.localeService._resolveCurrencyCode(),
53-
};
54-
55-
return this.localeService.localizeNumber(
56-
value,
57-
'currency',
58-
locale,
59-
options
60-
);
61-
}
62-
}
1+
import { inject, Pipe, PipeTransform } from '@angular/core';
2+
import { isNil } from '@ngneat/transloco';
3+
4+
import { getDefaultOptions } from '../shared';
5+
import { TRANSLOCO_LOCALE_CONFIG } from '../transloco-locale.config';
6+
import {
7+
Currency,
8+
Locale,
9+
LocaleConfig,
10+
NumberFormatOptions,
11+
} from '../transloco-locale.types';
12+
13+
import { BaseLocalePipe } from './base-locale.pipe';
14+
15+
@Pipe({
16+
name: 'translocoCurrency',
17+
pure: false,
18+
standalone: true,
19+
})
20+
export class TranslocoCurrencyPipe
21+
extends BaseLocalePipe<number | string, [
22+
display?: 'code' | 'symbol' | 'name',
23+
numberFormatOptions?: NumberFormatOptions,
24+
currencyCode?: Currency,
25+
locale?: Locale]>
26+
implements PipeTransform
27+
{
28+
private localeConfig: LocaleConfig = inject(TRANSLOCO_LOCALE_CONFIG);
29+
30+
/**
31+
* Transform a given number into the locale's currency format.
32+
*
33+
* @example
34+
*
35+
* 1000000 | translocoCurrency: 'symbol' : {} : USD // $1,000,000.00
36+
* 1000000 | translocoCurrency: 'name' : {} : USD // 1,000,000.00 US dollars
37+
* 1000000 | translocoCurrency: 'symbol' : {minimumFractionDigits: 0 } : USD // $1,000,000
38+
* 1000000 | translocoCurrency: 'symbol' : {minimumFractionDigits: 0 } : CAD // CA$1,000,000
39+
* 1000000 | translocoCurrency: 'narrowSymbol' : {minimumFractionDigits: 0 } : CAD // $1,000,000
40+
*
41+
*/
42+
protected override doTransform(
43+
value: number | string,
44+
display: 'code' | 'symbol' | 'narrowSymbol' | 'name' = 'symbol',
45+
numberFormatOptions: NumberFormatOptions = {},
46+
currencyCode?: Currency,
47+
locale?: Locale
48+
): string {
49+
if (isNil(value)) return '';
50+
locale = this.getLocale(locale);
51+
52+
const options = {
53+
...getDefaultOptions(locale, 'currency', this.localeConfig),
54+
...numberFormatOptions,
55+
currencyDisplay: display,
56+
currency: currencyCode || this.localeService._resolveCurrencyCode(),
57+
};
58+
59+
return this.localeService.localizeNumber(
60+
value,
61+
'currency',
62+
locale,
63+
options
64+
);
65+
}
66+
}
Lines changed: 56 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,56 @@
1-
import { inject, Pipe, PipeTransform } from '@angular/core';
2-
import { isNil } from '@ngneat/transloco';
3-
4-
import { getDefaultOptions } from '../shared';
5-
import { TRANSLOCO_LOCALE_CONFIG } from '../transloco-locale.config';
6-
import {
7-
DateFormatOptions,
8-
Locale,
9-
LocaleConfig,
10-
ValidDate,
11-
} from '../transloco-locale.types';
12-
13-
import { BaseLocalePipe } from './base-locale.pipe';
14-
15-
@Pipe({
16-
name: 'translocoDate',
17-
pure: false,
18-
standalone: true,
19-
})
20-
export class TranslocoDatePipe extends BaseLocalePipe implements PipeTransform {
21-
private localeConfig: LocaleConfig = inject(TRANSLOCO_LOCALE_CONFIG);
22-
23-
/**
24-
* Transform a date into the locale's date format.
25-
*
26-
* The date expression: a `Date` object, a number
27-
* (milliseconds since UTC epoch), or an ISO string (https://www.w3.org/TR/NOTE-datetime).
28-
*
29-
* @example
30-
*
31-
* date | translocoDate: {} : en-US // 9/10/2019
32-
* date | translocoDate: { dateStyle: 'medium', timeStyle: 'medium' } : en-US // Sep 10, 2019, 10:46:12 PM
33-
* date | translocoDate: { timeZone: 'UTC', timeStyle: 'full' } : en-US // 7:40:32 PM Coordinated Universal Time
34-
* 1 | translocoDate: { dateStyle: 'medium' } // Jan 1, 1970
35-
* '2019-02-08' | translocoDate: { dateStyle: 'medium' } // Feb 8, 2019
36-
*/
37-
transform(date: ValidDate, options: DateFormatOptions = {}, locale?: Locale) {
38-
if (isNil(date)) return '';
39-
locale = this.getLocale(locale);
40-
41-
return this.localeService.localizeDate(date, locale, {
42-
...getDefaultOptions(locale, 'date', this.localeConfig),
43-
...options,
44-
});
45-
}
46-
}
1+
import { inject, Pipe, PipeTransform } from '@angular/core';
2+
import { isNil } from '@ngneat/transloco';
3+
4+
import { getDefaultOptions } from '../shared';
5+
import { TRANSLOCO_LOCALE_CONFIG } from '../transloco-locale.config';
6+
import {
7+
DateFormatOptions,
8+
Locale,
9+
LocaleConfig,
10+
ValidDate,
11+
} from '../transloco-locale.types';
12+
13+
import { BaseLocalePipe } from './base-locale.pipe';
14+
15+
@Pipe({
16+
name: 'translocoDate',
17+
pure: false,
18+
standalone: true,
19+
})
20+
export class TranslocoDatePipe
21+
extends BaseLocalePipe<ValidDate, [options?: DateFormatOptions, locale?: Locale]>
22+
implements PipeTransform {
23+
private localeConfig: LocaleConfig = inject(TRANSLOCO_LOCALE_CONFIG);
24+
25+
/**
26+
* Transform a date into the locale's date format.
27+
*
28+
* The date expression: a `Date` object, a number
29+
* (milliseconds since UTC epoch), or an ISO string (https://www.w3.org/TR/NOTE-datetime).
30+
*
31+
* @example
32+
*
33+
* date | translocoDate: {} : en-US // 9/10/2019
34+
* date | translocoDate: { dateStyle: 'medium', timeStyle: 'medium' } : en-US // Sep 10, 2019, 10:46:12 PM
35+
* date | translocoDate: { timeZone: 'UTC', timeStyle: 'full' } : en-US // 7:40:32 PM Coordinated Universal Time
36+
* 1 | translocoDate: { dateStyle: 'medium' } // Jan 1, 1970
37+
* '2019-02-08' | translocoDate: { dateStyle: 'medium' } // Feb 8, 2019
38+
*/
39+
protected override doTransform(date: ValidDate, options: DateFormatOptions = {}, locale?: Locale) {
40+
if (isNil(date)) return '';
41+
locale = this.getLocale(locale);
42+
43+
return this.localeService.localizeDate(date, locale, {
44+
...getDefaultOptions(locale, 'date', this.localeConfig),
45+
...options,
46+
});
47+
}
48+
49+
protected override isSameValue(value?: ValidDate) {
50+
return this.getComparableDate(this.lastValue) === this.getComparableDate(value);
51+
}
52+
53+
private getComparableDate(value?: any) {
54+
return value?.getTime ? value.getTime() : value;
55+
}
56+
}

0 commit comments

Comments
 (0)