├── .nvmrc ├── .husky ├── .gitignore ├── pre-commit └── commit-msg ├── .prettierignore ├── projects └── lib │ ├── src │ ├── public_api.ts │ └── lib │ │ └── index.ts │ ├── ng-package.json │ ├── tsconfig.spec.json │ ├── tsconfig.lib.prod.json │ ├── tsconfig.lib.json │ ├── package.json │ └── .eslintrc.json ├── .vscode └── settings.json ├── commitlint.config.js ├── .editorconfig ├── .nycrc.json ├── spec ├── support │ └── jasmine.json ├── helpers │ └── reporter.ts └── index.spec.ts ├── .npmignore ├── typings.json ├── .gitignore ├── tsconfig.json ├── .eslintrc.json ├── LICENSE ├── .github └── PULL_REQUEST_TEMPLATE.md ├── CHANGELOG.md ├── angular.json ├── .circleci └── config.yml ├── package.json ├── README.md └── CONTRIBUTING.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 22.16.0 2 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | .github/PULL_REQUEST_TEMPLATE.md 3 | -------------------------------------------------------------------------------- /projects/lib/src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from "./lib/index"; 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true 3 | } 4 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | }; 4 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | indent_style = space 4 | indent_size = 2 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | 8 | -------------------------------------------------------------------------------- /projects/lib/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/lib", 4 | "lib": { 5 | "entryFile": "src/public_api.ts" 6 | }, 7 | "allowedNonPeerDependencies": ["deepmerge"] 8 | } 9 | -------------------------------------------------------------------------------- /projects/lib/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "types": ["jasmine", "node"] 5 | }, 6 | "files": ["src/test.ts", "src/polyfills.ts"], 7 | "include": ["**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /.nycrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@istanbuljs/nyc-config-typescript", 3 | "exclude": ["spec/**/*.ts"], 4 | "reporter": ["text", "html"], 5 | "check-coverage": true, 6 | "statements": 90, 7 | "branches": 82, 8 | "functions": 93, 9 | "lines": 90 10 | } 11 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": ["*spec.ts"], 4 | "helpers": [ 5 | "helpers/**/*.ts", 6 | "node_modules/core-js", 7 | "node_modules/rxjs/testing/*.js", 8 | "node_modules/core-js/client/core.js" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /projects/lib/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /projects/lib/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "../../tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "../../out-tsc/lib", 6 | "target": "es2020", 7 | "declaration": true, 8 | "declarationMap": true, 9 | "inlineSources": true, 10 | "types": [], 11 | "lib": ["dom", "es2018"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spec/helpers/reporter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DisplayProcessor, 3 | SpecReporter, 4 | StacktraceOption, 5 | } from "jasmine-spec-reporter"; 6 | 7 | class CustomProcessor extends DisplayProcessor { 8 | public displayJasmineStarted(info: any, log: string): string { 9 | return `TypeScript ${log}`; 10 | } 11 | } 12 | 13 | jasmine.getEnv().clearReporters(); 14 | jasmine.getEnv().addReporter( 15 | new SpecReporter({ 16 | spec: { 17 | displayStacktrace: StacktraceOption.NONE, 18 | }, 19 | customProcessors: [CustomProcessor], 20 | }), 21 | ); 22 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | .nyc_output 16 | 17 | # node-waf configuration 18 | .lock-wscript 19 | 20 | # Compiled binary addons (http://nodejs.org/api/addons.html) 21 | build/Release 22 | 23 | # Dependency directory 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 25 | node_modules 26 | tmp 27 | typings 28 | -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ambientDependencies": { 3 | "core-js": "github:DefinitelyTyped/DefinitelyTyped/core-js/core-js.d.ts#c777572e8532c4d358306fe4c0e17a533f940b04", 4 | "jasmine": "github:DefinitelyTyped/DefinitelyTyped/jasmine/jasmine.d.ts#26c98c8a9530c44f8c801ccc3b2057e2101187ee", 5 | "ng2": "github:gdi2290/typings-ng2/ng2.d.ts#32998ff5584c0eab0cd9dc7704abb1c5c450701c", 6 | "require": "github:DefinitelyTyped/DefinitelyTyped/requirejs/require.d.ts#853544cb7068af64bdb10ef1ae0c54d3aa3a80f6", 7 | "zone.js": "github:DefinitelyTyped/DefinitelyTyped/zone.js/zone.js.d.ts#b923a5aaf013ac84c566f27ba6b5843211981c7a" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.angular/cache 2 | # Logs 3 | logs 4 | *.log 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | .DS_Store 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | .nyc_output 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | tmp 29 | typings 30 | dist 31 | *.tgz 32 | .idea 33 | spec/ngfactory/ 34 | 35 | .eslintcache 36 | -------------------------------------------------------------------------------- /projects/lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngrx-store-localstorage", 3 | "version": "20.0.0", 4 | "description": "State and local storage syncing for @ngrx/store", 5 | "repository": { 6 | "type": "git", 7 | "url": "git@github.com:btroncone/ngrx-store-localstorage.git" 8 | }, 9 | "keywords": [ 10 | "redux", 11 | "ngrx", 12 | "store", 13 | "localstorage", 14 | "rxjs" 15 | ], 16 | "author": "Brian Troncone", 17 | "bugs": { 18 | "url": "https://github.com/btroncone/ngrx-store-localstorage/issues" 19 | }, 20 | "homepage": "https://github.com/btroncone/ngrx-store-localstorage#readme", 21 | "license": "MIT", 22 | "peerDependencies": { 23 | "@angular/common": "^20.0.0", 24 | "@angular/core": "^20.0.0", 25 | "@ngrx/store": "^20.0.0" 26 | }, 27 | "dependencies": { 28 | "deepmerge": "^4.2.2", 29 | "tslib": "^2.3.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /projects/lib/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "parserOptions": { 8 | "project": [ 9 | "projects/lib/tsconfig.lib.json", 10 | "projects/lib/tsconfig.spec.json" 11 | ], 12 | "createDefaultProgram": true 13 | }, 14 | "rules": { 15 | "@angular-eslint/directive-selector": [ 16 | "error", 17 | { 18 | "type": "attribute", 19 | "prefix": "lib", 20 | "style": "camelCase" 21 | } 22 | ], 23 | "@angular-eslint/component-selector": [ 24 | "error", 25 | { 26 | "type": "element", 27 | "prefix": "lib", 28 | "style": "kebab-case" 29 | } 30 | ] 31 | } 32 | }, 33 | { 34 | "files": ["*.html"], 35 | "rules": {} 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitAny": false, 10 | "strictNullChecks": false, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "paths": { 14 | "lib": ["dist/lib/lib", "dist/lib"] 15 | }, 16 | "sourceMap": true, 17 | "declaration": false, 18 | "experimentalDecorators": true, 19 | "esModuleInterop": true, 20 | "moduleResolution": "bundler", 21 | "importHelpers": true, 22 | "target": "es2020", 23 | "module": "es2020", 24 | "lib": ["es2018", "dom"] 25 | }, 26 | "angularCompilerOptions": { 27 | "enableI18nLegacyMessageIdFormat": false, 28 | "strictInjectionParameters": true, 29 | "strictInputAccessModifiers": true, 30 | "strictTemplates": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["projects/**/*"], 4 | "extends": ["prettier"], 5 | "overrides": [ 6 | { 7 | "files": ["*.ts"], 8 | "parserOptions": { 9 | "project": ["tsconfig.json", "e2e/tsconfig.json"], 10 | "createDefaultProgram": true 11 | }, 12 | "extends": [ 13 | "plugin:@angular-eslint/recommended", 14 | "plugin:@angular-eslint/template/process-inline-templates" 15 | ], 16 | "rules": { 17 | "@angular-eslint/component-selector": [ 18 | "error", 19 | { 20 | "prefix": "app", 21 | "style": "kebab-case", 22 | "type": "element" 23 | } 24 | ], 25 | "@angular-eslint/directive-selector": [ 26 | "error", 27 | { 28 | "prefix": "app", 29 | "style": "camelCase", 30 | "type": "attribute" 31 | } 32 | ] 33 | } 34 | }, 35 | { 36 | "files": ["*.html"], 37 | "extends": ["plugin:@angular-eslint/template/recommended"], 38 | "rules": {} 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Brian Troncone 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | 4 | 5 | ## PR Checklist 6 | 7 | Please check if your PR fulfills the following requirements: 8 | 9 | - [ ] The commit message follows [our guidelines](https://github.com/btroncone/ngrx-store-localstorage/blob/master/CONTRIBUTING.md#commit): 10 | - [ ] Tests for the changes have been added (for bug fixes / features) 11 | - [ ] Docs have been added / updated (for bug fixes / features) 12 | 13 | ## PR Type 14 | 15 | What kind of change does this PR introduce? 16 | 17 | 18 | 19 | - [ ] Bugfix 20 | - [ ] Feature 21 | - [ ] Code style update (formatting, local variables) 22 | - [ ] Refactoring (no functional changes, no api changes) 23 | - [ ] Build related changes 24 | - [ ] CI related changes 25 | - [ ] Documentation content changes 26 | - [ ] Other... Please describe: 27 | 28 | ## Current Behavior 29 | 30 | 31 | 32 | Issue Number: N/A 33 | 34 | 35 | 36 | ## New Behavior 37 | 38 | 39 | 40 | ## Breaking Change? 41 | 42 | - [ ] Yes 43 | - [ ] No 44 | 45 | 46 | 47 | ## Other information 48 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 10.0.0 4 | 5 | From version 10 onwards, check [GitHub Releases](https://github.com/btroncone/ngrx-store-localstorage/releases) for release notes. 6 | 7 | ## 9.0.0 8 | 9 | ### Breaking changes 10 | 11 | - Remove deprecated `localStorageSyncAndClean` function 12 | 13 | ### Features 14 | 15 | - Allow `@ngrx/store` v9 as a peer dependency [#142](https://github.com/btroncone/ngrx-store-localstorage/pull/142) 16 | - Add `mergeReducer` option to define the reducer to use to merge the rehydrated state from storage with the state from the ngrx store [#135](https://github.com/btroncone/ngrx-store-localstorage/pull/135) 17 | 18 | ## 8.0.0 19 | 20 | ### Potentially breaking changes 21 | 22 | - Switched from lodash to deepmerge to keep bundle size small and avoid lodash security issues. This is intended to have no impact. However we cannot guarantee merging old state from localstorage with new state will function 100% the same. See [#126](https://github.com/btroncone/ngrx-store-localstorage/pull/126) for more info. 23 | - Objects will now lose their function definitions when rehydrated. Using such was already against the [Redux way](https://redux.js.org/faq/organizing-state#can-i-put-functions-promises-or-other-non-serializable-items-in-my-store-state) but was previously supported. 24 | 25 | ### Features 26 | 27 | - Angular Universal support [#127](https://github.com/btroncone/ngrx-store-localstorage/pull/127) 28 | - Slightly reduced bundle size by avoiding lodash.merge 29 | 30 | ## 5.0.0 (2018-02-17) 31 | 32 | ### Bug Fixes 33 | 34 | - Support rehydration for feature modules 35 | 36 | ### Features 37 | 38 | - Upgrade @ngrx/store peer dependency to v5 39 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "lib": { 7 | "projectType": "library", 8 | "root": "projects/lib", 9 | "sourceRoot": "projects/lib/src", 10 | "prefix": "lib", 11 | "architect": { 12 | "build": { 13 | "builder": "@angular-devkit/build-angular:ng-packagr", 14 | "options": { 15 | "project": "projects/lib/ng-package.json" 16 | }, 17 | "configurations": { 18 | "production": { 19 | "tsConfig": "projects/lib/tsconfig.lib.prod.json" 20 | }, 21 | "development": { 22 | "tsConfig": "projects/lib/tsconfig.lib.json" 23 | } 24 | }, 25 | "defaultConfiguration": "production" 26 | }, 27 | "lint": { 28 | "builder": "@angular-eslint/builder:lint", 29 | "options": { 30 | "lintFilePatterns": [ 31 | "projects/lib/**/*.ts", 32 | "projects/lib/**/*.html" 33 | ] 34 | } 35 | } 36 | } 37 | } 38 | }, 39 | "cli": { 40 | "schematicCollections": ["@angular-eslint/schematics"], 41 | "analytics": false 42 | }, 43 | "schematics": { 44 | "@schematics/angular:component": { 45 | "type": "component" 46 | }, 47 | "@schematics/angular:directive": { 48 | "type": "directive" 49 | }, 50 | "@schematics/angular:service": { 51 | "type": "service" 52 | }, 53 | "@schematics/angular:guard": { 54 | "typeSeparator": "." 55 | }, 56 | "@schematics/angular:interceptor": { 57 | "typeSeparator": "." 58 | }, 59 | "@schematics/angular:module": { 60 | "typeSeparator": "." 61 | }, 62 | "@schematics/angular:pipe": { 63 | "typeSeparator": "." 64 | }, 65 | "@schematics/angular:resolver": { 66 | "typeSeparator": "." 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # Check {{ '/2.0/language-javascript/' | docs_url }} for more details 3 | # Via: https://circleci.com/blog/publishing-npm-packages-using-circleci-2-0/ 4 | version: 2 5 | 6 | defaults: &defaults 7 | working_directory: ~/repo 8 | docker: 9 | - image: cimg/node:22.16.0 10 | 11 | jobs: 12 | install-lint-build-test: 13 | <<: *defaults 14 | steps: 15 | - checkout 16 | 17 | - restore_cache: 18 | keys: 19 | - v1-dependencies-{{ checksum "package-lock.json" }} 20 | # fallback to using the latest cache if no exact match is found 21 | - v1-dependencies- 22 | 23 | - run: npm install 24 | 25 | - run: 26 | name: Define environment variable with latest commit message 27 | command: | 28 | echo 'export COMMIT_MESSAGE=$(git log -1 --pretty=format:"%s")' >> $BASH_ENV 29 | source $BASH_ENV 30 | 31 | - run: 32 | name: Lint commit message 33 | command: echo "$COMMIT_MESSAGE" | npx commitlint 34 | 35 | - run: npm run lint 36 | 37 | - run: npm run build_dist 38 | 39 | - run: npm run test 40 | 41 | - save_cache: 42 | paths: 43 | - node_modules 44 | key: v1-dependencies-{{ checksum "package-lock.json" }} 45 | 46 | - persist_to_workspace: 47 | root: ~/repo 48 | paths: [.] 49 | 50 | deploy: 51 | <<: *defaults 52 | steps: 53 | - attach_workspace: 54 | at: ~/repo 55 | - run: mkdir -p ~/.ssh 56 | - run: ssh-keyscan github.com >> ~/.ssh/known_hosts 57 | - run: 58 | name: Run semantic-release 59 | command: cd dist/lib && ../../node_modules/.bin/semantic-release 60 | 61 | workflows: 62 | version: 2 63 | test-deploy: 64 | jobs: 65 | - install-lint-build-test 66 | - deploy: 67 | requires: [install-lint-build-test] 68 | filters: 69 | tags: 70 | only: /^v.*/ 71 | branches: 72 | only: master 73 | # Ignore forked pull requests have CIRCLE_BRANCH set to pull/XXX 74 | ignore: /pull\/[0-9]+/ 75 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngrx-store-localstorage-root", 3 | "version": "0.0.0", 4 | "private": "true", 5 | "scripts": { 6 | "build_dist": "npm run clean && ng build lib --configuration production && npm run copy_lib_assets", 7 | "clean": "rimraf dist", 8 | "lint": "npm run eslint && npm run prettier:check", 9 | "eslint": "ng lint --fix", 10 | "ng": "ng", 11 | "prettier": "prettier --write \"**/*.{js,json,css,scss,less,md,ts,html,component.html}\"", 12 | "prettier:check": "prettier --check \"**/*.{js,json,css,scss,less,md,ts,html,component.html}\"", 13 | "test": "nyc ts-node --compiler-options {\\\"module\\\":\\\"commonjs\\\"} node_modules/jasmine/bin/jasmine", 14 | "prepare": "husky install", 15 | "copy_lib_assets": "copyfiles README.md CHANGELOG.md LICENSE ./dist/lib/" 16 | }, 17 | "dependencies": { 18 | "@angular/animations": "^20.0.0", 19 | "@angular/common": "^20.0.0", 20 | "@angular/compiler": "^20.0.0", 21 | "@angular/core": "^20.0.0", 22 | "@angular/forms": "^20.0.0", 23 | "@angular/platform-browser": "^20.0.0", 24 | "@angular/platform-browser-dynamic": "^20.0.0", 25 | "@angular/router": "^20.0.0", 26 | "@ngrx/store": "^20.0.0", 27 | "deepmerge": "^4.2.2", 28 | "ngrx-store-localstorage": "file:projects/lib", 29 | "rxjs": "~7.8.1", 30 | "tslib": "^2.6.2", 31 | "zone.js": "~0.15.1" 32 | }, 33 | "devDependencies": { 34 | "@angular-devkit/build-angular": "^20.0.0", 35 | "@angular-eslint/builder": "^20.1.1", 36 | "@angular-eslint/eslint-plugin": "^20.1.1", 37 | "@angular-eslint/eslint-plugin-template": "^20.1.1", 38 | "@angular-eslint/schematics": "^20.1.1", 39 | "@angular-eslint/template-parser": "^20.1.1", 40 | "@angular/cli": "^20.0.0", 41 | "@angular/compiler-cli": "^20.0.0", 42 | "@commitlint/cli": "^12.1.4", 43 | "@commitlint/config-conventional": "^12.1.4", 44 | "@istanbuljs/nyc-config-typescript": "^1.0.1", 45 | "@types/jasmine": "~3.6.0", 46 | "@types/node": "^22.15.29", 47 | "@typescript-eslint/eslint-plugin": "^5.59.2", 48 | "@typescript-eslint/parser": "^5.59.2", 49 | "copyfiles": "^2.4.1", 50 | "crypto-js": "^4.0.0", 51 | "es6-shim": "^0.35.6", 52 | "eslint": "^8.39.0", 53 | "eslint-config-prettier": "^10.1.2", 54 | "husky": "^6.0.0", 55 | "jasmine": "^3.10.0", 56 | "jasmine-core": "~3.10.0", 57 | "jasmine-spec-reporter": "^7.0.0", 58 | "lint-staged": "^11.0.0", 59 | "localstorage-polyfill": "^1.0.1", 60 | "ng-packagr": "^20.0.0", 61 | "nyc": "^15.1.0", 62 | "prettier": "^3.5.3", 63 | "rimraf": "^3.0.2", 64 | "semantic-release": "^17.4.4", 65 | "ts-node": "^10.8.1", 66 | "typescript": "~5.8.3" 67 | }, 68 | "lint-staged": { 69 | "*.ts": "eslint --cache --fix", 70 | "*.{js,json,css,scss,less,md,ts,html,component.html}": [ 71 | "prettier --write" 72 | ] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ngrx-store-localstorage 2 | 3 | ![bundle size](https://img.shields.io/bundlephobia/minzip/ngrx-store-localstorage) 4 | ![npm weekly downloads](https://img.shields.io/npm/dw/ngrx-store-localstorage) 5 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 6 | [![CircleCI](https://circleci.com/gh/btroncone/ngrx-store-localstorage.svg?style=svg)](https://circleci.com/gh/btroncone/ngrx-store-localstorage) 7 | 8 | Simple syncing between ngrx store and local or session storage. 9 | 10 | ## Dependencies 11 | 12 | `ngrx-store-localstorage` depends on [@ngrx/store](https://github.com/ngrx/platform) and [Angular 12+](https://github.com/angular/angular). 13 | 14 | ## Usage 15 | 16 | ```bash 17 | npm install ngrx-store-localstorage --save 18 | ``` 19 | 20 | 1. Wrap localStorageSync in an exported function. 21 | 2. Include in your meta-reducers array in `StoreModule.forRoot`. 22 | 23 | ```ts 24 | import { NgModule } from "@angular/core"; 25 | import { BrowserModule } from "@angular/platform-browser"; 26 | import { 27 | StoreModule, 28 | ActionReducerMap, 29 | ActionReducer, 30 | MetaReducer, 31 | } from "@ngrx/store"; 32 | import { localStorageSync } from "ngrx-store-localstorage"; 33 | import { reducers } from "./reducers"; 34 | 35 | const reducers: ActionReducerMap = { todos, visibilityFilter }; 36 | 37 | export function localStorageSyncReducer( 38 | reducer: ActionReducer, 39 | ): ActionReducer { 40 | return localStorageSync({ keys: ["todos"] })(reducer); 41 | } 42 | const metaReducers: Array> = [localStorageSyncReducer]; 43 | 44 | @NgModule({ 45 | imports: [BrowserModule, StoreModule.forRoot(reducers, { metaReducers })], 46 | }) 47 | export class MyAppModule {} 48 | ``` 49 | 50 | ## API 51 | 52 | ### `localStorageSync(config: LocalStorageConfig): Reducer` 53 | 54 | Provide state (reducer) keys to sync with local storage. _Returns a meta-reducer_. 55 | 56 | #### Arguments 57 | 58 | - `config` An object that matches with the `LocalStorageConfig` interface, `keys` is the only required property. 59 | 60 | ### **LocalStorageConfig** 61 | 62 | An interface defining the configuration attributes to bootstrap `localStorageSync`. The following are properties which compose `LocalStorageConfig`: 63 | 64 | - `keys` (required) State keys to sync with local storage. The keys can be defined in two different formats: 65 | - `string[]`: Array of strings representing the state (reducer) keys. Full state will be synced (e.g. `localStorageSync({keys: ['todos']})`). 66 | 67 | - `object[]`: Array of objects where for each object the key represents the state key and the value represents custom serialize/deserialize options. This can be one of the following: 68 | - An array of properties which should be synced. This allows for the partial state sync (e.g. `localStorageSync({keys: [{todos: ['name', 'status'] }, ... ]})`). 69 | 70 | - A reviver function as specified in the [JSON.parse documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse). 71 | 72 | - An object with properties that specify one or more of the following: 73 | - `serialize`: A function that takes a state object and returns a plain json object to pass to json.stringify. 74 | 75 | - `deserialize`: A function that takes that takes the raw JSON from JSON.parse and builds a state object. 76 | 77 | - `replacer`: A replacer function as specified in the [JSON.stringify documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify). 78 | 79 | - `space`: The space value to pass JSON.stringify. 80 | 81 | - `reviver`: A reviver function as specified in the [JSON.parse documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse). 82 | 83 | - `filter`: An array of properties which should be synced (same format as the stand-alone array specified above). 84 | 85 | - `encrypt`: A function that takes a state string and returns an encrypted version of that string. 86 | e.g. `(state: string) => btoa(state)` 87 | 88 | - `decrypt`: A function that takes a state string and returns a decrypted version of that string. 89 | e.g. `(state: string) => atob(state)` 90 | 91 | - `rehydrate` (optional) `boolean`: Pull initial state from local storage on startup, this will default to `false`. 92 | - `storage` (optional) `Storage`: Specify an object that conforms to the [Web Storage API interface](https://developer.mozilla.org/en-US/docs/Web/API/Storage) to use, this will default to `localStorage`. 93 | - `removeOnUndefined` (optional) `boolean`: Specify if the state is removed from the storage when the new value is undefined, this will default to `false`. 94 | - `storageKeySerializer` (optional) `(key: string) => string`: Custom serialize function for storage keys, used to avoid Storage conflicts. 95 | - `restoreDates` \(_boolean? = true_): Restore serialized date objects. If you work directly with ISO date strings, set this option to `false`. 96 | - `syncCondition` (optional) `(state) => boolean`: When set, sync to storage medium will only occur when this function returns a true boolean. Example: `(state) => state.config.syncToStorage` will check the state tree under config.syncToStorage and if true, it will sync to the storage. If undefined or false it will not sync to storage. Often useful for "remember me" options in login. 97 | - `checkStorageAvailability` \(_boolean? = false_): Specify if the storage availability checking is expected, i.e. for server side rendering / Universal. 98 | - `mergeReducer` (optional) `(state: any, rehydratedState: any, action: any) => any`: Defines the reducer to use to merge the rehydrated state from storage with the state from the ngrx store. If unspecified, defaults to performing a full deepmerge on an `INIT_ACTION` or an `UPDATE_ACTION`. 99 | 100 | ### Usage 101 | 102 | #### Key Prefix 103 | 104 | ```ts 105 | localStorageSync({ 106 | keys: ["todos", "visibilityFilter"], 107 | storageKeySerializer: (key) => `cool_${key}`, 108 | }); 109 | ``` 110 | 111 | In above example `Storage` will use keys `cool_todos` and `cool_visibilityFilter` keys to store `todos` and `visibilityFilter` slices of state). The key itself is used by default - `(key) => key`. 112 | 113 | #### Target Depth Configuration 114 | 115 | ```ts 116 | localStorageSync({ 117 | keys: [ 118 | { feature1: [{ slice11: ["slice11_1"], slice14: ["slice14_2"] }] }, 119 | { feature2: ["slice21"] }, 120 | ], 121 | }); 122 | ``` 123 | 124 | In this example, `feature1.slice11.slice11_1`, `feature1.slice14.slice14_2`, and `feature2.slice21` will be synced to `localStorage.feature1` and `localStorage.feature2`. 125 | 126 | ## Known Workarounds 127 | 128 | 1. [Sync state across multiple tabs](https://github.com/btroncone/ngrx-store-localstorage/issues/40#issuecomment-336283880) 129 | 130 | ## Release Notes / Changelog 131 | 132 | From version 10 onwards, check [GitHub Releases](https://github.com/btroncone/ngrx-store-localstorage/releases) for release notes. For older versions check the [CHANGELOG.md](./CHANGELOG.md). 133 | 134 | ## Contributing 135 | 136 | See [CONTRIBUTING.md](./CONTRIBUTING.md) for instructions on how to contribute. 137 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | First off, thank you for contributing to this library! As a contributor, here are the guidelines we would like you to follow: 4 | 5 | - [Issues and Bugs](#issue) 6 | - [Feature Requests](#feature) 7 | - [Submitting an Issue](#submit-issue) 8 | - [Submitting a PR](#submit-pr) 9 | - [Commit Message Guidelines](#commit) 10 | 11 | ## Found a Bug? 12 | 13 | If you find a bug in the source code, you can help us by [submitting an issue](#submit-issue) to our [GitHub Repository][github]. 14 | Even better, you can [submit a Pull Request](#submit-pr) with a fix. 15 | 16 | ## Missing a Feature? 17 | 18 | You can _request_ a new feature by [submitting an issue](#submit-issue) to our GitHub Repository. 19 | If you would like to _implement_ a new feature, please consider the size of the change in order to determine the right steps to proceed: 20 | 21 | - For a **Major Feature**, first open an issue and outline your proposal so that it can be discussed. 22 | This process allows us to better coordinate our efforts, prevent duplication of work, and help you to craft the change so that it is successfully accepted into the project. 23 | 24 | - **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). 25 | 26 | ## Submitting an Issue 27 | 28 | Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion might inform you of workarounds readily available. 29 | 30 | We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. 31 | In order to reproduce bugs, we require that you provide a minimal reproduction. 32 | Having a minimal reproducible scenario gives us a wealth of important information without going back and forth to you with additional questions. 33 | 34 | A minimal reproduction allows us to quickly confirm a bug (or point out a coding problem) as well as confirm that we are fixing the right problem. 35 | 36 | We require a minimal reproduction to save maintainers' time and ultimately be able to fix more bugs. 37 | Often, developers find coding problems themselves while preparing a minimal reproduction. 38 | We understand that sometimes it might be hard to extract essential bits of code from a larger codebase but we really need to isolate the problem before we can fix it. 39 | 40 | ## Submitting a Pull Request (PR) 41 | 42 | Before you submit your Pull Request (PR) consider the following guidelines: 43 | 44 | 1. Search GitHub for an open or closed PR that relates to your submission. 45 | You don't want to duplicate existing efforts. 46 | 47 | 2. Be sure that an issue describes the problem you're fixing, or documents the design for the feature you'd like to add. 48 | Discussing the design upfront helps to ensure that we're ready to accept your work. 49 | 50 | 3. Fork the repo 51 | 52 | 4. Make your changes in a new git branch: 53 | 54 | ```shell 55 | git checkout -b my-fix-branch master 56 | ``` 57 | 58 | 5. Create your patch, **including appropriate test cases**. 59 | 60 | 6. Run the full test suite (`npm test`) and ensure that all tests pass. 61 | 62 | 7. Commit your changes using a descriptive commit message that follows our [commit message conventions](#commit). 63 | Adherence to these conventions is necessary because release notes are automatically generated from these messages using `semantic-release`. 64 | 65 | ```shell 66 | git commit -a 67 | ``` 68 | 69 | 8. Push your branch to GitHub: 70 | 71 | ```shell 72 | git push origin my-fix-branch 73 | ``` 74 | 75 | 9. In GitHub, send a pull request to the `master` branch. 76 | 77 | If we ask for changes via code reviews then: 78 | - Make the required updates. 79 | - Re-run the test suites to ensure tests are still passing. 80 | - Rebase your branch and force push to your GitHub repository (this will update your Pull Request): 81 | 82 | ```shell 83 | git rebase master -i 84 | git push -f 85 | ``` 86 | 87 | That's it! Thank you for your contribution! 88 | 89 | ## Commit Message Format 90 | 91 | We have very precise rules over how our Git commit messages must be formatted. 92 | Adherence to these conventions is necessary because release notes are automatically generated from these messages using `semantic-release`. 93 | It also leads to **easier to read commit history**. 94 | 95 | Each commit message consists of a **header**, a **body**, and a **footer**. 96 | 97 | ``` 98 |
99 | 100 | 101 | 102 |