Skip to content

Commit

Permalink
fix(#632): locale pipes optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
ben12 committed Oct 26, 2023
1 parent 6daa2ea commit 8eb567e
Show file tree
Hide file tree
Showing 5 changed files with 284 additions and 238 deletions.
96 changes: 64 additions & 32 deletions libs/transloco-locale/src/lib/pipes/base-locale.pipe.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,64 @@
import {
ChangeDetectorRef,
inject,
Injectable,
OnDestroy,
} from '@angular/core';
import { Subscription } from 'rxjs';

import { Locale } from '../../lib/transloco-locale.types';
import { TranslocoLocaleService } from '../transloco-locale.service';

type Deps = [TranslocoLocaleService, ChangeDetectorRef];
@Injectable()
export abstract class BaseLocalePipe implements OnDestroy {
protected localeService = inject(TranslocoLocaleService);
protected cdr = inject(ChangeDetectorRef);

private localeChangeSub: Subscription | null =
this.localeService.localeChanges$.subscribe(() => this.cdr.markForCheck());

protected getLocale(locale?: Locale): Locale {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return locale || this.localeService.getLocale()!;
}

ngOnDestroy(): void {
this.localeChangeSub?.unsubscribe();
// Caretaker note: it's important to clean up references to subscriptions since they save the `next`
// callback within its `destination` property, preventing classes from being GC'd.
this.localeChangeSub = null;
}
}
import {
ChangeDetectorRef,
inject,
Injectable,
OnDestroy,
} from '@angular/core';
import { Subscription } from 'rxjs';

import { Locale } from '../../lib/transloco-locale.types';
import { TranslocoLocaleService } from '../transloco-locale.service';

type Deps = [TranslocoLocaleService, ChangeDetectorRef];
@Injectable()
export abstract class BaseLocalePipe<VALUE = unknown, ARGS extends unknown[] = []> implements OnDestroy {
protected localeService = inject(TranslocoLocaleService);
protected cdr = inject(ChangeDetectorRef);

private localeChangeSub: Subscription | null =
this.localeService.localeChanges$.subscribe(() => this.invalidate());

protected lastValue?: VALUE;
protected lastArgs?: string;

protected lastResult = '';

protected getLocale(locale?: Locale): Locale {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return locale || this.localeService.getLocale()!;
}

transform(value: VALUE, ...args: ARGS): string {
if (this.isSameValue(value) && this.isSameArgs(...args)) {
return this.lastResult;
}
this.lastResult = this.doTransform(value, ...args);
this.lastValue = value;
this.lastArgs = JSON.stringify(args);
return this.lastResult;
}

protected abstract doTransform(value: VALUE, ...args: ARGS): string;

protected isSameValue(value: VALUE): boolean {
return this.lastValue === value;
}

protected isSameArgs(...args: ARGS): boolean {
return JSON.stringify(args) === this.lastArgs;
}

invalidate() {
this.lastValue = undefined;
this.lastArgs = undefined;
this.lastResult = '';
this.cdr.markForCheck();
}

ngOnDestroy(): void {
this.localeChangeSub?.unsubscribe();
// Caretaker note: it's important to clean up references to subscriptions since they save the `next`
// callback within its `destination` property, preventing classes from being GC'd.
this.localeChangeSub = null;
}
}
128 changes: 66 additions & 62 deletions libs/transloco-locale/src/lib/pipes/transloco-currency.pipe.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,66 @@
import { inject, Pipe, PipeTransform } from '@angular/core';
import { isNil } from '@ngneat/transloco';

import { getDefaultOptions } from '../shared';
import { TRANSLOCO_LOCALE_CONFIG } from '../transloco-locale.config';
import {
Currency,
Locale,
LocaleConfig,
NumberFormatOptions,
} from '../transloco-locale.types';

import { BaseLocalePipe } from './base-locale.pipe';

@Pipe({
name: 'translocoCurrency',
pure: false,
standalone: true,
})
export class TranslocoCurrencyPipe
extends BaseLocalePipe
implements PipeTransform
{
private localeConfig: LocaleConfig = inject(TRANSLOCO_LOCALE_CONFIG);

/**
* Transform a given number into the locale's currency format.
*
* @example
*
* 1000000 | translocoCurrency: 'symbol' : {} : USD // $1,000,000.00
* 1000000 | translocoCurrency: 'name' : {} : USD // 1,000,000.00 US dollars
* 1000000 | translocoCurrency: 'symbol' : {minimumFractionDigits: 0 } : USD // $1,000,000
* 1000000 | translocoCurrency: 'symbol' : {minimumFractionDigits: 0 } : CAD // CA$1,000,000
* 1000000 | translocoCurrency: 'narrowSymbol' : {minimumFractionDigits: 0 } : CAD // $1,000,000
*
*/
transform(
value: number | string,
display: 'code' | 'symbol' | 'narrowSymbol' | 'name' = 'symbol',
numberFormatOptions: NumberFormatOptions = {},
currencyCode?: Currency,
locale?: Locale
): string {
if (isNil(value)) return '';
locale = this.getLocale(locale);

const options = {
...getDefaultOptions(locale, 'currency', this.localeConfig),
...numberFormatOptions,
currencyDisplay: display,
currency: currencyCode || this.localeService._resolveCurrencyCode(),
};

return this.localeService.localizeNumber(
value,
'currency',
locale,
options
);
}
}
import { inject, Pipe, PipeTransform } from '@angular/core';
import { isNil } from '@ngneat/transloco';

import { getDefaultOptions } from '../shared';
import { TRANSLOCO_LOCALE_CONFIG } from '../transloco-locale.config';
import {
Currency,
Locale,
LocaleConfig,
NumberFormatOptions,
} from '../transloco-locale.types';

import { BaseLocalePipe } from './base-locale.pipe';

@Pipe({
name: 'translocoCurrency',
pure: false,
standalone: true,
})
export class TranslocoCurrencyPipe
extends BaseLocalePipe<number | string, [
display?: 'code' | 'symbol' | 'name',
numberFormatOptions?: NumberFormatOptions,
currencyCode?: Currency,
locale?: Locale]>
implements PipeTransform
{
private localeConfig: LocaleConfig = inject(TRANSLOCO_LOCALE_CONFIG);

/**
* Transform a given number into the locale's currency format.
*
* @example
*
* 1000000 | translocoCurrency: 'symbol' : {} : USD // $1,000,000.00
* 1000000 | translocoCurrency: 'name' : {} : USD // 1,000,000.00 US dollars
* 1000000 | translocoCurrency: 'symbol' : {minimumFractionDigits: 0 } : USD // $1,000,000
* 1000000 | translocoCurrency: 'symbol' : {minimumFractionDigits: 0 } : CAD // CA$1,000,000
* 1000000 | translocoCurrency: 'narrowSymbol' : {minimumFractionDigits: 0 } : CAD // $1,000,000
*
*/
protected override doTransform(
value: number | string,
display: 'code' | 'symbol' | 'narrowSymbol' | 'name' = 'symbol',
numberFormatOptions: NumberFormatOptions = {},
currencyCode?: Currency,
locale?: Locale
): string {
if (isNil(value)) return '';
locale = this.getLocale(locale);

const options = {
...getDefaultOptions(locale, 'currency', this.localeConfig),
...numberFormatOptions,
currencyDisplay: display,
currency: currencyCode || this.localeService._resolveCurrencyCode(),
};

return this.localeService.localizeNumber(
value,
'currency',
locale,
options
);
}
}
102 changes: 56 additions & 46 deletions libs/transloco-locale/src/lib/pipes/transloco-date.pipe.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,56 @@
import { inject, Pipe, PipeTransform } from '@angular/core';
import { isNil } from '@ngneat/transloco';

import { getDefaultOptions } from '../shared';
import { TRANSLOCO_LOCALE_CONFIG } from '../transloco-locale.config';
import {
DateFormatOptions,
Locale,
LocaleConfig,
ValidDate,
} from '../transloco-locale.types';

import { BaseLocalePipe } from './base-locale.pipe';

@Pipe({
name: 'translocoDate',
pure: false,
standalone: true,
})
export class TranslocoDatePipe extends BaseLocalePipe implements PipeTransform {
private localeConfig: LocaleConfig = inject(TRANSLOCO_LOCALE_CONFIG);

/**
* Transform a date into the locale's date format.
*
* The date expression: a `Date` object, a number
* (milliseconds since UTC epoch), or an ISO string (https://www.w3.org/TR/NOTE-datetime).
*
* @example
*
* date | translocoDate: {} : en-US // 9/10/2019
* date | translocoDate: { dateStyle: 'medium', timeStyle: 'medium' } : en-US // Sep 10, 2019, 10:46:12 PM
* date | translocoDate: { timeZone: 'UTC', timeStyle: 'full' } : en-US // 7:40:32 PM Coordinated Universal Time
* 1 | translocoDate: { dateStyle: 'medium' } // Jan 1, 1970
* '2019-02-08' | translocoDate: { dateStyle: 'medium' } // Feb 8, 2019
*/
transform(date: ValidDate, options: DateFormatOptions = {}, locale?: Locale) {
if (isNil(date)) return '';
locale = this.getLocale(locale);

return this.localeService.localizeDate(date, locale, {
...getDefaultOptions(locale, 'date', this.localeConfig),
...options,
});
}
}
import { inject, Pipe, PipeTransform } from '@angular/core';
import { isNil } from '@ngneat/transloco';

import { getDefaultOptions } from '../shared';
import { TRANSLOCO_LOCALE_CONFIG } from '../transloco-locale.config';
import {
DateFormatOptions,
Locale,
LocaleConfig,
ValidDate,
} from '../transloco-locale.types';

import { BaseLocalePipe } from './base-locale.pipe';

@Pipe({
name: 'translocoDate',
pure: false,
standalone: true,
})
export class TranslocoDatePipe
extends BaseLocalePipe<ValidDate, [options?: DateFormatOptions, locale?: Locale]>
implements PipeTransform {
private localeConfig: LocaleConfig = inject(TRANSLOCO_LOCALE_CONFIG);

/**
* Transform a date into the locale's date format.
*
* The date expression: a `Date` object, a number
* (milliseconds since UTC epoch), or an ISO string (https://www.w3.org/TR/NOTE-datetime).
*
* @example
*
* date | translocoDate: {} : en-US // 9/10/2019
* date | translocoDate: { dateStyle: 'medium', timeStyle: 'medium' } : en-US // Sep 10, 2019, 10:46:12 PM
* date | translocoDate: { timeZone: 'UTC', timeStyle: 'full' } : en-US // 7:40:32 PM Coordinated Universal Time
* 1 | translocoDate: { dateStyle: 'medium' } // Jan 1, 1970
* '2019-02-08' | translocoDate: { dateStyle: 'medium' } // Feb 8, 2019
*/
protected override doTransform(date: ValidDate, options: DateFormatOptions = {}, locale?: Locale) {
if (isNil(date)) return '';
locale = this.getLocale(locale);

return this.localeService.localizeDate(date, locale, {
...getDefaultOptions(locale, 'date', this.localeConfig),
...options,
});
}

protected override isSameValue(value?: ValidDate) {
return this.getComparableDate(this.lastValue) === this.getComparableDate(value);
}

private getComparableDate(value?: any) {
return value?.getTime ? value.getTime() : value;
}
}
Loading

0 comments on commit 8eb567e

Please sign in to comment.