-
-
Notifications
You must be signed in to change notification settings - Fork 130
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ngx-utils): replaced Moment lib with date-fns
date-fns should lower build output size
- Loading branch information
Showing
12 changed files
with
1,180 additions
and
781 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { NgModule } from '@angular/core'; | ||
import { CommonModule } from '@angular/common'; | ||
import { FormatTimeInWordsPipe } from './format-time-in-words.pipe'; | ||
|
||
const PIPES = [FormatTimeInWordsPipe]; | ||
|
||
@NgModule({ | ||
declarations: [PIPES], | ||
imports: [CommonModule], | ||
exports: [PIPES], | ||
}) | ||
export class DateFnsModule {} |
65 changes: 65 additions & 0 deletions
65
libs/ngx-utils/src/lib/pipes/date-fns/format-time-in-words.pipe.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { inject, TestBed } from '@angular/core/testing'; | ||
import { FormatTimeInWordsPipe } from './format-time-in-words.pipe'; | ||
import { DateFnsModule } from './date-fns.module'; | ||
import { ChangeDetectorRef } from '@angular/core'; | ||
|
||
class MockChangeDetector { | ||
markForCheck(): void {} | ||
} | ||
|
||
function drinkFlavor(flavor) { | ||
if (flavor === 'octopus') { | ||
throw new Error('yuck, octopus flavor'); | ||
} | ||
} | ||
|
||
describe('FormatTimeInWordsPipe', () => { | ||
const fakeChangeDetectorRef = { | ||
markForCheck: () => {}, | ||
}; | ||
|
||
beforeEach(() => { | ||
TestBed.configureTestingModule({ | ||
providers: [FormatTimeInWordsPipe, { provide: ChangeDetectorRef, useValue: fakeChangeDetectorRef }], | ||
imports: [DateFnsModule], | ||
}); | ||
}); | ||
|
||
it('should transform current date to words', inject([FormatTimeInWordsPipe], (pipe: FormatTimeInWordsPipe) => { | ||
expect(pipe.transform(new Date(), { addSuffix: true })).toBe('less than a minute ago'); | ||
})); | ||
|
||
it('should transform current date to words without ago', inject( | ||
[FormatTimeInWordsPipe], | ||
(pipe: FormatTimeInWordsPipe) => { | ||
expect(pipe.transform(new Date(), { addSuffix: false })).toBe('less than a minute'); | ||
}, | ||
)); | ||
|
||
it('should transform future date to words', inject([FormatTimeInWordsPipe], (pipe: FormatTimeInWordsPipe) => { | ||
const today = new Date(); | ||
const tomorrow = new Date(); | ||
tomorrow.setDate(today.getDate() + 1); | ||
|
||
expect(pipe.transform(tomorrow)).toBe('in 1 day'); | ||
})); | ||
|
||
it('should transform past date to words', inject([FormatTimeInWordsPipe], (pipe: FormatTimeInWordsPipe) => { | ||
const today = new Date(); | ||
const yesterday = new Date(); | ||
yesterday.setDate(today.getDate() - 1); | ||
|
||
expect(pipe.transform(yesterday)).toBe('1 day ago'); | ||
})); | ||
|
||
it('should return `Invalid Date` when date is invalid', inject( | ||
[FormatTimeInWordsPipe], | ||
(pipe: FormatTimeInWordsPipe) => { | ||
expect(pipe.transform('err')).toBe('Invalid Date'); | ||
}, | ||
)); | ||
|
||
it('should throw error when date is null', inject([FormatTimeInWordsPipe], (pipe: FormatTimeInWordsPipe) => { | ||
expect(() => pipe.transform(null)).toThrowError(FormatTimeInWordsPipe.NO_ARGS_ERROR); | ||
})); | ||
}); |
74 changes: 74 additions & 0 deletions
74
libs/ngx-utils/src/lib/pipes/date-fns/format-time-in-words.pipe.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { OnDestroy, ChangeDetectorRef, Pipe, PipeTransform } from '@angular/core'; | ||
import { AsyncPipe } from '@angular/common'; | ||
import { of, Observable } from 'rxjs'; | ||
import { repeatWhen, takeWhile, map, tap, delay } from 'rxjs/operators'; | ||
|
||
import { Options } from 'date-fns'; | ||
// import { formatDistance, differenceInMinutes } from 'date-fns/esm'; | ||
import { formatDistance, differenceInMinutes } from 'date-fns'; | ||
|
||
const defaultConfig: Options = { addSuffix: true }; | ||
/** | ||
* impure pipe, which in general can lead to bad performance | ||
* but the backoff function limits the frequency the pipe checks for updates | ||
* so the performance is close to that of a pure pipe | ||
* the downside of this is that if you change the value of the input, the pipe might not notice for a while | ||
* so this pipe is intended for static data | ||
* | ||
* expected input is a time (number, string or Date) | ||
* output is a string expressing distance from that time to now, plus the suffix 'ago' | ||
* output refreshes at dynamic intervals, with refresh rate slowing down as the input time gets further away from now | ||
*/ | ||
@Pipe({ name: 'formatTimeInWords', pure: false }) | ||
export class FormatTimeInWordsPipe implements PipeTransform, OnDestroy { | ||
static readonly NO_ARGS_ERROR = 'formatTimeInWords: missing required arguments'; | ||
private readonly async: AsyncPipe; | ||
|
||
private isDestroyed = false; | ||
private agoExpression: Observable<string>; | ||
|
||
constructor(private cdr: ChangeDetectorRef) { | ||
this.async = new AsyncPipe(this.cdr); | ||
} | ||
|
||
ngOnDestroy() { | ||
this.isDestroyed = true; // pipe will stop executing after next iteration | ||
} | ||
|
||
transform(date: string | number | Date, options?: Options): string { | ||
if (date == null) { | ||
throw new Error(FormatTimeInWordsPipe.NO_ARGS_ERROR); | ||
} | ||
|
||
// set the pipe to the Observable if not yet done, and return an async pipe | ||
if (!this.agoExpression) { | ||
this.agoExpression = this.timeAgo(date, { ...defaultConfig, ...options }); | ||
} | ||
return this.async.transform(this.agoExpression); | ||
} | ||
|
||
private timeAgo(date: string | number | Date, options?: Options): Observable<string> { | ||
let nextBackoff = this.backoff(date); | ||
return of(true).pipe( | ||
repeatWhen(emitTrue => emitTrue.pipe(delay(nextBackoff))), // will not recheck input until delay completes | ||
takeWhile(_ => !this.isDestroyed), | ||
map(_ => formatDistance(date, new Date(), options)), | ||
tap(_ => (nextBackoff = this.backoff(date))), | ||
); | ||
} | ||
|
||
private backoff(date: string | number | Date): number { | ||
const minutesElapsed = Math.abs(differenceInMinutes(new Date(), date)); // this will always be positive | ||
let backoffAmountInSeconds: number; | ||
if (minutesElapsed < 2) { | ||
backoffAmountInSeconds = 5; | ||
} else if (minutesElapsed >= 2 && minutesElapsed < 5) { | ||
backoffAmountInSeconds = 15; | ||
} else if (minutesElapsed >= 5 && minutesElapsed < 60) { | ||
backoffAmountInSeconds = 30; | ||
} else if (minutesElapsed >= 60) { | ||
backoffAmountInSeconds = 300; // 5 minutes | ||
} | ||
return backoffAmountInSeconds * 1000; // return an amount of milliseconds | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from './helper/helper.module'; | ||
export * from './truncate/truncate.module'; | ||
export * from './date-fns/date-fns.module'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.