From 3c902eb8941575ca50fe622b5dd481bf41d12996 Mon Sep 17 00:00:00 2001 From: Joan Llenas Date: Mon, 8 Mar 2021 10:16:53 +0100 Subject: [PATCH 1/5] fix: Remove deprecations --- README.md | 14 +-- package.json | 2 +- projects/lib/src/lib/pipes.ts | 14 +-- projects/lib/src/lib/remote-data.spec.ts | 17 ++-- projects/lib/src/lib/remote-data.ts | 103 ++++++----------------- src/app/examples/ngrx/store/reducer.ts | 2 +- src/app/examples/pos/pos.service.ts | 2 +- 7 files changed, 55 insertions(+), 99 deletions(-) diff --git a/README.md b/README.md index 1e79dc4..cc47470 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,11 @@ Our html template will have to use complex `*ngIf` statements to make sure we ar Instead of using a complex data structures we use a single data type to express all possible request states: ```ts -type RemoteData = NotAsked | InProgress | Failure | Success; +type RemoteData + = NotAsked + | InProgress + | Failure + | Success; ``` This approach **makes it impossible to create invalid states**. @@ -233,7 +237,7 @@ const myRemoteData: RemoteData = inProgress({ email: 'john@doe.com' }); if (isInProgress(myRemoteData)) { // Here myRemoteData is narrowed to InProgress - console.log(`I have some data: ${myRemoteData.value().email}`); + console.log(`I have some data: ${myRemoteData.value.email}`); } ``` @@ -252,7 +256,7 @@ const myRemoteData: RemoteData = success({ email: 'john@doe.com' }); if (isSuccess(myRemoteData)) { // Here myRemoteData is narrowed to Success - console.log(`I have some data: ${myRemoteData.value().email}`); + console.log(`I have some data: ${myRemoteData.value.email}`); } ``` @@ -275,8 +279,8 @@ const myRemoteData: RemoteData = failure('Something went wrong.', { if (isFailure(myRemoteData)) { // Here myRemoteData is narrowed to Failure - console.log(`This is the failure: ${myRemoteData.error()}`); - console.log(`I have some data: ${myRemoteData.value().email}`); + console.log(`This is the failure: ${myRemoteData.error}`); + console.log(`I have some data: ${myRemoteData.value.email}`); } ``` diff --git a/package.json b/package.json index 5a8e155..5930d95 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ngx-remotedata", - "version": "5.1.0", + "version": "6.0.0", "description": "RemoteData: Slaying a UI Antipattern with Angular", "repository": { "type": "git", diff --git a/projects/lib/src/lib/pipes.ts b/projects/lib/src/lib/pipes.ts index 28da640..d0006bb 100644 --- a/projects/lib/src/lib/pipes.ts +++ b/projects/lib/src/lib/pipes.ts @@ -153,8 +153,8 @@ export class HasValuePipe implements PipeTransform { return true; } else if ( (isInProgress(rd) || isFailure(rd)) && - rd.value() !== null && - rd.value() !== undefined + rd.value !== null && + rd.value !== undefined ) { return true; } else if (rd === undefined || rd === null) { @@ -172,7 +172,7 @@ export class GetSuccessPipe implements PipeTransform { defaultValue?: T | undefined ): T | undefined { if (isSuccess(rd)) { - return rd.value(); + return rd.value; } else if (rd === undefined || rd === null) { return defaultValue; } @@ -188,7 +188,7 @@ export class GetInProgressPipe implements PipeTransform { defaultValue?: T | undefined ): T | undefined { if (isInProgress(rd)) { - return rd.value(); + return rd.value; } else if (rd === undefined || rd === null) { return defaultValue; } @@ -205,7 +205,7 @@ export class GetRemoteDataValuePipe implements PipeTransform { } assertIsRemoteData(rd); return isInProgress(rd) || isSuccess(rd) || isFailure(rd) - ? rd.value() + ? rd.value : undefined; } } @@ -217,7 +217,7 @@ export class GetFailureErrorPipe implements PipeTransform { return undefined; } assertIsRemoteData(rd); - return isFailure(rd) ? rd.error() : undefined; + return isFailure(rd) ? rd.error : undefined; } } @@ -228,6 +228,6 @@ export class GetFailureValuePipe implements PipeTransform { return undefined; } assertIsRemoteData(rd); - return isFailure(rd) ? rd.value() : undefined; + return isFailure(rd) ? rd.value : undefined; } } diff --git a/projects/lib/src/lib/remote-data.spec.ts b/projects/lib/src/lib/remote-data.spec.ts index 6b73adf..65bf60e 100644 --- a/projects/lib/src/lib/remote-data.spec.ts +++ b/projects/lib/src/lib/remote-data.spec.ts @@ -7,7 +7,9 @@ import { getOrElse, fold, map, - mapFailure + mapFailure, + isFailure, + isInProgress } from './remote-data'; describe('RemoteData', () => { @@ -28,7 +30,10 @@ describe('RemoteData', () => { describe('inProgress', () => { it('should be able to extract the wrapped value', () => { const value = { type: 'DoStuff' }; - expect((inProgress(value) as any).value()).toBe(value); + const p = inProgress(value); + if (isInProgress(p)) { + expect(p.value).toBe(value); + } }); }); @@ -36,9 +41,11 @@ describe('RemoteData', () => { it('should be able to extract the wrapped error and value', () => { const err = 'Ouch!'; const value = { type: 'DoStuff' }; - const f = failure(err, value) as any; - expect(f.error()).toBe(err); - expect(f.value()).toBe(value); + const f = failure(err, value); + if (isFailure(f)) { + expect(f.error).toBe(err); + expect(f.value).toBe(value); + } }); }); diff --git a/projects/lib/src/lib/remote-data.ts b/projects/lib/src/lib/remote-data.ts index 063a68c..6a4447d 100644 --- a/projects/lib/src/lib/remote-data.ts +++ b/projects/lib/src/lib/remote-data.ts @@ -1,73 +1,23 @@ type DefaultError = string; -/** - * @deprecated This will be removed in the next major version. - */ -export const RemoteDataTags = { - NotAsked: 'NotAsked', - InProgress: 'InProgress', - Failure: 'Failure', - Success: 'Success' -} as const; - -export class NotAsked { - readonly tag = 'NotAsked'; - private constructor() {} - /** - * @deprecated This will be removed in the next major version, use the `notAsked()` constructor function instead. - * @see notAsked - */ - static of(): RemoteData { - return new NotAsked(); - } +interface NotAsked { + readonly tag: 'NotAsked'; } -export class InProgress { - readonly tag = 'InProgress'; - private constructor(private val?: T) {} - /** - * @deprecated This will be removed in the next major version, use the `inProgress()` constructor function instead. - * @see inProgress - */ - static of(value?: T): RemoteData { - return new InProgress(value); - } - value(): T | undefined { - return this.val; - } +interface InProgress { + readonly tag: 'InProgress'; + readonly value: T | undefined; } -export class Failure { - readonly tag = 'Failure'; - private constructor(private err: E, private val?: T) {} - /** - * @deprecated This will be removed in the next major version, use the `failure()` constructor function instead. - * @see failure - */ - static of(err: E, val?: T): RemoteData { - return new Failure(err, val); - } - value(): T | undefined { - return this.val; - } - error(): E { - return this.err; - } +interface Failure { + readonly tag: 'Failure'; + readonly value: T | undefined; + error: E; } -export class Success { - readonly tag = 'Success'; - private constructor(private val: T) {} - /** - * @deprecated This will be removed in the next major version, use the `success()` constructor function instead. - * @see success - */ - static of(value: T): RemoteData { - return new Success(value); - } - value(): T { - return this.val; - } +interface Success { + readonly tag: 'Success'; + readonly value: T; } // ---------------------------- @@ -77,24 +27,24 @@ export class Success { // ---------------------------- export const notAsked = (): RemoteData => { - return NotAsked.of(); + return { tag: 'NotAsked' }; }; export const inProgress = ( value?: T ): RemoteData => { - return InProgress.of(value); + return { tag: 'InProgress', value }; }; export const failure = ( - err: E, - val?: T + error: E, + value?: T ): RemoteData => { - return Failure.of(err, val); + return { tag: 'Failure', error, value }; }; export const success = (value: T): RemoteData => { - return Success.of(value); + return { tag: 'Success', value }; }; // ---------------------------- @@ -165,18 +115,18 @@ export const fold = ( case 'NotAsked': return onNotAsked(); case 'InProgress': - return onInProgress(rd.value()); + return onInProgress(rd.value); case 'Failure': - return onFailure(rd.error(), rd.value()); + return onFailure(rd.error, rd.value); case 'Success': - return onSuccess(rd.value()); + return onSuccess(rd.value); } }; export const getOrElse = (rd: RemoteData, defaultValue: T): T => { switch (rd.tag) { case 'Success': - return rd.value(); + return rd.value; default: return defaultValue; } @@ -192,13 +142,13 @@ export const map = ( fn: (a: A) => B, rd: RemoteData ): RemoteData => - isSuccess(rd) ? success(fn(rd.value())) : (rd as RemoteData); + isSuccess(rd) ? success(fn(rd.value)) : (rd as RemoteData); export const mapFailure = ( fn: (e: E) => F, rd: RemoteData ): RemoteData => - isFailure(rd) ? failure(fn(rd.error())) : (rd as RemoteData); + isFailure(rd) ? failure(fn(rd.error)) : (rd as RemoteData); // ---------------------------- // @@ -211,8 +161,3 @@ export type RemoteData = | InProgress | Failure | Success; - -/** - * @deprecated This type will be removed in the next major version - */ -export type AnyRemoteData = RemoteData; diff --git a/src/app/examples/ngrx/store/reducer.ts b/src/app/examples/ngrx/store/reducer.ts index dea45ef..3b476c2 100644 --- a/src/app/examples/ngrx/store/reducer.ts +++ b/src/app/examples/ngrx/store/reducer.ts @@ -12,7 +12,7 @@ import { CatImage } from '../../../services/meow.service'; export const initialState: RemoteData = notAsked(); export const oldValue = (state: RemoteData) => - isSuccess(state) ? state.value() : undefined; + isSuccess(state) ? state.value : undefined; export function meowReducer(state = initialState, action: MeowActions) { switch (action.type) { diff --git a/src/app/examples/pos/pos.service.ts b/src/app/examples/pos/pos.service.ts index 45966e3..81afa8d 100644 --- a/src/app/examples/pos/pos.service.ts +++ b/src/app/examples/pos/pos.service.ts @@ -13,7 +13,7 @@ import { const oldValue = (obs$: Observable>) => { let value: RemoteData | undefined; obs$.subscribe(rd => (value = rd)); - return isSuccess(value) ? value.value() : undefined; + return isSuccess(value) ? value.value : undefined; }; @Injectable() From 0fc631fb1819286cb624e120c094e624a4b9799a Mon Sep 17 00:00:00 2001 From: Joan Llenas Date: Mon, 8 Mar 2021 10:49:23 +0100 Subject: [PATCH 2/5] feat: Added a chain() function to the RemoteData API --- projects/lib/src/lib/remote-data.spec.ts | 18 +++++++++++++++++- projects/lib/src/lib/remote-data.ts | 6 ++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/projects/lib/src/lib/remote-data.spec.ts b/projects/lib/src/lib/remote-data.spec.ts index 65bf60e..bf15832 100644 --- a/projects/lib/src/lib/remote-data.spec.ts +++ b/projects/lib/src/lib/remote-data.spec.ts @@ -9,7 +9,8 @@ import { map, mapFailure, isFailure, - isInProgress + isInProgress, + chain } from './remote-data'; describe('RemoteData', () => { @@ -114,4 +115,19 @@ describe('RemoteData', () => { expect(mapFailure(scream, hello)).toEqual(inProgress('hello!')); }); }); + + describe('chain', () => () => { + it('should chain successes', () => { + const indent = (str: string) => success(' ' + str); + let indented = chain(indent, success('hello')); + indented = chain(indent, indented); + expect(indented).toEqual(success(' success')); + }); + it('should not chain on non Success values', () => { + const indent = (str: string) => success(' ' + str); + let indented = chain(indent, failure('wrong!')); + indented = chain(indent, indented); + expect(indented).toEqual(failure('wrong!')); + }); + }); }); diff --git a/projects/lib/src/lib/remote-data.ts b/projects/lib/src/lib/remote-data.ts index 6a4447d..919ba95 100644 --- a/projects/lib/src/lib/remote-data.ts +++ b/projects/lib/src/lib/remote-data.ts @@ -150,6 +150,12 @@ export const mapFailure = ( ): RemoteData => isFailure(rd) ? failure(fn(rd.error)) : (rd as RemoteData); +export const chain = ( + fn: (a: A) => RemoteData, + rd: RemoteData +): RemoteData => + isSuccess(rd) ? fn(rd.value) : (rd as RemoteData); + // ---------------------------- // // RemoteData type From d5a10f1db1803a64485d098d83a036badad359ea Mon Sep 17 00:00:00 2001 From: Joan Llenas Date: Mon, 8 Mar 2021 11:07:17 +0100 Subject: [PATCH 3/5] docs: Improved docs --- README.md | 79 +++++++++++++++++------- projects/lib/src/lib/remote-data.spec.ts | 8 +++ 2 files changed, 66 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index cc47470..4a63672 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,7 @@ export class AppComponent { ## Api -### RemoteData +### RemoteData 📚 `RemoteData` @@ -202,7 +202,7 @@ export class AppComponent { - Type guard function: `isRemoteData = (value: unknown): value is RemoteData`. -### NotAsked +### NotAsked 📚 - Constructor function: `notAsked(): RemoteData`. - Type guard function: `isNotAsked(value: unknown): value is NotAsked`. @@ -220,7 +220,7 @@ if (isNotAsked(myRemoteData)) { } ``` -### InProgress +### InProgress 📚 - Constructor function: `inProgress(value?: T): RemoteData`. - Type guard function: `isInProgress(value: unknown): value is InProgress`. @@ -241,7 +241,7 @@ if (isInProgress(myRemoteData)) { } ``` -### Success +### Success 📚 - Constructor function: `success(value: T): RemoteData`. - Type guard function: `isSuccess(value: unknown): value is Success`. @@ -260,7 +260,7 @@ if (isSuccess(myRemoteData)) { } ``` -### Failure +### Failure 📚 - Constructor function: `failure(err: E, val?: T): RemoteData`. - Type guard function: `isFailure(value: unknown): value is Failure`. @@ -295,7 +295,7 @@ const myRemoteData: RemoteData = failure( ## Unwrapping RemoteData values -### getOrElse +### getOrElse 📚 ```ts getOrElse(rd: RemoteData, defaultValue: T): T; @@ -304,6 +304,7 @@ getOrElse(rd: RemoteData, defaultValue: T): T; `getOrElse` _unwraps_ and returns the value of `Success` instances or the `defaultValue` when it's any other `RemoteData` variant. ```ts +// Example let myRemoteData = success('ok!'); console.log(getOrElse(myRemoteData, 'The default value')); // ok! @@ -311,7 +312,7 @@ myRemoteData = failure('There has been an error'); console.log(getOrElse(myRemoteData, 'The default value')); // The default value ``` -### fold +### fold 📚 ```ts fold( @@ -325,9 +326,22 @@ fold( With `fold` you _unwrap_ the `RemoteData` value by providing a function for each of the type variants. +```ts +// Example +const rd = success('this is fine!'); +const result = fold( + () => 'not asked', + val => 'in progress: ' + val, + (error, value) => `failure: ${error} ${value}`, + value => 'success: ' + value, + rd +); +console.log(result); // success: this is fine! +``` + ## Transforming RemoteData values -### map +### map 📚 ```ts map( @@ -339,13 +353,14 @@ map( With `map` you provide a transformation function that is applied to a `RemoteData` only when it's a `Success` instance. ```ts +// Example const scream = (s: string) => s.toUpperCase(); const hello = success('hello!'); const helloScreaming = map(scream, hello); console.log(helloScreaming); // success('HELLO!') ``` -### mapFailure +### mapFailure 📚 ```ts mapFailure( @@ -357,23 +372,45 @@ mapFailure( With `mapFailure` you provide a transformation function that is applied to a `RemoteData` only when it's a `Failure` instance. ```ts +// Example const scream = (s: string) => s.toUpperCase(); const error = failure('wrong!'); const wrongScreaming = mapFailure(scream, error); console.log(wrongScreaming); // failure('WRONG!') ``` +### chain 📚 + +```ts +chain( + fn: (a: A) => RemoteData, + rd: RemoteData +): RemoteData; +``` + +With `chain` you can provide a transormation function that can change the returned `RemoteData` variant. + +```ts +// Example +const checkAge = (n: number) => + n >= 0 ? success(n) : failure(`${n} is an invalid age`); +let ageResult = chain(checkAge, success(25)); +expect(ageResult).toEqual(success(25)); +ageResult = chain(checkAge, success(-3)); +expect(ageResult).toEqual(failure('-3 is an invalid age')); +``` + ## Pipes -### isNotAsked +### isNotAsked 📚 `transform(rd: RemoteData): boolean;` Returns `true` when `RemoteData` is a `NotAsked` instance. -### anyIsNotAsked +### anyIsNotAsked 📚 ```ts transform( @@ -383,13 +420,13 @@ transform( Returns `true` when any `RemoteData[]` items is a `NotAsked` instance. -### isInProgress +### isInProgress 📚 `transform(rd: RemoteData): boolean;` Returns `true` when `RemoteData` is an `InProgress` instance. -### anyIsInProgress +### anyIsInProgress 📚 ```ts transform( @@ -399,25 +436,25 @@ transform( Returns `true` when any `RemoteData[]` item is an `InProgress` instance. -### isFailure +### isFailure 📚 `transform(rd: RemoteData): boolean;` Returns `true` when `RemoteData` is a `Failure` instance. -### isSuccess +### isSuccess 📚 `transform(rd: RemoteData): boolean;` Returns `true` when `RemoteData` is a `Success` instance. -### hasValue +### hasValue 📚 `transform(rd: RemoteData): boolean;` Returns `true` when `RemoteData` is a `Success` instance or is an `InProgress` or `Failure` instance with a value that is not `null` or `undefined`. -### successValue +### successValue 📚 ```ts transform( @@ -428,7 +465,7 @@ transform( Returns the `Success` payload (of type `T`) when the `RemoteData` is a `Success` instance, otherwise it returns the `defaultValue` when provided or `undefined` when not. -### inProgressValue +### inProgressValue 📚 ```ts transform( @@ -439,19 +476,19 @@ transform( Returns the `InProgress` payload (of type `T`) when `RemoteData` is an `InProgress` instance, otherwise it returns the provided `defaultValue` or `undefined` when not. -### remoteDataValue +### remoteDataValue 📚 `transform(rd: RemoteData): T | E | undefined;` Returns the `InProgress`, `Failure` or `Success` payload (of type `T`) when `RemoteData` is an `InProgress`, `Failure` or `Success` instance. Returns `undefined` otherwise. -### failureError +### failureError 📚 `transform(rd: RemoteData): E | undefined` Returns the `Failure` error payload (of type `E`) when `RemoteData` is a `Failure` instance or `undefined` otherwise. -### failureValue +### failureValue 📚 `transform(rd: RemoteData): T | undefined` diff --git a/projects/lib/src/lib/remote-data.spec.ts b/projects/lib/src/lib/remote-data.spec.ts index bf15832..5419f2d 100644 --- a/projects/lib/src/lib/remote-data.spec.ts +++ b/projects/lib/src/lib/remote-data.spec.ts @@ -129,5 +129,13 @@ describe('RemoteData', () => { indented = chain(indent, indented); expect(indented).toEqual(failure('wrong!')); }); + it('should chain transformations', () => { + const checkAge = (n: number) => + n >= 0 ? success(n) : failure(`${n} is an invalid age`); + let ageResult = chain(checkAge, success(25)); + expect(ageResult).toEqual(success(25)); + ageResult = chain(checkAge, success(-3)); + expect(ageResult).toEqual(failure('-3 is an invalid age')); + }); }); }); From 29e1de996fd192f0d72afcc14d91152680a63adb Mon Sep 17 00:00:00 2001 From: Joan Llenas Date: Sun, 14 Mar 2021 16:54:42 +0100 Subject: [PATCH 4/5] build: Fixed lib import paths in demo app --- package-lock.json | 2 +- tsconfig.json | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index daa74c9..6c10dc7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ngx-remotedata", - "version": "4.0.0", + "version": "6.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/tsconfig.json b/tsconfig.json index 14fefd5..fc9c4b7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,8 +20,13 @@ "dom" ], "paths": { + "ngx-remotedata/*": [ + "projects/lib/*", + "projects/lib" + ], "ngx-remotedata": [ - "projects/lib/src/public_api.ts" + "dist/lib/*", + "dist/lib" ] } }, From 3aa9cb01f3859d34a8a0a3e63c4d51d372c549fb Mon Sep 17 00:00:00 2001 From: Joan Llenas Date: Sun, 14 Mar 2021 18:16:19 +0100 Subject: [PATCH 5/5] fix: Removed custom store rehydration --- README.md | 4 +- package-lock.json | 34 +++++--- package.json | 7 +- src/app/app.module.ts | 52 +----------- .../examples/ngrx/store/local-store-sync.ts | 79 ------------------- .../examples/ngrx/store/localstorage-sync.ts | 11 +++ 6 files changed, 44 insertions(+), 143 deletions(-) delete mode 100644 src/app/examples/ngrx/store/local-store-sync.ts create mode 100644 src/app/examples/ngrx/store/localstorage-sync.ts diff --git a/README.md b/README.md index 4a63672..1514df3 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ export class AppComponent { -#### Demo +#### Demo 👀 - [Stackblitz Ngrx demo](https://stackblitz.com/edit/ngx-remotedata-demo?file=src%2Fapp%2Fmeow%2Freducer.ts) @@ -185,7 +185,7 @@ export class AppComponent { -- [Ngrx (includes store rehydration with de/serialization)](src/app/examples/ngrx) +- [Ngrx](src/app/examples/ngrx) _(includes store rehydration from `localStorage`)_ diff --git a/package-lock.json b/package-lock.json index 6c10dc7..4e240cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1593,25 +1593,25 @@ } }, "@ngrx/effects": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-10.1.2.tgz", - "integrity": "sha512-6pX6FEzLlqdbtFVMbCvscsaL6QC/L95e72JKj76Xz+8V77UTlpVsxWyMo7YU9pM4EXNpBGmOpMs2xKjfBfK05Q==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-11.0.1.tgz", + "integrity": "sha512-FJa0WVr0PcPgFXCv8i//RDAVV26pl0ieOLH3yDnZN0BH6/cDeUcKtWsWv+IAiJ63dFm9VedgNgwVo0I7CP9G+g==", "requires": { "tslib": "^2.0.0" } }, "@ngrx/store": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-10.1.2.tgz", - "integrity": "sha512-FUjN786ch4Qt9WgJ78ef7Yquq3mPCekgcWgZrs4ycZw1f+KdfTHLTk1bGDtO8A8CzOya5yTT7KhxbdVjbOS5ng==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-11.0.1.tgz", + "integrity": "sha512-ULk+z7fXg1S0mbSXatHplvg8Rqj9Hglo6pVugaDgLdFR3DD5Wpl0cefvLBscaeZF9DGrLBoPZVlFq/LACRr6tA==", "requires": { "tslib": "^2.0.0" } }, "@ngrx/store-devtools": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-10.1.2.tgz", - "integrity": "sha512-HE681GuZ+lRgSXpgt7y7LKzsfu/+Tgy9yPZpaitvkhg+eCIjnN5Uvs1rWqETHYWnsKliW25yoqFUAVw+xb7hug==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-11.0.1.tgz", + "integrity": "sha512-ez4GW08lqJonvLVtlWM/4MJY/oHFFItMtreOtE/US+yAPuDZTnhmfjX9hqk3ySkX+drPjOfeqQ+vTYCkbmgwrw==", "requires": { "tslib": "^2.0.0" } @@ -8672,6 +8672,22 @@ } } }, + "ngrx-store-localstorage": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/ngrx-store-localstorage/-/ngrx-store-localstorage-11.0.0.tgz", + "integrity": "sha512-/bLA9/0orlEffBGH4Nqp5XwdgFX9XkpBD5p2ZwRYPPYAHi+bs4yFdKl6c4RvKsS9IOGHXiO5mm2vLaK+n7mvTw==", + "requires": { + "deepmerge": "^3.2.0", + "tslib": "^2.0.0" + }, + "dependencies": { + "deepmerge": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", + "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==" + } + } + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", diff --git a/package.json b/package.json index 5930d95..8ca1144 100644 --- a/package.json +++ b/package.json @@ -48,9 +48,10 @@ "@angular/platform-browser": "~11.1.0", "@angular/platform-browser-dynamic": "~11.1.0", "@angular/router": "~11.1.0", - "@ngrx/effects": "^10.1.2", - "@ngrx/store": "^10.1.2", - "@ngrx/store-devtools": "^10.1.2", + "@ngrx/effects": "^11.0.1", + "@ngrx/store": "^11.0.1", + "@ngrx/store-devtools": "^11.0.1", + "ngrx-store-localstorage": "^11.0.0", "rxjs": "~6.6.0", "tslib": "^2.0.0", "zone.js": "~0.10.2" diff --git a/src/app/app.module.ts b/src/app/app.module.ts index d77f7bb..9d4158b 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -2,7 +2,7 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; -import { StoreModule, ActionReducer, MetaReducer } from '@ngrx/store'; +import { StoreModule, MetaReducer } from '@ngrx/store'; import { EffectsModule } from '@ngrx/effects'; import { StoreDevtoolsModule } from '@ngrx/store-devtools'; @@ -16,56 +16,8 @@ import { PosComponent } from './examples/pos/pos.component'; import { PosService } from './examples/pos/pos.service'; import { MeowEffects } from './examples/ngrx/store/effects'; import { BasicsComponent } from './examples/basics/basics.component'; -import { - notAsked, - inProgress, - failure, - success, - RemoteData -} from 'ngx-remotedata'; -import { localStorageSync } from './examples/ngrx/store/local-store-sync'; +import { localStorageSyncReducer } from './examples/ngrx/store/localstorage-sync'; -export function localStorageSyncReducer( - reducer: ActionReducer -): ActionReducer { - return localStorageSync({ - rehydrate: true, - storageKey: 'myAppStorage', - keys: { - meow: { - serialize: (rd: RemoteData) => rd, - deserialize: (json: { - tag: RemoteData['tag']; - err: any; - val: any; - }) => { - const rd = [ - { - matcher: (tag: RemoteData['tag']) => tag === 'NotAsked', - mapper: () => notAsked() - }, - { - matcher: (tag: RemoteData['tag']) => tag === 'InProgress', - mapper: () => inProgress(json.val) - }, - { - matcher: (tag: RemoteData['tag']) => tag === 'Failure', - mapper: () => failure(json.err, json.val) - }, - { - matcher: (tag: RemoteData['tag']) => tag === 'Success', - mapper: () => success(json.val) - } - ] - .filter(matchMap => matchMap.matcher(json.tag)) - .map(matchMap => matchMap.mapper()) - .pop(); - return rd || notAsked(); - } - } - } - })(reducer); -} const metaReducers: Array> = [localStorageSyncReducer]; @NgModule({ diff --git a/src/app/examples/ngrx/store/local-store-sync.ts b/src/app/examples/ngrx/store/local-store-sync.ts deleted file mode 100644 index a4e4d26..0000000 --- a/src/app/examples/ngrx/store/local-store-sync.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * This code is a simplified version of https://github.com/btroncone/ngrx-store-localstorage - * The ngrx-store-localstorage library doesn't work out of the box because it uses the `deepmerge` library, - * which internally converts instances into plain objects. - */ - -const INIT_ACTION = '@ngrx/store/init'; -const UPDATE_ACTION = '@ngrx/store/update-reducers'; - -type Keys = { - [key: string]: { - deserialize: (...rest: any[]) => any; - serialize: (...rest: any[]) => any; - }; -}; - -const rehydrateApplicationState = (storageKey: string, keys: Keys) => { - const storage = window.localStorage.getItem(storageKey); - if (!storage) { - return undefined; - } - const store = JSON.parse(storage); - return Object.keys(keys).reduce((acc, key) => { - const value = store[key]; - if (value) { - acc[key] = keys[key].deserialize(value); - } - return acc; - }, {} as any); -}; - -const syncStateUpdate = ( - storageKey: string, - state: { [key: string]: any }, - keys: Keys -) => { - const store = Object.keys(keys).reduce((acc, key) => { - acc[key] = keys[key].serialize(state[key]); - return acc; - }, {} as any); - localStorage.setItem(storageKey, JSON.stringify(store)); -}; - -export const localStorageSync = (config: { - rehydrate: boolean; - storageKey: string; - keys: Keys; -}) => (reducer: any) => { - const rehydratedState = config.rehydrate - ? rehydrateApplicationState(config.storageKey, config.keys) - : undefined; - - return function(state: any, action: any) { - let nextState; - - // If state arrives undefined, we need to let it through the supplied reducer - // in order to get a complete state as defined by user - if (action.type === INIT_ACTION && !state) { - nextState = reducer(state, action); - } else { - nextState = { ...state }; - } - - if ( - (action.type === INIT_ACTION || action.type === UPDATE_ACTION) && - rehydratedState - ) { - nextState = { ...nextState, ...rehydratedState }; - } - - nextState = reducer(nextState, action); - - if (action.type !== INIT_ACTION) { - syncStateUpdate(config.storageKey, nextState, config.keys); - } - - return nextState; - }; -}; diff --git a/src/app/examples/ngrx/store/localstorage-sync.ts b/src/app/examples/ngrx/store/localstorage-sync.ts new file mode 100644 index 0000000..4b9c6a3 --- /dev/null +++ b/src/app/examples/ngrx/store/localstorage-sync.ts @@ -0,0 +1,11 @@ +import { ActionReducer } from '@ngrx/store'; +import { localStorageSync } from 'ngrx-store-localstorage'; + +/** + * Meta reducer used in app.module.ts to sync reducer keys to/from localStorage + */ +export function localStorageSyncReducer( + reducer: ActionReducer +): ActionReducer { + return localStorageSync({ rehydrate: true, keys: ['meow'] })(reducer); +}