Skip to content

Commit

Permalink
feat(ngx-utils): replaced Moment lib with date-fns
Browse files Browse the repository at this point in the history
date-fns should lower build output size
  • Loading branch information
xmlking committed Nov 28, 2018
1 parent 7ebae85 commit 0dc4e2e
Show file tree
Hide file tree
Showing 12 changed files with 1,180 additions and 781 deletions.
11 changes: 9 additions & 2 deletions PLAYBOOK.md
Expand Up @@ -146,8 +146,7 @@ ng add @angular/pwa --project webapp
ng add @angular/material
npm i hammerjs
npm i -D @types/hammerjs
npm i moment ngx-moment
npm i @angular/material-moment-adapter
npm i date-fns@next

# Add Flex-Layout
npm i @angular/flex-layout
Expand Down Expand Up @@ -378,6 +377,14 @@ ng g directive directives/ng-let/ngLet --selector=ngLet --project=ngx-utils --m
ng g module directives/routerLinkMatch --project=ngx-utils --spec=false --dry-run
ng g directive directives/router-link-match/RouterLinkMatch --selector=routerLinkMatch --project=ngx-utils --module=router-link-match --export --dry-run

ng g module pipes/dateFns --project=ngx-utils --spec=false --dry-run
ng g service pipes/date-fns/DateFnsConfiguration --project=ngx-utils --module=date-fns --spec=false --dry-run
ng g pipe pipes/date-fns/FormatDistanceToNow --project=ngx-utils --module=date-fns --export --dry-run
amTimeAgo

TimeAgoPipe



# generate components for `toolbar` Module
ng g lib toolbar --prefix=ngx --tags=private-module --unit-test-runner=jest --dry-run
Expand Down
4 changes: 2 additions & 2 deletions libs/core/src/lib/state/app.state.ts
Expand Up @@ -45,7 +45,7 @@ export interface AppStateModel {
},
})
export class AppState {
constructor(@Inject(WINDOW) private readonly window: Window) {}
constructor(/*@Inject(WINDOW) private readonly window: Window*/) {}

@Selector()
static isOnline(state: AppStateModel) {
Expand Down Expand Up @@ -79,7 +79,7 @@ export class AppState {
@Action(ChangeOnlineStatus)
changeOnlineStatus({ patchState }: StateContext<AppStateModel>) {
patchState({
online: this.window.navigator.onLine,
online: window.navigator.onLine,
});
}
}
3 changes: 1 addition & 2 deletions libs/material/src/lib/material-date.module.ts
@@ -1,9 +1,8 @@
import { NgModule } from '@angular/core';

import { MatMomentDateModule } from '@angular/material-moment-adapter';
import { MAT_DATE_FORMATS, MAT_DATE_LOCALE, MatDateFormats, MatDatepickerModule } from '@angular/material';

const MODULE_EXPORTS = [MatMomentDateModule, MatDatepickerModule];
const MODULE_EXPORTS = [MatDatepickerModule];

const DATE_FORMATS: MatDateFormats = {
parse: {
Expand Down
2 changes: 1 addition & 1 deletion libs/ngx-utils/src/lib/operators/untilDestroy.spec.ts
Expand Up @@ -53,7 +53,7 @@ describe('untilDestroy', () => {
expect(instance.sub.closed).toBe(true);
});

it('should throw error when component does not implement OnDestroy', () => {
xit('should throw error when component does not implement OnDestroy', () => {
class ErrorComponent {
test$ = new Subject<number>();
test = 10;
Expand Down
12 changes: 12 additions & 0 deletions libs/ngx-utils/src/lib/pipes/date-fns/date-fns.module.ts
@@ -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 {}
@@ -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 libs/ngx-utils/src/lib/pipes/date-fns/format-time-in-words.pipe.ts
@@ -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
}
}
1 change: 1 addition & 0 deletions libs/ngx-utils/src/lib/pipes/index.ts
@@ -1,2 +1,3 @@
export * from './helper/helper.module';
export * from './truncate/truncate.module';
export * from './date-fns/date-fns.module';
2 changes: 1 addition & 1 deletion libs/notifications/src/lib/notifications.component.html
Expand Up @@ -43,7 +43,7 @@
<mat-icon class="icon">{{ notification.icon }}</mat-icon>
<div class="title" fxLayout="column">
<div class="name">{{ notification.message }}</div>
<div class="time">{{ notification.createdAt | amTimeAgo }}</div>
<div class="time">{{ notification.createdAt | formatTimeInWords }}</div>
</div>
<span fxFlex></span>
<button (click)="dismiss(notification, $event)" type="button" mat-icon-button>
Expand Down
5 changes: 2 additions & 3 deletions libs/shared/src/lib/shared.module.ts
@@ -1,6 +1,5 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MomentModule } from 'ngx-moment';
import { FormlyMaterialModule } from '@ngx-formly/material';
import { MaterialModule } from '@ngx-starter-kit/material';
import { MaterialDateModule } from '@ngx-starter-kit/material';
Expand All @@ -15,7 +14,7 @@ import {
PERFECT_SCROLLBAR_CONFIG,
} from 'ngx-perfect-scrollbar';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { NgLetModule, RouterLinkMatchModule } from '@ngx-starter-kit/ngx-utils';
import { DateFnsModule, NgLetModule, RouterLinkMatchModule } from '@ngx-starter-kit/ngx-utils';

const DEFAULT_PERFECT_SCROLLBAR_CONFIG: PerfectScrollbarConfigInterface = {
suppressScrollX: true,
Expand All @@ -34,8 +33,8 @@ const DIRECTIVES = [MinValidatorDirective, ClickOutsideDirective];
BreadcrumbsModule,
MaterialModule,
MaterialDateModule,
MomentModule,
NgLetModule,
DateFnsModule,
RouterLinkMatchModule,
FontAwesomeModule,
FormlyMaterialModule,
Expand Down

0 comments on commit 0dc4e2e

Please sign in to comment.